Skip to content

Migrate sound_matrix (Tone Matrix) bundle to Conductor#802

Open
Akshay-2007-1 wants to merge 13 commits into
conductor-migrationfrom
feat/migrate-sound-matrix
Open

Migrate sound_matrix (Tone Matrix) bundle to Conductor#802
Akshay-2007-1 wants to merge 13 commits into
conductor-migrationfrom
feat/migrate-sound-matrix

Conversation

@Akshay-2007-1

@Akshay-2007-1 Akshay-2007-1 commented Jul 18, 2026

Copy link
Copy Markdown

Summary

Migrates the sound_matrix bundle (Quest Q5B, "The Magical Tone Matrix") to Conductor, following the same pattern as the recent sound/midi/binary_tree migrations.

Fixes #787

  • sound_matrix bundle: get_matrix, clear_matrix, set_timeout, clear_all_timeout reimplemented as @moduleMethod-decorated BaseModulePlugin methods, talking to a new host-side tab over Conductor's RPC channel instead of touching the DOM directly (module code runs in the Conductor Worker, not the main thread).
  • New SoundMatrix tab: owns the actual 16x16 grid state and canvas rendering (click-to-toggle), replies to the bundle's RPC calls (getMatrix, clearMatrix).
  • get_matrix() keeps the original row-0-is-bottom-row convention (verified against the actual Quest Q5B spec: rows are counted from the bottom).
  • Matrix state persists correctly across Runs (it's tied to the tab instance, not per-bundle-instantiation - two earlier commits here fix regressions where the grid was wrongly resetting/not resetting).

Tested manually end-to-end against a live frontend + both the CSE machine and the (not yet merged) PVML-in-browser evaluator for SICPy, combined with the sound module - a full Tone Matrix pattern combining simultaneously()'d columns with a set_timeout-driven sequence() loop plays correctly on both engines.

Test plan

  • yarn tsc clean (bundle + tab)
  • yarn lint clean (bundle + tab)
  • yarn test passing (bundle: 6 tests, tab: 8 tests)
  • yarn build succeeds (bundle + tab)
  • yarn buildtools build docs succeeds
  • yarn constraints clean
  • Manually verified in a live frontend: grid state persists across Runs, get_matrix() row/column convention matches Quest Q5B, set_timeout/clear_all_timeout drive a full playback sequence correctly

renovate Bot and others added 9 commits July 16, 2026 09:30
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…sn't real

The host loads this tab bundle via a require-wrapper (export default require
=> {...}) and calls that factory function fresh on every hostLoadPlugin, i.e.
every Run - so a plain module-level `let sharedMatrix` was reinitialised each
time despite looking module-level in source. Stash the grid on globalThis
instead, which is the one thing that's actually the same object across
repeated factory invocations in the same page.
Quest Q5B's spec ("the first sound is assigned to the bottom-most row")
and the original soundToneMatrix.js (`result[i] = matrix_list[15 - i]`)
both confirm get_matrix()'s row 0 is the grid's bottom row, not the top.
The tab draws matrix[0] at the top of the canvas, so matrixToConductorList
needs to walk the array bottom-up - it was walking top-down, a real
correctness regression from the original behaviour.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request ports the sound_matrix module to Conductor, splitting it into a runner-side plugin and a host-side tab plugin that communicate via RPC over a dedicated channel. Key feedback includes addressing a useSyncExternalStore contract violation caused by mutating the shared matrix in-place, implementing the destroy lifecycle method in SoundMatrixModulePlugin to clear active timeouts and prevent memory leaks, and defensively checking for a null canvas context in __setColor.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/tabs/SoundMatrix/src/index.tsx Outdated
Comment thread src/bundles/sound_matrix/src/index.ts
Comment thread src/tabs/SoundMatrix/src/index.tsx Outdated
Akshay-2007-1 and others added 2 commits July 19, 2026 00:37
…y scope

topoSortPackages treated any dependency name starting with "@sourceacademy"
as a local workspace package and added it to the dependency graph. That
assumption broke the moment a bundle actually depends on the real published
@sourceacademy/conductor npm package (sound_matrix and its SoundMatrix tab
are the first to declare it directly, via "npm:^0.7.1" rather than
"workspace:^") - conductor has no RawPackageRecord since it isn't a
workspace member, so processRawPackages crashed reading .hasChanges off
undefined for it, taking down the whole "Get packages within the
repository" job (and everything downstream) on any PR touching these
packages.

Fixed by only graphing a @sourceacademy-scoped dependency when it's an
actual node in the packages record passed in, not just by name prefix.
Added a regression test mirroring this exact shape.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ssing playwright deps

Two separate, previously-unhit CI gaps, both surfaced by sound_matrix being the
first bundle in the repo whose entire public surface is a class rather than
plain function exports:

1. modules-typedoc-plugin's json/validation code only ever understood
   Function and Variable top-level reflections - a class-typed export hit the
   "is a Class, which is not supported" validation warning (fatal under --ci)
   with no way to produce useful docs for it either way, regardless of
   whether it's a default or named export. Taught buildJson/validateModuleEntry
   to flatten a Class export's own public methods (kind Method) into
   individual doc entries via the same logic already used for standalone
   functions - inherited members (e.g. BaseModulePlugin's `initialise`),
   the constructor, and private members are excluded, so only the four
   student-facing functions (get_matrix/clear_matrix/set_timeout/
   clear_all_timeout) end up documented, matching what a plain-function
   bundle would have produced. Added a class-shaped fixture + regression
   tests covering the flattening and the exclusions.

2. SoundMatrix tab's vitest.config.ts already runs in browser mode, but its
   package.json never declared the devDependencies that requires
   (@vitest/browser-playwright, playwright, vitest, @vitest/coverage-v8) -
   worked locally only because other tabs (Curve, Rune) happen to already
   hoist them into the shared node_modules. CI's per-package scoped install
   has no such luck, and separately only installs Playwright's browser
   binaries when a package's own devDependencies list "playwright" - failing
   with "Cannot find package 'playwright'". Added the same four
   devDependencies Curve already declares for this reason.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Akshay-2007-1 Akshay-2007-1 self-assigned this Jul 18, 2026
@Akshay-2007-1
Akshay-2007-1 changed the base branch from master to conductor-migration July 18, 2026 17:18
Akshay-2007-1 and others added 2 commits July 19, 2026 01:23
…ontext

- __handleClick mutated the shared matrix array in place, then handed the
  same reference back to setSharedMatrix. useSyncExternalStore's snapshot
  function is getSharedMatrix itself, so React's Object.is comparison saw
  no change and skipped re-rendering even though the click did register
  (the canvas still drew correctly via __setColor's direct DOM write,
  independent of React). Clone before mutating, matching the pattern
  getMatrix() already used. Added a regression test reading the shared
  matrix's global symbol directly to check the reference actually changes
  on click - confirmed it fails without the fix and passes with it.
- SoundMatrixModulePlugin never cleared its pending timeouts on teardown.
  A scheduled but not-yet-fired set_timeout surviving past a Run's end
  would still fire later and call closure_call_unchecked against a
  torn-down evaluator. Added destroy() (IPlugin's optional cleanup hook)
  sharing the same clearing logic clear_all_timeout already used, now
  factored into __clearAllTimeouts.
- __setColor cast getContext('2d')'s result instead of checking it -
  getContext can return null, which would throw a TypeError instead of
  failing gracefully.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…igrate-sound-matrix

# Conflicts:
#	yarn.lock
@Akshay-2007-1
Akshay-2007-1 requested a review from leeyi45 July 19, 2026 13:32
@leeyi45

leeyi45 commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Is sound_matrix being used?

I thought the intention was for it to be superseded by a proper matrix module

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.

Conductor Migration: Sound Matrix Module

2 participants