Skip to content

refactor(domain): ツリー可視性ルールをvisibleChildrenOfに単一化#72

Open
hashrock wants to merge 1 commit into
mainfrom
claude/blissful-brown-decnjs
Open

refactor(domain): ツリー可視性ルールをvisibleChildrenOfに単一化#72
hashrock wants to merge 1 commit into
mainfrom
claude/blissful-brown-decnjs

Conversation

@hashrock

Copy link
Copy Markdown
Owner

観測した問題(SSoT違反)

「ノードの子がどこまで見えるか」という同一のルール — collapsedなノードは子孫を全て隠す。objectノードは直接の子だけをカードの行として見せ、孫はカード内に隠す — が、3箇所で独立に再実装されていました。

  • app/domain/model.tsgetFlatOrder(キーボード操作の移動順)
  • app/application/nodeUtils.tsflattenToNodes(canvasレイアウト)
  • app/application/outline.tsoutlineRows(アウトライン/モバイル行リスト)

各所のコメントがこの重複を認識していたのが根拠です(nodeUtils.ts: 「mirroring getFlatOrder」、outline.ts: 「Mirror getFlatOrder」)。つまり実装ではなく散文コメントだけで3箇所の同期を保っていました。

ドリフトのシナリオ: 新しいNodeTypeで孫を隠す挙動を追加/変更する、あるいはcollapsedの意味を拡張するとき、開発者はテストが直接紐づく getFlatOrder は更新しても、flattenToNodesoutlineRows の“ミラー”実装を見落とし得ます。結果として、canvasの矢印キー移動がアウトラインに描画されないノードへ着地する、あるいはアウトラインがcanvasのレイアウトが配置しないノードを表示する、という不整合が生まれます(本リポジトリのCLAUDE.mdが定めるキーボード不変条件と同じ種類のバグですが、対象はエディタサーフェスではなく可視性です)。

変更内容

app/domain/model.tsvisibleChildrenOf(node) を追加し、上記3関数すべてがこの1つの判定に委譲するようにしました。

export type VisibleChildren =
  | { kind: "none" }
  | { kind: "leaves"; children: MindMapModel[] }
  | { kind: "recurse"; children: MindMapModel[] };

export function visibleChildrenOf(node: MindMapModel): VisibleChildren {
  if (node.collapsed) return { kind: "none" };
  if (node.type === "object") return { kind: "leaves", children: node.children };
  return { kind: "recurse", children: node.children };
}

既存挙動は変更していません(純粋なルール抽出のリファクタリング)。visibleChildrenOf の単体テストを app/domain/model.test.ts に追加しました。

守りたい設計意図

「ノードの子孫がどこまで到達可能か」という事実は、このアプリの3つの異なる出力(キーボード移動順・canvasレイアウト・アウトライン行)すべてが依存する共有ルールであり、1箇所で定義され他はそれを参照するべきである、というSSoT原則です。今後NodeTypeやcollapsedの意味を拡張する際は visibleChildrenOf 一箇所を変更すれば3箇所すべてに反映されます。

確認方法

  • 型チェック: npx tsc --noEmit -p . → エラーなし
  • 単体テスト: pnpm test → 変更前と同じ2件の失敗(editorKeymap.test.ts の Right/Left collapse系、本PRとは無関係・mainブランチでも同様に失敗することを確認済み)。それ以外は新規3件を含め全て成功(450 passed)。
  • 特に app/domain/model.test.tsgetFlatOrder/新規visibleChildrenOf)、app/application/outline.test.tsapp/application/objectNode.test.tsapp/application/editorReducer.test.ts を個別実行し全件成功を確認。
  • ブラウザe2e(keyboardEscape.browser.test.tsx)はこのサンドボックス環境のPlaywrightブラウザバージョン不整合により実行できず未検証です(環境起因、本変更とは無関係)。ローカル環境でのCIでの実行を推奨します。

レビュー観点

  • visibleChildrenOf の3分岐(none/leaves/recurse)が、置き換え前の各関数のif分岐と1対1で対応していることの確認(特に「collapsedなobjectノード」はtype === "object" && !collapsedの条件が false になるため元々 leaves 分岐に入らず、collapsed優先判定と一致している点)。
  • flattenToNodes 側で collapsed 変数を可視性判定とは別にノードのプロパティ値(children: collapsed ? [] : ..., collapsed, フィールド)としてそのまま残している点が妥当か。

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01CVSS2TV3EqjYHspRQ5HyYj


Generated by Claude Code

getFlatOrder/flattenToNodes/outlineRowsが「collapsedなら子を隠す、
objectノードは直接の子だけ見せて孫は隠す」というルールをそれぞれ
独立に再実装しており、コード中のコメントも "mirroring getFlatOrder"
と手動同期を認めていた。domain/model.tsにvisibleChildrenOfを追加し、
3箇所とも同じ判定に委譲するよう変更(挙動は変えていない)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVSS2TV3EqjYHspRQ5HyYj
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants