Skip to content

Use chart data from bundle (after chart-transform plugin effects) - #12

Open
jphinspace wants to merge 2 commits into
got-feedBack:mainfrom
jphinspace:main
Open

Use chart data from bundle (after chart-transform plugin effects)#12
jphinspace wants to merge 2 commits into
got-feedBack:mainfrom
jphinspace:main

Conversation

@jphinspace

@jphinspace jphinspace commented Jul 24, 2026

Copy link
Copy Markdown

Summary

  • Replaces the server-side GP5 conversion (rs2gp.py + routes.py) with building the alphaTab Score directly from the renderer bundle in the browser (src/chart-quantize.js + src/score-builder.js).
  • Because the bundle is the same one every other renderer sees, Tab View now reflects any active chart-transform provider's output (e.g. retuning). Previously it only ever showed the original, untransformed chart.
  • Reuses one AlphaTabApi instance across chart rebuilds instead of destroying/recreating it, and drives its own boundsLookup-based marker instead of alphaTab's built-in cursor.
  • Also drops GP5's hard 7-string cap, since alphaTab's own model has no string-count ceiling.
  • The marker is now a custom overlay synced to the highway's clock, not alphaTab's built-in player cursor (which needed the soundfont downloaded and could vanish silently).

Notes

  • Chart math (chart-quantize.js) has no alphaTab dependency and is unit-tested directly; alphaTab object construction (score-builder.js) is tested against the real @coderline/alphatab package in Node.
  • No server route or Python conversion path remains for this plugin. The bundle only exists client-side, in memory, at draw time. It's the same data the other highways already use for rendering.
    • Shipping it to a Python server and back just to get a GP5 file would add a network round trip for data that's already sitting in the browser, and would need to re-run on every transform change. Building the alphaTab objects directly in JS skips that round trip entirely and reacts immediately when the bundle changes. It also removes a dependency (pyguitarpro) and a server route.

Why drop the GP5 path?

The GP5 file was converted server-side from the original arrangement XML, so it had no visibility into chart-transform plugins that modify notes/strings/tuning client-side. Those changes happened after conversion, so Tab View could never see them. Building the Score from the same live bundle every other renderer draws from is the only way to pick up a transform's output. GP5's format also caps out at 7 strings, which doesn't fit extended-range instruments the bundle already supports.

Test plan

  • npm test — 38 tests passing
  • Manual: load a song, confirm tab renders and cursor tracks playback in single-player and splitscreen
  • Manual: toggle a chart-transform provider (e.g. retuning) and confirm the tab updates to match

Summary by CodeRabbit

  • New Features
    • Tabs now render directly in the browser from chart data (no server conversion).
    • Supports extended-range instruments, tuning layouts, chord events, preserved techniques, and per-measure tempo changes.
    • Cursor/markers stay synchronized with existing audio playback, with improved multi-instance independence.
  • Bug Fixes
    • More reliable recovery for missing/invalid chart data, including sensible defaults for empty content.
  • Documentation
    • Updated the README for the new client-side build flow.
    • Refreshed the project “Core Principles” and governance constraints.
  • Tests
    • Added Node.js coverage for the new chart quantization and score-building pipeline.

* Use chart data from bundle (post-chart-transformation plugins) (#1)

* Read chart data from bundle without gp5

* Code review

* PR comments

* Coderabbit feedback

Signed-off-by: Joe <jphinspace@gmail.com>
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: df19ba17-9d6f-457a-9387-56fd6d0cc5f2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Tab View now builds alphaTab scores directly from renderer bundles instead of server-side GP5 conversion. New quantization and score-builder modules support timing, tuning, notation, techniques, and rests, while screen.js manages rendering, cursor synchronization, multi-instance state, failures, and teardown.

Changes

Bundle-driven alphaTab rendering

Layer / File(s) Summary
Module contract and project setup
.specify/memory/constitution.md, plugin.json, package.json, README.md, .gitignore
The plugin switches to an ES-module, client-side alphaTab architecture, documents bundle-based score construction, adds Node test tooling, and updates manifest metadata.
Chart timing and tuning utilities
src/chart-quantize.js, test/chart-quantize.test.mjs
Measure parsing, event merging, 32nd-note quantization, duration decomposition, and guitar/bass tuning generation are implemented and tested.
Bundle-to-score construction
src/score-builder.js, test/score-builder.test.mjs
Bundle data becomes alphaTab tracks, bars, beats, notes, rests, ties, tempo automation, tuning, and mapped performance techniques, with end-to-end model coverage.
Rendering lifecycle and cursor synchronization
screen.js
Per-instance alphaTab state, generation-aware rebuilds, marker timing, resize handling, error recovery, listener cleanup, and teardown are updated for direct score construction.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RendererBundle
  participant screenjs
  participant ScoreBuilder
  participant AlphaTabApi
  participant Highway
  RendererBundle->>screenjs: provide chart bundle and current time
  screenjs->>ScoreBuilder: buildScoreFromBundle(bundle)
  ScoreBuilder-->>screenjs: return alphaTab Score
  screenjs->>AlphaTabApi: render score
  AlphaTabApi-->>screenjs: emit render completion
  screenjs->>Highway: synchronize marker and visibility
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: switching Tab View to use chart data from the transformed bundle.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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: 3

🤖 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 `@plugin.json`:
- Around line 4-8: Update the plugin manifest and screen.js host-loading
integration so screen.js is loaded according to the host’s classic-script
contract rather than relying on scriptType: "module". Remove or avoid the static
import in screen.js, and use a compatible loading pattern that works when the
host ignores the manifest’s module setting.

In `@screen.js`:
- Around line 838-858: Move the bundle.notes identity check from draw() into
_tvCursorLoop so single-player mode detects chart updates while highway
rendering is hidden. Read the current bundle/notes from window.highway using the
same guard pattern as the existing getBeats() check, then reuse the existing
_tvCurrentNotesRef, _tvPendingNotesRef, _tvFailedNotesRef, mount, and token
guards before calling _tvRenderFromBundle.

In `@src/chart-quantize.js`:
- Around line 166-175: Update the tuning-table construction around the tunings
calculation to validate each selected tuningOffsets value and use 0 whenever it
is non-finite, while preserving finite offsets unchanged. Ensure the resulting
tunings array always contains numeric values before it is returned.
🪄 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: da3c4667-6ac4-4fa4-a828-d86489bb765f

📥 Commits

Reviewing files that changed from the base of the PR and between 1285844 and 0fcbb2d.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (15)
  • .gitignore
  • .specify/memory/constitution.md
  • README.md
  • package.json
  • plugin.json
  • requirements.txt
  • routes.py
  • rs2gp.py
  • screen.js
  • src/chart-quantize.js
  • src/score-builder.js
  • test/chart-quantize.test.mjs
  • test/score-builder.test.mjs
  • tests/conftest.py
  • tests/test_rs2gp_helpers.py
💤 Files with no reviewable changes (5)
  • tests/test_rs2gp_helpers.py
  • rs2gp.py
  • requirements.txt
  • tests/conftest.py
  • routes.py

Comment thread plugin.json
Comment on lines +4 to +8
"version": "4.0.0",
"type": "visualization",
"private": false,
"script": "screen.js",
"routes": "routes.py",
"scriptType": "module",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Any leftover Python / server-route artifacts from the removed GP5 route?
fd -e py -e pyi . || true
rg -n --hidden -g '!node_modules' -e 'routes' -e 'pyguitarpro' -e 'guitarpro' -e 'gp5' -i . || true
# Confirm the manifest keys the host is expected to read
fd -t f 'plugin.json' --exec cat {}

Repository: got-feedBack/feedBack-plugin-tabview

Length of output: 1346


🌐 Web query:

Grafana plugin manifest scriptType module scriptType classic visualization plugin manifest documentation

💡 Result:

In Grafana plugin development, the terms "module" and "classic" do not refer to a specific scriptType field in the plugin.json manifest. Instead, they relate to how plugins are loaded and structured [1][2]. The plugin.json file is the required metadata manifest for all Grafana plugins [3][4]. While it does not contain a scriptType field, it does define a loadingStrategy (found in internal plugin metadata) that dictates how frontend assets are loaded [5][1]. Key Concepts: 1. Loading Strategies: Grafana defines a PluginLoadingStrategy which can be set to either fetch or script [1]. This strategy signals to the underlying system (e.g., SystemJS) how to load the frontend JavaScript assets [1]. 2. Module vs. Classic (Context): - The term "module" typically refers to the modern standard for Grafana plugins, where module.ts (compiled to module.js) serves as the entry point [4][2]. This file exports the plugin implementation (such as a class extending PanelPlugin) [4][6]. - "Classic" often refers to older versions or legacy plugin architectures (e.g., those using PanelCtrl or older AngularJS-based approaches) [7]. Modern development relies on the current plugin SDK and standard ESM/module-based loading [4][2]. 3. Plugin Manifest (plugin.json): - This file is mandatory for all plugins [3]. It contains essential metadata such as the plugin id, name, and type (e.g., panel, datasource, app) [3][4]. It does not configure the loading script type explicitly via a scriptType manifest key [3][8]. For current development, you should follow the official Grafana Plugin Tools documentation, which focuses on modern React-based plugins using the standard module.ts entry point [4][2]. There is no requirement to configure a "script type" in the manifest, as the build process handles the necessary module format [4].

Citations:


🌐 Web query:

site:grafana.com developers plugin-tools reference plugin.json scriptType

💡 Result:

Based on the official Grafana Plugin Tools documentation, there is no field named scriptType in the plugin.json file [1][2][3]. The plugin.json file is a mandatory metadata file used to define plugin configuration, such as its id, type (e.g., "app", "datasource", "panel", "renderer"), name, and various dependencies or includes [1][2][3]. If you are looking for information on how to configure build scripts or tooling configurations, those are typically managed within package.json (for npm scripts) or via configuration files in the project root, such as webpack.config.ts or files within the.config/ directory [4][5]. If you are encountering a reference to scriptType, it may be from an outdated version of the Grafana plugin framework, a third-party tool, or a misunderstanding of a different configuration property. For current development, please refer to the official plugin.json reference guide provided by Grafana [1].

Citations:


🌐 Web query:

grafana visualization plugin script scriptType classic module plugin.json

💡 Result:

In Grafana plugin development, the plugin.json file serves as the mandatory metadata configuration for all plugins [1][2][3]. While the specific terms "classic" or "module" do not define distinct plugin types in the current Grafana plugin schema, they reflect the evolution of how plugins are structured, loaded, and built [2][4][5]. Plugin Structure and Loading The module field in plugin.json specifies the entry point for the plugin's frontend code, typically pointing to module.js (compiled from src/module.ts) [2][4][5]. Modern Grafana plugins use a build process that outputs this module file, which Grafana loads to initialize the plugin [2][6]. Evolution of Plugin Loading Grafana internally tracks how plugins are loaded via a loadingStrategy [4]. This strategy dictates how frontend assets are handled: 1. module: In modern development, plugins are built as modules that Grafana loads as a bundle [2][5]. 2. script: Historically, or in specific integration scenarios, plugins might have relied on different loading mechanisms, sometimes referred to as "classic" (often associated with older Angular-based plugins or manual script inclusion) [4][7]. Current Standards The term "classic" is generally deprecated in modern Grafana plugin documentation, which now emphasizes using the create-plugin tool to scaffold plugins [6][3]. This tool generates a standard configuration that expects a modern module-based structure [5][6]. If you are working with an older plugin, you may encounter references to "classic" loading styles, but current official documentation focuses on the standard module-based approach where the plugin.json defines the entry point and the build system handles the module creation [2][5]. For definitive schema information, refer to the official plugin.json schema, which defines the expected structure for current plugin development [1][2].

Citations:


🌐 Web query:

"scriptType: \"module\"" "plugin.json" "script" visualization grafana

💡 Result:

In the context of Grafana plugin development, there is no standardized or required scriptType: "module" field within the plugin.json file [1][2][3]. The plugin.json file is primarily used to store essential plugin metadata such as its unique id, type, name, version, and dependencies [1][4][5]. The frontend entry point for a Grafana plugin is defined by the module file, typically named module.ts (or module.js after compilation) [6][4][7]. Grafana automatically discovers and loads this file when the plugin is initialized [4][7]. While some internal Grafana structures, such as the PluginMeta interface found in @grafana/data, contain a loadingStrategy field with options like PluginLoadingStrategy.fetch or PluginLoadingStrategy.script [8], this configuration is generally handled by the Grafana plugin framework and build tools (such as @grafana/toolkit or the newer create-plugin tool) rather than manual entry in the plugin.json file [5][9]. If you are encountering a scriptType: "module" reference, it may be related to custom build configurations, specific SystemJS setups, or an experimental feature not included in the core plugin metadata schema [1][2]. For standard plugin development, follow the official Grafana Plugin Tools documentation to ensure your plugin.json and module.ts files are correctly structured [1][7][10].

Citations:


No orphaned server route artifacts remain.

The manifest’s added scriptType does not address the host-loader contract for screen.js; if the host ignores it, the static import will fail when injected as a classic script.

🤖 Prompt for 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.

In `@plugin.json` around lines 4 - 8, Update the plugin manifest and screen.js
host-loading integration so screen.js is loaded according to the host’s
classic-script contract rather than relying on scriptType: "module". Remove or
avoid the static import in screen.js, and use a compatible loading pattern that
works when the host ignores the manifest’s module setting.

Comment thread screen.js
Comment on lines +838 to 858
// Rebuilds whenever bundle.notes' identity differs — covers a
// song switch and a chart-transform provider rerunning, since
// highway.js restages a fresh array either way. Guarded
// against retry storms: skip while in flight or previously
// failed for this exact ref.
const notesRef = bundle.notes || null;
const chartChanged = notesRef !== _tvCurrentNotesRef;
const buildInFlight = _tvPendingNotesRef === notesRef;
const previouslyFailed = _tvFailedNotesRef === notesRef;
if (chartChanged && !buildInFlight && !previouslyFailed) {
// Defense-in-depth: _tvRenderFromBundle also checks this,
// but avoids a wasted token bump while the mount is
// transiently null.
if (_resolveMount(_tvHighwayCanvas)) {
const myToken = ++_tvInitToken;
_tvLastTick = -1;
_tvFetchAndInit(filename, arrIdx, myToken);
_tvLastTick = -9999;
_tvRenderFromBundle(bundle, myToken);
// fall through — cursor sync below will be a no-op
// until _tvReady flips true again after the re-init.
// until _tvReady flips true again after the re-render.
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Does the host keep pumping the active renderer's draw() once setVisible(false) gates the highway?
fd -t f 'highway.js' --exec rg -n -C4 -e 'setVisible' -e '_visible' -e 'draw\(' {} \; | head -80
# Any accessor the cursor loop could use to read the current notes/bundle?
fd -t f 'highway.js' --exec rg -n -e 'getBeats' -e 'getNotes' -e 'getBundle' -e 'return\s+bundle' {} \;

Repository: got-feedBack/feedBack-plugin-tabview

Length of output: 174


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo files =="
git ls-files | sed -n '1,200p'

echo "== locate screen/highway/readme =="
fd -t f '(^screen\.js$|^highway\.js$|^README\.md$)'

Repository: got-feedBack/feedBack-plugin-tabview

Length of output: 1474


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== screen.js size =="
wc -l screen.js

echo "== outlines/highway references in screen.js =="
rg -n -C 3 'highway\.js|setVisible|getBeats|_tvCursorLoop|draw\\(|bundle\.notes|highway' screen.js README.md

echo "== relevant screen.js sections =="
sed -n '630,680p' screen.js
sed -n '820,870p' screen.js
sed -n '1,80p' screen.js
echo "== README around line 46 =="
sed -n '36,56p' README.md

Repository: got-feedBack/feedBack-plugin-tabview

Length of output: 435


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== relevant screen.js references =="
rg -n -C 3 'highway-js|setVisible:|setVisible|getBeats|_tvCursorLoop|draw\(|bundle\.notes|highway|init\(' screen.js README.md || true

echo "== relevant screen.js sections =="
sed -n '630,680p' screen.js
sed -n '820,870p' screen.js
sed -n '1,80p' screen.js
sed -n '36,56p' README.md

echo "== all highway-related terms with escaped regex =="
rg -n -C 2 'highway|setVisible|draw\(' screen.js README.md

Repository: got-feedBack/feedBack-plugin-tabview

Length of output: 37620


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== renderer renderFinished listeners in the repo =="
rg -n -C 3 'renderFinished|on\(.*render|\.on|addEventListener\(' screen.js README.md package.json plugin.json || true

echo "== visibility setter behavior in codebase =="
rg -n -C 3 'function setVisible|setVisible\s*[=:]|_visible|visibility\.change|visibilitychange|highway:visibility' . || true

echo "== single-player render finished and set visible call sites =="
sed -n '460,500p' screen.js
sed -n '813,828p' screen.js

Repository: got-feedBack/feedBack-plugin-tabview

Length of output: 10440


Move the chart change dirty-check into the single-player cursor loop

In single-player mode, init() finishes, the highway is set to setVisible(false), and only _tvCursorLoop continues running. That loop currently only updates the cursor from window.highway; a changed bundle.notes identity later is only detected in draw(), so the tab can stay stale after mastery-slider moves or chart-transform reruns, contrary to the documented behavior. Expose/read the current bundle/notes from window.highway in the loop with the same guard shape as the existing getBeats() check.

🤖 Prompt for 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.

In `@screen.js` around lines 838 - 858, Move the bundle.notes identity check from
draw() into _tvCursorLoop so single-player mode detects chart updates while
highway rendering is hidden. Read the current bundle/notes from window.highway
using the same guard pattern as the existing getBeats() check, then reuse the
existing _tvCurrentNotesRef, _tvPendingNotesRef, _tvFailedNotesRef, mount, and
token guards before calling _tvRenderFromBundle.

Comment thread src/chart-quantize.js
Signed-off-by: Joe <jphinspace@gmail.com>
@jphinspace

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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