MAS-356: Tremor + v0.dev 採用(フロントエンド UI コンポーネント生成効率化)
概要
| 項目 | 内容 |
|---|---|
| 案件 ID | MAS-356 |
| 案件名 | Tremor + v0.dev 採用(フロントエンド UI コンポーネント生成効率化) |
| カテゴリ | 🎨 UI / フロントエンド技術基盤 |
| 優先度 | P2 ★★ |
| 推奨実装順 | MAS-356 → MAS-358 → MAS-357(本案件が MAS-358 左サイドバー実装の前提) |
| 関連案件 | MAS-355(CapitalAllocationPanel が Phase 2 の試験ターゲット)/ MAS-358(本案件の Tremor 基盤を活用した左サイドバー実装)/ MAS-357(Firebase 移行・本案件完了後に実施) |
| 新規追加ファイル | webapp_client/tailwind.config.ts(Phase 1)/ webapp_client/src/styles/tremor-scope.css(Phase 1) |
| 変更ファイル | webapp_client/vite.config.ts(Tailwind v4 プラグイン追加)/ webapp_client/package.json(依存追加)/ webapp_client/src/cockpit/CapitalAllocationPanel.tsx(Phase 2 試験移行) |
| 実装ステータス | 完了 |
背景・目的
現状の課題
bizlp コックピットのフロントエンドは React + Vite + カスタム CSS で構成されている。
| 課題 | 定量影響 |
|---|---|
| UI コンポーネントのスクラッチ実装コスト | KPI カード 1 枚に HTML + CSS + TypeScript で 50〜100 行。cockpit.css が 2,673 行に肥大化 |
| ゲージ / スピードメーター / プログレスバーの自作 | Rule of 40 スコア・ROE 等の視覚的指標を都度 SVG / div で実装。再利用性低 |
| パネル新規追加の参入障壁 | Jr エンジニア (MAS-230) が担当できる難易度まで下げるために、コンポーネント生成の自動化が必要 |
| v0.dev 産物の活用不可 | Vercel v0.app が出力する React + Tailwind + shadcn/ui コードは、Tailwind 未導入の現スタックでは動作しない |
解決策
Tailwind CSS v4 + Tremor v3 (@tremor/react) をダッシュボード専用 UI 基盤として導入し、v0.dev をコード生成ワークフローに組み込む。
現状: カスタム CSS (2,673 行) + React JSX 手書き
導入後: Tailwind ユーティリティ + Tremor コンポーネント + v0.dev で生成加速
Tremor / v0.dev の位置づけ
| ツール | 役割 | 採用形態 |
|---|---|---|
| Tailwind CSS v4 | ユーティリティクラス + Tremor / shadcn の前提条件 | npm 依存 (@tailwindcss/vite) |
| Tremor v3 | ダッシュボード専用コンポーネント (KPI カード / ゲージ / バー / テーブル) | npm 依存 (@tremor/react) |
| v0.dev | プロンプト → React + Tailwind + shadcn/ui コード生成 | 外部 Web ツール (依存なし・コード貼り付け) |
技術スタックの変更
現状
React 18 + Vite 5 + TypeScript
vite-plugin-singlefile (全 JS/CSS を 1 HTML にインライン化)
カスタム CSS のみ (cockpit.css / sidebar.css)
chart.js + d3-sankey (チャート)
導入後
React 18 + Vite 5 + TypeScript ← 変更なし
vite-plugin-singlefile ← 変更なし (制約:後述)
Tailwind CSS v4 (@tailwindcss/vite) ← 新規追加
Tremor v3 (@tremor/react) ← 新規追加 (ダッシュボード用)
カスタム CSS (既存パネルはそのまま維持) ← 共存
chart.js + d3-sankey ← 変更なし
重要制約と事前検証項目
制約 1: vite-plugin-singlefile によるインライン化
GAS HtmlService の CSP 制約上、vite-plugin-singlefile で全 JS/CSS を 1 HTML にインライン化している。Tailwind と Tremor の追加はバンドルサイズに直接影響する。
| ファイル | 現状サイズ | 目標上限 |
|---|---|---|
financial_cockpit.html | 364 KB | 600 KB 以内 |
multiyear_cockpit.html | 290 KB | 500 KB 以内 |
sidebar_spa_shell.html | 179 KB | 350 KB 以内 |
GAS HtmlService のファイルサイズ上限は公式ドキュメント未記載だが、実績上 数 MB は問題ない。ただしブラウザロード時間は GAS Web App の ?embedded=1 iframe 内で体感するため、目標上限を設ける。
Tailwind v4 の PurgeCSS 相当機能 (@tailwindcss/vite の tree-shaking) により、使用していないユーティリティクラスは最終バンドルに含まれない。Phase 2 完了後にビルドサイズを実測してから続行判断する(ゴーサイン判定)。
制約 2: 既存 cockpit.css との共存
cockpit.css (2,673 行) は既存パネル全体の見た目を定義している。Tailwind を導入すると CSS Reset (preflight) が既存スタイルと衝突する可能性がある。
対処方針: @layer base { ... } で Tailwind の preflight を Tremor 専用スコープに閉じ込める。具体的には tremor-scope.css で @scope .tremor-root { @tailwind base; ... } として既存 CSS に影響させない。
制約 3: Tremor v3 の安定性
Tremor は v2 が安定版・v3 が shadcn/ui ベースへの移行版(2025 年時点で開発中)。Phase 2 着手前に @tremor/react の最新安定版をピン留めし、CHANGELOG を確認すること。 v3 の API が破壊的変更を含む場合は v2 (@tremor/[email protected]) で代替する。
| バージョン | 状態 | 備考 |
|---|---|---|
v2 (@tremor/[email protected]) | 安定版 | Tailwind v3 が前提 |
v3 (@tremor/[email protected]) | 移行版 | Tailwind v4 + shadcn/ui ベース。着手前に安定性確認必須 |
実装仕様
Phase 1 — Tailwind CSS v4 導入
目的: Tremor と v0.dev 産物コードの両方の前提条件を整える。
1-A. 依存追加
cd webapp_client
npm install --save-dev tailwindcss @tailwindcss/vite
# Tremor が v2 なら: npm install @tremor/react tailwindcss@3 (v3 なら上記のみ)
1-B. vite.config.ts 変更
import tailwindcss from '@tailwindcss/vite'; // 追加
export default defineConfig({
plugins: [
react(),
tailwindcss(), // ← react() の後に追加
viteSingleFile(),
],
// ... 既存設定は変更なし
});
1-C. tremor-scope.css — Tailwind を Tremor 専用スコープに閉じ込める
ファイル: webapp_client/src/styles/tremor-scope.css
/*
* MAS-356: Tremor コンポーネントにのみ Tailwind を適用するスコープ境界。
* .tremor-root クラスを持つ要素の内側にのみ Tailwind の preflight と
* ユーティリティが適用され、既存 cockpit.css との衝突を防ぐ。
*/
@import "tailwindcss" layer(tremor);
@layer tremor.base {
/* Tailwind preflight をここに閉じ込める (既存 CSS に影響させない) */
}
既存の cockpit.css の先頭に @import './tremor-scope.css'; を追加する(cockpit / multiyear ビルドのみ)。
1-D. 動作確認
npm run build:cockpit が成功し、financial_cockpit.html のサイズが大幅増加していないことを確認する(Tailwind のみでは PurgeCSS が効くため数 KB 増程度が期待値)。
Phase 2 — Tremor 試験採用(CapitalAllocationPanel)
目的: CapitalAllocationPanel.tsx(639 行)の KPI 表示部分を Tremor コンポーネントに置き換えて、実装品質とバンドルサイズを実測する。
2-A. Tremor 依存追加
npm install @tremor/react
# v3 の場合は追加で: npm install @headlessui/react (依存)
2-B. 置き換えターゲット
CapitalAllocationPanel 内の以下の表示を Tremor コンポーネントに移行する。
| 現状の実装 | Tremor 置換コンポーネント |
|---|---|
| 3 バケツ残高バー(カスタム div + CSS) | <ProgressBar /> × 3 (color="blue" / "amber" / "rose") |
| ROE / Rule of 40 スコア表示(数値 + ラベル) | <Metric /> + <Badge /> |
| アロケーション提案カード(手書き card div) | <Card /> + <List /> |
| 機会損失金額ハイライト(カスタム CSS) | <Callout /> (color="rose") |
移行方針: 既存コンポーネントを一括置換せず、セクション単位で段階的に置き換える。Tremor コンポーネントには .tremor-root クラスを持つ親 <div> でラップして CSS スコープを確保する。
// 移行前
<div className="cap-alloc-bucket-bar">
<div style={{ width: `${defenseRatio * 100}%` }} className="bucket-defense" />
...
</div>
// 移行後
<div className="tremor-root">
<ProgressBar value={defenseRatio * 100} color="blue" label="防衛" />
<ProgressBar value={strategicRatio * 100} color="amber" label="戦略投資" />
<ProgressBar value={surplusRatio * 100} color="rose" label="純余剰" />
</div>
2-C. ゴーサイン判定(バンドルサイズ計測)
Phase 2 完了後に npm run build:cockpit を実行し、サイズを計測する。
ls -la templates/financial_cockpit.html
| 計測結果 | 判断 |
|---|---|
| 600 KB 以内 | ✅ Phase 3・MAS-358 への展開を継続 |
| 600 KB 〜 800 KB | ⚠️ Tremor の使用範囲を限定(KPI カード / バーのみ)して再計測 |
| 800 KB 超 | ⚠️ ファイル分割を前提に Tremor 採用を継続。vite-plugin-singlefile による 1 HTML インライン化を複数ファイルに分割(cockpit / multiyear / sidebar を独立エントリに)することでサイズを分散させる。MAS-357 Firebase Hosting 移行と合わせて対処。Tremor 自体の採用は撤回しない。 |
Phase 3 — v0.dev ワークフロー確立
目的: v0.app を使ったコンポーネント高速生成フローをチームの開発標準として文書化する。
3-A. v0.dev の位置づけと制限
v0.dev は 外部 Web ツール であり npm 依存ではない。出力されたコードを手動でプロジェクトにコピーして使う。
制限事項:
- v0 の産物は Tailwind + shadcn/ui ベースのため、Phase 1 完了(Tailwind 導入済み)が前提
google.script.run/ GAS API 等のビジネスロジックは v0 では生成できない → UI シェルのみ生成してロジックを後付けする- v0 産物の外部 CDN 参照(フォントリンク等)は GAS CSP 違反 → 産物受け取り後に必ず削除
3-B. cockpit パネル向けプロンプトテンプレート
v0.dev に入力するプロンプトのひな型を確立する。
以下の仕様で React + Tailwind + Tremor のダッシュボードパネルを生成してください。
## コンテキスト
- GAS Web App 内の iframe で動作する SPA
- 外部フォント・外部 CDN の参照禁止(import 文でのみ依存解決)
- 既存: 背景色 #1e2330 のダークナビ + #f5f5f5 のメインコンテンツ
## 生成するコンポーネント
[コンポーネント名と要件をここに記述]
## 使用可能なコンポーネント
- Tremor: Card, Metric, Badge, ProgressBar, List, Callout, AreaChart
- shadcn/ui: Button, Select, Tabs
## 制約
- Props 経由で全データを受け取る(fetch 禁止)
- className は既存の cockpit-section クラスと共存可能な形で
3-C. ワークフロー文書
以下のフローを docs/_internal/ai_agent_tips.md の「§8 フロントエンド UI 生成」として追記する。
1. v0.app でプロンプト入力(上記テンプレート使用)
2. 産物コードをコピー
3. 外部 CDN 参照を除去(import 'https://...' 等を削除)
4. .tremor-root ラッパーを追加
5. Props 型定義を sidebar_api.d.ts のデータ型に合わせる
6. npm run build:cockpit でサイズ確認
7. 動作確認(dev サーバーまたは GAS dev 環境)
エッジケース・異常系
| # | 条件 | 検知方法 | 期待される挙動 |
|---|---|---|---|
| 1 | Tailwind preflight が既存 cockpit.css の * リセットと二重適用 | ビルド後のブラウザ確認で既存パネルの見た目が崩れる | tremor-scope.css の @scope 境界を調整して既存 CSS を保護 |
| 2 | financial_cockpit.html が 800KB 超 | ls -la templates/financial_cockpit.html で計測 | ファイル分割(複数 HTML エントリ化)でサイズ分散。Tremor 採用は継続。MAS-357 Firebase Hosting 移行と合わせて対処 |
| 3 | Tremor v3 の API が破壊的変更を含む | npm install @tremor/react 後に TypeScript エラー | v2 (@tremor/[email protected]) に固定してTailwind v3 で代替 |
| 4 | v0 産物に import ... from 'https://...' が含まれる | コード貼り付け後の TypeScript エラー / ビルドエラー | 該当 import を削除または npm パッケージに置き換え |
| 5 | Tremor コンポーネントの color prop が Tailwind の JIT に認識されない(動的クラス問題) | ビルド後にゲージの色が出ない | tailwind.config.ts の safelist に使用カラーを列挙 |
| 6 | .tremor-root 外で Tremor コンポーネントを使用 | Tremor のスタイルが適用されない(無色・サイズなし) | すべての Tremor 使用箇所に .tremor-root ラッパーを追加するルールを周知 |
| 7 | vite-plugin-singlefile と @tailwindcss/vite の処理順序衝突 | ビルドエラー or CSS がインライン化されない | vite.config.ts で tailwindcss() を viteSingleFile() より前に配置 |
| 8 | npm run build:multiyear でも Tremor CSS が二重インライン化 | multiyear.html のサイズが過大 | tremor-scope.css を cockpit エントリ HTML のみで import し、multiyear は未使用のままにする |
| 9 | Tailwind の dark: variants が GAS の配色と干渉 | ダークモード適用で文字色が白抜けする | tailwind.config.ts で darkMode: 'class' を設定し、.dark クラスを付与しない限り適用されないようにする |
| 10 | Tremor の AreaChart が chart.js と競合 | 同一パネル内で両チャートが表示されない | Tremor Chart は Recharts ベース。chart.js と共存は可能だが不要ならチャートは chart.js に統一 |
ファイル変更マトリクス
| ファイル | 変更種別 | 変更内容 |
|---|---|---|
webapp_client/package.json | 変更 | tailwindcss, @tailwindcss/vite, @tremor/react を追加 |
webapp_client/vite.config.ts | 変更 | tailwindcss() プラグイン追加(viteSingleFile() の前) |
webapp_client/src/styles/tremor-scope.css | 新規 | Tailwind を .tremor-root スコープに閉じ込める CSS |
webapp_client/src/styles/cockpit.css | 変更 | @import './tremor-scope.css' を先頭に追加(cockpit / multiyear エントリのみ) |
webapp_client/src/cockpit/CapitalAllocationPanel.tsx | 変更 | Phase 2: KPI 表示部分を Tremor コンポーネントに置き換え |
docs/_internal/ai_agent_tips.md | 変更 | Phase 3: §8 フロントエンド UI 生成(v0.dev ワークフロー)追記 |
推奨実行モデル
| ステップ | 推奨モデル | 理由 |
|---|---|---|
| Phase 1 Tailwind 導入 | Haiku | vite.config.ts + package.json の機械的な変更 |
| Phase 1 CSS スコープ設計 | Sonnet | 既存 cockpit.css との共存判断が必要 |
| Phase 2 Tremor 試験移行 | Sonnet | 既存 JSX パターンを読んで対称的な Tremor コンポーネントに置き換える判断 |
| Phase 3 ワークフロー文書化 | Haiku | 決まったフローをテンプレート化するのみ |
受け入れ条件
-
npm run build:cockpitが成功する(サイズが 800KB 超の場合はファイル分割で対処・Tremor 採用は継続) -
npm run type-checkがエラーなし - 既存コックピットパネル(CompensationStrategyPanel / SoloFinancialStatementsPanel 等)の表示に変化なし
-
CapitalAllocationPanelの 3 バケツバー / ROE / Rule of 40 が Tremor コンポーネントで表示される - v0.dev プロンプトテンプレートが
ai_agent_tips.mdに追記されている -
.tremor-root外のパネルには Tailwind の影響が出ない
依存関係
- MAS-355 部分完了:
CapitalAllocationPanel.tsxの試験ターゲットとして使用するため、Phase 2 の CSS/JSX 構造を確認済みであること(実装済み・webapp_client/src/cockpit/CapitalAllocationPanel.tsxとして存在確認済み) - 後続 MAS-358: 本案件完了後、
CockpitNavSidebar.tsx実装で TremorSidebar/SidebarItemを活用できる - 後続 MAS-357: Firebase Hosting 移行後も Tailwind / Tremor は動作継続(npm バンドルのため外部 CDN 参照なし)
Phase 4: ローカル UI 開発環境 + ビジュアル回帰テスト(実装済)
背景
GAS HtmlService は Google 認証が必要なため、UI の確認のたびに clasp push → デプロイ(20〜30秒)が必要だった。
Playwright によるスクリーンショット比較も GAS URL では自動化が困難。
解決策
Vite dev server + GAS モック + Playwright の 3 層構成で、GAS なしで UI 開発・テストができる環境を整備した。
UI の形を作る → localhost で高速イテレーション(GAS 不要)
↓
形が決まったら → GAS にデプロイして実データで最終確認
構成ファイル
| ファイル | 役割 |
|---|---|
webapp_client/src/dev-gas-mock.ts | google.script.run を Proxy で差し替え、fixture データで cockpit を起動可能にする。本番ビルドでは import.meta.env.DEV === false により tree-shaken される |
webapp_client/src/cockpit-main.tsx | DEV 時のみ dev-gas-mock.ts を動的 import(if (import.meta.env.DEV) で条件分岐) |
webapp_client/playwright.config.ts | Playwright 設定。Vite dev server を自動起動し、スクリーンショット差分 2% 超で失敗 |
webapp_client/tests/cockpit-visual.spec.ts | ビジュアル回帰テスト 4 件(全体・ヘッダー・4アプローチ比較・資本効率パネル) |
webapp_client/tests/*/snapshots/*.png | baseline スクリーンショット(macOS: -darwin.png / Linux CI: -linux.png) |
.github/workflows/ui-test.yml | PR 時に自動実行。Linux baseline が未存在の場合は自動生成してコミット |
開発フロー
# 1. ローカルで UI を確認
npm run dev:cockpit # http://localhost:5173/financial_cockpit.html
# 2. 変更後にテストで差分確認
npm run test:ui # 差分 2% 超で失敗 + diff 画像生成
# 3. 意図した変更なら baseline を更新
npm run test:ui:update # 新しいスクリーンショットを baseline として保存
# 4. GAS にデプロイして実データで最終確認
npm run deploy:dev
CI フロー(GitHub Actions)
PR 作成
↓
ui-test.yml が自動実行(Ubuntu)
↓
Linux snapshot が未存在 → 自動生成してコミット(初回のみ)
Linux snapshot が存在 → 前回 baseline と比較
↓
差分なし → 通過
差分あり → 失敗 + visual-diff artifact に diff 画像をアップロード
PR の Artifacts から visual-diff をダウンロードすると、変更箇所を赤くハイライトした diff 画像で確認できる。
意図した UI 変更の場合は npm run test:ui:update を実行して baseline を更新してから再 push する。
制約・注意事項
- GAS 実データとの差異: localhost はモックデータ(固定値)を使用。実データの edge case(金額が極端に大きい等)はGAS で最終確認が必要
- GAS iframe 環境: HtmlService の iframe 内での挙動は Playwright では再現できない
- プラットフォーム別 baseline: macOS(
-darwin.png)と Linux CI(-linux.png)でフォント描画が微妙に異なるため別ファイルで管理 - ブラウザ: Chromium(Chrome)のみ。Safari / Firefox は対象外
変更履歴
| 日時 | バージョン | 変更内容 |
|---|---|---|
| 2026-05-03 | v1.2 | Phase 4(ローカル UI 開発環境 + ビジュアル回帰テスト)追記。実装ステータスを「完了」に更新 |
| 2026-05-03 | v1.1 | go/no-go ゲート更新: 800KB 超の場合「Tremor 見直し」→「ファイル分割で継続」に方針変更。ファイル分割は MAS-357 Firebase Hosting 移行と合わせて対処する方針が確定したため |
| 2026-05-03 | v1.0 | 初版起票 |