Files
Beyond-KLE/mock/src/domain/generator.ts
T
ewdernaandClaude Opus 5.5 fccbc28bee mock: 仮決定に沿ってモックを実装する(PHY Editor・IDX・ELC・MAP・Generator)
provisional/decisions.md の仮決定(T-1〜T-28)に従い、mock/ に Vue 3 + Vite + TypeScript のモックを置いた。
T-3 の1〜3(回転したクラスター内のドラッグと保存の往復、IDX・MAPとの連携、ELCと出力の対応表)まで。疑似デバイスとの往復(T-24)は未着手。

- KLE JSONの入出力は、KLE本家の serial.js の deserialize の結果を期待値とする fixture(tests/fixtures/kle-oracle.json)で照合した。本家のコードは取り込んでいない(T-7)
- 基本キーコードの表は QMK の data/constants/keycodes/keycodes_0.0.1_basic.hjson(1e340cd)から抽出した
- 実装は Codex(Luna)、レビューは Codex(Sol)。検証: Vitest 53件、Playwright 20件、vue-tsc、vite build
- TypeScript は vue-tsc が 7 系に未対応のため 6 系に固定した
- 操作性の改善(ELCの提供者の表示、一括配線とGeneratorのピン設定の引き継ぎ、キー内の文字のはみ出し)は別途行う

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AATqjRyp6rrnwTNdYw8XBT
2026-09-25 22:42:30 +09:00

103 lines
3.6 KiB
TypeScript

import type { ProjectFile } from "./project";
import { flattenKeys } from "./geometry";
import { validateProject, type ValidationIssue } from "./validate";
export type MockTarget = {
format: "mock-target";
version: "0.1.0";
layout: Array<{ keyId: string; x: number; y: number; rotation: number; w: number; h: number }>;
idx: string[];
matrix: { rowPins: string[]; colPins: string[]; rows: number[]; cols: number[] };
keymaps: Array<{ name: string; keycodes: string[] }>;
mapping: Array<{ number: number; keyId: string; row: number; col: number; label: string }>;
};
export type GeneratorResult = { target?: MockTarget; errors: ValidationIssue[] };
/** Generates preview output after validation, without modifying the project. T-2/T-19. */
export function generateMockTarget(p: ProjectFile): GeneratorResult {
const errors = validateProject(p).filter(
(x) => x.severity === "blocksOutput" || x.severity === "blocksSave",
);
if (!p.output.matrix)
errors.push({
severity: "blocksOutput",
code: "matrix-settings-missing",
message: "Matrix rowPins and colPins must be configured",
});
if (p.output.matrix?.kind && p.output.matrix.kind !== "matrix")
errors.push({
severity: "blocksOutput",
code: "unsupported-scan-mode",
message: `${p.output.matrix.kind} scan mode is not supported`,
});
if (errors.length) return { errors };
const selected = flattenKeys(p.phy, p.output.variantSelection),
active = new Set(selected.map((x) => x.key.id)),
order = p.idx.order.filter((id) => active.has(id)),
matrix = p.output.matrix!;
const rowIndex = new Map(matrix.rowPins.map((x, i) => [x, i])),
colIndex = new Map(matrix.colPins.map((x, i) => [x, i]));
const wiring = new Map(p.elc.keys.map((x) => [x.key, x]));
const row: number[] = [],
col: number[] = [];
for (const id of order) {
const w = wiring.get(id);
if (!w || w.detect !== "contact")
return {
errors: [
{
severity: "blocksOutput",
code: "unsupported-detection",
message: "Only contact matrix wiring is supported",
ids: [id],
},
],
};
const a = w.terminals.a,
b = w.terminals.b;
let r: number | undefined, c: number | undefined;
if (rowIndex.has(a) && colIndex.has(b)) {
r = rowIndex.get(a);
c = colIndex.get(b);
} else if (rowIndex.has(b) && colIndex.has(a)) {
r = rowIndex.get(b);
c = colIndex.get(a);
}
if (r === undefined || c === undefined)
return {
errors: [
{
severity: "blocksOutput",
code: "unsupported-matrix-wiring",
message: "Wiring is not a supported row/column matrix",
ids: [id],
},
],
};
row.push(r);
col.push(c);
}
const poses = new Map(selected.map((x) => [x.key.id, x]));
const keymaps = p.map.layers.map((l) => ({
name: l.name,
keycodes: order.map((id) => l.keymap.find((a) => a.keyId === id)!.keycode),
}));
const target: MockTarget = {
format: "mock-target",
version: "0.1.0",
layout: order.map((id) => {
const x = poses.get(id)!;
return { keyId: id, x: x.x, y: x.y, rotation: x.rotation, w: x.key.size.w, h: x.key.size.h };
}),
idx: order,
matrix: { rowPins: [...matrix.rowPins], colPins: [...matrix.colPins], rows: row, cols: col },
keymaps,
mapping: order.map((id, i) => ({
number: i,
keyId: id,
row: row[i]!,
col: col[i]!,
label: poses.get(id)!.key.labels?.find(Boolean) ?? "",
})),
};
return { target, errors: [] };
}