From 55243f626d183973cd68af6681e37c45b6f318f1 Mon Sep 17 00:00:00 2001 From: EwdErna Date: Fri, 18 Sep 2026 08:43:01 +0900 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=9C=9F=E6=A4=9C=E8=A8=8E=E6=AE=B5?= =?UTF-8?q?=E9=9A=8E=E3=81=AE=E3=83=89=E3=82=AD=E3=83=A5=E3=83=A1=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=81=A8=E9=81=8B=E7=94=A8=E3=83=AB=E3=83=BC=E3=83=AB?= =?UTF-8?q?=E3=82=92=E5=8F=96=E3=82=8A=E8=BE=BC=E3=82=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ルール策定前に書かれた設計ドキュメント7点を、以降の基準点として そのままの状態で記録する。CLAUDE.md のルール1〜4は未適用。 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01MdLT5x5ka5y6CaWLeE3EKb --- .claude/skills/decide/SKILL.md | 87 +++++ .claude/skills/doc-lint/SKILL.md | 64 ++++ .claude/skills/doc-lint/doc-lint.py | 157 ++++++++ .claude/skills/restructure-doc/SKILL.md | 96 +++++ CLAUDE.md | 91 +++++ PHYレイヤー.md | 188 +++++++++ keyboard-design-layers.md | 167 ++++++++ keyboard-making-workflow.md | 157 ++++++++ kle-compatibility-analysis.md | 453 ++++++++++++++++++++++ kle-compatibility.md | 249 ++++++++++++ phase1-json-schema.md | 483 ++++++++++++++++++++++++ typescript-implementation-plan.md | 203 ++++++++++ 12 files changed, 2395 insertions(+) create mode 100644 .claude/skills/decide/SKILL.md create mode 100644 .claude/skills/doc-lint/SKILL.md create mode 100644 .claude/skills/doc-lint/doc-lint.py create mode 100644 .claude/skills/restructure-doc/SKILL.md create mode 100644 CLAUDE.md create mode 100644 PHYレイヤー.md create mode 100644 keyboard-design-layers.md create mode 100644 keyboard-making-workflow.md create mode 100644 kle-compatibility-analysis.md create mode 100644 kle-compatibility.md create mode 100644 phase1-json-schema.md create mode 100644 typescript-implementation-plan.md diff --git a/.claude/skills/decide/SKILL.md b/.claude/skills/decide/SKILL.md new file mode 100644 index 0000000..5c8100d --- /dev/null +++ b/.claude/skills/decide/SKILL.md @@ -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-.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 だけ書けばよい。 +- 複数の決定をまとめてコミットしない。分ける。 diff --git a/.claude/skills/doc-lint/SKILL.md b/.claude/skills/doc-lint/SKILL.md new file mode 100644 index 0000000..aa52234 --- /dev/null +++ b/.claude/skills/doc-lint/SKILL.md @@ -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-.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 を個別に直そうとしない。 diff --git a/.claude/skills/doc-lint/doc-lint.py b/.claude/skills/doc-lint/doc-lint.py new file mode 100644 index 0000000..fa155e8 --- /dev/null +++ b/.claude/skills/doc-lint/doc-lint.py @@ -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): + """## 配下の (行番号, 本文) を返す。無ければ 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)) diff --git a/.claude/skills/restructure-doc/SKILL.md b/.claude/skills/restructure-doc/SKILL.md new file mode 100644 index 0000000..d8395a2 --- /dev/null +++ b/.claude/skills/restructure-doc/SKILL.md @@ -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 +``` + +## 注意 + +- 複数ファイルを一度に触らない。ファイル間で内容が重複していると気づいた場合は、 + 勝手に統合せず、指摘だけしてそのファイルの再編を終える。 +- 再編の途中で事実関係の誤りを見つけても、勝手に直さない。指摘する。 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b64d9a4 --- /dev/null +++ b/CLAUDE.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/` に落とす。 +- 既存ドキュメントの記述と食い違う事実を見つけたら、勝手に上書きせず指摘する。 + +## 未確認事項 + +(なし) diff --git a/PHYレイヤー.md b/PHYレイヤー.md new file mode 100644 index 0000000..35166e5 --- /dev/null +++ b/PHYレイヤー.md @@ -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出力時に不要な情報を含めず、ファイルサイズを最小化 diff --git a/keyboard-design-layers.md b/keyboard-design-layers.md new file mode 100644 index 0000000..a0cb139 --- /dev/null +++ b/keyboard-design-layers.md @@ -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. リファレンス実装の開発 diff --git a/keyboard-making-workflow.md b/keyboard-making-workflow.md new file mode 100644 index 0000000..8f295cb --- /dev/null +++ b/keyboard-making-workflow.md @@ -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. **テストケース作成**: 代表的なキーボード設計での検証 diff --git a/kle-compatibility-analysis.md b/kle-compatibility-analysis.md new file mode 100644 index 0000000..6861808 --- /dev/null +++ b/kle-compatibility-analysis.md @@ -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の既存データモデルを活用 +- 見た目に影響しない形でメタデータ保存 +- 階層構造の完全な再現が可能 +- 段階的な実装が可能 + +階層型クラスターが「絶対実装したい」要求に対して、技術的に実現可能な解決策を提供できます。 diff --git a/kle-compatibility.md b/kle-compatibility.md new file mode 100644 index 0000000..48423fc --- /dev/null +++ b/kle-compatibility.md @@ -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エコシステムとの互換性を保てると思いますが、いかがでしょうか? diff --git a/phase1-json-schema.md b/phase1-json-schema.md new file mode 100644 index 0000000..980e385 --- /dev/null +++ b/phase1-json-schema.md @@ -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からの変換機能** diff --git a/typescript-implementation-plan.md b/typescript-implementation-plan.md new file mode 100644 index 0000000..510a82c --- /dev/null +++ b/typescript-implementation-plan.md @@ -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変換機能のプロトタイプ** + +実装を開始しますか?最初にどの部分から取り組みたいでしょうか?