初期検討段階のドキュメントと運用ルールを取り込む
ルール策定前に書かれた設計ドキュメント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