-
Notifications
You must be signed in to change notification settings - Fork 1
feat(workspace): name the selected part's first entrance cue #1150
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
seonghobae
wants to merge
27
commits into
develop
Choose a base branch
from
feat/workspace-selected-part-entrance-cue
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
abfb399
feat(workspace): name the selected part's first entrance cue
seonghobae 3a09ae1
feat(workspace): consolidate selected-part first-pass domain
seonghobae 5c9d96f
test(workspace): preserve selected-part first-pass mount
seonghobae baddc60
test(workspace): preserve selected-part first-pass regressions
seonghobae ff9260e
docs(workspace): preserve selected-part first-pass evidence
seonghobae 39f7fae
feat(workspace): mount consolidated selected-part first-pass guidance
seonghobae e583873
feat(i18n): consolidate selected-part first-pass copy
seonghobae 424821b
feat(i18n): consolidate Korean first-pass copy
seonghobae 7037ce7
feat(workspace): consolidate selected-part confirmed chord
seonghobae 9375cf1
test(workspace): preserve confirmed-chord mount
seonghobae 7ad3924
test(workspace): preserve confirmed-chord regressions
seonghobae a5d0750
docs(workspace): preserve confirmed-chord evidence
seonghobae 9a8b30e
feat(workspace): mount consolidated confirmed-chord guidance
seonghobae 958b096
feat(i18n): consolidate confirmed-chord copy
seonghobae d662173
feat(i18n): consolidate Korean confirmed-chord copy
seonghobae 8581afb
refactor(workspace): share canonical section labels
seonghobae 8821ae1
docs(ui): consolidate selected-part guidance contracts
seonghobae 7d03dc9
test(workspace): reject stale selected roles
seonghobae c264458
test(workspace): hide stale selected-part guidance
seonghobae 10d9ed0
test(i18n): require particle-safe confirmed chord copy
seonghobae 141d8e9
fix(workspace): fail closed on chord override conflicts
seonghobae c5ce688
test(workspace): cover conflicting chord overrides
seonghobae 81d022c
fix(i18n): make confirmed chord copy particle-safe
seonghobae 44b1233
fix(workspace): clear roles missing from replacement project
seonghobae 84443ce
docs(workspace): record selected-part ordering invariants
seonghobae f8a6c2b
docs(workspace): record confirmed chord conflict policy
seonghobae 1f78675
Merge protected develop into selected-part guidance owner
seonghobae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
apps/desktop/src/features/workspace/Workspace.confirmed-chord.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import { fireEvent, render, screen } from "@testing-library/react"; | ||
| import { createDemoRehearsalSong } from "@bandscope/shared-types"; | ||
| import { afterEach, describe, expect, it } from "vitest"; | ||
| import { Workspace } from "./Workspace"; | ||
|
|
||
| const originalLanguage = navigator.language; | ||
|
|
||
| function setNavigatorLanguage(language: string) { | ||
| Object.defineProperty(navigator, "language", { | ||
| configurable: true, | ||
| value: language | ||
| }); | ||
| } | ||
|
|
||
| describe("Workspace selected-part confirmed chord", () => { | ||
| afterEach(() => { | ||
| setNavigatorLanguage(originalLanguage); | ||
| }); | ||
|
|
||
| it("stays hidden until a part with a room-confirmed chord is selected", () => { | ||
| setNavigatorLanguage("en-US"); | ||
| render(<Workspace song={createDemoRehearsalSong()} />); | ||
|
|
||
| expect(screen.queryByTestId("selected-part-confirmed-chord")).toBeNull(); | ||
|
|
||
| fireEvent.click(screen.getByRole("tab", { name: "Bass Guitar" })); | ||
| expect(screen.queryByTestId("selected-part-confirmed-chord")).toBeNull(); | ||
| }); | ||
|
|
||
| it("names the selected part's confirmed chord and the next lock-in action", () => { | ||
| setNavigatorLanguage("en-US"); | ||
| render(<Workspace song={createDemoRehearsalSong()} />); | ||
|
|
||
| fireEvent.click(screen.getByRole("tab", { name: "Lead Vocal" })); | ||
|
|
||
| const callout = screen.getByTestId("selected-part-confirmed-chord"); | ||
| expect(callout).toHaveTextContent("Tonight's confirmed chord"); | ||
| expect(callout).toHaveTextContent( | ||
| "Lead Vocal uses the room's C#m11 in verse. Lock that chord before the verse." | ||
| ); | ||
| }); | ||
|
|
||
| it("keeps Korean copy particle-safe for arbitrary chord symbols", () => { | ||
| setNavigatorLanguage("ko-KR"); | ||
| render(<Workspace song={createDemoRehearsalSong()} />); | ||
|
|
||
| fireEvent.click(screen.getByRole("tab", { name: "Lead Vocal" })); | ||
|
|
||
| expect(screen.getByTestId("selected-part-confirmed-chord")).toHaveTextContent( | ||
| "verse의 Lead Vocal 파트는 방이 확인한 C#m11 코드로 맞춥니다. verse 전에 그 코드를 고정하세요." | ||
| ); | ||
| }); | ||
| }); |
99 changes: 99 additions & 0 deletions
99
apps/desktop/src/features/workspace/Workspace.entrance-cue.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| import { fireEvent, render, screen } from "@testing-library/react"; | ||
| import { createDemoRehearsalSong } from "@bandscope/shared-types"; | ||
| import { afterEach, describe, expect, it } from "vitest"; | ||
| import { Workspace } from "./Workspace"; | ||
|
|
||
| const originalLanguage = navigator.language; | ||
|
|
||
| function setNavigatorLanguage(language: string) { | ||
| Object.defineProperty(navigator, "language", { | ||
| configurable: true, | ||
| value: language | ||
| }); | ||
| } | ||
|
|
||
| describe("Workspace selected-part entrance cue", () => { | ||
| afterEach(() => { | ||
| setNavigatorLanguage(originalLanguage); | ||
| }); | ||
|
|
||
| it("hides the entrance cue until a named part is selected", () => { | ||
| setNavigatorLanguage("en-US"); | ||
| render(<Workspace song={createDemoRehearsalSong()} />); | ||
|
|
||
| expect(screen.queryByTestId("selected-part-entrance-cue")).toBeNull(); | ||
| }); | ||
|
|
||
| it("names the selected bass part's transition as the next entrance", () => { | ||
| setNavigatorLanguage("en-US"); | ||
| render(<Workspace song={createDemoRehearsalSong()} />); | ||
| fireEvent.click(screen.getByRole("tab", { name: "Bass Guitar" })); | ||
|
|
||
| const callout = screen.getByTestId("selected-part-entrance-cue"); | ||
| expect(callout).toHaveTextContent("Tonight's entrance cue"); | ||
| expect(callout).toHaveTextContent( | ||
| "Catch this transition in verse before Bass Guitar enters: Hold through the pickup before the downbeat." | ||
| ); | ||
| }); | ||
|
|
||
| it("names the selected vocal lyric as the next entrance", () => { | ||
| setNavigatorLanguage("en-US"); | ||
| render(<Workspace song={createDemoRehearsalSong()} />); | ||
| fireEvent.click(screen.getByRole("tab", { name: "Lead Vocal" })); | ||
|
|
||
| expect(screen.getByTestId("selected-part-entrance-cue")).toHaveTextContent( | ||
| 'Listen for "city lights" in verse, then Lead Vocal enters.' | ||
| ); | ||
| }); | ||
|
|
||
| it("keeps Korean copy particle-safe for a Latin role name", () => { | ||
| setNavigatorLanguage("ko-KR"); | ||
| render(<Workspace song={createDemoRehearsalSong()} />); | ||
| fireEvent.click(screen.getByRole("tab", { name: "Bass Guitar" })); | ||
|
|
||
| const callout = screen.getByTestId("selected-part-entrance-cue"); | ||
| expect(callout).toHaveTextContent("오늘 이 파트의 첫 입장 큐"); | ||
| expect(callout).toHaveTextContent("Bass Guitar 파트"); | ||
| expect(callout).not.toHaveTextContent("Bass Guitar으로"); | ||
| expect(callout).toHaveTextContent( | ||
| "verse에서 이 전환을 잡고 Bass Guitar 파트로 들어오세요: Hold through the pickup before the downbeat." | ||
| ); | ||
| }); | ||
|
|
||
| it("tells the player to confirm a missing cue instead of hiding the next action", () => { | ||
| setNavigatorLanguage("en-US"); | ||
| const song = createDemoRehearsalSong(); | ||
| song.sections[0]!.roles[0] = { | ||
| ...song.sections[0]!.roles[0]!, | ||
| cue: { kind: "transition", value: "none" } | ||
| }; | ||
|
|
||
| render(<Workspace song={song} />); | ||
| fireEvent.click(screen.getByRole("tab", { name: "Bass Guitar" })); | ||
|
|
||
| expect(screen.getByTestId("selected-part-entrance-cue")).toHaveTextContent( | ||
| "This part still needs a trusted entrance cue. Confirm the lyric, count, or transition before the first entrance." | ||
| ); | ||
| }); | ||
|
|
||
| it("clears selected-part guidance when the next project no longer contains the selected role", () => { | ||
| setNavigatorLanguage("en-US"); | ||
| const firstSong = createDemoRehearsalSong(); | ||
| const { rerender } = render(<Workspace song={firstSong} />); | ||
| fireEvent.click(screen.getByRole("tab", { name: "Bass Guitar" })); | ||
|
|
||
| expect(screen.getByTestId("selected-part-entrance-cue")).toBeInTheDocument(); | ||
|
|
||
| const nextSong = createDemoRehearsalSong(); | ||
| nextSong.id = "replacement-project"; | ||
| nextSong.sections = nextSong.sections.map((section) => ({ | ||
| ...section, | ||
| roles: section.roles.filter((role) => role.id !== "bass-guitar") | ||
| })); | ||
|
|
||
| rerender(<Workspace song={nextSong} />); | ||
|
|
||
| expect(screen.queryByTestId("selected-part-entrance-cue")).toBeNull(); | ||
| expect(screen.queryByTestId("selected-part-first-pass")).toBeNull(); | ||
| }); | ||
| }); |
78 changes: 78 additions & 0 deletions
78
apps/desktop/src/features/workspace/Workspace.first-pass.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| import { fireEvent, render, screen } from "@testing-library/react"; | ||
| import { createDemoRehearsalSong } from "@bandscope/shared-types"; | ||
| import { afterEach, describe, expect, it } from "vitest"; | ||
| import { Workspace } from "./Workspace"; | ||
|
|
||
| const originalLanguage = navigator.language; | ||
|
|
||
| function setNavigatorLanguage(language: string) { | ||
| Object.defineProperty(navigator, "language", { | ||
| configurable: true, | ||
| value: language | ||
| }); | ||
| } | ||
|
|
||
| describe("Workspace selected-part first-pass take", () => { | ||
| afterEach(() => { | ||
| setNavigatorLanguage(originalLanguage); | ||
| }); | ||
|
|
||
| it("hides the first-pass take until a named part is selected", () => { | ||
| setNavigatorLanguage("en-US"); | ||
| render(<Workspace song={createDemoRehearsalSong()} />); | ||
|
|
||
| expect(screen.queryByTestId("selected-part-first-pass")).toBeNull(); | ||
| }); | ||
|
|
||
| it("names the selected bass part's simpler take as the first pass", () => { | ||
| setNavigatorLanguage("en-US"); | ||
| render(<Workspace song={createDemoRehearsalSong()} />); | ||
| fireEvent.click(screen.getByRole("tab", { name: "Bass Guitar" })); | ||
|
|
||
| const callout = screen.getByTestId("selected-part-first-pass"); | ||
| expect(callout).toHaveTextContent("Tonight's first-pass take"); | ||
| expect(callout).toHaveTextContent( | ||
| "First pass for Bass Guitar in verse: Stay on roots if the chorus entrance gets muddy. Play that simpler take before adding the rest." | ||
| ); | ||
| }); | ||
|
|
||
| it("names the selected vocal part's simpler take as the first pass", () => { | ||
| setNavigatorLanguage("en-US"); | ||
| render(<Workspace song={createDemoRehearsalSong()} />); | ||
| fireEvent.click(screen.getByRole("tab", { name: "Lead Vocal" })); | ||
|
|
||
| expect(screen.getByTestId("selected-part-first-pass")).toHaveTextContent( | ||
| "First pass for Lead Vocal in verse: Keep the sustained note centered; skip the ad-lib on the first pass. Play that simpler take before adding the rest." | ||
| ); | ||
| }); | ||
|
|
||
| it("keeps Korean copy particle-safe for a Latin role name", () => { | ||
| setNavigatorLanguage("ko-KR"); | ||
| render(<Workspace song={createDemoRehearsalSong()} />); | ||
| fireEvent.click(screen.getByRole("tab", { name: "Bass Guitar" })); | ||
|
|
||
| const callout = screen.getByTestId("selected-part-first-pass"); | ||
| expect(callout).toHaveTextContent("오늘 이 파트의 첫 간소화"); | ||
| expect(callout).toHaveTextContent("Bass Guitar 파트"); | ||
| expect(callout).not.toHaveTextContent("Bass Guitar으로"); | ||
| expect(callout).toHaveTextContent( | ||
| "verse에서 Bass Guitar 파트의 첫 패스: Stay on roots if the chorus entrance gets muddy. 나머지를 더하기 전에 그 간소화된 버전으로 연습하세요." | ||
| ); | ||
| }); | ||
|
|
||
| it("tells the player to confirm a missing first-pass take instead of hiding the next action", () => { | ||
| setNavigatorLanguage("en-US"); | ||
| const song = createDemoRehearsalSong(); | ||
| song.sections[0]!.roles[0] = { | ||
| ...song.sections[0]!.roles[0]!, | ||
| simplification: "none" | ||
| }; | ||
|
|
||
| render(<Workspace song={song} />); | ||
| fireEvent.click(screen.getByRole("tab", { name: "Bass Guitar" })); | ||
|
|
||
| expect(screen.getByTestId("selected-part-first-pass")).toHaveTextContent( | ||
| "This part still needs a trusted first-pass take. Confirm the simpler version before the first run." | ||
| ); | ||
| }); | ||
| }); | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.