この記事で扱うこと
ギャラリーカードからドキュメントへ入る peek 流れ を、横(side)→ 中央(center)→ 再帰 push → 編集可能な peek まで上げ、一つに固まっていた大きな editor.css を @layer + デザイントークン 構造に割って消費者がテーマを上書きできるようにした。各作業がどんな状況で何を目標にどう解けて何を学んだかを、作業ごとに整理する。
一日のまとめ
| 作業 | 何をしたかったか | やったこと | 結果 |
|---|---|---|---|
| Peek UX | 単一 shell + in-place push + breadcrumb stack | overlay を一つ維持し activeDocumentId だけ交換、RELATED は in-modal push、editable promotion 時に onPeekContentChange persist | side/center → 再帰 push → 編集可能な peek、spatial context 維持 |
| State | stale 中間フレームを遮断、refresh 時に cache 政策を分離 | id 変更時に render-time reset、reopen 経路は skip または force network | (newId, oldContent)「戻ったような」フレーム・以前の revision を遮断 |
| CSS | tokens/base/blocks/chrome/print @layer 分離 | 単一 CSS を @layer sdui-doc.* に機械分離、byte-identical 確認 | 視覚変更なしに layer 別ファイル分離、index/viewer 入口の分離 |
| Token | --sdui-doc-* トークン — 値は同一、構造だけ layer へ | --sdui-doc-surface などのトークン昇格 + 既定値 alias | 消費者が unlayered 一行でテーマ override 可能 |
| 配布 | exports/files に CSS entry を含める、byte-identical 検証 | manifest に CSS export を追加、changeset minor(視覚変更)メモ | unstyled viewer の配布事故を防止 |
1. Nested modal document navigation
前提知識(概念)
- Peek / nested modal navigation: オーバーレイ窓(dialog)を 一つだけ 維持し、その中の
documentIdだけ替えながらドキュメントを掘り進む UX。毎回新しい窓を開かない。
どんな状況だったか
gallery → peek → related page とつながるとき、depth ごとに 新しい modal/route を開くと focus trap·scroll lock·animation が重なる。document async resolve の途中で (newId, oldDoc content) が一フレーム描かれると、ユーザーは「戻った」と感じる。refresh 直後の peek reopen が cache-first だと以前の revision が再び見えうる。
中心の作業
overlay 一つ を維持し内部の activeDocumentId だけ交換すれば spatial context が維持される。readOnly → editable promotion 時は onPeekContentChange で persist し、RELATED リンクは in-modal push(新 tab ではない)で処理した。id 変更時に render-time reset で stale 中間フレームを遮断し、reopen 経路は skip または force network で cache 政策を分離した。router.back()·popstate は view transition と incompatible なので explicit stack pop + URL push が安全だ。結果として side/center → 再帰 push → 編集可能な peek まで spatial context を維持した。
教訓
- peek はドキュメントスタック UX — 見た目ではなく境界設計だ。
- async resolve hook は id 変更時に 即座に stale を遮断 — 「一瞬だけ古いドキュメント」が UX バグだ。
- refresh + cache-first は reopen 経路で skip または force network に分ける。
- E2E の drag 失敗は baseline を分離してから製品 vs シミュレーションの限界を区別する。
→ Nested modal document navigation
2. Layered CSS package
前提知識(概念)
- Cascade layer (
@layer): CSS ルールに優先順位の階層を付ける機能。@layerの中に入れたルールは、階層に入れていない(unlayered)消費者ルールに 常に負ける — よく使う selector の詳細度(specificity)と無関係に。 - specificity(詳細度): 同じ属性をめぐってどの CSS ルールが勝つかを決める計算法。layer がなければこの詳細度争いだけが残る。
- デザイントークン: 色・間隔のような値を
--sdui-doc-*のような CSS 変数で名付けたもの。消費者は値だけ替えてテーマを作る。
どんな状況だったか
単一 editor.css は fork·テーマ·viewer-only バンドルすべて難しく、消費者のカスタム CSS が layer の外だと specificity 戦争 だけが残る。npm publish manifest に CSS export が抜けると消費者は unstyled viewer を受け取る。
中心の作業
一つに固まっていた単一 CSS を @layer sdui-doc.* に機械的に分離し --sdui-doc-surface のようなトークンを昇格した。区間を分けてつなぎ合わせた結果が 原本とバイト単位で同一(byte-identical) であることを確認して「視覚変更なし」を証明したあと、README に消費者 override レシピを書いた。核心のルール: パッケージ CSS は全部 layer の中、消費者テーマは layer の外(unlayered)一行で上書きする。--sdui-doc-surface が定義されずルールが丸ごと無効化されていた問題は既定値 alias で捕まえた。manifest に CSS export を追加し changeset minor(視覚変更)メモを残して unstyled viewer の配布事故を防いだ。
教訓
- CSS split は cascade 優先順位 UX — 見た目ではなく境界設計だ。
- layer 分離は「値を替える」ではなく 消費者 override の可能性 を開く構造作業だ。