Skip to content

feat(workspace): cue the final part back in after staggered returns - #1106

Open
seonghobae wants to merge 20 commits into
developfrom
feat/workspace-first-leftover-last-return
Open

feat(workspace): cue the final part back in after staggered returns#1106
seonghobae wants to merge 20 commits into
developfrom
feat/workspace-first-leftover-last-return

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Product outcome

When an earlier reduction returns in stages, the rehearsal map names the one unambiguous final part back in and gives either that selected player a direct come-in cue or the band a count-in cue. Ambiguous simultaneous final returns, incomplete activity evidence, and intervening untracked dropouts fail closed.

Exact current identity

  • Protected target: develop@749511c3ad4000090048718f685c6bee6b3d2c25
  • Exact current head: fcf201a904586733171d116d76e2295cb96013cb
  • Branch: feat/workspace-first-leftover-last-return

Current contract

  • firstLeftoverLastReturn requires a reduction, a partial return that leaves a named leftover cohort, another staggered return that still leaves at least one part out, and then a unique final return.
  • Missing/duplicate/inherited/non-boolean graph activity, unnamed identities, new untracked dropouts during the tracked chain, or multiple final returners fail closed.
  • An unrelated completed sequence in selected-role mode is discarded and the search continues for a later sequence involving the selected part.
  • Customer EN/KO copy uses rehearsal language (final re-entry, who is last back, where the band began returning, and the concrete count-in/come-in action), not internal state-machine jargon.

Exact failure repaired this run

Release-preflight on predecessor exact head 09b32bffbde5e5a9c25e7a789f0656d47704658b failed in the desktop test suite at firstLeftoverLastReturn.review-regressions.test.ts while claiming to exercise a simultaneous final return. The fixture used acoustic-guitar, but the demo song contains only bass-guitar, keys-right, and lead-vocal; the helper silently ignored the unknown ID. The scenario therefore contained one real final return and correctly produced Lead Vocal instead of exercising the intended tied-return branch.

The current test fixture now adds acoustic-guitar to both roles and partGraph before constructing the tied-return sequence. The section builder also rejects unknown fixture IDs instead of silently turning them into no-ops. The selected-role continuation regression was repaired to use three actual demo roles for its earlier unrelated complete return, preserving the intended state-machine path without fabricated IDs.

Production tie handling was independently re-read at the predecessor and merge refs: it already rejects returningLast.length !== 1; no product-code weakening or symptom workaround was introduced to satisfy the test.

Security / ownership boundary

This helper consumes already-loaded RehearsalSong data only. It gains no filesystem, network, IPC, subprocess, WebView, model, export, dependency, or credential authority. No gate, security threshold, dependency policy, or central .github contract is weakened.

Verification contract

All predecessor CI/review evidence is historical after fcf201a904586733171d116d76e2295cb96013cb. Repository CI/build/release/security/SAST/SBOM workflows were freshly dispatched for this exact head and are currently queued, so they are not passing evidence.

Merge only after one unchanged exact head has every applicable protected required check terminal-success, current-head coverage/security/SAST/SBOM/supply-chain/release evidence, zero valid unresolved actionable findings, and a qualifying independent non-author approval. Queued, pending, skipped-required, cancelled, neutral, failed, stale, predecessor, protected-base, self/author, status-only, model-only, synthetic, or administrative-bypass evidence is non-passing.

Name the first leftover that comes back last after remaining leftover
from existing partGraph evidence so the leftover part comes in last
and the band counts that leftover in. Keep customer-facing copy on
the next rehearsal action without mixing #811, #828, #897, or other
first-X map PRs.
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

곡의 역할별 활성 상태에서 첫 번째 leftover last-return을 계산합니다. Workspace는 계산 결과와 선택된 역할에 따라 안내 문구를 표시합니다. 영어·한국어 번역, 회귀 테스트, 관련 문서를 추가합니다.

Changes

Leftover last-return 기능

Layer / File(s) Summary
Leftover last-return 분석
apps/desktop/src/features/workspace/firstLeftoverLastReturn.ts, apps/desktop/src/features/workspace/firstLeftoverLastReturn*.test.ts
곡의 section과 partGraph를 검증합니다. remaining leftover 이후의 첫 번째 leftover last-return을 계산합니다. 선택 역할, malformed 입력, 역할 활성 상태, 회귀 시나리오를 테스트합니다.
Workspace 안내 및 번역 연결
apps/desktop/src/features/workspace/Workspace.tsx, apps/desktop/src/features/workspace/Workspace.test.tsx, apps/desktop/src/locales/*/common.json, apps/desktop/src/i18n/*
Workspace가 분석 결과를 메모이제이션합니다. 역할별 안내 또는 누락 안내를 주황색 강조 영역에 렌더링합니다. 영어·한국어 번역과 번역 테스트를 추가합니다.
문서 및 변경 기록 정렬
AGENTS.md, ARCHITECTURE.md, CLAUDE.md, CHANGELOG.md, docs/design-system/component-contract.md, docs/doctoring/first-leftover-last-return.md
ready workspace의 명명 내용을 갱신합니다. leftover last-return 개념과 변경 사항을 문서화합니다.

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

Merge Risk: 🟡 Moderate · up to ae4f9

The PR adds Workspace guidance for identifying the final leftover return, but its dropout behavior is inconsistently documented and tested, the required lint gate has an error, and the English display copy does not match the intended user-facing contract. Merge should wait until these bounded correctness and validation issues are corrected.

Sequence Diagram(s)

sequenceDiagram
  participant Workspace
  participant firstLeftoverLastReturn
  participant TranslationResources
  Workspace->>firstLeftoverLastReturn: 곡과 활성 역할 전달
  firstLeftoverLastReturn-->>Workspace: leftover last-return 결과 반환
  Workspace->>TranslationResources: 안내 번역 키 선택
  TranslationResources-->>Workspace: 안내 문구 반환
  Workspace-->>Workspace: 안내 영역 렌더링
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 11 functions across 8 files. (8 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 분산된 복귀 이후 마지막 파트의 복귀를 안내하는 핵심 변경을 정확하게 요약합니다.
Full details: Docstring Coverage

Explanation

Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 11 functions across 8 files. (8 skipped: 8 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/workspace-first-leftover-last-return

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.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 4 potential issues.

Devin Review

Comment thread apps/desktop/src/features/workspace/firstLeftoverLastReturn.ts Outdated
Comment thread apps/desktop/src/features/workspace/firstLeftoverLastReturn.ts Outdated
Comment on lines +94 to +126
function namedGraphNodes(
sectionValue: Record<string, unknown>,
namedRoles: NamedRoleCatalog
): NamedGraphNode[] | null {
if (!Array.isArray(sectionValue.partGraph)) {
return null;
}

const nodes: NamedGraphNode[] = [];
const seenRoleIds = new Set<string>();
for (const nodeValue of sectionValue.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: Complete-graph check matches current output

Current analysis emits every known role in each graph, while section roles remain active-only. The strict completeness check accepts that contract.

Devin Review

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

Comment on lines +209 to +212
const sectionLabel = meaningfulRangeText(sectionValue.label);
if (!sectionLabel) {
continue;
}

@devin-ai-integration devin-ai-integration Bot Aug 31, 2026

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: Unnamed sections stay outside cue authority

meaningfulRangeText skips blank and none labels before graph evaluation. Such sections cannot become named rehearsal milestones.

Devin Review

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

Copy link
Copy Markdown
Collaborator Author

@OpenCode Apply superpowers:using-superpowers, receiving-code-review, systematic-debugging, test-driven-development, and verification-before-completion. Become the sole writer for canonical feat/workspace-first-leftover-last-return ONLY if a fresh refetch still shows exact head 538139e930e2b555b7e411b0b2bb2c93f9506171 and protected develop@749511c3ad4000090048718f685c6bee6b3d2c25; immediately before every write refetch head/base, target source/test blobs, review threads/check state, and active-writer evidence. If the branch moved, inspect the intervening delta and adapt instead of overwriting. Do not create another branch/PR, force-push, rebase, weaken gates, touch foreign repositories, self-approve, or resolve unrelated threads.

Fresh current-head review findings discussion_r3891736040 and discussion_r3891736093 are valid against the exact source and share one state-machine owner:

  1. Tied final returns are falsely collapsed to one solo role. returningLast[0] arbitrarily selects one role when multiple remainingIds become active in the same named section. The current result/copy model is singular, so fail closed on an ambiguous simultaneous final return rather than inventing a solo entrance.
  2. An unrelated completed sequence can permanently block selected-role search. In the final-return branch, failed selectedPartBelongs(...) uses continue, leaving pendingRemaining occupied forever, so a later independent valid reduction/partial-return/remaining/last-return involving the selected role can never become the candidate.

TDD/root repair on this existing lane:

  • First add a RED helper regression where two remaining leftovers return together. The helper must not name either one as lastRoleId; with the current singular contract, reject that candidate fail-closed. Cover selected-role behavior too so a selected member of a tied final return is not silently promoted to a unique last return.
  • Add a separate RED regression where an early fully completed leftover-last-return sequence excludes activeRole, followed by a distinct later valid sequence that includes it. The helper must find the later relevant cue.
  • Repair at the owning state-machine boundary, not in copy. On an ambiguous tied final return, invalidate the completed candidate. On a completed but selected-role-irrelevant sequence, clear/reseed candidate state from the current section as appropriate so subsequent reductions can be observed; do not preserve stale pendingRemaining. Preserve valid progressive shrinking, current fail-closed graph validation, and the existing selected-part semantics for a role that newly drops during a pending sequence.
  • Run focused helper + selected-role + Workspace tests, then full desktop tests/typecheck/lint with configured coverage and repository quickcheck if available. Commit only the minimal regression/source/copy-test changes required by these two findings on this same branch. Reply with exact successor SHA and exact RED→GREEN evidence. Resolve discussion_r3891736040 and discussion_r3891736093 only after successor exact-head deterministic evidence is terminal GREEN; informational threads only after re-verification. Do not dispatch or manufacture approval/review.

@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.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 3 new potential issues.

Devin Review

Comment thread apps/desktop/src/features/workspace/firstLeftoverLastReturn.ts
Comment on lines +152 to +166
function selectedPartBelongs(
pending: PendingRemainingLeftover,
lastRoleId: string,
activeRole: string | null
): boolean {
if (!activeRole) {
return true;
}
return (
pending.originalSitOutIds.includes(activeRole) ||
pending.leftoverIds.includes(activeRole) ||
pending.remainingIds.includes(activeRole) ||
lastRoleId === activeRole
);
}

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: Selected roles receive scoped guidance

An always-active selected part gets no unrelated cue. Included parts retain the cue, and only the final returning part receives first-person instructions.

Devin Review

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

Comment on lines +249 to +257
if (returningLast.length > 0 && stillRemaining.length === 0) {
const trackedRemainingIds = new Set(pendingRemaining.remainingIds);
const concurrentDropout = sittingOut.some(
(node) => !trackedRemainingIds.has(node.roleId)
);
if (concurrentDropout || returningLast.length !== 1) {
restartTrackingFromCurrentSection(sectionLabel, sittingOut);
continue;
}

@devin-ai-integration devin-ai-integration Bot Aug 31, 2026

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: Ambiguous final returns fail closed

Multiple final returners or a concurrent new dropout restart tracking. The callout never chooses an arbitrary part from an ambiguous final transition.

Devin Review

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

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Collaborator Author

@opencode-agent Take over as the sole writer for canonical feat/workspace-first-leftover-last-return only while a fresh refetch still shows exact head ae4f9a260a8b259d770336d65d0444b1c88ced75 over protected develop@749511c3ad4000090048718f685c6bee6b3d2c25. I have stopped writing this lane after adding the test-only commit ae4f9a2.... Apply superpowers:using-superpowers, receiving-code-review, systematic-debugging, test-driven-development, and verification-before-completion. Immediately before every write, refetch head/base, firstLeftoverLastReturn.ts, firstLeftoverLastReturn.review-regressions.test.ts, the locale-copy test/dictionaries, current review threads and active-writer evidence; abort/adapt if the lane moved. No new PR/branch, force-push/rebase, gate weakening, foreign-repo mutation, self-review or reviewer dispatch.

Two realistic RED regressions are now committed on ae4f9a2... for current Devin bug thread PRRT_kwDORjvEXs6d1fB8: after pendingRemaining starts, an untracked role drops out during an intermediate section and then either (1) returns before the tracked final role or (2) returns alongside it. The current production branch checks outside dropouts only in the final-candidate path, so both interrupted sequences can later emit a false unique last-return cue. Run the focused test file on the untouched ae4f9a2... first and preserve the actual RED output before changing source.

Then make the narrowest state-machine repair at the first causal boundary: while pendingRemaining is active, detect any currently inactive role outside the tracked remainingIds on every named section, invalidate that sequence immediately, and deliberately restart tracking from the current section so a later independent valid sequence can still be found. Preserve existing fail-closed behavior for simultaneous final returners, current-section concurrent dropouts, malformed graphs and selected-role scoping. Do not merely special-case the final candidate.

A second current-head review finding PRRT_kwDORjvEXs6d1qQ3 is also verified: workspaceLeftoverLastReturnCopy.test.ts expects customer-facing final re-entry copy, while both locale dictionaries still expose internal leftover last-return state-machine language. After the state-machine repair, make the dictionaries match the already-committed regression exactly (English and Korean together) and update only stale Workspace assertions if the focused run proves they still encode the old copy. This is customer-facing copy, so keep it concrete/action-oriented and do not reintroduce internal state terminology.

Run focused RED→GREEN for the review-regression file and locale copy test, then relevant Workspace/helper tests, full desktop Vitest with configured coverage, typecheck/lint and repository quickcheck. Commit the narrow causal repairs to this same branch. Refetch the successor exact head/base and report exact RED/GREEN evidence. Resolve only PRRT_kwDORjvEXs6d1fB8 and PRRT_kwDORjvEXs6d1qQ3 after successor evidence supports them; leave informational threads alone unless independently verified.

coderabbitai[bot]

This comment was marked as resolved.

@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.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 2 new potential issues.

Devin Review

Comment on lines +32 to +60
function templateWithAdditionalRole(
template: RehearsalSong["sections"][number],
roleId: string,
roleName: string
): RehearsalSong["sections"][number] {
const sourceRole = template.roles[0]!;
const sourceNode = template.partGraph[0]!;
return {
...template,
roles: [
...template.roles,
{
...sourceRole,
id: roleId,
name: roleName,
overlapWarnings: []
}
],
partGraph: [
...template.partGraph,
{
...sourceNode,
role_id: roleId,
handoff_to: [],
handoff_from: []
}
]
};
}

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: Tied-return coverage is genuine

templateWithAdditionalRole adds acoustic guitar to both identity and activity data. The final section now returns two actual parts, exercising the multiple-return rejection.

Devin Review

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

Comment on lines +12 to +17
const knownRoleIds = new Set(template.partGraph.map((node) => node.role_id));
for (const inactiveRoleId of inactiveRoleIds) {
if (!knownRoleIds.has(inactiveRoleId)) {
throw new Error(`Unknown test role id: ${inactiveRoleId}`);
}
}

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: Unknown fixture roles fail loudly

sectionWithInactiveRoles rejects unknown role IDs before building activity data. Misspelled or absent roles can no longer silently weaken a regression scenario.

Devin Review

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

@seonghobae seonghobae changed the title feat(workspace): name tonight's first leftover last-return on the map feat(workspace): cue the final part back in after staggered returns Aug 31, 2026

@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 2 new potential issues.

Devin Review

Comment on lines +12 to +17
const knownRoleIds = new Set(template.partGraph.map((node) => node.role_id));
for (const inactiveRoleId of inactiveRoleIds) {
if (!knownRoleIds.has(inactiveRoleId)) {
throw new Error(`Unknown test role id: ${inactiveRoleId}`);
}
}

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: Regression fixtures use real roles

sectionWithInactiveRoles now rejects unknown IDs instead of silently ignoring them. The tied-return case adds its fourth role to both required catalogs.

Devin Review

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

Comment on lines +198 to +205
sectionWithInactiveRoles(selectedTemplate, "verse-1", "verse", 0, [
"bass-guitar",
"keys-right",
"lead-vocal"
]),
sectionWithInactiveRoles(selectedTemplate, "chorus-1", "chorus", 20, [
"keys-right",
"lead-vocal"

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: Selected-role sequence stays unrelated

The first sequence uses valid demo roles while the selected part remains active. selectedPartBelongs rejects it, preserving the later asserted result.

Devin Review

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

@opencode-agent

Copy link
Copy Markdown
Contributor

Queued @opencode-agent for PR #1106 at head fcf201a904586733171d116d76e2295cb96013cb. Central exact-name Actions artifacts are the durable dispatch ledger; existing review workflows remain authoritative for the final verdict and failure 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