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
254 lines
8.2 KiB
JSON
254 lines
8.2 KiB
JSON
{
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"$id": "https://beyond-kle.org/schemas/phy-layer.json",
|
|
"title": "PHY Layer - Physical Key Layout",
|
|
"description": "Physical positioning and sizing of keys",
|
|
"type": "object",
|
|
"required": ["version", "metadata", "profiles", "root"],
|
|
"properties": {
|
|
"version": {
|
|
"type": "string",
|
|
"pattern": "^\\d+\\.\\d+\\.\\d+$",
|
|
"description": "Schema version (semver)"
|
|
},
|
|
"metadata": {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": { "type": "string" },
|
|
"author": { "type": "string" },
|
|
"description": { "type": "string" },
|
|
"created": { "type": "string", "format": "date-time" },
|
|
"modified": { "type": "string", "format": "date-time" }
|
|
}
|
|
},
|
|
"kleCompatibility": {
|
|
"type": "object",
|
|
"description": "File-level opaque KLE fields kept for round-trip output (switchMount, switchBrand, switchType, backcolor, radii, background, css). Kept here for now; the eventual owning layer is deferred in backlog/README.md, PHY-P-6",
|
|
"additionalProperties": true
|
|
},
|
|
"profiles": {
|
|
"type": "array",
|
|
"minItems": 1,
|
|
"items": {
|
|
"$ref": "#/$defs/profile"
|
|
}
|
|
},
|
|
"root": {
|
|
"type": "array",
|
|
"items": {
|
|
"oneOf": [
|
|
{ "$ref": "#/$defs/key" },
|
|
{ "$ref": "#/$defs/cluster" },
|
|
{ "$ref": "#/$defs/component" },
|
|
{ "$ref": "#/$defs/variantGroup" }
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"$defs": {
|
|
"transform": {
|
|
"type": "object",
|
|
"description": "Placement relative to the parent: rotate by rotation around the local origin, then translate by (x, y)",
|
|
"required": ["x", "y", "rotation"],
|
|
"properties": {
|
|
"x": { "type": "number" },
|
|
"y": { "type": "number" },
|
|
"rotation": { "type": "number", "description": "Degrees" }
|
|
}
|
|
},
|
|
"profile": {
|
|
"type": "object",
|
|
"required": ["id", "name", "unitSize"],
|
|
"properties": {
|
|
"id": { "type": "string" },
|
|
"name": { "type": "string" },
|
|
"unitSize": {
|
|
"type": "object",
|
|
"description": "Millimeters per 1U",
|
|
"required": ["x", "y"],
|
|
"properties": {
|
|
"x": { "type": "number", "minimum": 0 },
|
|
"y": { "type": "number", "minimum": 0 }
|
|
}
|
|
},
|
|
"visualOffset": {
|
|
"type": "object",
|
|
"description": "Key top rectangle, in millimeters: inward distance from each edge of the outline, plus corner radius",
|
|
"required": ["top", "right", "bottom", "left", "radius"],
|
|
"properties": {
|
|
"top": { "type": "number" },
|
|
"right": { "type": "number" },
|
|
"bottom": { "type": "number" },
|
|
"left": { "type": "number" },
|
|
"radius": { "type": "number", "minimum": 0 }
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"key": {
|
|
"type": "object",
|
|
"required": ["type", "id", "profile", "transform", "size"],
|
|
"properties": {
|
|
"type": {
|
|
"type": "string",
|
|
"const": "key"
|
|
},
|
|
"id": {
|
|
"type": "string",
|
|
"description": "Unique key identifier"
|
|
},
|
|
"profile": {
|
|
"type": "string",
|
|
"description": "Reference to profile ID"
|
|
},
|
|
"transform": { "$ref": "#/$defs/transform" },
|
|
"size": {
|
|
"type": "object",
|
|
"description": "Local size. Not affected by the parent transform",
|
|
"required": ["w", "h"],
|
|
"properties": {
|
|
"w": { "type": "number", "minimum": 0 },
|
|
"h": { "type": "number", "minimum": 0 },
|
|
"x2": {
|
|
"type": "number",
|
|
"description": "Second rectangle for non-rectangular keys, relative to the local origin"
|
|
},
|
|
"y2": { "type": "number" },
|
|
"w2": { "type": "number", "minimum": 0 },
|
|
"h2": { "type": "number", "minimum": 0 }
|
|
}
|
|
},
|
|
"switchOffset": {
|
|
"type": "object",
|
|
"description": "Switch position, offset from the center of the (w, h) rectangle. Key local coordinates, unit U. Defaults to 0",
|
|
"required": ["x", "y"],
|
|
"properties": {
|
|
"x": { "type": "number" },
|
|
"y": { "type": "number" }
|
|
}
|
|
},
|
|
"partGroup": {
|
|
"type": "string",
|
|
"description": "Label grouping keys that use the same part. Not interpreted by Beyond KLE"
|
|
},
|
|
"orientation": {
|
|
"type": "integer",
|
|
"enum": [0, 90, 180, 270],
|
|
"default": 0,
|
|
"description": "Switch orientation in the key local coordinates"
|
|
},
|
|
"perKeyLED": {
|
|
"type": "boolean",
|
|
"default": false,
|
|
"description": "Whether a per-key LED is mounted"
|
|
},
|
|
"decal": { "type": "boolean", "default": false },
|
|
"ghost": { "type": "boolean", "default": false },
|
|
"labels": {
|
|
"type": "array",
|
|
"items": { "type": "string" },
|
|
"description": "Reference information. Used for matching on KLE JSON import"
|
|
},
|
|
"kleCompatibility": {
|
|
"type": "object",
|
|
"description": "Opaque KLE fields kept for round-trip output (c, t, p, a, f, f2, fa, l, n, sm, sb, st). Kept here for now; the eventual owning layer is deferred in backlog/README.md, PHY-P-6",
|
|
"additionalProperties": true
|
|
}
|
|
}
|
|
},
|
|
"component": {
|
|
"type": "object",
|
|
"description": "Non-key part placed by the designer (LED, OLED, encoder, screw hole, ...)",
|
|
"required": ["type", "id", "transform"],
|
|
"properties": {
|
|
"type": {
|
|
"type": "string",
|
|
"const": "component"
|
|
},
|
|
"id": { "type": "string" },
|
|
"transform": { "$ref": "#/$defs/transform" },
|
|
"partGroup": {
|
|
"type": "string",
|
|
"description": "Names starting with @ are reserved for editor rendering"
|
|
}
|
|
}
|
|
},
|
|
"variantGroup": {
|
|
"type": "object",
|
|
"description": "Mutually exclusive layout alternatives. Children are variants only",
|
|
"required": ["type", "id", "name", "transform", "default", "children"],
|
|
"properties": {
|
|
"type": {
|
|
"type": "string",
|
|
"const": "variantGroup"
|
|
},
|
|
"id": { "type": "string" },
|
|
"name": {
|
|
"type": "string",
|
|
"description": "Heading shown for the choice in the editor"
|
|
},
|
|
"transform": { "$ref": "#/$defs/transform" },
|
|
"default": {
|
|
"type": "string",
|
|
"description": "id of the variant used when a single layout must be chosen"
|
|
},
|
|
"children": {
|
|
"type": "array",
|
|
"minItems": 2,
|
|
"description": "Display order",
|
|
"items": { "$ref": "#/$defs/variant" }
|
|
}
|
|
}
|
|
},
|
|
"variant": {
|
|
"type": "object",
|
|
"description": "One alternative. Appears only directly under a variantGroup. Carries no transform",
|
|
"required": ["type", "id", "name", "children"],
|
|
"properties": {
|
|
"type": {
|
|
"type": "string",
|
|
"const": "variant"
|
|
},
|
|
"id": { "type": "string" },
|
|
"name": { "type": "string" },
|
|
"children": {
|
|
"type": "array",
|
|
"description": "May be empty",
|
|
"items": {
|
|
"oneOf": [
|
|
{ "$ref": "#/$defs/key" },
|
|
{ "$ref": "#/$defs/cluster" },
|
|
{ "$ref": "#/$defs/component" },
|
|
{ "$ref": "#/$defs/variantGroup" }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"cluster": {
|
|
"type": "object",
|
|
"required": ["type", "id", "transform", "children"],
|
|
"properties": {
|
|
"type": {
|
|
"type": "string",
|
|
"const": "cluster"
|
|
},
|
|
"id": { "type": "string" },
|
|
"name": { "type": "string" },
|
|
"transform": { "$ref": "#/$defs/transform" },
|
|
"children": {
|
|
"type": "array",
|
|
"items": {
|
|
"oneOf": [
|
|
{ "$ref": "#/$defs/key" },
|
|
{ "$ref": "#/$defs/cluster" },
|
|
{ "$ref": "#/$defs/component" },
|
|
{ "$ref": "#/$defs/variantGroup" }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|