イジェクト — 一方通行の凍結
EXPERIMENTAL
tabula eject(一方通行の凍結と --report の逆引きマップ分析の両方)は、v0.3.0 では実験的機能として 提供されます:コマンドは動作し、テストもされていますが、そのサーフェス――フラグ、レポートの形式、 そしてティア表――は、将来のマイナーリリースで変更される可能性があります。フィードバックは GitHub の issue で歓迎します。
tabula eject は、検証済みの .tabula/ をプロジェクト所有のディレクトリへコピーし、トークンパイプラインがそこへ流れ込むのを止めます。研究(そして .orchestrator/plan.md に記録されているプローブ)により、.tabula/source.css はクラスの変更なしに標準の @tailwindcss/cli でコンパイルできることが確立されています――そのためイジェクトは書き換えではなく凍結です:あなたは自分が出荷した正確な CSS を保持し、cn() を保持し、そしてジェネレーターを手放します。
これは、移行なしにプロファイルから離れたい日のために存在します:メンテナンスフェーズに入るプロジェクト、Tabula を採用しないチームへの引き継ぎ、あるいは何年か先に Tailwind だけでビルドしなければならないアーカイブなどです。
これは一方通行のドアです。 イジェクトの後にはリビルドも、
tabula build --checkのドリフトゲートも、凍結されたコピーに対するtabula scanの閉包ゲートもありません。tokens/でのトークンの変更はもうそこには届きません。元に戻すには、凍結されたディレクトリを削除し、ソースからリビルドしてください――逆方向には何も流れません。イジェクトがデフォルトでドライランなのは、まさにこのドアを渡ることが意図的な二段階目のステップ(--yes)になるようにするためであり、事故として起こらないようにするためです。
コマンド
tabula eject [--to <dir>] [--yes] [--force] [--write-imports]--to <dir>— 凍結対象。デフォルトはプロジェクトルート配下のtabula-frozen/。--yes— 実行します。指定しない場合、eject はドライランです: コピーするすべてのファイル、パーミッションの変更、見つかった@importの書き換え、有効期限の警告、一方通行のドアのバナー、そしてcn()のポリシー注記からなる完全な計画を出力し、何にも触れません。--force— すでにファイルを含んでいるターゲットディレクトリを許可します(指定しない場合、空でないターゲットは拒否されます)。--write-imports—.tabula/source.cssを指しているプロジェクトの CSS の@import行を、凍結されたコピーを指すように書き換えます。指定しない場合、eject は見つかったファイルと、正確な新しい import 行を出力するだけなので、自分で変更を適用できます。
前提条件(この順序でフェイルクローズ)
- 読み込み可能なプロジェクト — ルートに
tabula.config.json+tokens/*.tokens.json(なければTAB-E901)。eject はプロジェクトルートから実行してください。 - 既存の、検証済みの
.tabula/— マニフェストが存在し、入力が今もそれと同じハッシュに一致し(陳腐化していない)、すべてのアーティファクトのsha256がマニフェストと一致し、マニフェストがカバーしていないものがディレクトリの中に何も隠れていないこと。これはtabula doctorが使うのと同じマニフェストハッシュのドリフトゲートであり、同じコードを再利用します:TAB-E303(欠落/陳腐化)とTAB-E601(手編集または未カバー)。イジェクトは検証済みの状態だけを凍結します――ドリフトした、あるいは陳腐化した.tabula/は拒否されます。それを凍結することは、もはやトークンと一致しない何かを凍結することになるからです。 - 使用可能なターゲット — ターゲットディレクトリは存在しないか、
--forceが与えられない限り空でなければなりません(TAB-E240)。書き込めないターゲットも同様にTAB-E240です。
4 つのハザードと、eject がそれをどう扱うか
イジェクトは、微妙に間違えやすい作業です。前もって 4 つの落とし穴が特定されており、それぞれが偶然任せではなく明示的に処理されます。
1. cn() は依然として @tabula-css/merge を必要とします — tailwind-merge に差し替えないでください
凍結された CSS は標準の Tailwind ですが、ランタイムはそうではありません。cn() はあなたの registry.json からクラスの競合を解決します。tailwind-merge はあなたの語彙を知らないため、cn() を置き換えると描画される出力が変わります。concepts.md からの反例です:
cn("pt-sm", "p-md") // Tabula → "p-md" (p-md is later and covers padding-top)
cn("pt-sm", "p-md") // tailwind-merge → "pt-sm p-md" (keeps pt-sm — different CSS)そのため、イジェクトされたディレクトリは registry.json を保持し、EJECTED.md は @tabula-css/merge を保持し続けるようにと文書で伝えます。マージランタイムを静かに置き換えることを示唆するものは何もありません。
2. イジェクトされたコピーはあなたのもの(0644)であり、読み取り専用のアーティファクト(0444)ではありません
.tabula/ 配下のアーティファクトは読み取り専用(0444) で書き込まれ、マニフェストでゲートされているため、エディタが生成済みファイルの上に静かに上書き保存することはできません。イジェクトされたコピーはその逆です:それらはもうあなたのファイルです。イジェクトはすべてのコピーを、プロジェクト所有のディレクトリの中に 0644 で書き込みます。そのため、パーミッションビットや、もう動かないドリフトチェックと戦うことなく編集できます。
3. 期限切れの例外は警告するだけで、ブロックしません
通常、期限切れの例外はハードなビルドエラー(TAB-E141)です――これは、閉じた語彙が静かに開いた語彙になってしまうのを止めるメカニズムです。しかしイジェクトの後にはリビルドがないため、そのエラーは二度と発火しえません。それをブロックすることは、イジェクトがすでにキャンセルした未来のリビルドをブロックすることになります。代わりに、イジェクトは、期限切れの、または 90 日以内に期限切れとなるすべての例外について TAB-W402 で警告し、EJECTED.md はすべての例外をそのステータスとともに列挙します。そのため、その負債を凍結することは、あなたが受け継ぐ不意打ちではなく、あなたが目にする選択になります。
4. 一方通行のドアは見逃しようがありません
すべての実行――ドライランと実行の両方――で、トークンの変更が流れなくなること、そしてリビルドがないことを述べるバナーが出力されます。デフォルトがドライランであることによって、何かが書き込まれる前に、常に計画全体を目にすることになります。
動き続けるもの、止まるもの
動き続けるもの:
- ランタイムの
cn()—@tabula-css/mergeとコピーされたregistry.jsonとともに(ハザード 1)。 types.d.ts— クラス名の型は CSS と一緒に凍結されるため、エディタのオートコンプリートとクラス文字列の型チェックは変わりません。llms.txt/llms-full.txt/AGENTS.md.snippet— エージェントサーフェスもコピーされるため、凍結されたディレクトリを読むアシスタントは、依然として語彙とルールを得られます。- 標準の Tailwind でのコンパイル —
source.cssは@tailwindcss/cliで直接ビルドできます。
止まるもの:
- リビルド —
tabula buildはもう凍結されたディレクトリを対象にしません。トークンの編集はそこには届きません。 - トークンの変更の伝播 — パイプラインは切断され、凍結はある時点でのスナップショットになります。
- スキャンとドリフトのゲート —
tabula scanの閉包とbuild --checkのドリフトは、もう凍結されたコピーを統制しません。それは今や、普通のプロジェクト CSS です。
イジェクトの後で
凍結されたディレクトリに書き込まれる EJECTED.md は、プロヴェナンス(プロファイル id、バージョン、入力ハッシュ)、一方通行のドアについての声明、cn() のポリシー注記、例外の完全な一覧、そして検証コマンドを記録します。凍結が標準の Tailwind でコンパイルされることを確認するには:
npx @tailwindcss/cli -i tabula-frozen/source.css -o out.css--write-imports を使った場合、あなたのアプリのスタイルシートの @import はすでに tabula-frozen/source.css を指しています。そうでない場合、イジェクトが貼り付けるべき正確な行を出力しています。
逆引きマップレポート (--report)
イジェクトは Tabula の CSS を凍結し、レンダリングをそのまま正確に保ちます。それとは別の問いがあります: 自分のクラス使用のうち、どれだけを素の (vanilla) Tailwind v4 へ移せるだろうか? tabula eject --report はそれに正直に答えます。これは分析モードです――凍結はせず、.tabula/ への書き込みもなく、 常に exit 0――スキャンされたソースで実際に使われているクラスを、凍結された逆引きマップテーブルに照らして、 4 つのティアに分類します:
| tier | meaning | --write? |
|---|---|---|
| A | 素の Tailwind にも同じ綴りが存在し、生成される宣言が等価――ツールチェーンを切り替えてもレンダリングは変わらない。 | not needed |
| B | 証明可能に 1 対 1 で、レンダリングを保つリネーム(例:opacity-disabled → opacity-50)。 | yes |
| C | マッピングは存在するが、レンダリングまたはセマンティクスが変わる――レポートのみ、正確な注意事項付き。 | never |
| D | 素の Tailwind に相当するものがない(type-* タイポグラフィバンドル)――凍結された CSS を維持するか、手で再設計する。 | never |
バリアントチェーンはその最も弱い部分によって分類されます:Tabula が :where() で下げるすべての セルフステートバリアント(hover、focus、active など)はティア C です――その詳細度は (0,1,0) で あるのに対し、素の Tailwind は (0,2,0) であり、さらに hover は素の Tailwind の @media (hover:hover) ゲートも失います――そのため、どれほどベースが移植可能であっても、実際のチェーンは 例外なく C に着地します。
tabula eject --report [--theme-port] [--format=json] [--write]--report—tabula-eject-report.mdをプロジェクトのそばに書き込みます(標準出力にもエコーします): ヘッダーの判定結果、ティアごとの件数、ファイルごとの作業リスト(file:line、クラス、ティア、 ターゲットまたは注意事項)、ティア D の一覧、そして主要な注意事項。--format=jsonは同じデータを JSON ドキュメントとして出力します。--theme-port— with-theme-port シナリオをレポートし、名前空間の移植手順を追加します。 以下の2つのシナリオを参照してください。--write—tabula migrateと同じコードモッドエンジンを通じて、ティア B のリネームのみを 適用します(本物の統合 diff、クラスのシンクのみ)。それ以外はすべて、構造上レポートのみです。 適用可能なものが何もない状態で--writeを使うと0 rewritesと出力され、exit 0 になります。
2 つのシナリオ
Tabula の theme.css は --tb-* のカスタムプロパティだけを定義し、その source.css は素の Tailwind のデフォルトテーマの上で動作します。そのため、bg-accent や p-lg のような名前付きトークンユーティリティ は、有効な vanilla ユーティリティの形ではありますが、vanilla が読むのは --color-accent / --spacing-lg であり、Tabula はそれらを決して埋めません:
- as-is — イジェクトし、綴りをそのまま保ち、テーマ変数をまったく移植しない場合。トークン ユーティリティは何も出力しない(色、間隔、サイズ)か、vanilla のデフォルト値に解決されます (
rounded-mdは Tabula の0.5remではなく0.375remになります)。これらのファミリーは as-is ではティア C です。 - with-theme-port(
--theme-port) — Tabula のトークン値を vanilla の名前空間にコピーする場合 (--tb-color-*→--color-*、--tb-spacing-*→--spacing-*、--tb-radius-*→--radius-*)。 同じファミリーがティア A になります。
移植によってすべてのファミリーが救われるわけではありません。物理軸 vs 論理軸の間隔ファミリー(px、 mx、inset-x、scroll-px、border-x――Tabula では論理インライン、vanilla では物理的な左右である ため、LTR では同一でも RTL では鏡像になります)と、ring-* / shadow-* のカラーユーティリティ (Tabula では不活性な --tb-ring-color が、vanilla ではアクティブな --tw-ring-color になります―― リングを描画しない要素が、突然リングを描画するようになることがあります)は、両方のシナリオでティア C のままです。
実測値 (reference-ui)
examples/reference-ui プロジェクトで測定(478 個のベースクラス + 1131 個のバリアントチェーン = 1609 トークン、レジストリに対して jq で検証済み――.orchestrator/R2.4-reverse-map.md):
| scenario | A | B | C | D |
|---|---|---|---|---|
| as-is | 83 | 11 | 1511 | 4 |
| with-theme-port | 405 | 11 | 1189 | 4 |
--write に対して安全なのは、スカラートークンのリネームである opacity-disabled、duration-fast、 ease-standard、border-{t,b,y,s,e}-thin、align-start、align-end、not-prose のわずか 11 トークン(0.7%) だけであり、その集合は両方のシナリオで同一です(テーマ移植によってもこの数は増えません)。 すべてのバリアントチェーンは、両方のシナリオで C です。このコマンドが体現している結論は意図的なもの です:完全な vanilla 移行が製品なのではなく、正直なレポートこそが製品です――そして tabula eject (CSS を凍結すること)は、レンダリングを正確に保つ手段であり続けます。
イジェクト vs. 復元
イジェクトは出力を凍結します。.tabula/ からプロファイルを復元するはソースを再構築します。これらは逆方向です:ジェネレーターの使用をやめて CSS を保持したいときはイジェクトし、トークンを取り戻して生成を続けたいときは復元してください。