mock: PHY Editorを作り直す(T-29)

provisional/phy-editor-feedback.md の合意事項と phy-editor-keybindings.md の割り当て案に沿って、PHY 画面を一から書き直した。
App.vue は共通のシェル(入出力・履歴・タブ)だけにし、PHY は src/ui/phy/ に、コンポーネント・composable・純粋モジュール(logic.ts, viewport.ts)を分けて置いた。

- 画面確認で見つかった不具合(読み込み時の全体表示、Shift+クリック、削除の確認の大きさ、Alt+↑、フォーカス中の二重描画、長押し中の選択など)を直し、キャンバス上の実際の操作で確かめる e2e を足した
- 階層パネルでクラスターの子を余分な折りたたみで包まず、件数を配下のキー・部品の数にそろえた
- 実装は Codex(Luna)。検証: vue-tsc 0件、Vitest 64件、Playwright 30件、prettier --check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VbfgHT848s3JbuFw8MKFF8
This commit is contained in:
2026-10-07 20:54:37 +09:00
co-authored by Claude Opus 5.5
parent beac52c20b
commit 4d1eb23cf8
24 changed files with 3807 additions and 1131 deletions
+10 -13
View File
@@ -5,14 +5,11 @@ test.beforeEach(async ({ page }) => {
await page.getByRole("button", { name: "IDX", exact: true }).click();
});
test("register unindexed key, reorder its output number, and undo", async ({ page }) => {
test("new PHY key is registered at the end, reorder its output number, and undo", async ({ page }) => {
await page.getByRole("button", { name: "PHY", exact: true }).click();
await page.getByRole("button", { name: "キー追加" }).click();
await page.getByRole("button", { name: "キーを足す" }).click();
const keyId = await page.locator('[data-node-type="key"]').last().getAttribute("data-node-id");
await page.getByRole("button", { name: "IDX", exact: true }).click();
const unregisteredRow = page.locator(".idx-all-keys li.unregistered").first();
const keyId = (await unregisteredRow.locator("b").innerText()).trim();
await unregisteredRow.getByRole("button", { name: "登録" }).click();
const row = page.getByTestId(`idx-row-${keyId}`);
await expect(row).toBeVisible();
const previewKey = page.locator(`[data-testid="keyboard-view"] [data-key-id="${keyId}"] text`);
@@ -28,15 +25,15 @@ test("output numbers recalculate after switching to a variant that excludes orde
page,
}) => {
await page.getByRole("button", { name: "PHY", exact: true }).click();
const treeKeys = page.locator(".tree-node").filter({ hasText: "キー ·" });
const firstId = await treeKeys.nth(0).getAttribute("data-tree-id");
const secondId = await treeKeys.nth(1).getAttribute("data-tree-id");
await treeKeys.nth(0).click();
await treeKeys.nth(1).click({ modifiers: ["Shift"] });
await page.getByRole("button", { name: "選択からVariantGroup" }).click();
const keys = page.locator('[data-node-type="key"]');
const firstId = await keys.nth(0).getAttribute("data-node-id");
const secondId = await keys.nth(1).getAttribute("data-node-id");
await keys.nth(0).click();
await keys.nth(1).click({ modifiers: ["Shift"] });
await page.getByRole("button", { name: "選択からバリアントグループ" }).click();
await page.getByRole("button", { name: "IDX", exact: true }).click();
await expect(page.getByTestId(`idx-row-${firstId}`)).toContainText("出力 0");
await page.getByLabel("Layout 出力バリアント").selectOption({ label: "Option B" });
await page.getByLabel("Variant Group 出力バリアント").selectOption({ label: "Option B" });
await expect(page.getByTestId(`idx-row-${firstId}`)).toContainText("出力対象外");
await expect(page.getByTestId(`idx-row-${secondId}`)).toContainText("出力対象外");
const remaining = page.locator(".idx-all-keys .idx-preview").filter({ hasText: "出力 0" });