From 85eb2c258ce6353ed372723c84f3831b92cb2ff9 Mon Sep 17 00:00:00 2001 From: Thales <> Date: Mon, 21 Sep 2026 10:51:15 +0100 Subject: [PATCH] Name the structure toggle for the import it changes A reporter clicked "Song structure" on an open track, watched the button change colour, and saw the sections bar stay exactly where it was (#634). The button and the bar are unrelated. The button is a server-side flag the pipeline reads once per job, so it can only affect the next import; the bar is a permanent part of the studio showing whatever sections a track already has, detected or added by hand. Nothing the button does can reach a track that has already been separated. Two things made that read as a fault. The name described the structure rather than the work, so it looked like the switch that shows and hides the bar. And the toggle clears itself every time a song loads, since an inference pass costs minutes of CPU and should never be inherited from the last track: the user therefore finds it off after an import, clicks it expecting off, and gets on. So it is called "Detect structure" now, and the tooltip says it applies to the next import rather than to an open track, and points at the control that does own the bar: "Sections" in the Collapse row, which has been there since before this was reported. No behaviour changes. The title is three sentences, which is right for a tooltip and wrong for a screen reader, so structure.toggleAria carries a short label instead of pointing at the same key. Ten tables, each naming its own locale's menu path rather than the English one. Two tests: that the toggle leaves the sections bar alone in both directions while the panel toggle still hides it, and that the label names the import. The first is the reported behaviour, the second is the fix. Verified: pytest (1078 passing), ruff, bandit, the full Playwright suite (183), node --check, the JS unit tests, i18n coverage clean at 525 keys per table, and cargo fmt, clippy and test unchanged at 99. Co-Authored-By: Claude Opus 5 --- static/index.html | 13 ++++-- static/js/i18n.js | 50 ++++++++++++++---------- tests/e2e/song-structure-toggle.spec.mjs | 39 ++++++++++++++++++ 3 files changed, 79 insertions(+), 23 deletions(-) diff --git a/static/index.html b/static/index.html index d5d25858..37e99874 100644 --- a/static/index.html +++ b/static/index.html @@ -123,10 +123,17 @@ - diff --git a/static/js/i18n.js b/static/js/i18n.js index cae25a97..3687a965 100644 --- a/static/js/i18n.js +++ b/static/js/i18n.js @@ -231,8 +231,9 @@ const en = { "sections.clear": "Clear", "sections.clearAria": "Clear all sections", "sections.clearConfirm": "Confirm?", - "structure.toggle": "Song structure", - "structure.toggleTitle": "Experimental Song Structure Extraction: automatically label intro, verse and chorus after a split", + "structure.toggle": "Detect structure", + "structure.toggleTitle": "Experimental. Labels intro, verse and chorus while a track is imported. Applies to the next import, not to a track that is already open. To hide the sections bar, use Collapse > Sections.", + "structure.toggleAria": "Detect song structure on import (experimental)", "structure.experimental": "Experimental", "doc.title": "StemDeck — split any track into stems", @@ -848,8 +849,9 @@ const pl = { "sections.clear": "Wyczyść", "sections.clearAria": "Wyczyść wszystkie sekcje", "sections.clearConfirm": "Potwierdzić?", - "structure.toggle": "Struktura utworu", - "structure.toggleTitle": "Eksperymentalne wykrywanie struktury utworu: automatycznie oznacza intro, zwrotkę i refren po podziale", + "structure.toggle": "Wykryj strukturę", + "structure.toggleTitle": "Eksperymentalne. Oznacza intro, zwrotkę i refren podczas importu utworu. Dotyczy następnego importu, a nie utworu już otwartego. Aby ukryć pasek sekcji, użyj Zwiń > Sekcje.", + "structure.toggleAria": "Wykryj strukturę utworu podczas importu (eksperymentalne)", "structure.experimental": "Eksperymentalne", "doc.title": "StemDeck — rozdziel dowolny utwór na ścieżki", @@ -1455,8 +1457,9 @@ const ja = { "sections.clear": "クリア", "sections.clearAria": "すべてのセクションを削除", "sections.clearConfirm": "確認?", - "structure.toggle": "曲の構成", - "structure.toggleTitle": "実験的な楽曲構造抽出: 分離後にイントロ、Aメロ、サビを自動でラベル付けします", + "structure.toggle": "構成を検出", + "structure.toggleTitle": "実験的機能です。読み込み時にイントロ、Aメロ、サビを自動でラベル付けします。次回の読み込みに適用され、すでに開いている曲には影響しません。セクションバーを隠すには「折りたたむ > セクション」を使ってください。", + "structure.toggleAria": "読み込み時に曲の構成を検出(実験的)", "structure.experimental": "試験的", "doc.title": "StemDeck — トラックをパートごとに分離", @@ -2037,8 +2040,9 @@ const zhHans = { "sections.clear": "清除", "sections.clearAria": "清除所有段落", "sections.clearConfirm": "确认?", - "structure.toggle": "歌曲结构", - "structure.toggleTitle": "实验性歌曲结构提取: 分离后自动标记前奏、主歌和副歌", + "structure.toggle": "检测结构", + "structure.toggleTitle": "实验性功能。在导入时标记前奏、主歌和副歌。仅对下次导入生效,不影响已经打开的歌曲。要隐藏段落栏,请使用“折叠 > 段落”。", + "structure.toggleAria": "导入时检测歌曲结构(实验性)", "structure.experimental": "实验性", "doc.title": "StemDeck — 将任意曲目分离为音轨", @@ -2619,8 +2623,9 @@ const de = { "sections.clear": "Löschen", "sections.clearAria": "Alle Abschnitte löschen", "sections.clearConfirm": "Sicher?", - "structure.toggle": "Songstruktur", - "structure.toggleTitle": "Experimentelle Songstruktur-Erkennung: beschriftet Intro, Strophe und Refrain nach dem Trennen automatisch", + "structure.toggle": "Struktur erkennen", + "structure.toggleTitle": "Experimentell. Beschriftet Intro, Strophe und Refrain beim Importieren. Gilt für den nächsten Import, nicht für einen bereits geöffneten Titel. Zum Ausblenden der Abschnittsleiste: Einklappen > Abschnitte.", + "structure.toggleAria": "Songstruktur beim Import erkennen (experimentell)", "structure.experimental": "Experimentell", "doc.title": "StemDeck — jeden Track in Stems zerlegen", @@ -3212,8 +3217,9 @@ const pt = { "sections.clear": "Limpar", "sections.clearAria": "Limpar todas as seções", "sections.clearConfirm": "Confirmar?", - "structure.toggle": "Estrutura da música", - "structure.toggleTitle": "Extração experimental da estrutura da música: marca intro, verso e refrão automaticamente após a separação", + "structure.toggle": "Detectar estrutura", + "structure.toggleTitle": "Experimental. Marca intro, verso e refrão durante a importação. Vale para a próxima importação, não para uma música já aberta. Para esconder a barra de seções, use Recolher > Seções.", + "structure.toggleAria": "Detectar a estrutura da música na importação (experimental)", "structure.experimental": "Experimental", "doc.title": "StemDeck — separe qualquer faixa em stems", @@ -3807,8 +3813,9 @@ const id = { "sections.clear": "Hapus", "sections.clearAria": "Hapus semua bagian", "sections.clearConfirm": "Yakin?", - "structure.toggle": "Struktur lagu", - "structure.toggleTitle": "Ekstraksi struktur lagu eksperimental: menandai intro, bait, dan refrein secara otomatis setelah pemisahan", + "structure.toggle": "Deteksi struktur", + "structure.toggleTitle": "Eksperimental. Menandai intro, bait, dan refrein saat lagu diimpor. Berlaku untuk impor berikutnya, bukan untuk lagu yang sudah terbuka. Untuk menyembunyikan bilah bagian, gunakan Ciutkan > Bagian.", + "structure.toggleAria": "Deteksi struktur lagu saat impor (eksperimental)", "structure.experimental": "Eksperimental", "doc.title": "StemDeck — pisahkan trek apa pun menjadi stem", @@ -4389,8 +4396,9 @@ const fr = { "sections.clear": "Effacer", "sections.clearAria": "Effacer toutes les sections", "sections.clearConfirm": "Confirmer ?", - "structure.toggle": "Structure du morceau", - "structure.toggleTitle": "Extraction expérimentale de la structure: étiquette automatiquement intro, couplet et refrain après la séparation", + "structure.toggle": "Détecter la structure", + "structure.toggleTitle": "Expérimental. Étiquette intro, couplet et refrain pendant l'import. S'applique au prochain import, pas à un morceau déjà ouvert. Pour masquer la barre des sections, utilisez Réduire > Sections.", + "structure.toggleAria": "Détecter la structure du morceau à l'import (expérimental)", "structure.experimental": "Expérimental", "doc.title": "StemDeck — séparez n'importe quel morceau en pistes", @@ -5100,8 +5108,9 @@ const es = { "sections.clear": "Borrar", "sections.clearAria": "Borrar todas las secciones", "sections.clearConfirm": "¿Confirmar?", - "structure.toggle": "Estructura de la canción", - "structure.toggleTitle": "Extracción experimental de la estructura: etiqueta automáticamente intro, verso y estribillo tras la separación", + "structure.toggle": "Detectar estructura", + "structure.toggleTitle": "Experimental. Etiqueta intro, verso y estribillo durante la importación. Se aplica a la próxima importación, no a una canción ya abierta. Para ocultar la barra de secciones, usa Contraer > Secciones.", + "structure.toggleAria": "Detectar la estructura de la canción al importar (experimental)", "structure.experimental": "Experimental", "doc.title": "StemDeck — separa cualquier pista en stems", @@ -5716,8 +5725,9 @@ const ko = { "sections.clear": "지우기", "sections.clearAria": "모든 구간 지우기", "sections.clearConfirm": "정말요?", - "structure.toggle": "곡 구조", - "structure.toggleTitle": "실험적 곡 구조 분석: 분리한 뒤 인트로, 벌스, 코러스를 자동으로 표시해요", + "structure.toggle": "구조 감지", + "structure.toggleTitle": "실험적 기능이에요. 가져오는 동안 인트로, 벌스, 코러스를 표시해요. 이미 열려 있는 곡이 아니라 다음에 가져오는 곡부터 적용돼요. 구간 막대를 숨기려면 접기 > 구간을 사용하세요.", + "structure.toggleAria": "가져올 때 곡 구조 감지 (실험적)", "structure.experimental": "실험적", "doc.title": "StemDeck, 어떤 곡이든 스템으로 분리", diff --git a/tests/e2e/song-structure-toggle.spec.mjs b/tests/e2e/song-structure-toggle.spec.mjs index c0493f09..a966c5f5 100644 --- a/tests/e2e/song-structure-toggle.spec.mjs +++ b/tests/e2e/song-structure-toggle.spec.mjs @@ -57,4 +57,43 @@ test.describe("song structure toggle", () => { await expect(button(page)).toHaveAttribute("aria-pressed", "false"); await expect.poll(() => setting(page)).toBe(false); }); + + // Two controls, one word between them. This one runs a pass at import time; + // the sections bar is shown and hidden by "Sections" in the Collapse row. A + // reporter read the first as the second, clicked it on an open track, and saw + // a button change colour while nothing else moved (#634). + test("it does not show or hide the sections bar, which has its own control", async ({ page }) => { + await openStudio(page, { tauri: true }); + const app = page.locator(".app"); + const ribbon = page.locator(".daw-section-ribbon"); + const panelToggle = page.locator('.daw-panel-toggle[data-panel="sections"]'); + + await expect(ribbon).toBeVisible(); + + // Both directions: the bar is still there whether the toggle goes on or off. + await button(page).click(); + await expect(button(page)).toHaveAttribute("aria-pressed", "true"); + await expect(ribbon).toBeVisible(); + await button(page).click(); + await expect(button(page)).toHaveAttribute("aria-pressed", "false"); + await expect(ribbon).toBeVisible(); + + // And the control that does own the bar still owns it. + await panelToggle.click(); + await expect(app).toHaveClass(/panel-sections-off/); + await panelToggle.click(); + await expect(app).not.toHaveClass(/panel-sections-off/); + await expect(ribbon).toBeVisible(); + }); + + // The label is the fix for #634. Naming the work it does at import time is + // what stops it reading as the switch for the sections bar, so it is pinned + // rather than left to the next person tidying up wording. + test("the button names the import, not the sections bar", async ({ page }) => { + await openStudio(page, { tauri: true }); + + await expect(button(page)).toContainText(/detect/i); + await expect(button(page)).toHaveAttribute("aria-label", /import/i); + await expect(button(page)).toHaveAttribute("title", /next import/i); + }); });