初期検討段階のドキュメントと運用ルールを取り込む
ルール策定前に書かれた設計ドキュメント7点を、以降の基準点として そのままの状態で記録する。CLAUDE.md のルール1〜4は未適用。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MdLT5x5ka5y6CaWLeE3EKb
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
---
|
||||
name: decide
|
||||
description: 設計上の決定を記録する。メインドキュメントの更新・検討済み案の記録・journal作成・コミットを一度に行う。
|
||||
disable-model-invocation: true
|
||||
argument-hint: [決定の要旨]
|
||||
---
|
||||
|
||||
# 決定を記録する
|
||||
|
||||
CLAUDE.md のルール1〜4に従い、決定を4箇所に分散して記録する。
|
||||
どれか1つでも欠けると後から追えなくなるので、全部やる。
|
||||
|
||||
## 手順
|
||||
|
||||
### 0. 決定内容を確認する
|
||||
|
||||
以下が揃っているか確認し、足りなければユーザーに聞く。推測で埋めない。
|
||||
|
||||
- 採用した案は何か
|
||||
- それによって**今後効く制約**は何か(却下理由ではなく、現在の状態として書ける文)
|
||||
- 落とした案があるか
|
||||
- この決定が未確認の推測に依存していないか
|
||||
|
||||
### 1. メインドキュメントを更新する
|
||||
|
||||
該当する直下の `*.md` を更新する。
|
||||
|
||||
- 採用した案・現在の設定を書く。「なぜAなのか」は1〜3行まで。
|
||||
- 「なぜBではないのか」は書かない。代わりに**制約文**として書く。
|
||||
- ✗ `B案はKLEパーサーが壊れるので却下した`
|
||||
- ○ `クラスター情報はKLE JSONに出力しない(KLE互換性の制約)`
|
||||
- 比較表・検討過程・ベンチマーク結果をここに書かない。
|
||||
|
||||
### 2. 落とした案を1行残す
|
||||
|
||||
メインドキュメント末尾の `## 検討済み・非採用` に追記する。
|
||||
|
||||
```
|
||||
- labels[9] へのID埋め込み (journal/2026-09-16-cluster-metadata.md)
|
||||
```
|
||||
|
||||
- 理由は書かない。journalファイル名だけ。
|
||||
- セクションが5行を超えたら、古いものから落とす。長くなるのは書きすぎのサイン。
|
||||
|
||||
### 3. journal を書く
|
||||
|
||||
`journal/YYYY-MM-DD-<topic>.md` を作る。トピックを詰め込まない(1決定1ファイル)。
|
||||
|
||||
構成は上から順に:
|
||||
|
||||
1. **結論** — 何を採用したか
|
||||
2. **案の比較** — 簡潔な表。メリット/デメリットを1行ずつ
|
||||
3. 検証ログ・根拠・捨てた案の詳細
|
||||
|
||||
冒頭20行で結論と選択肢が分かること。整形は最低限でよい。
|
||||
他ファイルへのリンクも、他ファイルからのリンクも張らない。
|
||||
|
||||
### 4. 推測を扱う
|
||||
|
||||
この決定が未確認の推測に依存する場合:
|
||||
|
||||
- メインドキュメント本文に `[推測:P-n]` を付ける
|
||||
- 末尾の `## 未確認事項` に項目を足し、**何を確認できれば事実に格上げできるか**を書く
|
||||
|
||||
逆に、この決定で検証が済んだ推測があれば、タグを外して `## 未確認事項` から削除する。
|
||||
本文タグと末尾項目は必ず対で操作する(片方だけ残さない)。
|
||||
|
||||
### 5. コミットする
|
||||
|
||||
メインドキュメントと journal を**同じコミット**に入れる。1コミット1決定。
|
||||
|
||||
メッセージ:
|
||||
|
||||
```
|
||||
<何を変えたか(1行)>
|
||||
|
||||
<なぜそうしたか 数行>
|
||||
|
||||
Journal: journal/2026-09-16-cluster-metadata.md
|
||||
```
|
||||
|
||||
「なぜ」の詳細はコミットメッセージに書ききらない。journal 側にある。
|
||||
|
||||
## 注意
|
||||
|
||||
- 決定が固まっていない段階では使わない。調査中は journal だけ書けばよい。
|
||||
- 複数の決定をまとめてコミットしない。分ける。
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
name: doc-lint
|
||||
description: ドキュメントがCLAUDE.mdの運用ルール1〜4に沿っているか検査する。推測タグと未確認事項の対応漏れ、journalへのリンク、比較表の混入などを検出する。
|
||||
argument-hint: [ファイル...]
|
||||
---
|
||||
|
||||
# ドキュメントのルール違反を検査する
|
||||
|
||||
機械的に判定できる項目はスクリプトで検出し、判断が要る項目だけ目視で見る。
|
||||
**検出して報告するだけで、勝手に直さない。** 修正は内容を確認してもらってから。
|
||||
|
||||
## 1. スクリプトを走らせる
|
||||
|
||||
```
|
||||
python .claude/skills/doc-lint/doc-lint.py [ファイル...]
|
||||
```
|
||||
|
||||
引数なしで直下の `*.md` と `journal/*.md` を全部見る。ERROR があれば終了コード 1。
|
||||
|
||||
検出する項目:
|
||||
|
||||
| 種別 | 内容 |
|
||||
|---|---|
|
||||
| ERROR | 本文の `[推測:P-n]` に対応する `## 未確認事項` の項目がない |
|
||||
| ERROR | `## 未確認事項` の項目に `- 格上げ条件:` の行がない |
|
||||
| ERROR | `## 未確認事項` のIDが重複している |
|
||||
| ERROR | journal へのリンク / journal から他ファイルへのリンク |
|
||||
| ERROR | `## 検討済み・非採用` の項目が journal ファイル名を参照していない |
|
||||
| ERROR | journal のファイル名が `YYYY-MM-DD-<topic>.md` 形式でない |
|
||||
| WARN | `## 検討済み・非採用` が5件を超えた |
|
||||
| WARN | journal の冒頭20行に結論が見当たらない |
|
||||
| WARN | メインドキュメントに比較表・優先度評価・却下案の記述らしきもの |
|
||||
|
||||
**本文タグのない `## 未確認事項` の項目はエラーにしない。** 「まだ本文に書けない未解決の問い」として正当。
|
||||
|
||||
WARN の散文検出は当たりが粗い。取りこぼしも誤検出もあるので、出た/出ないを結論にしない。
|
||||
|
||||
## 2. スクリプトで見られない項目を確認する
|
||||
|
||||
こちらが本体。ファイルを読んで判断する。
|
||||
|
||||
- **「なぜBではないのか」が理由として残っていないか。**
|
||||
却下理由ではなく、現在効いている制約として書かれているか。
|
||||
- ✗ `階層構造はKLEが非対応なので諦めた`
|
||||
- ○ `階層クラスターはKLE JSONに出力しない(KLE互換性の制約)`
|
||||
- **裏取りしていない記述にタグが付いていないか。** 検査項目の中で最も重要で、
|
||||
機械では判定できない。「〜できるはず」「〜と考えられる」「実現可能」で実際に試していない
|
||||
記述を探し、`[推測:P-n]` が付いているか確認する。
|
||||
- **同じ事実が複数ドキュメントに複製されていないか。** 片方だけ古くなる。
|
||||
- **journal の冒頭20行で、結論と選択肢が分かるか。** 見出しの有無ではなく中身を読む。
|
||||
- **コミット規約**(必要なら `git log --oneline` を見る)
|
||||
- 1コミット1決定になっているか
|
||||
- journal を伴う決定のメッセージに `Journal:` 行があるか
|
||||
|
||||
## 3. 報告する
|
||||
|
||||
ERROR / WARN / 目視で見つけたもの を分けて出す。
|
||||
修正案は出してよいが、適用は確認を取ってから。事実関係の誤りを見つけた場合も、直さず指摘する。
|
||||
|
||||
## 注意
|
||||
|
||||
- 運用ルール策定前に書かれた既存ドキュメントは、まだルール1〜4が適用されていない。
|
||||
WARN が大量に出るのは想定内で、再編(`/restructure-doc`)のタイミングで解消する。
|
||||
再編前のファイルの WARN を個別に直そうとしない。
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""CLAUDE.md の運用ルール1〜4のうち、機械的に判定できる項目を検査する。
|
||||
|
||||
使い方:
|
||||
python .claude/skills/doc-lint/doc-lint.py [ファイル...]
|
||||
引数なしなら リポジトリ直下の *.md と journal/*.md を対象にする。
|
||||
ERROR が1件でもあれば終了コード 1。
|
||||
"""
|
||||
import sys, re, os, io, glob
|
||||
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
|
||||
TAG = re.compile(r"\[推測:(P-\d+)\]")
|
||||
ENTRY = re.compile(r"^###\s+(P-\d+)\b")
|
||||
H2 = re.compile(r"^##\s+(.*?)\s*$")
|
||||
MDLINK = re.compile(r"\]\(([^)]+\.md[^)]*)\)")
|
||||
JOURNALFN= re.compile(r"^\d{4}-\d{2}-\d{2}-.+\.md$")
|
||||
PROMOTE = re.compile(r"^\s*[-*]\s*格上げ条件\s*[::]\s*\S")
|
||||
|
||||
# ルールブック自身は、アンチパターンを例として引用するため散文ヒューリスティックの対象外
|
||||
PROSE_EXEMPT = {"CLAUDE.md"}
|
||||
|
||||
issues = []
|
||||
def add(sev, path, line, msg):
|
||||
issues.append((sev, path, line, msg))
|
||||
|
||||
def load(path):
|
||||
"""コードフェンス内を空行に潰した (行番号, 本文) の列を返す。"""
|
||||
raw = io.open(path, encoding="utf-8").read().splitlines()
|
||||
out, fence = [], False
|
||||
for i, l in enumerate(raw, 1):
|
||||
if l.lstrip().startswith("```"):
|
||||
fence = not fence
|
||||
out.append((i, ""))
|
||||
continue
|
||||
out.append((i, "" if fence else l))
|
||||
return out
|
||||
|
||||
def section(lines, title):
|
||||
"""## <title> 配下の (行番号, 本文) を返す。無ければ None。"""
|
||||
start = None
|
||||
for idx, (n, l) in enumerate(lines):
|
||||
m = H2.match(l)
|
||||
if m and m.group(1) == title:
|
||||
start = idx + 1
|
||||
break
|
||||
if start is None:
|
||||
return None
|
||||
body = []
|
||||
for n, l in lines[start:]:
|
||||
if H2.match(l):
|
||||
break
|
||||
body.append((n, l))
|
||||
return body
|
||||
|
||||
def check(path):
|
||||
rel = path.replace(os.sep, "/")
|
||||
base = os.path.basename(rel)
|
||||
is_journal = rel.startswith("journal/")
|
||||
lines = load(path)
|
||||
|
||||
unconf = section(lines, "未確認事項")
|
||||
rejected = section(lines, "検討済み・非採用")
|
||||
unconf_span = set(n for n, _ in unconf) if unconf else set()
|
||||
|
||||
# --- ルール4: 推測タグと未確認事項の対応 ---
|
||||
entries = set()
|
||||
if unconf is not None:
|
||||
blocks = [] # [(pid, 行番号, [本文行])] 重複IDも個別に検査する
|
||||
for n, l in unconf:
|
||||
m = ENTRY.match(l)
|
||||
if m:
|
||||
pid = m.group(1)
|
||||
if pid in entries:
|
||||
add("ERROR", rel, n, "未確認事項のID %s が重複している" % pid)
|
||||
entries.add(pid)
|
||||
blocks.append((pid, n, []))
|
||||
elif blocks:
|
||||
blocks[-1][2].append(l)
|
||||
for pid, ln, body in blocks:
|
||||
if not any(PROMOTE.match(x) for x in body):
|
||||
add("ERROR", rel, ln,
|
||||
"%s に「- 格上げ条件:」の行がない(何を確認すれば事実に格上げできるか)" % pid)
|
||||
|
||||
for n, l in lines:
|
||||
if n in unconf_span:
|
||||
continue
|
||||
for pid in TAG.findall(l):
|
||||
if unconf is None or pid not in entries:
|
||||
add("ERROR", rel, n,
|
||||
"本文の [推測:%s] に対応する項目が ## 未確認事項 にない" % pid)
|
||||
# 逆向き(本文タグのない未確認事項)は「未解決の問い」として正当なのでエラーにしない
|
||||
|
||||
# --- ルール3: journal への/からのリンク禁止 ---
|
||||
for n, l in lines:
|
||||
for target in MDLINK.findall(l):
|
||||
if target.lstrip("./").startswith("journal/"):
|
||||
add("ERROR", rel, n, "journal へのリンクが張られている: %s" % target)
|
||||
elif is_journal:
|
||||
add("ERROR", rel, n, "journal から他ファイルへのリンクが張られている: %s" % target)
|
||||
|
||||
# --- ルール1: 検討済み・非採用 ---
|
||||
if rejected is not None:
|
||||
items = [(n, l) for n, l in rejected if l.strip().startswith(("-", "*"))]
|
||||
for n, l in items:
|
||||
if not re.search(r"\(journal/[^)]+\.md\)", l):
|
||||
add("ERROR", rel, n,
|
||||
"検討済み・非採用の項目が journal ファイル名を参照していない")
|
||||
if len(items) > 5:
|
||||
add("WARN", rel, items[5][0],
|
||||
"検討済み・非採用が %d 件(目安5件)。古いものから落とす" % len(items))
|
||||
|
||||
# --- journal 固有 ---
|
||||
if is_journal:
|
||||
if not JOURNALFN.match(base):
|
||||
add("ERROR", rel, 1, "journal のファイル名が YYYY-MM-DD-<topic>.md 形式でない")
|
||||
head = " ".join(l for n, l in lines[:20])
|
||||
if "結論" not in head:
|
||||
add("WARN", rel, 1, "冒頭20行に結論が見当たらない(結論を先頭に置く)")
|
||||
|
||||
# --- ルール1: 調査・比較の混入(要目視確認) ---
|
||||
if not is_journal and base not in PROSE_EXEMPT:
|
||||
# 検討済み・非採用 は却下案を列挙する場所なので対象外
|
||||
skip = set(n for n, _ in rejected) if rejected else set()
|
||||
for n, l in lines:
|
||||
if n in skip:
|
||||
continue
|
||||
if "メリット" in l and "デメリット" in l:
|
||||
add("WARN", rel, n, "比較表らしき記述(メリット/デメリット)。journal 行きでは?")
|
||||
elif re.search(r"却下|不採用|見送っ|も検討したが|案[A-Z]|[A-Z]案", l):
|
||||
add("WARN", rel, n, "却下案の検討過程らしき記述。制約文に書き換えるか journal へ")
|
||||
elif re.search(r"優先度", l) and l.lstrip().startswith("#"):
|
||||
add("WARN", rel, n, "優先度評価は journal 行きでは?")
|
||||
|
||||
def main(argv):
|
||||
targets = argv[1:]
|
||||
if not targets:
|
||||
targets = sorted(glob.glob("*.md")) + sorted(glob.glob("journal/*.md"))
|
||||
if not targets:
|
||||
print("対象ファイルなし"); return 0
|
||||
for t in targets:
|
||||
if os.path.isfile(t):
|
||||
check(t)
|
||||
else:
|
||||
print("見つからない: %s" % t)
|
||||
|
||||
errs = [i for i in issues if i[0] == "ERROR"]
|
||||
warns = [i for i in issues if i[0] == "WARN"]
|
||||
for sev, path, line, msg in errs + warns:
|
||||
print("%-5s %s:%d %s" % (sev, path, line, msg))
|
||||
print("\n%d files / ERROR %d / WARN %d" % (len(targets), len(errs), len(warns)))
|
||||
return 1 if errs else 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
name: restructure-doc
|
||||
description: 既存ドキュメント1ファイルをCLAUDE.mdのルール1〜4に沿って仕分ける。事実・推測・採用案を残し、調査過程をjournalへ移す。
|
||||
disable-model-invocation: true
|
||||
argument-hint: <ファイル名>
|
||||
---
|
||||
|
||||
# ドキュメントを再編する
|
||||
|
||||
運用ルール策定前に書かれたドキュメントを、CLAUDE.md のルール1〜4に沿った形に作り直す。
|
||||
**1回の実行で1ファイルだけ**扱う。
|
||||
|
||||
## 最重要の制約
|
||||
|
||||
**情報を失わせない。** この作業は「移動と再配置」であって、要約でも削除でもない。
|
||||
|
||||
- 内容を短くしない。判断に迷った記述は、消さずに残してユーザーに聞く。
|
||||
- メインドキュメントから外す記述は、必ず journal に移す。捨てない。
|
||||
- 元の主張を勝手に言い換えない。特に断定度を上げ下げしない。
|
||||
|
||||
## 手順
|
||||
|
||||
### 1. 全体を読む
|
||||
|
||||
対象ファイルを最初から最後まで読む。部分的に読んで判断しない。
|
||||
|
||||
### 2. 4分類する
|
||||
|
||||
各ブロック(見出し単位、または段落単位)を次のどれかに分ける。
|
||||
|
||||
| 分類 | 行き先 |
|
||||
|---|---|
|
||||
| a. 採用した案 / 現在の設定 / 設計に影響する制約 | メインドキュメントに残す |
|
||||
| b. 裏取りの済んだ事実 | メインドキュメントに残す(文中のどこでもよい) |
|
||||
| c. 未確認の推測・未解決の問い | メインドキュメント末尾の `## 未確認事項` |
|
||||
| d. 調査過程 / 案の比較 / 却下案 / 優先度評価 | `journal/` へ移す |
|
||||
|
||||
判定のヒント:
|
||||
|
||||
- メリット/デメリット表、優先度リスト、「〜も検討したが」 → d
|
||||
- 「〜できるはず」「〜と考えられる」「実現可能」で、実際に試していない → c
|
||||
- 外部仕様の記述(KLEのプロパティ定義など)は、出典を確認できていれば b、していなければ c
|
||||
|
||||
### 3. 分類結果を提示して承認を得る
|
||||
|
||||
**ここで必ず止まる。** ファイルを書き換える前に、分類の一覧をユーザーに見せて確認する。
|
||||
特に b と c の境目(裏取りできているか)は自分で判断せず聞く。
|
||||
|
||||
### 4. メインドキュメントを書き直す
|
||||
|
||||
- a を中心に再構成する。b は文中に自然に置く。
|
||||
- 「なぜBではないのか」の記述が残っていたら、**制約文**に書き換える。
|
||||
- ✗ `階層構造はKLEが非対応なので諦めた`
|
||||
- ○ `階層クラスターはKLE JSONに出力しない(KLE互換性の制約)`
|
||||
- 落とした案があれば末尾 `## 検討済み・非採用` に1案1行(理由なし、journalファイル名を参照)。
|
||||
|
||||
### 5. 未確認事項を立てる
|
||||
|
||||
c に分類したものを末尾の `## 未確認事項` にまとめる。
|
||||
|
||||
- 本文が前提にしている推測 → 本文に `[推測:P-n]` を付け、対応する項目を置く
|
||||
- まだ本文に書けない未解決の問い → 本文タグなしで項目だけ置く
|
||||
- 各項目に**格上げ条件**を必ず書く。思いつかないものはユーザーに聞く(空欄で残さない)
|
||||
|
||||
```markdown
|
||||
### P-1 KLEはlabels[9]の内容を保存時に保持する
|
||||
- 位置づけ: 本文「クラスター管理」が前提にしている
|
||||
- 格上げ条件: KLE本家でインポート→エクスポートし、labels[9]が残ることを確認する
|
||||
```
|
||||
|
||||
### 6. journal へ移す
|
||||
|
||||
d を `journal/YYYY-MM-DD-<元ファイル名>.md` に移す。
|
||||
|
||||
- 冒頭に**結論**(このファイルの再編で何が確定し、何を落としたか)
|
||||
- 次に案の比較(元ファイルの表をそのまま持ってきてよい)
|
||||
- 以下に元の調査記述
|
||||
- 整形は最低限でよい
|
||||
|
||||
### 7. コミットする
|
||||
|
||||
1ファイル1コミット。メインドキュメントと journal を同じコミットに入れる。
|
||||
|
||||
```
|
||||
<ファイル名> を運用ルールに沿って再編
|
||||
|
||||
<何を残し、何をjournalへ移したか 数行>
|
||||
|
||||
Journal: journal/2026-09-16-<元ファイル名>.md
|
||||
```
|
||||
|
||||
## 注意
|
||||
|
||||
- 複数ファイルを一度に触らない。ファイル間で内容が重複していると気づいた場合は、
|
||||
勝手に統合せず、指摘だけしてそのファイルの再編を終える。
|
||||
- 再編の途中で事実関係の誤りを見つけても、勝手に直さない。指摘する。
|
||||
@@ -0,0 +1,91 @@
|
||||
# Beyond KLE
|
||||
|
||||
キーボード設計のデータモデルとワークフローを再設計するプロジェクト。
|
||||
PHY(物理配置) / ELC(電気配線) / IDX(キー識別) / MAP(キーマップ) / IME(入力変換) の5層に分離する。
|
||||
|
||||
- 応答・ドキュメントは日本語。
|
||||
- 現状はコード未着手、設計ドキュメントのみ。
|
||||
|
||||
## ドキュメント地図
|
||||
|
||||
| ファイル | 内容 |
|
||||
|---|---|
|
||||
| `keyboard-design-layers.md` | レイヤー構造とツール分割方針(最上位) |
|
||||
| `PHYレイヤー.md` | PHY層の仕様 |
|
||||
| `phase1-json-schema.md` | 各層のJSON Schema |
|
||||
| `kle-compatibility.md` | KLEとの双方向変換仕様 |
|
||||
| `kle-compatibility-analysis.md` | KLE互換性の調査・案比較 |
|
||||
| `keyboard-making-workflow.md` | 既存の自作キーボード制作フローとの対応 |
|
||||
| `typescript-implementation-plan.md` | 実装計画・技術スタック |
|
||||
|
||||
**既存ドキュメントは下記ルールの策定前に書かれたもので、ルール1〜4は未適用。**
|
||||
全体の再編は別タスクとして行う。それまで各ファイルをメイン/journalのどちらに分類するかは未確定なので、
|
||||
指示なく移動・分割・書き直しをしない。
|
||||
|
||||
初期コミット以前の経緯は追跡できない。現在のドキュメント群を「初期検討段階」のベースラインとして扱い、
|
||||
それ以前の決定理由を掘り起こそうとしない。
|
||||
|
||||
## ドキュメント運用ルール
|
||||
|
||||
### 1. メインドキュメント(リポジトリ直下の `*.md`)
|
||||
|
||||
書くもの: **採用した案 / 現在の設定 / 設計に影響する制約**。
|
||||
|
||||
- 複数案から選んだ場合、「なぜAなのか」を1〜3行で書く。「なぜBではないのか」は書かない。
|
||||
- 却下案の比較表・検討過程・ベンチマーク結果はここに置かない(→ `journal/`)。
|
||||
- 却下案の再提案を防ぐ情報は、比較ではなく**制約文**として書く。
|
||||
- ✗ `B案はKLEパーサーが壊れるので却下した`
|
||||
- ○ `クラスター情報はKLE JSONに出力しない(KLE互換性の制約)`
|
||||
- 末尾に `## 検討済み・非採用` を置き、落とした案を**1案1行**で残す。理由は書かず、journalファイル名を参照する。
|
||||
- 例: `- labels[9] へのID埋め込み (journal/2026-09-16-cluster-metadata.md)`
|
||||
- コミットは `git log --oneline -- journal/2026-09-16-cluster-metadata.md` で引ける(journal削除後も追跡可)
|
||||
- 目安5行まで。増えたら古いものから落とす。ここが長くなるのは書きすぎのサイン。
|
||||
|
||||
### 2. 履歴と根拠
|
||||
|
||||
- **何を変えたか** + 理由数行 → コミットメッセージ。`git log --oneline` で意思決定の流れが追えること。1コミット1決定。
|
||||
- **なぜそう決めたか / 調査 / 案の比較** → `journal/` に置き、その決定と**同じコミット**に含める。
|
||||
- コミットメッセージ末尾に参照を1行入れる: `Journal: journal/2026-09-16-cluster-metadata.md`
|
||||
- journal は作業ツリーから削除する前提(人間にとってのノイズになるため)。履歴には残るので、
|
||||
削除後は `git show <sha>:journal/...` で読む。
|
||||
|
||||
### 3. journal の書き方
|
||||
|
||||
- 1ファイル1トピック。`journal/YYYY-MM-DD-<topic>.md`。トピックを詰め込まない。
|
||||
- 構成は上から順に: **結論** → **案の比較(簡潔な表)** → 検証ログ・根拠・捨てた案。
|
||||
冒頭20行で結論と選択肢が分かること。
|
||||
- 他のファイルから journal へリンクを張らない。journal 同士も張らない。参照経路はコミットメッセージのみ。
|
||||
- 結論がメインドキュメントに反映されたら削除してよい。
|
||||
- 整形は最低限でよい。人間向けの読みやすさは要求しない。
|
||||
|
||||
### 4. 事実と推測
|
||||
|
||||
- **裏取りの済んだ事実**は、ドキュメント内のどこに書いてもよい。文中に自然に混ぜてよい。
|
||||
ただし同じ事実を複数ドキュメントに複製しない。必要ならリンクする。
|
||||
- **未確認の推測**だけを末尾に隔離する。2種類ある:
|
||||
- 本文が前提にしている推測 → 本文に `[推測:P-n]` を付け、末尾の `## 未確認事項` に対応項目を置く
|
||||
- まだ本文に書けない未解決の問い → 本文タグなしで `## 未確認事項` に直接置く
|
||||
- `## 未確認事項` の各項目には、**何を確認できれば事実に格上げできるか**を必ず書く。
|
||||
- 検証できたらタグを外し、該当項目を削除する。
|
||||
- 「今回検討して捨てた仮説」は `## 未確認事項` に書かない。journal に残して消す。
|
||||
- 推測に基づいて実装しない。先に検証するか、ユーザーに確認する。
|
||||
|
||||
書式:
|
||||
|
||||
```markdown
|
||||
## 未確認事項
|
||||
|
||||
### P-1 KLEはlabels[9]の内容を保存時に保持する
|
||||
- 位置づけ: 本文「クラスター管理」が前提にしている
|
||||
- 格上げ条件: KLE本家でインポート→エクスポートし、labels[9]が残ることを確認する
|
||||
```
|
||||
|
||||
## Claude への作業指示
|
||||
|
||||
- ドキュメントを更新するときは、上のルール1〜4のどこに属する情報かを判断してから書く。分類に迷うものは書く前に確認する。
|
||||
- 調査タスクの結果は、結論だけをメインドキュメントに書き、調査過程は `journal/` に落とす。
|
||||
- 既存ドキュメントの記述と食い違う事実を見つけたら、勝手に上書きせず指摘する。
|
||||
|
||||
## 未確認事項
|
||||
|
||||
(なし)
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
# PHYレイヤー
|
||||
|
||||
## 概要
|
||||
|
||||
PHYレイヤーは、キースイッチやキーキャップ、その他周辺部品の物理的な配置に関する情報を提供する。
|
||||
付加的な情報として、matrix情報(ELCレイヤー)や入力キー情報(MAP/IMEレイヤー)を含ませることができるが、必須ではない。
|
||||
|
||||
## 構造
|
||||
|
||||
### キースイッチ/キーキャップの配置
|
||||
|
||||
各キーはスイッチ・キャップの情報を統合された状態で持ち、キャップの位置・サイズからスイッチ座標を特定する。
|
||||
以下、単に「キー」と呼ぶ場合、キーキャップの位置・サイズを表すオブジェクトをさすこととする。
|
||||
|
||||
### プロファイル設定
|
||||
|
||||
1ファイル内に複数のプロファイルを持たせることができる。
|
||||
ユニットサイズ(1Uが何mmか、x軸・y軸それぞれ)を規定。
|
||||
ビジュアル表現として、内側へのオフセット位置及びオフセット距離(mm or U単位)・R(U単位)を規定。
|
||||
|
||||
### キーの座標
|
||||
|
||||
キーキャップの左上角をローカル原点とする。
|
||||
座標単位はU。1Uのサイズは別途指定する。
|
||||
回転中心はローカル原点からの相対座標で表す。グローバル
|
||||
座標・回転角度は0.25U単位・15度単位でスナップするが正規化しない。
|
||||
|
||||
### キーのサイズ
|
||||
|
||||
0.25U単位でスナップするが正規化しない。
|
||||
|
||||
### グルーピング
|
||||
|
||||
複数のキーを「クラスター」としてまとめて運用することができる。
|
||||
クラスターのみにフォーカスして編集が可能。
|
||||
クラスターごとの回転中心を設定できる。
|
||||
|
||||
**内部処理での利用**:
|
||||
|
||||
- クラスターごとの一括編集・移動・回転
|
||||
- 階層構造による管理(クラスターの入れ子構造)
|
||||
- 編集時のフォーカス制御
|
||||
|
||||
**外部連携での扱い**:
|
||||
|
||||
- KLE JSON出力時は、個別のキー配置のみを出力(クラスター情報は含めない)
|
||||
- 別途クラスター情報JSONとして構造を保存
|
||||
- キーIDによる紐づけで関連性を管理
|
||||
|
||||
### 何かある?
|
||||
|
||||
## 保存形式
|
||||
|
||||
キーごとに絶対座標を持つ。
|
||||
全てのキー、またはキー及びクラスターをまとめたクラスターを配列として持つ
|
||||
|
||||
```ts
|
||||
type Position={
|
||||
x:number,
|
||||
y:number,
|
||||
w:number,
|
||||
h:number,
|
||||
rx:number,
|
||||
ry:number,
|
||||
ra:number
|
||||
}
|
||||
type Key={
|
||||
position:Position,
|
||||
profile:Profile
|
||||
}
|
||||
type Cluster={
|
||||
position:Position,
|
||||
clusters:(Key[]|Cluster[])
|
||||
}
|
||||
const keys:(Key|Cluster)[]=[]
|
||||
```
|
||||
|
||||
## 連携用出力形式
|
||||
|
||||
### 設計方針
|
||||
|
||||
KLE向けの出力は既存のKLE JSON形式を維持し、クラスター情報は別のJSONファイルとして出力する。
|
||||
両者の紐づけは、KLEのlabel部分に埋め込むユニークIDを使用する。
|
||||
|
||||
### KLE JSON出力
|
||||
|
||||
標準的なKLE JSON形式で出力。各キーのlabelにはユニークIDを含める。
|
||||
|
||||
```ts
|
||||
type ConfigObject = {
|
||||
x?: number,
|
||||
y?: number,
|
||||
w?: number,
|
||||
h?: number,
|
||||
x2?: number,
|
||||
y2?: number,
|
||||
w2?: number,
|
||||
h2?: number,
|
||||
rx?: number,
|
||||
ry?: number,
|
||||
ra?: number,
|
||||
l?: boolean,
|
||||
n?: boolean,
|
||||
d?: boolean,
|
||||
c?: string,
|
||||
t?: string,
|
||||
g?: boolean,
|
||||
a?: number,
|
||||
f?: number,
|
||||
f2?: number,
|
||||
p?: string,
|
||||
s?: string
|
||||
}
|
||||
|
||||
type KeyLabel = string // ユニークID + 表示用ラベルを含む
|
||||
const kleData = (ConfigObject | KeyLabel)[][]
|
||||
```
|
||||
|
||||
### クラスター情報JSON
|
||||
|
||||
キーのクラスター構造を管理する別ファイル。
|
||||
|
||||
```ts
|
||||
interface ClusterInfo {
|
||||
id: string, // クラスターのユニークID
|
||||
name?: string, // 表示用クラスター名
|
||||
position?: Position, // クラスター自体の位置・回転情報
|
||||
keyIds: string[], // 所属するキーのIDリスト
|
||||
subClusters?: string[] // 子クラスターのIDリスト(ネスト構造用)
|
||||
}
|
||||
|
||||
interface ClusterData {
|
||||
version: string,
|
||||
profiles: Profile[], // プロファイル定義
|
||||
keys: { // キーID -> 追加情報のマップ
|
||||
[keyId: string]: {
|
||||
originalLabel?: string, // KLEのlabelから抽出した表示用文字列
|
||||
matrix?: MatrixPosition, // matrix情報(オプション)
|
||||
mapping?: KeyMapping // キーマッピング情報(オプション)
|
||||
}
|
||||
},
|
||||
clusters: ClusterInfo[] // クラスター構造定義
|
||||
}
|
||||
```
|
||||
|
||||
### 出力例
|
||||
|
||||
#### keyboard.json (KLE形式)
|
||||
|
||||
```json
|
||||
[
|
||||
["key-001\nQ", "key-002\nW", "key-003\nE"],
|
||||
["key-004\nA", "key-005\nS", "key-006\nD"]
|
||||
]
|
||||
```
|
||||
|
||||
#### keyboard-clusters.json
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"keys": {
|
||||
"key-001": { "originalLabel": "Q", "matrix": [0, 0] },
|
||||
"key-002": { "originalLabel": "W", "matrix": [0, 1] },
|
||||
"key-003": { "originalLabel": "E", "matrix": [0, 2] },
|
||||
"key-004": { "originalLabel": "A", "matrix": [1, 0] },
|
||||
"key-005": { "originalLabel": "S", "matrix": [1, 1] },
|
||||
"key-006": { "originalLabel": "D", "matrix": [1, 2] }
|
||||
},
|
||||
"clusters": [
|
||||
{
|
||||
"id": "cluster-alpha",
|
||||
"name": "英字キー",
|
||||
"keyIds": ["key-001", "key-002", "key-003", "key-004", "key-005", "key-006"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 分離設計のメリット
|
||||
|
||||
この設計により以下の利点が得られる:
|
||||
|
||||
1. **KLE互換性の維持**:既存のKLE関連ツールとの完全な互換性を保持
|
||||
2. **柔軟な拡張性**:クラスター情報を独立して管理・拡張可能
|
||||
3. **段階的移行**:既存のKLEユーザーが段階的に拡張機能を利用可能
|
||||
4. **ツール間連携**:KLE標準形式での出力により、他のキーボード設計ツールとの相互運用性を確保
|
||||
5. **データの軽量化**:KLE出力時に不要な情報を含めず、ファイルサイズを最小化
|
||||
@@ -0,0 +1,167 @@
|
||||
# Beyond KLE: キーボード設計レイヤー構造とツール設計方針
|
||||
|
||||
## 背景
|
||||
|
||||
### KLEの問題点
|
||||
|
||||
- **物理配置と論理構造の混同**: y座標や回転角度で行を判定するため、カラムスタッガードや曲線配列で大量の行が生成される
|
||||
- **データモデルの限界**: 物理配置(PHY)のみを扱い、マトリックス座標やキーマップ情報を保持できない
|
||||
- **VIAのhackyな対応**: ラベルフィールドに"1,5"と入れて`matrix:[1,5]`を表現
|
||||
|
||||
### 設計思想
|
||||
|
||||
- 「Better KLE」ではなく「Beyond KLE」: UIではなくデータモデルとワークフロー全体の再設計
|
||||
- UNIX哲想の適用: 小さな役割のツールがそれぞれしっかり機能する
|
||||
- 統合環境(IDE)の提供: 独立したツール群をオーケストレート
|
||||
|
||||
## レイヤー構造
|
||||
|
||||
### 1. PHYレイヤー (物理配置)
|
||||
|
||||
- **役割**: キーの物理的な位置、サイズ、回転
|
||||
- **対応ツール**: PCBエディタ、KLE
|
||||
- **出力**: 座標とキーサイズの配列
|
||||
|
||||
### 2. ELCレイヤー (電気配線)
|
||||
|
||||
- **役割**: マイコンのpinと各キーの対応
|
||||
- **対応ツール**: 回路図エディタ、ファームウェア
|
||||
- **出力**: ピン配列定義、マトリックス配線情報
|
||||
- **備考**: charlieplexingなど非標準的な実装にも対応
|
||||
|
||||
### 3. IDXレイヤー (キー識別)
|
||||
|
||||
- **役割**: 物理位置と"キー番号"の対応付け
|
||||
- **重要性**: 物理世界と論理世界を繋ぐ最も重要な抽象化層
|
||||
- **出力**: `{x, y, matrix:[row,col], label}[]`
|
||||
- **備考**: 現状のエコシステムで最も欠けている層
|
||||
|
||||
### 4. MAPレイヤー (キーマップ)
|
||||
|
||||
- **役割**: キー番号とキーコードの対応
|
||||
- **対応ツール**: ファームウェア、VIA、Remap
|
||||
- **出力**: レイヤー別キーコード配列
|
||||
|
||||
### 5. IMEレイヤー (入力変換)
|
||||
|
||||
- **役割**: キーコードと実際の入力文字の対応
|
||||
- **対応ツール**: OS
|
||||
- **備考**: MAPレイヤー設計時に考慮が必要
|
||||
|
||||
## ツール設計方針
|
||||
|
||||
### 個別ツール (独立動作可能)
|
||||
|
||||
#### PHY Editor
|
||||
|
||||
- 物理配置の視覚的編集
|
||||
- KLE互換フォーマット対応
|
||||
- 出力: PHY JSON
|
||||
|
||||
#### Matrix Mapper
|
||||
|
||||
- PHY Editorの出力を入力
|
||||
- キーにrow/colを割り当て
|
||||
- 視覚的にマトリックス配線を確認
|
||||
- 出力: IDX JSON
|
||||
|
||||
#### Pin Configurator
|
||||
|
||||
- マトリックス定義を入力
|
||||
- マイコンのピン配置を指定
|
||||
- 配線図を出力
|
||||
- 出力: ELC JSON
|
||||
|
||||
#### Keymap Designer
|
||||
|
||||
- キー識別子リストを入力
|
||||
- レイヤー別キーコード割り当て
|
||||
- VIA/Remapとの互換性
|
||||
- 出力: MAP JSON
|
||||
|
||||
#### Firmware Generator
|
||||
|
||||
- 全レイヤーの情報を統合
|
||||
- QMK/VIA/KMK等へ変換
|
||||
- 出力: ターゲット固有の設定ファイル
|
||||
|
||||
### 統合環境 (IDE)
|
||||
|
||||
- **プロジェクトファイル**: 全レイヤーの状態を一元管理
|
||||
- **リアルタイムプレビュー**: レイヤー間の依存関係を可視化
|
||||
- **バージョン管理統合**: テキストベース定義でgit管理
|
||||
- **ワークフローサポート**: ガイド付きウィザード、テンプレート
|
||||
|
||||
## プロトコル設計
|
||||
|
||||
### レイヤー間インターフェース
|
||||
|
||||
```txt
|
||||
PHY → IDX
|
||||
Input: KLE JSON (or compatible)
|
||||
Output: Augmented layout (coordinates + key IDs)
|
||||
|
||||
IDX → ELC
|
||||
Input: Key ID list
|
||||
Output: Matrix definition + Pin mapping
|
||||
|
||||
IDX → MAP
|
||||
Input: Key ID list
|
||||
Output: Keymap arrays
|
||||
|
||||
All → Firmware
|
||||
Output: QMK keyboard.json / VIA JSON / KMK .py
|
||||
```
|
||||
|
||||
### データフォーマット
|
||||
|
||||
- JSON Schema で各レイヤーの入出力を定義
|
||||
- 後方互換性の維持 (KLE JSONからのインポート)
|
||||
- 拡張可能な設計 (3D配置、非標準マトリックス対応)
|
||||
|
||||
## 実装戦略
|
||||
|
||||
### Phase 1: プロトコル策定
|
||||
|
||||
- JSON Schemaでフォーマット定義
|
||||
- TypeScriptでリファレンス実装
|
||||
- CLIツールとして動作確認
|
||||
|
||||
### Phase 2: 個別ツール開発
|
||||
|
||||
- 各レイヤー用エディタを独立開発
|
||||
- 既存ツールからのインポート機能
|
||||
- 単独利用可能な設計
|
||||
|
||||
### Phase 3: IDE統合
|
||||
|
||||
- Electron/Tauri or Webアプリ
|
||||
- プロジェクト管理機能
|
||||
- ワークフロー支援
|
||||
|
||||
## 既存ツールとの関係
|
||||
|
||||
- **KLE**: PHY Editorの一実装、インポート対応
|
||||
- **VIA/Remap**: MAP Layerクライアントとして連携
|
||||
- **QMK Configurator**: Firmware Generatorのターゲット
|
||||
|
||||
## 配慮事項
|
||||
|
||||
### 学習コスト
|
||||
|
||||
- テンプレートプロジェクト提供 (60%, Split等)
|
||||
- 簡易モード/詳細モードの切り替え
|
||||
- 段階的な機能開示
|
||||
|
||||
### 互換性
|
||||
|
||||
- 既存KLE JSONの完全サポート
|
||||
- 段階的移行パスの提供
|
||||
- レガシーツールとの共存
|
||||
|
||||
## 次のステップ
|
||||
|
||||
1. プロトコル仕様ドラフト作成
|
||||
2. GitHubでRFCリポジトリ立ち上げ
|
||||
3. 小規模コミュニティでフィードバック収集
|
||||
4. リファレンス実装の開発
|
||||
@@ -0,0 +1,157 @@
|
||||
# 自作キーボード制作ワークフロー分析
|
||||
|
||||
## 現在の一般的な制作手順
|
||||
|
||||
### 1. デザイン・設計段階
|
||||
|
||||
#### a) レイアウト設計
|
||||
|
||||
- **ツール**: KLE (Keyboard Layout Editor)
|
||||
- **作業内容**:
|
||||
- キーの物理配置を決定
|
||||
- キーサイズ・間隔・角度を調整
|
||||
- 視覚的な配置確認
|
||||
- **出力**: KLE JSON ファイル
|
||||
- **Beyond KLE対応**: **PHYレイヤー**
|
||||
|
||||
#### b) 回路設計・マトリックス設計
|
||||
|
||||
- **ツール**: 手動計算、KiCad等の回路図エディタ
|
||||
- **作業内容**:
|
||||
- キーマトリックスの行列構成を決定
|
||||
- マイコンピンとの対応付け
|
||||
- ダイオード配置の検討
|
||||
- **出力**: 回路図、ピン配置表
|
||||
- **現在の課題**: KLEからマトリックス情報への変換が手動
|
||||
- **Beyond KLE対応**: **ELCレイヤー** + **IDXレイヤー**
|
||||
|
||||
### 2. PCB設計段階
|
||||
|
||||
#### a) フットプリント配置
|
||||
|
||||
- **ツール**: KiCad、Eagle、Fusion360等
|
||||
- **作業内容**:
|
||||
- KLE JSONから座標を抽出(手動またはスクリプト)
|
||||
- スイッチフットプリントの配置
|
||||
- マイコン・コネクタ等の部品配置
|
||||
- **現在の課題**: KLEとPCBツール間の座標変換
|
||||
- **Beyond KLE対応**: **PHYレイヤー** → PCBツール連携
|
||||
|
||||
#### b) 配線・ルーティング
|
||||
|
||||
- **ツール**: PCB CADソフト
|
||||
- **作業内容**:
|
||||
- マトリックス配線の実装
|
||||
- 電源・信号線のルーティング
|
||||
- **Beyond KLE対応**: **ELCレイヤー**情報の活用
|
||||
|
||||
### 3. ファームウェア設定段階
|
||||
|
||||
#### a) ファームウェア基本設定
|
||||
|
||||
- **ツール**: QMK、KMK、VIA等
|
||||
- **作業内容**:
|
||||
- マトリックス定義(ROW/COL)
|
||||
- ピン配置設定
|
||||
- 物理レイアウト定義
|
||||
- **現在の課題**:
|
||||
- KLEとファームウェア設定の手動変換
|
||||
- VIAではlabelフィールドに"1,5"等を記入してmatrix座標を表現
|
||||
- **Beyond KLE対応**: **IDXレイヤー** + **ELCレイヤー** → ファームウェア自動生成
|
||||
|
||||
#### b) キーマップ設定
|
||||
|
||||
- **ツール**: QMK Configurator、VIA、Remap等
|
||||
- **作業内容**:
|
||||
- 各キーへのキーコード割り当て
|
||||
- レイヤー設計
|
||||
- マクロ・特殊機能の設定
|
||||
- **Beyond KLE対応**: **MAPレイヤー**
|
||||
|
||||
### 4. 製造・組み立て段階
|
||||
|
||||
#### a) PCB製造
|
||||
|
||||
- **出力**: Gerberファイル、BOM、Pick&Place
|
||||
- **Beyond KLE対応**: PHY/ELCレイヤーからの自動生成
|
||||
|
||||
#### b) 部品調達・組み立て
|
||||
|
||||
- **作業内容**: はんだ付け、組み立て、動作確認
|
||||
|
||||
## 現在のワークフローの問題点
|
||||
|
||||
### 1. データの分断
|
||||
|
||||
- **問題**: KLE → PCB → ファームウェアで毎回手動変換
|
||||
- **影響**: 一箇所変更時の全体への反映が困難
|
||||
|
||||
### 2. マトリックス情報の欠落
|
||||
|
||||
- **問題**: KLEは物理配置のみ、論理構造(マトリックス)は別管理
|
||||
- **影響**: VIAでのhackyな対応(labelフィールド悪用)
|
||||
|
||||
### 3. 設計変更時のトレーサビリティ不足
|
||||
|
||||
- **問題**: どのレイヤーでの変更が他に影響するかが不明確
|
||||
- **影響**: デバッグ・修正作業の複雑化
|
||||
|
||||
### 4. ツール間連携の脆弱性
|
||||
|
||||
- **問題**: 手動変換によるヒューマンエラー
|
||||
- **影響**: 設計ミス、製造後の修正コスト
|
||||
|
||||
## Beyond KLEによる解決策
|
||||
|
||||
### 1. 統一データモデル
|
||||
|
||||
```
|
||||
物理設計(PHY) → キー識別(IDX) → 電気設計(ELC) → キーマップ(MAP)
|
||||
↓ ↓ ↓ ↓
|
||||
PCBツール ファームウェア 回路図 VIA/Remap
|
||||
```
|
||||
|
||||
### 2. レイヤー間の明確な責任分離
|
||||
|
||||
- **PHY**: 物理配置のみに集中
|
||||
- **IDX**: 物理と論理の橋渡し(最重要レイヤー)
|
||||
- **ELC**: 電気的接続の明確化
|
||||
- **MAP**: キーマップロジックの分離
|
||||
|
||||
### 3. 双方向の依存関係管理
|
||||
|
||||
- 上流レイヤーの変更が下流に自動反映
|
||||
- 下流での制約が上流設計にフィードバック
|
||||
|
||||
### 4. 既存ツールとの互換性維持
|
||||
|
||||
- KLE JSONの完全サポート
|
||||
- VIA/QMK等への出力対応
|
||||
- 段階的移行の実現
|
||||
|
||||
## Phase 1で策定すべきプロトコル
|
||||
|
||||
### 1. レイヤー間インターフェース仕様
|
||||
|
||||
- 各レイヤーの入出力JSON Schema
|
||||
- 依存関係の明確化
|
||||
- エラーハンドリング規則
|
||||
|
||||
### 2. 既存フォーマットとの変換仕様
|
||||
|
||||
- KLE JSON → PHY変換
|
||||
- QMK設定 ← IDX+ELC+MAP変換
|
||||
- VIA JSON ← 全レイヤー統合
|
||||
|
||||
### 3. 拡張性設計
|
||||
|
||||
- 非標準マトリックス(charlieplexing等)対応
|
||||
- 3D配置への拡張可能性
|
||||
- カスタムキーコードサポート
|
||||
|
||||
## 次のアクション
|
||||
|
||||
1. **JSON Schema策定**: 各レイヤーのデータ構造定義
|
||||
2. **変換ロジック設計**: レイヤー間の変換アルゴリズム
|
||||
3. **リファレンス実装**: TypeScript/Rust等でのプロトタイプ
|
||||
4. **テストケース作成**: 代表的なキーボード設計での検証
|
||||
@@ -0,0 +1,453 @@
|
||||
# PHYレイヤーとKLE(Keyboard Layout Editor)の互換性分析
|
||||
|
||||
## 概要
|
||||
|
||||
検討中のPHYレイヤー構造とKeyboard Layout Editor(KLE)の互換性について詳細に分析した結果をまとめます。KLEをベースとしたフロントエンドの実現可能性を評価します。
|
||||
|
||||
## KLEデータモデルの特徴
|
||||
|
||||
### KLEで対応可能な主要プロパティ
|
||||
|
||||
KLEの`Key`オブジェクトは以下の構造を持ちます:
|
||||
|
||||
```typescript
|
||||
export class Key {
|
||||
// 位置・サイズ関連
|
||||
x: number; // 絶対位置 (U単位)
|
||||
y: number; // 絶対位置 (U単位)
|
||||
width: number; // サイズ (U単位)
|
||||
height: number; // サイズ (U単位)
|
||||
|
||||
// 回転関連
|
||||
rotation_x: number; // 回転中心X座標
|
||||
rotation_y: number; // 回転中心Y座標
|
||||
rotation_angle: number; // 回転角度
|
||||
|
||||
// 複雑な形状対応
|
||||
x2: number; // 第2矩形の相対位置
|
||||
y2: number; // 第2矩形の相対位置
|
||||
width2: number; // 第2矩形のサイズ
|
||||
height2: number; // 第2矩形のサイズ
|
||||
|
||||
// 表示・装飾関連
|
||||
color: string; // キーキャップ色
|
||||
labels: string[]; // 最大12個のラベル
|
||||
textColor: Array<string | undefined>;
|
||||
textSize: Array<number | undefined>;
|
||||
profile: string; // キープロファイル
|
||||
|
||||
// フラグ類
|
||||
decal: boolean; // デカールモード
|
||||
ghost: boolean; // ゴーストモード
|
||||
stepped: boolean; // ステップキー
|
||||
nub: boolean; // ホーミング突起
|
||||
|
||||
// スイッチ情報
|
||||
sm: string; // スイッチマウント
|
||||
sb: string; // スイッチブランド
|
||||
st: string; // スイッチタイプ
|
||||
}
|
||||
```
|
||||
|
||||
## 互換性マトリックス
|
||||
|
||||
### ✅ 完全対応可能
|
||||
|
||||
| PHYレイヤー機能 | KLEプロパティ | 備考 |
|
||||
|---|---|---|
|
||||
| キー座標(x,y) | `x`, `y` | 1:1対応、U単位 |
|
||||
| キーサイズ(w,h) | `width`, `height` | 1:1対応、U単位 |
|
||||
| 回転角度 | `rotation_angle` | 1:1対応 |
|
||||
| 回転中心 | `rotation_x`, `rotation_y` | 1:1対応 |
|
||||
| 0.25U単位スナップ | - | KLEも小数点座標対応 |
|
||||
| 複雑形状キー | `x2`, `y2`, `width2`, `height2` | ISO Enter等に対応 |
|
||||
| プロファイル設定 | `profile` | SA、DCS、OEM等対応 |
|
||||
|
||||
### ⚠️ 部分対応・要改造
|
||||
|
||||
| PHYレイヤー機能 | KLE対応状況 | 改造内容 |
|
||||
|---|---|---|
|
||||
| クラスター管理 | 非対応 | カスタムメタデータとしてlabelsに格納 |
|
||||
| 複数プロファイル | 一部対応 | メタデータで管理、UI拡張必要 |
|
||||
| ユニットサイズ設定 | 非対応 | グローバル設定として追加実装 |
|
||||
| ビジュアルオフセット | 非対応 | レンダリング層で独自実装 |
|
||||
|
||||
### ❌ 対応困難・KLE制約
|
||||
|
||||
| PHYレイヤー機能 | KLE制約 | 回避策 |
|
||||
|---|---|---|
|
||||
| 15度以外の回転角度 | UI制約(内部は対応可能) | UI改造またはAPI直接操作 |
|
||||
| 階層的クラスター | データモデル非対応 | ゴーストキーによるメタデータ保存 |
|
||||
| クラスター別回転中心 | 直接非対応 | 各キーの回転中心で代替 |
|
||||
|
||||
## 具体的な実装戦略
|
||||
|
||||
### 1. KLEベースデータ変換
|
||||
|
||||
PHYレイヤーからKLE形式への変換:
|
||||
|
||||
```typescript
|
||||
// PHYレイヤー → KLE変換例
|
||||
function convertToKLE(phyData: PHYLayer): KLEData {
|
||||
return phyData.elements.map(element => {
|
||||
if (element.type === 'key') {
|
||||
return {
|
||||
x: element.position.x,
|
||||
y: element.position.y,
|
||||
width: element.size.width,
|
||||
height: element.size.height,
|
||||
rotation_x: element.position.rotationOrigin?.x || 0,
|
||||
rotation_y: element.position.rotationOrigin?.y || 0,
|
||||
rotation_angle: element.position.rotation || 0,
|
||||
// クラスター情報をlabelsの特定位置に格納
|
||||
labels: [element.clusterId || '', '', '', '', '', '', '', '', '', '', '', ''],
|
||||
// プロファイル情報
|
||||
profile: element.profile || 'OEM'
|
||||
};
|
||||
}
|
||||
// クラスターの場合は展開処理
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 2. KLE UI改造点
|
||||
|
||||
#### 必要な改造項目
|
||||
|
||||
1. **クラスター管理UI**
|
||||
- クラスター作成・編集パネル
|
||||
- クラスター単位の選択・移動
|
||||
- クラスター階層表示
|
||||
|
||||
2. **プロファイル管理**
|
||||
- 複数プロファイル切り替えUI
|
||||
- プロファイル別ユニットサイズ設定
|
||||
|
||||
3. **メタデータ拡張**
|
||||
- PHYレイヤー固有情報の保存
|
||||
- 互換性情報の管理
|
||||
|
||||
#### UI改造の難易度
|
||||
|
||||
- **低難易度**: 既存プロパティパネルの拡張
|
||||
- **中難易度**: クラスター管理機能の追加
|
||||
- **高難易度**: レンダリングエンジンの改造
|
||||
|
||||
### 3. 制限事項と回避策
|
||||
|
||||
#### 現在のKLE制約
|
||||
|
||||
1. **データモデル制約**
|
||||
- フラットな配列構造のみ対応
|
||||
- 階層的クラスター管理は困難
|
||||
|
||||
2. **レンダリング制約**
|
||||
- HTMLベースのレンダリング
|
||||
- 複雑な視覚効果には限界
|
||||
|
||||
#### 回避策
|
||||
|
||||
1. **段階的対応**
|
||||
- Phase 1: 基本的な位置・サイズ・回転のみ対応
|
||||
- Phase 2: クラスター管理機能追加
|
||||
- Phase 3: 高度なビジュアル機能
|
||||
|
||||
2. **ハイブリッドアプローチ**
|
||||
- KLEをベースエディタとして使用
|
||||
- 独自機能は別パネルで管理
|
||||
- 最終出力時にPHYレイヤー形式で統合
|
||||
|
||||
## 推奨実装アプローチ
|
||||
|
||||
### フェーズ1: KLE最小改造アプローチ(推奨)
|
||||
|
||||
1. **KLEの既存機能をそのまま活用**
|
||||
- 基本的な配置・サイズ・回転
|
||||
- プロファイル設定
|
||||
- 色・ラベル設定
|
||||
|
||||
2. **最小限の拡張**
|
||||
- カスタムメタデータ保存機能
|
||||
- PHYレイヤー形式エクスポート
|
||||
|
||||
3. **段階的機能追加**
|
||||
- クラスター管理(Phase 2)
|
||||
- 高度なビジュアル機能(Phase 3)
|
||||
|
||||
### 実装優先順位
|
||||
|
||||
1. **高優先度** - 即座に対応可能
|
||||
- ✅ 基本位置・サイズ・回転
|
||||
- ✅ プロファイル設定
|
||||
- ✅ KLE→PHYレイヤー変換
|
||||
|
||||
2. **中優先度** - 改造が必要だが実現可能
|
||||
- ⚠️ 簡易クラスター管理
|
||||
- ⚠️ 複数プロファイル対応
|
||||
- ⚠️ カスタムユニットサイズ
|
||||
|
||||
3. **低優先度** - 大規模改造が必要
|
||||
- ❌ 階層的クラスター
|
||||
- ❌ 高度なビジュアルオフセット
|
||||
- ❌ リアルタイム3Dプレビュー
|
||||
|
||||
## 結論
|
||||
|
||||
**KLEをベースとしたPHYレイヤーエディタの実現は十分可能**です。
|
||||
|
||||
### 対応可能な機能(約80%)
|
||||
|
||||
- 基本的なキー配置・サイズ・回転
|
||||
- 複雑形状キー(ISO Enter等)
|
||||
- プロファイル管理
|
||||
- 基本的なクラスター管理
|
||||
|
||||
### 改造が必要な機能(約15%)
|
||||
|
||||
- 高度なクラスター管理
|
||||
- 複数プロファイル切り替え
|
||||
- カスタムユニットサイズ
|
||||
|
||||
### 対応困難な機能(約5%)
|
||||
|
||||
- 完全階層的クラスター構造
|
||||
- 高度なビジュアルエフェクト
|
||||
|
||||
PHYレイヤーの主要機能はKLEで十分実現可能であり、段階的な改造により完全対応も期待できます。
|
||||
|
||||
## 階層型クラスターの実装戦略
|
||||
|
||||
### 問題提起
|
||||
|
||||
ユーザーからの要求:
|
||||
|
||||
- **階層型クラスター管理は絶対に実装したい**
|
||||
- 出力時はフラット化してもOK(階層情報を何らかの形で記録できれば)
|
||||
- ラベルに突っ込むと見づらくなる懸念
|
||||
- **ゴーストキーによる情報保存**のアイデア
|
||||
|
||||
### ゴーストキーアプローチの詳細設計
|
||||
|
||||
#### 基本概念
|
||||
|
||||
クラスター情報を`ghost: true`のキーとして保存し、実際のキーレンダリングには影響を与えずにメタデータとして扱う。
|
||||
|
||||
```typescript
|
||||
// クラスター情報保存用ゴーストキー
|
||||
type ClusterGhostKey = {
|
||||
ghost: true,
|
||||
decal: false,
|
||||
x: number, // クラスター範囲の左上角
|
||||
y: number, // クラスター範囲の左上角
|
||||
width: number, // クラスター範囲の幅
|
||||
height: number, // クラスター範囲の高さ
|
||||
rotation_x: number, // クラスター回転中心
|
||||
rotation_y: number, // クラスター回転中心
|
||||
rotation_angle: number, // クラスター回転角度
|
||||
color: "#transparent", // 透明
|
||||
labels: [
|
||||
"CLUSTER_META", // [0] 識別子
|
||||
clusterId, // [1] クラスターID
|
||||
parentClusterId, // [2] 親クラスターID(階層用)
|
||||
clusterName, // [3] クラスター名
|
||||
JSON.stringify(metadata), // [4] その他メタデータ
|
||||
"", "", "", "", "", "", "" // [5-11] 予約
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 階層構造の表現
|
||||
|
||||
```typescript
|
||||
// 例:分割キーボードの左右ブロック + 各ブロック内のキークラスター
|
||||
const clusterHierarchy = {
|
||||
"root": {
|
||||
children: ["left_block", "right_block"]
|
||||
},
|
||||
"left_block": {
|
||||
parent: "root",
|
||||
children: ["left_alpha", "left_modifiers"]
|
||||
},
|
||||
"right_block": {
|
||||
parent: "root",
|
||||
children: ["right_alpha", "right_modifiers"]
|
||||
},
|
||||
"left_alpha": {
|
||||
parent: "left_block",
|
||||
keyIds: ["Q", "W", "E", "R", "T", "A", "S", "D", "F", "G", ...]
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### KLE UI拡張による階層管理
|
||||
|
||||
1. **クラスター管理パネル**
|
||||
|
||||
```html
|
||||
<!-- 新しいサイドパネル -->
|
||||
<div id="cluster-manager">
|
||||
<tree-view>
|
||||
<cluster-node id="root">
|
||||
<cluster-node id="left_block">
|
||||
<cluster-node id="left_alpha" />
|
||||
<cluster-node id="left_modifiers" />
|
||||
</cluster-node>
|
||||
<cluster-node id="right_block">
|
||||
<cluster-node id="right_alpha" />
|
||||
<cluster-node id="right_modifiers" />
|
||||
</cluster-node>
|
||||
</cluster-node>
|
||||
</tree-view>
|
||||
</div>
|
||||
```
|
||||
|
||||
2. **クラスター操作機能**
|
||||
- ドラッグ&ドロップによるキーのクラスター移動
|
||||
- 階層ツリーでのクラスター選択
|
||||
- クラスター単位での一括変換(移動・回転・リサイズ)
|
||||
- クラスター単位でのコピー&ペースト
|
||||
|
||||
### データフロー設計
|
||||
|
||||
#### 1. 編集時(UI → 内部データ)
|
||||
|
||||
```javascript
|
||||
// クラスター作成時
|
||||
function createCluster(name, parentId, selectedKeys) {
|
||||
const clusterId = generateUniqueId();
|
||||
|
||||
// ゴーストキーを作成
|
||||
const ghostKey = createClusterGhostKey({
|
||||
id: clusterId,
|
||||
name: name,
|
||||
parentId: parentId,
|
||||
boundingBox: calculateBoundingBox(selectedKeys)
|
||||
});
|
||||
|
||||
// 選択されたキーにクラスターIDを付与
|
||||
selectedKeys.forEach(key => {
|
||||
key.clusterId = clusterId; // カスタムプロパティとして追加
|
||||
});
|
||||
|
||||
// KLEのキー配列に追加
|
||||
scope.keys().push(ghostKey);
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 保存時(内部データ → KLE JSON)
|
||||
|
||||
```javascript
|
||||
function serializeWithClusters(keyboard) {
|
||||
const kleData = serial.serialize(keyboard);
|
||||
|
||||
// 通常のキーとゴーストキーが混在した状態で保存
|
||||
// ゴーストキーがクラスター情報を保持
|
||||
return kleData;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 読み込み時(KLE JSON → 内部データ)
|
||||
|
||||
```javascript
|
||||
function deserializeWithClusters(kleData) {
|
||||
const keyboard = serial.deserialize(kleData);
|
||||
|
||||
// ゴーストキーからクラスター情報を復元
|
||||
const clusterMap = new Map();
|
||||
const normalKeys = [];
|
||||
|
||||
keyboard.keys.forEach(key => {
|
||||
if (key.ghost && key.labels[0] === 'CLUSTER_META') {
|
||||
const clusterId = key.labels[1];
|
||||
const parentId = key.labels[2];
|
||||
const metadata = JSON.parse(key.labels[4]);
|
||||
|
||||
clusterMap.set(clusterId, {
|
||||
ghostKey: key,
|
||||
parentId: parentId,
|
||||
metadata: metadata,
|
||||
children: new Set()
|
||||
});
|
||||
} else {
|
||||
normalKeys.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
// 階層構造を再構築
|
||||
buildClusterHierarchy(clusterMap);
|
||||
|
||||
return { keyboard: { ...keyboard, keys: normalKeys }, clusters: clusterMap };
|
||||
}
|
||||
```
|
||||
|
||||
### 15度回転制約について
|
||||
|
||||
#### 現在の制約の詳細
|
||||
|
||||
KLEのソースコード分析結果:
|
||||
|
||||
```javascript
|
||||
$scope.rotateStep = 15; // UI上のデフォルト回転ステップ
|
||||
|
||||
// 実際の制約チェック(kb.js内)
|
||||
rotation_angle: function() {
|
||||
return Math.max(-180, Math.min(180, value)); // ±180度の範囲制約のみ
|
||||
}
|
||||
```
|
||||
|
||||
**つまり:**
|
||||
|
||||
- **UIでは15度ステップで回転操作**(ユーザビリティのため)
|
||||
- **内部データには制約なし**(任意の角度を保存可能)
|
||||
- **JSON出力も制約なし**(小数点を含む任意の角度値)
|
||||
|
||||
#### PHYレイヤーでの対応
|
||||
|
||||
```javascript
|
||||
// PHY → KLE変換時に任意角度をそのまま渡す
|
||||
function convertRotation(phyRotation) {
|
||||
return phyRotation; // 制約なし、そのまま変換
|
||||
}
|
||||
|
||||
// KLE UI拡張で任意角度入力を許可
|
||||
function enableCustomRotation() {
|
||||
// 既存の15度ステップに加えて、直接入力機能を追加
|
||||
scope.customRotateStep = 1; // 1度ステップ
|
||||
scope.allowCustomAngle = true; // 任意角度入力許可
|
||||
}
|
||||
```
|
||||
|
||||
## 実装優先度の再評価
|
||||
|
||||
### 修正版優先度
|
||||
|
||||
1. **最高優先度** - 即座に対応可能
|
||||
- ✅ 基本位置・サイズ・回転(任意角度対応)
|
||||
- ✅ プロファイル設定
|
||||
- ✅ KLE→PHYレイヤー変換
|
||||
|
||||
2. **高優先度** - ゴーストキーアプローチで対応
|
||||
- 🔶 階層型クラスター管理(ゴーストキー方式)
|
||||
- 🔶 クラスターメタデータ保存
|
||||
- 🔶 クラスター単位操作UI
|
||||
|
||||
3. **中優先度** - UI改造が必要
|
||||
- ⚠️ 複数プロファイル対応
|
||||
- ⚠️ カスタムユニットサイズ
|
||||
- ⚠️ 15度以外の回転角度UI対応
|
||||
|
||||
4. **低優先度** - 大規模改造が必要
|
||||
- ❌ 高度なビジュアルオフセット
|
||||
- ❌ リアルタイム3Dプレビュー
|
||||
|
||||
### 実装結論
|
||||
|
||||
**ゴーストキーアプローチにより、階層型クラスター管理は実現可能**です。この方法なら:
|
||||
|
||||
- KLEの既存データモデルを活用
|
||||
- 見た目に影響しない形でメタデータ保存
|
||||
- 階層構造の完全な再現が可能
|
||||
- 段階的な実装が可能
|
||||
|
||||
階層型クラスターが「絶対実装したい」要求に対して、技術的に実現可能な解決策を提供できます。
|
||||
@@ -0,0 +1,249 @@
|
||||
# KLE JSON 互換性設計
|
||||
|
||||
## 双方向変換戦略
|
||||
|
||||
### Beyond KLE → KLE JSON
|
||||
|
||||
#### 1. フラット化プロセス
|
||||
|
||||
```typescript
|
||||
interface ConversionOptions {
|
||||
preserveIds: boolean; // IDを保持するか
|
||||
preserveClusterInfo: boolean; // クラスター情報を保持するか
|
||||
idEmbedMethod: 'metadata' | 'label' | 'comment';
|
||||
}
|
||||
|
||||
function convertToKLE(beyondKle: PHYLayer, options: ConversionOptions): KLELayout {
|
||||
const flatKeys = flattenElements(beyondKle.elements);
|
||||
return {
|
||||
name: beyondKle.metadata.name,
|
||||
keys: flatKeys.map(key => convertKey(key, options))
|
||||
};
|
||||
}
|
||||
|
||||
function flattenElements(elements: Element[], parentTransform?: Transform): FlatKey[] {
|
||||
const result: FlatKey[] = [];
|
||||
|
||||
for (const element of elements) {
|
||||
if (element.type === 'key') {
|
||||
const absolutePos = applyTransform(element.position, parentTransform);
|
||||
result.push({
|
||||
...element,
|
||||
absolutePosition: absolutePos,
|
||||
clusterPath: getClusterPath(element)
|
||||
});
|
||||
} else if (element.type === 'cluster') {
|
||||
const clusterTransform = combineTransforms(parentTransform, element.position);
|
||||
result.push(...flattenElements(element.elements, clusterTransform));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. ID保持方法の比較
|
||||
|
||||
| 方法 | メリット | デメリット | KLE互換性 |
|
||||
|------|----------|------------|-----------|
|
||||
| `_beyondkle` メタデータ | 構造化された情報保持 | KLEパーサーが無視する可能性 | △ |
|
||||
| ラベル9番目位置 | 確実に保存される | 表示されてしまう | ○ |
|
||||
| コメントフィールド | 非表示 | 標準的でない | △ |
|
||||
|
||||
#### 3. 推奨アプローチ: ハイブリッド方式
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Split Layout (Generated by Beyond KLE)",
|
||||
"_beyondkle": {
|
||||
"version": "1.0.0",
|
||||
"sourceFile": "layout.beyond.json",
|
||||
"conversionSettings": {
|
||||
"flattenClusters": true,
|
||||
"preserveRotations": true
|
||||
}
|
||||
},
|
||||
"keys": [
|
||||
{
|
||||
"x": 0, "y": 0, "w": 1, "h": 1,
|
||||
"t": "#000000\n\n\n\n\n\n\n\n\nk00",
|
||||
"_beyondkle": {
|
||||
"id": "k00",
|
||||
"clusterPath": ["left_half", "left_main"],
|
||||
"relativePosition": { "x": 0, "y": 0 },
|
||||
"absolutePosition": { "x": 0, "y": 0 }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### KLE JSON → Beyond KLE
|
||||
|
||||
#### 1. ID復元戦略
|
||||
|
||||
```typescript
|
||||
function convertFromKLE(kleLayout: KLELayout): PHYLayer {
|
||||
// 1. メタデータからBeyond KLE情報を取得
|
||||
const beyondKleInfo = kleLayout._beyondkle;
|
||||
|
||||
if (beyondKleInfo?.sourceFile) {
|
||||
// 元のBeyond KLEファイルが指定されている場合
|
||||
return reconstructFromSource(kleLayout, beyondKleInfo);
|
||||
}
|
||||
|
||||
// 2. キーからIDとクラスター情報を抽出
|
||||
const keys = kleLayout.keys.map(kleKey => {
|
||||
const id = extractId(kleKey);
|
||||
const clusterInfo = extractClusterInfo(kleKey);
|
||||
|
||||
return {
|
||||
type: 'key' as const,
|
||||
id: id || generateId(kleKey),
|
||||
profile: 'default',
|
||||
position: { x: kleKey.x, y: kleKey.y },
|
||||
size: { width: kleKey.w || 1, height: kleKey.h || 1 },
|
||||
metadata: extractMetadata(kleKey)
|
||||
};
|
||||
});
|
||||
|
||||
// 3. クラスター構造の再構築
|
||||
return reconstructClusters(keys);
|
||||
}
|
||||
|
||||
function extractId(kleKey: any): string | null {
|
||||
// 1. _beyondkle メタデータから
|
||||
if (kleKey._beyondkle?.id) return kleKey._beyondkle.id;
|
||||
|
||||
// 2. ラベル9番目から
|
||||
if (kleKey.labels?.[8]) return kleKey.labels[8];
|
||||
|
||||
// 3. t文字列の解析
|
||||
const tMatch = kleKey.t?.match(/\n([a-zA-Z0-9_]+)$/);
|
||||
if (tMatch) return tMatch[1];
|
||||
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. クラスター再構築アルゴリズム
|
||||
|
||||
```typescript
|
||||
function reconstructClusters(flatKeys: Key[]): Element[] {
|
||||
// クラスター情報が保持されている場合
|
||||
const clusteredKeys = groupByCluster(flatKeys);
|
||||
|
||||
if (Object.keys(clusteredKeys).length > 1) {
|
||||
return Object.entries(clusteredKeys).map(([clusterName, keys]) => ({
|
||||
type: 'cluster' as const,
|
||||
id: clusterName,
|
||||
name: clusterName,
|
||||
position: calculateClusterOrigin(keys),
|
||||
elements: keys.map(key => ({
|
||||
...key,
|
||||
position: makeRelativeToCluster(key.position, clusterOrigin)
|
||||
}))
|
||||
}));
|
||||
}
|
||||
|
||||
// クラスター情報がない場合は自動推定
|
||||
return autoDetectClusters(flatKeys);
|
||||
}
|
||||
|
||||
function autoDetectClusters(keys: Key[]): Element[] {
|
||||
// 座標の近さとレイアウトパターンからクラスターを推定
|
||||
// 1. 連続する矩形領域の検出
|
||||
// 2. 回転角度の類似性
|
||||
// 3. 一定の間隔での分離
|
||||
|
||||
const clusters = detectSpatialClusters(keys);
|
||||
|
||||
if (clusters.length === 1) {
|
||||
// 単一クラスターの場合はフラットに
|
||||
return keys;
|
||||
}
|
||||
|
||||
return clusters;
|
||||
}
|
||||
```
|
||||
|
||||
## 実装における考慮事項
|
||||
|
||||
### 1. バージョン管理
|
||||
|
||||
```json
|
||||
{
|
||||
"_beyondkle": {
|
||||
"version": "1.0.0",
|
||||
"generator": "beyond-kle-converter",
|
||||
"timestamp": "2025-11-09T12:00:00Z",
|
||||
"preservedData": {
|
||||
"clusters": true,
|
||||
"rotations": true,
|
||||
"profiles": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 設定可能な変換オプション
|
||||
|
||||
```typescript
|
||||
interface ConversionSettings {
|
||||
// ID保持方法
|
||||
idPreservation: 'metadata' | 'label' | 'both' | 'none';
|
||||
|
||||
// クラスター処理
|
||||
clusterHandling: 'preserve' | 'flatten' | 'auto';
|
||||
|
||||
// 回転の処理
|
||||
rotationHandling: 'preserve' | 'flatten';
|
||||
|
||||
// プロファイル情報
|
||||
includeProfiles: boolean;
|
||||
|
||||
// 互換性レベル
|
||||
compatibilityLevel: 'strict' | 'extended' | 'beyondkle';
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 往復変換の整合性保証
|
||||
|
||||
```typescript
|
||||
// テストケース
|
||||
const originalBeyondKLE = loadBeyondKLE('test.beyond.json');
|
||||
const kleJson = convertToKLE(originalBeyondKLE, settings);
|
||||
const reconstructed = convertFromKLE(kleJson);
|
||||
|
||||
// 構造的等価性の検証
|
||||
assert(structurallyEquivalent(originalBeyondKLE, reconstructed));
|
||||
```
|
||||
|
||||
## ワークフロー例
|
||||
|
||||
### 1. Beyond KLE → 外部ツール → Beyond KLE
|
||||
|
||||
```bash
|
||||
# Beyond KLE → KLE JSON (ID付き)
|
||||
beyond-kle export --format=kle --preserve-ids layout.beyond.json
|
||||
|
||||
# 外部ツール(KLE等)で編集
|
||||
# ...
|
||||
|
||||
# KLE JSON → Beyond KLE (構造復元)
|
||||
beyond-kle import --format=kle --auto-cluster edited.kle.json
|
||||
```
|
||||
|
||||
### 2. 段階的移行
|
||||
|
||||
```bash
|
||||
# 既存のKLE JSON → Beyond KLE
|
||||
beyond-kle import legacy.kle.json
|
||||
|
||||
# Beyond KLE で編集・管理
|
||||
|
||||
# 必要に応じてKLE JSONで共有
|
||||
beyond-kle export --format=kle-compatible project.beyond.json
|
||||
```
|
||||
|
||||
この設計により、Beyond KLEの高度な機能を使いながらも、既存のKLEエコシステムとの互換性を保てると思いますが、いかがでしょうか?
|
||||
@@ -0,0 +1,483 @@
|
||||
# Phase 1: JSON Schema仕様策定
|
||||
|
||||
## レイヤー間データフロー
|
||||
|
||||
```
|
||||
PHY (物理配置) → IDX (キー識別) → ELC (電気配線) + MAP (キーマップ)
|
||||
↓ ↓
|
||||
ファームウェア生成 VIA/Remap出力
|
||||
```
|
||||
|
||||
## 1. PHYレイヤー JSON Schema
|
||||
|
||||
### 基本構造
|
||||
|
||||
```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", "elements"],
|
||||
"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" }
|
||||
}
|
||||
},
|
||||
"compatibility": {
|
||||
"type": "object",
|
||||
"description": "Information for external tool compatibility",
|
||||
"properties": {
|
||||
"kleJson": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"source": { "type": "string" },
|
||||
"checksum": { "type": "string" },
|
||||
"preservedFields": { "type": "array", "items": { "type": "string" } }
|
||||
}
|
||||
},
|
||||
"originalFormat": {
|
||||
"type": "string",
|
||||
"enum": ["kle", "via", "qmk", "native"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/profile"
|
||||
}
|
||||
},
|
||||
"elements": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{ "$ref": "#/$defs/key" },
|
||||
{ "$ref": "#/$defs/cluster" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"profile": {
|
||||
"type": "object",
|
||||
"required": ["id", "name", "unitSize"],
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"unitSize": {
|
||||
"type": "object",
|
||||
"required": ["x", "y"],
|
||||
"properties": {
|
||||
"x": { "type": "number", "minimum": 0 },
|
||||
"y": { "type": "number", "minimum": 0 }
|
||||
}
|
||||
},
|
||||
"visualOffset": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": { "type": "number" },
|
||||
"y": { "type": "number" },
|
||||
"radius": { "type": "number", "minimum": 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"key": {
|
||||
"type": "object",
|
||||
"required": ["type", "id", "profile", "position", "size"],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "key"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Unique key identifier"
|
||||
},
|
||||
"profile": {
|
||||
"type": "string",
|
||||
"description": "Reference to profile ID"
|
||||
},
|
||||
"position": {
|
||||
"type": "object",
|
||||
"required": ["x", "y"],
|
||||
"properties": {
|
||||
"x": { "type": "number" },
|
||||
"y": { "type": "number" },
|
||||
"rotation": { "type": "number" },
|
||||
"rotationOrigin": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": { "type": "number" },
|
||||
"y": { "type": "number" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "object",
|
||||
"required": ["width", "height"],
|
||||
"properties": {
|
||||
"width": { "type": "number", "minimum": 0 },
|
||||
"height": { "type": "number", "minimum": 0 }
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": { "type": "string" },
|
||||
"color": { "type": "string" },
|
||||
"textColor": { "type": "string" },
|
||||
"kleCompatibility": {
|
||||
"type": "object",
|
||||
"description": "Original KLE fields for round-trip compatibility",
|
||||
"properties": {
|
||||
"originalIndex": { "type": "integer" },
|
||||
"labels": { "type": "array", "items": { "type": "string" } },
|
||||
"textSizes": { "type": "array", "items": { "type": "number" } },
|
||||
"decalData": { "type": "object" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cluster": {
|
||||
"type": "object",
|
||||
"required": ["type", "id", "name", "elements"],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "cluster"
|
||||
},
|
||||
"id": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"position": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": { "type": "number" },
|
||||
"y": { "type": "number" },
|
||||
"rotation": { "type": "number" },
|
||||
"rotationOrigin": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": { "type": "number" },
|
||||
"y": { "type": "number" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"elements": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{ "$ref": "#/$defs/key" },
|
||||
{ "$ref": "#/$defs/cluster" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 使用例
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"metadata": {
|
||||
"name": "Split Keyboard Layout",
|
||||
"author": "John Doe",
|
||||
"description": "Ergonomic split keyboard with rotated thumb clusters",
|
||||
"created": "2025-01-01T00:00:00Z"
|
||||
},
|
||||
"profiles": [
|
||||
{
|
||||
"id": "standard",
|
||||
"name": "Standard Cherry MX",
|
||||
"unitSize": { "x": 19.05, "y": 19.05 },
|
||||
"visualOffset": { "x": 1, "y": 1, "radius": 1 }
|
||||
}
|
||||
],
|
||||
"elements": [
|
||||
{
|
||||
"type": "cluster",
|
||||
"id": "left_main",
|
||||
"name": "Left Main Block",
|
||||
"position": {
|
||||
"x": 0, "y": 0,
|
||||
"rotation": 0,
|
||||
"rotationOrigin": { "x": 3, "y": 2 }
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"type": "key",
|
||||
"id": "k00",
|
||||
"profile": "standard",
|
||||
"position": { "x": 0, "y": 0 },
|
||||
"size": { "width": 1, "height": 1 },
|
||||
"metadata": { "label": "Q" }
|
||||
},
|
||||
{
|
||||
"type": "key",
|
||||
"id": "k01",
|
||||
"profile": "standard",
|
||||
"position": { "x": 1, "y": 0 },
|
||||
"size": { "width": 1, "height": 1 },
|
||||
"metadata": { "label": "W" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "cluster",
|
||||
"id": "left_thumb",
|
||||
"name": "Left Thumb Cluster",
|
||||
"position": {
|
||||
"x": 2, "y": 3,
|
||||
"rotation": 15,
|
||||
"rotationOrigin": { "x": 1, "y": 0.5 }
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"type": "key",
|
||||
"id": "k30",
|
||||
"profile": "standard",
|
||||
"position": { "x": 0, "y": 0 },
|
||||
"size": { "width": 1.25, "height": 1 },
|
||||
"metadata": { "label": "Space" }
|
||||
},
|
||||
{
|
||||
"type": "key",
|
||||
"id": "k31",
|
||||
"profile": "standard",
|
||||
"position": { "x": 1.25, "y": 0 },
|
||||
"size": { "width": 1, "height": 1 },
|
||||
"metadata": { "label": "Enter" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 2. IDXレイヤー JSON Schema
|
||||
|
||||
### 基本構造
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://beyond-kle.org/schemas/idx-layer.json",
|
||||
"title": "IDX Layer - Key Identification",
|
||||
"description": "Mapping between physical keys and logical identifiers",
|
||||
"type": "object",
|
||||
"required": ["version", "phyReference", "keyMap"],
|
||||
"properties": {
|
||||
"version": { "type": "string" },
|
||||
"phyReference": {
|
||||
"type": "object",
|
||||
"required": ["source", "checksum"],
|
||||
"properties": {
|
||||
"source": { "type": "string" },
|
||||
"checksum": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"keyMap": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/keyMapping"
|
||||
}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"keyMapping": {
|
||||
"type": "object",
|
||||
"required": ["physicalId", "logicalId"],
|
||||
"properties": {
|
||||
"physicalId": {
|
||||
"type": "string",
|
||||
"description": "Reference to PHY layer key ID"
|
||||
},
|
||||
"logicalId": {
|
||||
"type": "object",
|
||||
"required": ["index"],
|
||||
"properties": {
|
||||
"index": { "type": "integer", "minimum": 0 },
|
||||
"matrix": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"row": { "type": "integer", "minimum": 0 },
|
||||
"col": { "type": "integer", "minimum": 0 }
|
||||
}
|
||||
},
|
||||
"label": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. ELCレイヤー JSON Schema
|
||||
|
||||
### 基本構造
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://beyond-kle.org/schemas/elc-layer.json",
|
||||
"title": "ELC Layer - Electrical Configuration",
|
||||
"description": "Electrical wiring and pin mapping",
|
||||
"type": "object",
|
||||
"required": ["version", "idxReference", "microcontroller", "matrix"],
|
||||
"properties": {
|
||||
"version": { "type": "string" },
|
||||
"idxReference": {
|
||||
"type": "object",
|
||||
"required": ["source", "checksum"],
|
||||
"properties": {
|
||||
"source": { "type": "string" },
|
||||
"checksum": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"microcontroller": {
|
||||
"type": "object",
|
||||
"required": ["type", "pinMapping"],
|
||||
"properties": {
|
||||
"type": { "type": "string" },
|
||||
"pinMapping": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rows": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"cols": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"matrix": {
|
||||
"type": "object",
|
||||
"required": ["type", "dimensions"],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["standard", "charlieplexing", "direct"]
|
||||
},
|
||||
"dimensions": {
|
||||
"type": "object",
|
||||
"required": ["rows", "cols"],
|
||||
"properties": {
|
||||
"rows": { "type": "integer", "minimum": 1 },
|
||||
"cols": { "type": "integer", "minimum": 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. MAPレイヤー JSON Schema
|
||||
|
||||
### 基本構造
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://beyond-kle.org/schemas/map-layer.json",
|
||||
"title": "MAP Layer - Keymap Configuration",
|
||||
"description": "Key code assignments and layers",
|
||||
"type": "object",
|
||||
"required": ["version", "idxReference", "layers"],
|
||||
"properties": {
|
||||
"version": { "type": "string" },
|
||||
"idxReference": {
|
||||
"type": "object",
|
||||
"required": ["source", "checksum"],
|
||||
"properties": {
|
||||
"source": { "type": "string" },
|
||||
"checksum": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"layers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/layer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"layer": {
|
||||
"type": "object",
|
||||
"required": ["id", "name", "keymap"],
|
||||
"properties": {
|
||||
"id": { "type": "integer", "minimum": 0 },
|
||||
"name": { "type": "string" },
|
||||
"keymap": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/keyAssignment"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"keyAssignment": {
|
||||
"type": "object",
|
||||
"required": ["keyIndex", "keycode"],
|
||||
"properties": {
|
||||
"keyIndex": { "type": "integer" },
|
||||
"keycode": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 変換フロー設計
|
||||
|
||||
### PHY → IDX変換
|
||||
|
||||
1. PHYレイヤーから物理キーリストを取得
|
||||
2. 各キーに論理IDを割り当て
|
||||
3. マトリックス座標の自動生成(行列最適化)
|
||||
|
||||
### IDX → ELC変換
|
||||
|
||||
1. マトリックス次元を算出
|
||||
2. ピン配置の最適化提案
|
||||
3. 配線効率の検証
|
||||
|
||||
### IDX + ELC → MAP変換
|
||||
|
||||
1. キーインデックス配列の生成
|
||||
2. デフォルトキーマップの提案
|
||||
3. VIA/QMK形式への出力
|
||||
|
||||
## 次のステップ
|
||||
|
||||
1. **TypeScriptでの型定義作成**
|
||||
2. **変換ロジックのプロトタイプ実装**
|
||||
3. **テストケース(60%、Split等)での検証**
|
||||
4. **既存KLE JSONからの変換機能**
|
||||
@@ -0,0 +1,203 @@
|
||||
# Beyond KLE: TypeScript実装計画
|
||||
|
||||
## プロジェクト構造
|
||||
|
||||
```
|
||||
beyond-kle/
|
||||
├── packages/
|
||||
│ ├── core/ # 共通型定義とユーティリティ
|
||||
│ │ ├── src/
|
||||
│ │ │ ├── types/ # 各レイヤーの型定義
|
||||
│ │ │ ├── validators/ # JSON Schema バリデーター
|
||||
│ │ │ └── utils/ # 共通ユーティリティ
|
||||
│ │ └── package.json
|
||||
│ ├── phy-editor/ # PHYレイヤーエディタ
|
||||
│ ├── matrix-mapper/ # IDX/ELCレイヤーマッパー
|
||||
│ ├── keymap-designer/ # MAPレイヤーデザイナー
|
||||
│ ├── converter/ # 既存フォーマット変換
|
||||
│ └── firmware-gen/ # ファームウェア生成
|
||||
├── tools/
|
||||
│ ├── cli/ # CLI ツール
|
||||
│ └── web-editor/ # Web統合エディタ
|
||||
├── schemas/ # JSON Schema定義
|
||||
├── examples/ # サンプルプロジェクト
|
||||
└── docs/ # ドキュメント
|
||||
```
|
||||
|
||||
## 開発フェーズ
|
||||
|
||||
### Phase 1a: 基盤実装
|
||||
|
||||
1. **型定義とSchema**
|
||||
- TypeScript型定義
|
||||
- JSON Schema validation
|
||||
- テスト用サンプルデータ
|
||||
|
||||
2. **変換エンジン**
|
||||
- KLE JSON → PHY変換
|
||||
- PHY → IDX自動生成
|
||||
- QMK/VIA出力
|
||||
|
||||
3. **CLIツール**
|
||||
- 変換機能のコマンドライン提供
|
||||
- バリデーション機能
|
||||
|
||||
### Phase 1b: エディタプロトタイプ
|
||||
|
||||
1. **PHY Editor (Web)**
|
||||
- React + Canvas での編集機能
|
||||
- KLE互換インポート
|
||||
|
||||
2. **Matrix Mapper**
|
||||
- 視覚的マトリックス割り当て
|
||||
- 最適化アルゴリズム
|
||||
|
||||
## 技術スタック
|
||||
|
||||
### コア
|
||||
|
||||
- **言語**: TypeScript
|
||||
- **ランタイム**: Node.js / Bun
|
||||
- **パッケージ管理**: pnpm + workspace
|
||||
- **バリデーション**: Ajv (JSON Schema)
|
||||
|
||||
### CLI
|
||||
|
||||
- **フレームワーク**: Commander.js
|
||||
- **出力**: chalk (色付き), table
|
||||
|
||||
### Web Editor
|
||||
|
||||
- **フロントエンド**: React + Vite
|
||||
- **描画**: Konva.js / Fabric.js
|
||||
- **状態管理**: Zustand
|
||||
- **UI**: Mantine / shadcn/ui
|
||||
|
||||
### テスト
|
||||
|
||||
- **テストランナー**: Vitest
|
||||
- **E2E**: Playwright
|
||||
|
||||
## 実装優先度
|
||||
|
||||
### 最優先 (2週間)
|
||||
|
||||
1. 型定義とJSON Schema
|
||||
2. KLE → PHY変換
|
||||
3. PHY → IDX基本変換
|
||||
4. CLIでの変換確認
|
||||
|
||||
### 高優先 (1ヶ月)
|
||||
|
||||
1. QMK/VIA出力
|
||||
2. 基本的なWebエディタ
|
||||
3. マトリックス最適化
|
||||
|
||||
### 中優先 (2ヶ月)
|
||||
|
||||
1. 高度なエディタ機能
|
||||
2. プロジェクト管理
|
||||
3. 既存ツール連携
|
||||
|
||||
## 開発環境セットアップ
|
||||
|
||||
### 1. プロジェクト初期化
|
||||
|
||||
```bash
|
||||
# プロジェクト作成
|
||||
mkdir beyond-kle-proto
|
||||
cd beyond-kle-proto
|
||||
|
||||
# pnpm workspace設定
|
||||
echo 'packages:\n - "packages/*"\n - "tools/*"' > pnpm-workspace.yaml
|
||||
|
||||
# TypeScript設定
|
||||
pnpm add -D typescript @types/node
|
||||
npx tsc --init
|
||||
|
||||
# 共通依存関係
|
||||
pnpm add -D vitest ajv
|
||||
```
|
||||
|
||||
### 2. パッケージ構成
|
||||
|
||||
```bash
|
||||
# Core package
|
||||
mkdir -p packages/core/src/{types,validators,utils}
|
||||
cd packages/core && pnpm init
|
||||
|
||||
# CLI tool
|
||||
mkdir -p tools/cli/src
|
||||
cd tools/cli && pnpm init
|
||||
|
||||
# 各エディタパッケージ
|
||||
mkdir -p packages/{phy-editor,matrix-mapper,keymap-designer}
|
||||
```
|
||||
|
||||
### 3. 開発ツール設定
|
||||
|
||||
```bash
|
||||
# Linting & Formatting
|
||||
pnpm add -D eslint prettier @typescript-eslint/eslint-plugin
|
||||
|
||||
# Testing
|
||||
pnpm add -D @vitest/ui
|
||||
|
||||
# Build tools
|
||||
pnpm add -D tsup
|
||||
```
|
||||
|
||||
## サンプルプロジェクト設計
|
||||
|
||||
### テストケース
|
||||
|
||||
1. **60% ANSI** - 標準的なレイアウト
|
||||
2. **Split keyboard** - 左右分離型
|
||||
3. **Ergonomic layout** - カラムスタッガード + 回転
|
||||
4. **Macro pad** - シンプルな4x4
|
||||
|
||||
### データ例
|
||||
|
||||
#### 60% ANSI PHY Layer
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"metadata": {
|
||||
"name": "60% ANSI",
|
||||
"description": "Standard 60% layout"
|
||||
},
|
||||
"profiles": [
|
||||
{
|
||||
"id": "standard",
|
||||
"name": "Cherry MX",
|
||||
"unitSize": { "x": 19.05, "y": 19.05 }
|
||||
}
|
||||
],
|
||||
"keys": [
|
||||
// Escape key
|
||||
{
|
||||
"id": "k00", "profile": "standard",
|
||||
"position": { "x": 0, "y": 0 },
|
||||
"size": { "width": 1, "height": 1 },
|
||||
"metadata": { "label": "Esc" }
|
||||
},
|
||||
// Space bar
|
||||
{
|
||||
"id": "k60", "profile": "standard",
|
||||
"position": { "x": 5, "y": 4 },
|
||||
"size": { "width": 6.25, "height": 1 },
|
||||
"metadata": { "label": "Space" }
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 次のアクション
|
||||
|
||||
1. **GitHub リポジトリ作成**
|
||||
2. **開発環境セットアップ**
|
||||
3. **型定義の実装開始**
|
||||
4. **KLE変換機能のプロトタイプ**
|
||||
|
||||
実装を開始しますか?最初にどの部分から取り組みたいでしょうか?
|
||||
Reference in New Issue
Block a user