From 4729517deb51df42f0d35f6df7d65b0c0d11d414 Mon Sep 17 00:00:00 2001 From: hitalin Date: Sat, 10 Oct 2026 11:06:34 +0900 Subject: [PATCH 1/9] =?UTF-8?q?fix(perf):=20=E3=82=A2=E3=83=8B=E3=83=A1?= =?UTF-8?q?=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3=E5=80=8D=E7=8E=87=E3=81=AE?= =?UTF-8?q?=E5=9F=BA=E6=BA=96=E6=99=82=E9=96=93=E3=82=92=20global.css=20?= =?UTF-8?q?=E3=81=AB=E6=8F=83=E3=81=88=E3=80=81medium=20=E3=82=92=E8=B6=B3?= =?UTF-8?q?=E3=81=99=20(#1224)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 倍率を掛ける元の fast / base が global.css より長く、medium が入っていなかったため、 倍率 1 以外で出入りの見た目と閉じきるまでの待ち時間が食い違っていた。 global.css と一致することをテストで検査する。 Co-Authored-By: Claude Opus 5.5 --- src/stores/performanceData.test.ts | 21 +++++++++++++++++++++ src/stores/performanceData.ts | 5 +++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/stores/performanceData.test.ts b/src/stores/performanceData.test.ts index d3f864286..8075f3308 100644 --- a/src/stores/performanceData.test.ts +++ b/src/stores/performanceData.test.ts @@ -1,5 +1,8 @@ +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' import { describe, expect, it } from 'vitest' import { + CSS_BASE_DURATIONS, categoryKeys, detectPosition, FADER_CATEGORIES, @@ -86,3 +89,21 @@ describe('detectPosition', () => { expect(found.t).toBeLessThanOrEqual(1) }) }) + +describe('CSS_BASE_DURATIONS', () => { + // 倍率を掛ける元が global.css とずれると、倍率 1 以外で見た目の長さと + // 閉じきるまでの待ち時間が食い違う (#1224) + it('global.css の :root の --nd-duration-* と一致する', () => { + const css = readFileSync( + resolve(import.meta.dirname, '../styles/global.css'), + 'utf8', + ) + const fromCss: Record = {} + for (const [, name, sec] of css.matchAll( + /(--nd-duration-[\w-]+):\s*([\d.]+)s;/g, + )) { + if (name && sec) fromCss[name] ??= Number(sec) + } + expect(CSS_BASE_DURATIONS).toEqual(fromCss) + }) +}) diff --git a/src/stores/performanceData.ts b/src/stores/performanceData.ts index b9e23aa5a..6b36c4fe8 100644 --- a/src/stores/performanceData.ts +++ b/src/stores/performanceData.ts @@ -1064,8 +1064,9 @@ export function detectSliderPosition(cfg: PerformanceConfig): number | null { /** Base durations (seconds) matching global.css :root values. */ export const CSS_BASE_DURATIONS: Record = { - '--nd-duration-fast': 0.15, - '--nd-duration-base': 0.2, + '--nd-duration-fast': 0.1, + '--nd-duration-base': 0.15, + '--nd-duration-medium': 0.2, '--nd-duration-slow': 0.28, '--nd-duration-slower': 0.38, '--nd-duration-tl-enter': 0.5, From 8c740dc1467e24f579446602f6e2254a1e23255e Mon Sep 17 00:00:00 2001 From: hitalin Date: Sat, 10 Oct 2026 11:08:53 +0900 Subject: [PATCH 2/9] =?UTF-8?q?fix(tasks):=20ID=20=E3=81=8C=E7=A9=BA?= =?UTF-8?q?=E3=83=BB=E4=BD=BF=E3=81=88=E3=81=AA=E3=81=84=E6=96=87=E5=AD=97?= =?UTF-8?q?=E3=81=AE=E3=81=A8=E3=81=8D=E3=82=82=E6=AC=84=E3=81=AE=E7=9B=B4?= =?UTF-8?q?=E4=B8=8B=E3=81=AB=E4=BF=9D=E5=AD=98=E3=81=95=E3=82=8C=E3=81=AA?= =?UTF-8?q?=E3=81=84=E7=90=86=E7=94=B1=E3=82=92=E5=87=BA=E3=81=99=20(#1225?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重複のときだけ警告していたため、空や不正な文字の ID では理由が見えないまま 自動保存が止まっていた。規則は parseTasks と同じ。 Co-Authored-By: Claude Opus 5.5 --- locales/en-US.source.json | 2 ++ locales/en-US.yml | 2 ++ locales/ja-JP.yml | 2 ++ .../window/TasksEditorContent.dom.test.ts | 29 +++++++++++++++++++ src/components/window/TasksEditorContent.vue | 10 ++++++- src/i18n/locale.generated.ts | 4 +++ 6 files changed, 48 insertions(+), 1 deletion(-) diff --git a/locales/en-US.source.json b/locales/en-US.source.json index 80dc5fa01..7a0780537 100644 --- a/locales/en-US.source.json +++ b/locales/en-US.source.json @@ -2536,8 +2536,10 @@ "_tasksEditorContent.dragToReorder": "ffdd225c08b2", "_tasksEditorContent.duplicateId": "122f9194a900", "_tasksEditorContent.empty": "fcc12e0330c4", + "_tasksEditorContent.emptyId": "23bf92dfde94", "_tasksEditorContent.groupPlaceholder": "c6995d781d05", "_tasksEditorContent.inputFields": "09e30c9f6451", + "_tasksEditorContent.invalidId": "938867c0566b", "_tasksEditorContent.label": "7b06e1c8563f", "_tasksEditorContent.labelPlaceholder": "e23186b35e16", "_tasksEditorContent.loadFailed": "e015910cc54e", diff --git a/locales/en-US.yml b/locales/en-US.yml index 4d7439cd8..f0e9e162b 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1860,6 +1860,8 @@ _tasksEditorContent: saveFailed: "Couldn’t save: {error}" objectRequired: "An object ({}) is required" duplicateId: "Another task already uses this ID. Changes are not saved while IDs are duplicated." + emptyId: "Enter an ID. Changes are not saved while it is empty." + invalidId: "IDs may contain only letters, digits, _ and -. Changes are not saved until this is fixed." resetFailed: "Couldn’t reset: {error}" newTaskLabel: "New task" newInputPrompt: "Enter a value" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index fdee7485b..dc46bdb3d 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1621,6 +1621,8 @@ _tasksEditorContent: saveFailed: "保存に失敗しました: {error}" objectRequired: "オブジェクト ({}) が必要です" duplicateId: "この ID は他のタスクでも使われています。重複している間は保存されません" + emptyId: "ID を入力してください。空の間は保存されません" + invalidId: "ID に使えるのは英数字・_・- だけです。直すまで保存されません" resetFailed: "リセットに失敗しました: {error}" newTaskLabel: "新しいタスク" newInputPrompt: "入力してください" diff --git a/src/components/window/TasksEditorContent.dom.test.ts b/src/components/window/TasksEditorContent.dom.test.ts index b2495e5e5..45066d0bc 100644 --- a/src/components/window/TasksEditorContent.dom.test.ts +++ b/src/components/window/TasksEditorContent.dom.test.ts @@ -93,3 +93,32 @@ describe('TasksEditorContent — ID の重複', () => { wrapper.unmount() }) }) + +describe('TasksEditorContent — ID の形式', () => { + beforeEach(() => { + setActivePinia(createPinia()) + }) + + it.each([ + ['空', ''], + ['使えない文字', 'my task!'], + ])('ID を%sにすると欄の直下にエラーを出し、直すと消える', async (_, bad) => { + const wrapper = await mountEditor() + await wrapper.find('button[aria-expanded]').trigger('click') + await flushPromises() + + const input = wrapper.find('input[placeholder="my-task"]') + const original = (input.element as HTMLInputElement).value + await input.setValue(bad) + await flushPromises() + + expect(input.attributes('aria-invalid')).toBe('true') + const describedBy = input.attributes('aria-describedby') + expect(document.getElementById(describedBy ?? '')?.textContent).toBeTruthy() + + await input.setValue(original) + await flushPromises() + expect(input.attributes('aria-invalid')).toBeUndefined() + wrapper.unmount() + }) +}) diff --git a/src/components/window/TasksEditorContent.vue b/src/components/window/TasksEditorContent.vue index 676e21bdc..0b5d79f90 100644 --- a/src/components/window/TasksEditorContent.vue +++ b/src/components/window/TasksEditorContent.vue @@ -220,6 +220,14 @@ const duplicateIds = computed(() => { return dup }) +// 保存を止める ID の誤りを、欄の直下に出す文言にする (規則は parseTasks と同じ) +function idError(id: string): string { + if (!id) return i18n.ts._tasksEditorContent.emptyId + if (!/^[\w-]+$/.test(id)) return i18n.ts._tasksEditorContent.invalidId + if (duplicateIds.value.has(id)) return i18n.ts._tasksEditorContent.duplicateId + return '' +} + // ── Visual edit helpers ── function uniqueId(base: string): string { const ids = new Set(visualTasks.value.map((t) => t.id)) @@ -578,7 +586,7 @@ function handleReset() { :class="$style.idInput" pattern="[\w-]+" placeholder="my-task" - :error="duplicateIds.has(t.id) ? i18n.ts._tasksEditorContent.duplicateId : ''" + :error="idError(t.id)" />