Selection / Focus Contract (Modeler)

このドキュメントは UIの発火点(pick / outliner / quickcheck / undo-redo) が起きたときに、 何が「必ず」同期されるべきか を固定するための契約です。

対象:Modeler UI(apps/modeler/ssot/ui/**)と Hub/Core/Renderer の境界。

用語

  • selection: 選択中の要素UUID配列(通常は単一選択、複数選択あり)
  • focus: ある要素へ「視点+UI」を寄せること(selection を伴う)
  • issueLike: QuickCheck等から来る { uuid?, kind?, path? }

入口(UI発火点)と必須の結果

共通(すべての入口):

  • selection が変更されたら、Property/Preview/Outliner は最新の selection と doc に整合する(取りこぼしゼロ)。
  • focus が発火したら、同一選択であっても Outliner の reveal+flash は必ず走る(視認性の保証)。

1) Preview pick(キャンバス上クリック)

入口:

  • uiCanvasControlleruiSelectionController.selectFromPick(issueLike, ev)

必須結果:

  • selection が更新される(複数選択修飾:ctrl/cmd toggle)
  • 単一選択なら core.focusByIssue({uuid, kind?, path?}) が呼ばれる
  • Property は selection の内容へ同期(単一なら該当要素の編集UI、複数なら閉じる)
  • Outliner は該当行を可視化+フラッシュ(selection 変更が無い場合でも focus イベントで保証)

2) Outliner クリック

入口:

  • UiOutlinerController.onRowSelect(issueLike, ev)uiSelectionController.selectFromOutliner(...)

必須結果:

  • selection が更新される(shift範囲選択 / ctrl/cmd toggle)
  • 単一選択なら focus が発火する
  • Property は selection の内容へ同期(単一/複数ルールは共通)

3) QuickCheck issue クリック

入口:

  • uiToolbarController(QuickCheck)→ uiSelectionController.selectIssue(issueLike)core.focusByIssue(issueLike)

必須結果(uuid/pathどっちでも):

  • issueLike を解決して uuid を確定(uuidが無ければ path→uuid 解決)
  • selection を確実に単一選択へセット
  • focus イベントにより
    • 視点(renderer.focus / camera)更新
    • Outliner 可視化+フラッシュ
    • Property は該当要素へ同期(必要なら issue の field へ誘導)

※「同じuuidの再クリック」でも focus イベントでUI更新が起きること。

4) Undo / Redo

入口:

  • core edit undo/redo

必須結果:

  • document が更新される
  • 現 selection の要素が doc 上で無効になった場合は selection が prune される
  • Property/Preview は最新docと整合(選択が同一でも値は必ず更新)

未適用dirty(Property)と selection 変更のガード

  • selection を変える入口(pick/outliner/quickcheck)は 必ず ensureEditsAppliedOrConfirm({reason}) を通す
  • ガードの結果:
    • Apply / Discard / Cancel のいずれかが確定するまで selection は変わらない(Cancelなら元に戻す)

参照(コード内リンク)

各UI発火点にはこのドキュメントへのリンクコメントを付与する: packages/docs/docs/modeler/selection-contract.md