Skip to content

feat(workspace): name tonight's first new dropout on the map - #1105

Open
seonghobae wants to merge 1 commit into
developfrom
feat/workspace-first-new-dropout
Open

feat(workspace): name tonight's first new dropout on the map#1105
seonghobae wants to merge 1 commit into
developfrom
feat/workspace-first-new-dropout

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Product outcome

After analysis, the ready rehearsal map names tonight's first new dropout so the room can take one next action: Open that landing on the renderer-owned map section. This is who newly sits out after leftover parts return. It is not a leftover sit-out (#1100), leftover return (#1101), remaining leftover (#1102), come-in, tacet, tutti, or a continued sit-out with nobody returning.

Gould (2011) treats a later rest or tacet after a return as a new sit-out for that part, not leftover of the earlier rest. MusicXML 4.0 records the same activity on each part at each measure; a later rest after every earlier silent part has resumed is new dropout evidence (MakeMusic & W3C Music Notation Community Group, 2021).

Next action

  • Named new dropout: stay out from the top of the named section.
  • Named returning or other included part: count that part out from the top of the named section.
  • Trustworthy all-active timeline: rehearse from the first section without a new sit-out cue.
  • Missing evidence: confirm who newly sits out after the leftover return before the first section.
  • Open uses [data-testid=song-structure-grid] [data-section-index=N]. Analysis section.id is never DOM-ID authority. Reduced-motion Open uses behavior: auto.

Trust boundary

Own data-property is_active only. Inherited members, own accessors, Proxy get-traps, sparse arrays, duplicate role/graph identities, unnamed roles, blank labels, and malformed roots fail closed. Local-audio fallback does not fabricate a new dropout.

Exact current identity

Merge gate

Keep unmerged until the unchanged then-current head has repository CI/coverage/security/SBOM/build-baseline terminal-success, exact owned statement+branch coverage on changed files, a qualifying independent non-author last-push APPROVE, and ordinary protected-branch acceptance. Queued, pending, skipped, cancelled, predecessor-head, protected-base, model-only, self/author, or administrative-bypass evidence is not success. Inherited #783 npm HIGH must not be suppressed here.


Devin Review

After leftover parts return, the ready rehearsal map names who newly sits out so the room can count that part out, or the newly sitting-out part can stay out, from the renderer-owned landing.
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 53 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 54a45b18-1ec0-4347-a57e-16a422845f46

📥 Commits

Reviewing files that changed from the base of the PR and between 749511c and f35dcfc.

📒 Files selected for processing (15)
  • AGENTS.md
  • ARCHITECTURE.md
  • CHANGELOG.md
  • CLAUDE.md
  • apps/desktop/src/features/workspace/Workspace.test.tsx
  • apps/desktop/src/features/workspace/Workspace.tsx
  • apps/desktop/src/features/workspace/firstNewDropout.selected-role.test.ts
  • apps/desktop/src/features/workspace/firstNewDropout.test.ts
  • apps/desktop/src/features/workspace/firstNewDropout.ts
  • apps/desktop/src/i18n/index.test.ts
  • apps/desktop/src/locales/en/common.json
  • apps/desktop/src/locales/ko/common.json
  • apps/desktop/vite.config.ts
  • docs/design-system/component-contract.md
  • docs/doctoring/first-new-dropout.md

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.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 3 potential issues.

Devin Review

Comment on lines +308 to +310
if (returning.length === baselineIds.size && leftovers.length === 0) {
const newDropouts = sittingOut.filter((node) => !baselineIds.has(node.roleId));
const found = namedDropout(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Continued sit-outs become false new dropouts

When another part sits out before leftovers fully return, newDropouts labels its continued absence as new. The map points to the wrong section.

Prompt for agents
In apps/desktop/src/features/workspace/firstNewDropout.ts, firstNewDropout only remembers the original sittingOutIds. During a partial return, a different role can become inactive; when the final original role returns, lines 308-310 classify that already-inactive role as a same-section new dropout. Track activity transitions across each named section, and only accept a role as a new dropout if it changes from active to inactive at or after the completed leftover return. A role that became inactive before the return completed and remains inactive is a continued sit-out and must not produce a cue. Update the tests that currently expect this continued sit-out to be named, and add coverage for both same-section active-to-inactive transitions and genuine later transitions.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +110 to +150
/**
* Collect one complete, unique activity record for every song-wide named role.
*
* Missing, unknown, duplicate, inherited, accessor, Proxy, sparse, or
* non-boolean graph evidence fails closed so a leftover part cannot also
* count as tonight's new dropout in the same section.
*/
function namedGraphNodes(
sectionValue: Record<string, unknown>,
namedRoles: NamedRoleCatalog
): NamedGraphNode[] | null {
const partGraph = denseOwnArray(sectionValue.partGraph);
if (!partGraph) {
return null;
}

const nodes: NamedGraphNode[] = [];
const seenRoleIds = new Set<string>();
for (const nodeValue of partGraph) {
if (
!isRuntimeObject(nodeValue) ||
!Object.prototype.hasOwnProperty.call(nodeValue, "role_id")
) {
return null;
}

const roleId = meaningfulRangeText(nodeValue.role_id);
if (!roleId || !namedRoles.has(roleId) || seenRoleIds.has(roleId)) {
return null;
}

const active = ownActiveFlag(nodeValue);
if (active === null) {
return null;
}

seenRoleIds.add(roleId);
nodes.push({ roleId, active });
}

return seenRoleIds.size === namedRoles.size ? nodes : null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Strict graph admission matches production

Production emits complete activity graphs and active-only role lists. The song-wide catalog can therefore name inactive parts without rejecting normal analysis results.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +194 to +207
const grid = document.querySelector("[data-testid='song-structure-grid']");
if (!(grid instanceof HTMLElement)) {
return;
}
const landing = grid.querySelector(`[data-section-index="${namedNewDropout.sectionIndex}"]`);
if (!(landing instanceof HTMLElement)) {
return;
}
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
landing.scrollIntoView({
behavior: reduceMotion ? "auto" : "smooth",
block: "nearest",
inline: "nearest"
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Scrolling remains renderer-owned

Open targets a renderer-created numeric index. Repeated labels remain distinct, and analysis section IDs never control DOM identifiers.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Collaborator Author

@opencode-agent You are the sole writer for canonical BandScope branch feat/workspace-first-new-dropout, exact head f35dcfc8a9351e2e41b4c5bc22b177c0f4273e9e, against protected develop@749511c3ad4000090048718f685c6bee6b3d2c25. Apply receiving-code-review, systematic-debugging, test-driven-development, then verification-before-completion; do not create another PR, force-push, destructively rebase, weaken gates, or mutate foreign repositories.

Fresh source verification confirms Devin thread PRRT_kwDORjvEXs6dnCvP is a real buyer-visible state-transition defect. Current firstNewDropout.ts remembers only the original sittingOutIds. When the final original leftover returns, it computes newDropouts = sittingOut.filter(node => !baselineIds.has(node.roleId)); therefore a different role that already transitioned active→inactive during an earlier partial-return section and merely remains inactive at the completed return is mislabeled as a new dropout at the later section. That points the rehearsal map at the wrong landing.

Repair this existing branch TDD-first. Add the smallest RED timeline where original leftovers reduce, one original role partially returns while a previously active non-baseline role drops out, then the final original leftover returns while that non-baseline role stays inactive. Expected: the continued sit-out is not a new-dropout cue at the completed-return section. Add positive coverage for (a) a genuine active→inactive transition in the same section that completes the original leftover return and (b) a genuine later active→inactive transition after the return is complete. Prove the predecessor failure before source repair.

Then make the narrowest causal state fix: carry trustworthy previous-section activity for named candidates (or equivalent transition provenance) and accept a new dropout only on a real active→inactive transition at or after completed leftover return. A role already inactive before completion and still inactive is continued absence, never a new transition. Preserve current own-property boolean admission, duplicate/malformed graph fail-closed behavior, active-role scoping, deterministic section ordering, and renderer-owned Open targeting; do not infer activity from section role-list presence alone.

Run focused firstNewDropout + selected-role + Workspace tests first, then full desktop tests/typecheck/lint/docs/security checks and configured statement/branch/function/line coverage at exact 100% where exposed. Refetch exact new head/base after writing. Resolve only the validated bug thread after repaired exact-head focused/full GREEN; the two informational threads may be resolved only if the current code still matches their stated contracts. Let exact-head CI/security/SBOM/release/review gates run; do not transfer predecessor evidence.

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