Skip to content

feat(workspace): name tonight's first breakdown plan on the map - #1039

Open
seonghobae wants to merge 17 commits into
developfrom
feat/workspace-first-breakdown-plan
Open

feat(workspace): name tonight's first breakdown plan on the map#1039
seonghobae wants to merge 17 commits into
developfrom
feat/workspace-first-breakdown-plan

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Buyer-visible next action

This PR names tonight's first breakdown plan on the rehearsal map when an active part stays in after a density drop: previous graph ≥3 distinct sources, current graph 1–2 of those same staying sources, no new entrance, not a full stop. Open scrolls that rendered map section so the staying part can hold the sparse texture until the drop.

Breakdown is a corroborated density drop that keeps playing. Dropout is a leaving part. Cutoff is a stop-time. Pickup is rest-then-enter. Turnaround is shared continuation at section end. Heuristic-only topology stays unnamed.

Exact current identity

Current implemented boundary

  • Fail-closed own-data / Proxy-safe resolver admits bounded breakdownPlan copy only from owned data properties and snapshots it once before ranking.
  • Engine emits Hold this breakdown with {target}; keep it sparse until the drop. (or the solo hold) only when previous graph source count is ≥3, current graph holds 1–2 of those same sources, and no new entrance arrives. Custom role-owned guidance is preserved verbatim unless model provenance is explicit.
  • i18n EN/KO next-action copy is particle-safe. Korean Open is {at} {role} 브레이크다운 열기.
  • Navigation uses the current song-structure renderer (data-section-index) and fails closed on ambiguous or missing targets. Reduced motion uses behavior: "auto".
  • Demo and heuristic topology stay unnamed until real stem activity corroborates the density drop.

Dependency / merge gate

Canonical #783 is protected develop shipped truth. This branch inherits that JavaScript baseline and does not duplicate or suppress it. Inherited npm HIGH must not be suppressed elsewhere.

Keep unmerged until the unchanged then-current head has every applicable repository and central CI/build/release/security/SAST/SBOM/supply-chain/coverage/review gate terminal-success, zero valid unresolved findings, and a qualifying independent non-author last-push approval under live branch protection.

Queued, pending, skipped, cancelled, failed, predecessor-head, protected-base, model-only, self/author, or administrative-bypass evidence is not success.


Devin Review

Summary by CodeRabbit

  • 새로운 기능
    • 리허설 워크스페이스에서 곡의 첫 번째 브레이크다운 계획을 확인할 수 있습니다.
    • 계획의 역할, 섹션, 시작 시간이 표시되며 해당 구조 구간으로 바로 이동할 수 있습니다.
    • 분석 결과에 따라 밀도가 크게 줄어드는 구간에만 안내가 제공됩니다.
  • 개선 사항
    • 사용자 작성 안내는 원문 그대로 유지됩니다.
    • 영어와 한국어 표시를 지원하며, 탐색 실패·정보 없음 상태에 안내 메시지를 제공합니다.
    • 접근성 설정에 따라 부드러운 스크롤 또는 즉시 이동을 적용합니다.

Name the earliest corroborated density drop so the staying part can hold
the sparse texture until the drop. Engine copy is emitted only from real
stem activity when the previous graph has at least three distinct sources
and the current graph holds one or two of those same sources, with no new
entrance. Heuristic topology, first sections, full stops, and mixed
entrances stay unnamed. Open scrolls the matching rendered map section.
@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

Next included review available in 16 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: 8df4680a-2543-4259-a724-e901c6fcdeef

📥 Commits

Reviewing files that changed from the base of the PR and between 0759f16 and cd0d5b0.

📒 Files selected for processing (7)
  • apps/desktop/src/features/workspace/FirstBreakdownPlanCallout.loaded-instance.test.tsx
  • apps/desktop/src/features/workspace/FirstBreakdownPlanCallout.particle.test.tsx
  • apps/desktop/src/features/workspace/FirstBreakdownPlanCallout.tsx
  • apps/desktop/src/features/workspace/Workspace.breakdown-state.test.tsx
  • apps/desktop/src/features/workspace/Workspace.tsx
  • services/analysis-engine/src/bandscope_analysis/roles/extractor.py
  • services/analysis-engine/tests/test_breakdown_plan.py
📝 Walkthrough

Walkthrough

분석 엔진이 인접 섹션의 stem 밀도 감소를 기반으로 첫 breakdownPlan을 생성합니다. 공유 타입과 네이티브 직렬화가 provenance 필드를 지원합니다. 데스크톱 워크스페이스가 계획을 현지화하고 해당 섹션으로 이동합니다.

Changes

첫 breakdown plan 흐름

Layer / File(s) Summary
Stem 활동 기반 계획 생성
services/analysis-engine/src/bandscope_analysis/roles/*, services/analysis-engine/tests/test_breakdown_plan.py
이전 source가 3개 이상이고 현재 유지 source가 1–2개인 연속 섹션에서 모델 출처의 계획을 생성합니다. full stop, 새 entrance, 불충분한 밀도, 휴리스틱 토폴로지는 제외합니다.
Breakdown plan 계약과 왕복 처리
packages/shared-types/src/index.ts, packages/shared-types/test/*, apps/desktop/core/src/lib.rs, apps/desktop/core/tests/breakdown_plan_contract.rs
RehearsalRoleRehearsalRolePayload에 선택적 계획 및 provenance 필드를 추가합니다. 타입, provenance, 직렬화 검증을 추가합니다.
첫 계획 해석과 방어적 검증
apps/desktop/src/features/workspace/firstBreakdownPlan.ts, apps/desktop/src/features/workspace/firstBreakdownPlan*.test.ts
유효한 인접 섹션, 활성 역할, 시간 범위, 계획 문자열을 검사합니다. 시간, 우선순위, 역할 ID로 결과를 안정적으로 선택합니다. own-data와 bounded array를 사용하고 예외에서는 null을 반환합니다.
워크스페이스 콜아웃과 맵 탐색
apps/desktop/src/features/workspace/FirstBreakdownPlanCallout*, apps/desktop/src/features/workspace/Workspace.tsx
계획을 표시하고 사용자 및 모델 provenance를 구분합니다. 렌더링된 섹션으로 스크롤하며 reduced-motion, 탐색 실패, 워크스페이스 범위, 컴포넌트 상태를 처리합니다.
현지화와 프로젝트 문서
apps/desktop/src/i18n/*, apps/desktop/src/locales/*, AGENTS.md, ARCHITECTURE.md, CHANGELOG.md, CLAUDE.md
섹션 라벨과 영어·한국어 breakdown plan 문구를 추가합니다. 기능 동작과 계획 문구 제약을 프로젝트 문서에 기록합니다.

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

Merge Risk: 🟡 Moderate · up to 0759f

The new breakdown callout behavior is covered by a fixture missing the explicit model provenance required by the expected post-open state. This can block or weaken CI validation of the provenance-gated UI behavior, so the fixture should be corrected before merge.

Sequence Diagram(s)

sequenceDiagram
  participant AnalysisEngine
  participant SharedTypes
  participant Workspace
  participant SongStructureGrid
  AnalysisEngine->>SharedTypes: breakdownPlan 및 provenance 포함 역할 생성
  SharedTypes->>Workspace: 검증된 RehearsalSong 전달
  Workspace->>Workspace: 첫 breakdown plan 해석 및 현지화
  Workspace->>SongStructureGrid: 대상 섹션으로 스크롤
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.71% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 70 functions across 32 files. (6 skipped:… 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 PR 제목은 워크스페이스 지도에서 오늘 밤의 첫 breakdown plan을 명명하는 주요 변경 사항을 정확하고 간결하게 설명합니다.
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.
Full details: Docstring Coverage

Explanation

Docstring coverage is 75.71% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 70 functions across 32 files. (6 skipped: 6 unsupported.)

✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/workspace-first-breakdown-plan

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.

chatgpt-codex-connector[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 potential issues.

Devin Review

Comment on lines +362 to +372
if (
typeof sectionId !== "string" ||
sectionId.trim().length === 0 ||
typeof sectionLabel !== "string" ||
!SECTION_FORM_LABEL_SET.has(sectionLabel) ||
timeRange === null ||
previousTimeRange === null ||
previousTimeRange.end !== timeRange.start
) {
return [];
}

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: UI resolver adds abutment gate the engine lacks

resolveSafeFirstBreakdownPlan requires the array-adjacent previous section to abut (previousTimeRange.end === timeRange.start), while the engine's _activity_breakdown_plan emits from activity alone with no timing constraint. Any non-contiguous section reaching the UI silently suppresses an engine-emitted breakdown. Section boundaries are contiguous today, so this holds.

Devin Review

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

Comment thread services/analysis-engine/src/bandscope_analysis/roles/extractor.py

Copy link
Copy Markdown
Collaborator Author

@opencode-agent implement

Work only on canonical branch feat/workspace-first-breakdown-plan if its exact head is still 18acb454234939551a2ad2cdf72cef4d2b956001 against protected develop@749511c3ad4000090048718f685c6bee6b3d2c25; if it moved, inspect/remap the intervening delta first. Apply TDD and do not resolve threads until the successor exact head is GREEN.

Four current-head findings are verified BandScope-owned defects, not report-only notes:

  1. Native payload contract / P1packages/shared-types/src/index.ts emits optional breakdownPlan / breakdownPlanSource, but apps/desktop/core/src/lib.rs::RehearsalRolePayload has deny_unknown_fields and lacks both. First add a Rust regression that deserializes and round-trips a realistic RehearsalSongPayload containing both fields (and preserves legacy payloads without them), proving current RED. Then add optional Rust mirror fields with camelCase serde behavior. Do not weaken deny_unknown_fields.

  2. Generic other stem ownership / P1map_stems_to_roles projects one other source onto keys-left, keys-right, and acoustic-guitar; current _build_activity_topology therefore attaches model breakdown ownership to all three, and _activity_breakdown_plan can name one synthetic part as a partner. Add regressions for an other-only surviving breakdown and a named+other surviving breakdown. The other source may corroborate density, but must never imply a specific keys/guitar owner. Model guidance for a named role may refer to a generic accompaniment source if that is the evidence; do not invent a specific part.

  3. Project/song instance state leakage / P2 — real analysis uses constant song id analyzed-song, while project load replaces the song without unmounting Workspace. Add a regression opening a breakdown in project/song object A, replacing it with a distinct object B carrying the same id and same breakdown signature, and assert armed/navigation-failure state resets. Use a true loaded-song instance authority rather than broadening object getters or weakening Proxy-safe admission.

  4. Custom guidance contradiction after Open / P2 — for user/legacy guidance, clicking Open currently swaps the leading copy to model-specific firstBreakdownPlanArmed even when the owned plan says something else. Add a regression with custom text such as Mute for four bars; after Open, no invented keep sparse until the drop instruction may appear. Preserve owned custom text and keep post-navigation copy neutral unless model provenance authorizes model-specific armed guidance.

Run focused tests first, then repository-pinned format/lint/typecheck, Rust tests, Python tests at exact 100% branch/statement coverage, canonical quickcheck, and leave all protection/security/release gates intact. Resolve only the four addressed threads after exact successor-head verification. Do not touch foreign repositories or unrelated feature lanes.

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

Devin Review

Comment thread services/analysis-engine/tests/test_breakdown_plan.py
Comment on lines +442 to +453
role = roles[role_key]
breakdown_plan = self._activity_breakdown_plan(
role_id,
roles,
role_activity,
previous_role_activity,
)
if breakdown_plan is not None:
role = role.copy()
role["breakdownPlan"] = breakdown_plan
role["breakdownPlanSource"] = "model"
active_roles.append(role)

@devin-ai-integration devin-ai-integration Bot Aug 27, 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: Two-source hold tags both staying parts

When a dense section drops to two named staying sources (bass + vocal), _build_activity_topology sets a model breakdownPlan on both: bass gets with Lead Vocal, vocal gets with Bass Guitar. The UI resolver picks one by priority, so display is fine, but the persisted song carries two roles each naming the other as the hold partner.

Devin Review

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

Comment thread apps/desktop/src/features/workspace/FirstBreakdownPlanCallout.tsx
Comment thread apps/desktop/src/features/workspace/firstBreakdownPlan.ts

@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 +397 to +414
const holdingRole = pickHoldingRole(
rankedActiveRoles(section as RehearsalSection).flatMap((metadata) => {
if (!previousActiveIds.has(metadata.id)) {
return [];
}
const breakdownPlan = ownedBreakdownPlan(metadata.role);
return breakdownPlan === null
? []
: [
{
...metadata,
breakdownPlan: breakdownPlan.text,
breakdownPlanSource: breakdownPlan.source,
breakdownPlanGuidance: breakdownPlan.guidance
}
];
})
);

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: Resolver allows accompaniment roles to hold a plan; engine never does

The engine's _activity_breakdown_plan refuses to assign a plan to keys-left, keys-right, or acoustic-guitar. The resolver's holding-role selection in resolveSafeFirstBreakdownPlan has no such exclusion, so any active role carrying own-data breakdownPlan can be named. Harmless for user-authored plans, but the two layers disagree on eligible holders.

Devin Review

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

Comment on lines +184 to +189
/** Preserve workspace-instance authority for immutable edits emitted by this workspace. */
const commitSongUpdate = (nextSong: RehearsalSong) => {
if (!onSongUpdate) return;
localSongUpdateRef.current = nextSong;
onSongUpdate(nextSong);
};

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: commitSongUpdate identity changes each render

commitSongUpdate is a fresh closure every render, now passed to SectionRoadmap in place of the previously-forwarded onSongUpdate. Prop identity changes each render, defeating any downstream memoization. Runtime behavior is unchanged.

Devin Review

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

@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 1 new potential issue.

Devin Review

Comment on lines +134 to +141
if (song !== previousSongRef.current) {
const isLocalWorkspaceUpdate = song === localSongUpdateRef.current;
if (!isLocalWorkspaceUpdate) {
workspaceInstanceRef.current = song;
}
localSongUpdateRef.current = null;
previousSongRef.current = song;
}

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: Opened-state persistence assumes parent echoes the exact song object

commitSongUpdate records the exact nextSong reference so a local edit keeps workspaceInstanceRef stable and the opened breakdown survives. A parent that clones or transforms the song before passing it back makes isLocalWorkspaceUpdate false, advancing the instance key and resetting the opened breakdown. Today all in-workspace edits route through it, but the contract is fragile for future callers.

Devin Review

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

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