building-react-quill-canvases について
単一ファイルの React と Quill コンポーネントで PostHog のデータキャンバスを構築するスキルです。UI の制約、データの読み込みとエラー処理、ダッシュボードのパターンを扱います。
主な機能
- 01単一ファイルの React/TSX 構成
- 02Quill コンポーネントとスタイル規則
- 03個別の読み込み・エラー状態
- 04ダッシュボードと探索パターン
- 05日付コントロールと PostHog データアクセス
活用シーン
- 01PostHog 分析ダッシュボードの構築
- 02対話型データエクスプローラーの作成
- 03チェックリストや手順書キャンバスの設計
MCP 対応
PostHog MCP ツールを使った操作を説明し、データアクセス、機能宣言、ツール呼び出しについて querying-canvas-data を参照しています。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ posthog/ ai-plugin --skill building-react-quill-canvasesインストール後、Claude Code で/building-react-quill-canvasesを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/checklist-example.mdplatform-libraries.mdstarter-scaffold.md詳細
React + Quill キャンバスの構築
アプリケーション全体は 1 つの React/TSX ファイル(ソースプロジェクトの src/canvas.tsx)です。ホストがマウントするため、props を受け取らない単一の React コンポーネントを export default してください。react-dom をインポートしたり、createRoot を呼び出したりしないでください。
初回のビルドでは、references/starter-scaffold.md の動作するスキャフォールドから始めてください。日付ピッカー、テーマトークン、クエリごとの読み込み状態(各カードはそれぞれのデータが届くと個別に表示されます)、型付きノードの正しい結果読み取りがすでに組み込まれています。その配線は維持し、サンプルの指標とレイアウトを置き換えてください。
各カードにつき 1 つのコンポーネントを作り、editable() パラメーターを使うパターンを維持してください。追加するカードごとに、building-canvases スキルの「Params」セクションに従ってください。
インポート
標準的なアプリケーションシェルには、React、Quill、Recharts、Lucide、Day.js を使用してください。このプラットフォームでは、専門用途向けのオプションライブラリも 10 個利用できます。選択する前にreferences/platform-libraries.mdをお読みください。
PostHog のデータは import { ph } from "@posthog/canvas-sdk" で取得します。これはプラットフォームが提供するモジュールなので、dependencies への登録は不要です。同じオブジェクトは window.ph グローバルとしても存在します(既存のキャンバスはこの方法でアクセスしています)。新しいコードではインポートを優先してください。
その他の裸のインポート、動的な import()、require()、<script> タグ、リモートコードは検証に失敗します。直接のネットワークリクエストと外部の画像、フォント、メディア、フレームを使うには、capabilities.network.origins に正確な HTTPS オリジンを指定する必要があります。これらが機能するのは公開済みキャンバスのみです。編集モードのプレビューでは、宣言の有無にかかわらず直接のネットワークアクセスがブロックされます。宣言済みのオリジンからのスタイルシートは許可されますが、リモートスクリプトは引き続きブロックされるため、コードはキャンバスにバンドルしてください。
Quill コンポーネントのルール
PostHog のデータボードは、すべて @posthog/quill のコンポーネントで構築する必要があります。ネイティブのコントロールや、代わりにスタイルを適用した <div> を使ってはいけません。
- ドロップダウン/ピッカー →
Select(ネイティブの<select>は使用不可)、ボタン →Button(<button>は使用不可)、テキストフィールド →Input/Textarea、チェックボックス →Checkbox、ラベル →Label。 - テーブル →
Table(TableHeader>TableRow>TableHead、続いてTableBody>TableRow>TableCell)、パネル →Card(CardHeader+CardTitle+CardContent)、ピル →Badge、タイトル →Heading、本文 →Text。 Card内のTableは、<Table size="sm" fullWidth>を<Card size="sm" flush>で囲む必要があります。flushがないと、カードのパディングによってテーブルが内側に入り、ヘッダーと行がカードの端に揃わなくなります。flushを指定すると、カードのセクション間隔、下部のパディング、CardContentのインライン方向のパディングがなくなります。一方、ヘッダーには独自のパディングと区切り線が維持されます。両方の行でsize="sm"を指定すると、テーブルの両端の列がカードのタイトルに揃います。- 許可される Quill 以外のタグは、通常のレイアウト用
<div>とrecharts要素のみです。 - Quill は Base UI を基盤としています。複合パーツ(
Select+SelectTrigger/SelectContent/SelectItem)を組み合わせ、制御されたvalue+onValueChangeを使用してください。また、パーツをラップするのではなく、renderprop を使ってその要素を差し替えてください(例:<PopoverTrigger render={<Button …/>} />)。 - Quill コンポーネントにはすでにテーマが適用されています。Tailwind クラスやインラインの
styleでスタイルを変更せず、variant/sizeprops を使用してください。レイアウトユーティリティ(flex、grid、gap-4、p-4)は独自のラッパー<div>に設定してください。 - ボタンは、基本的に
variant="outline"を使用してください。variant="primary"はメインアクション 1 つだけに使用してください。
スタイルとテーマ
- キャンバスの最外部要素に
min-h-screen(min-height: 100vh)を指定して、iframe のビューポート全体を埋め、コンテンツに応じてその先まで伸びるようにします。 ここではh-screenを使わないでください。ビューポートの高さが固定されると flex の列が制限され、背の高い子要素が縮んでクリップされ、スクロールできなくなります。 ここではh-fullも使わないでください。公開されたキャンバスのアーティファクトシェルでは、html、body、#root要素に明示的な高さが指定されていないため、ルートのパーセント高さはコンテンツの高さに縮みます。 - ルートに
overflow-y-autoは必要ありません。ビューポートより大きくなると、iframe 自体のドキュメントがスクロールします。 min-heightのルートは確定した高さではないため、直接の子要素にh-fullを指定してもコンテンツの高さに縮みます。 子要素をボックスいっぱいに広げる必要がある場合は、中間ラッパーに明示的な高さ(h-[280px])を指定します。- スタイルには Tailwind ユーティリティと Quill コンポーネントを使用し、インラインの
styleは実行時に本当に動的な値が必要な場合に限ります(固定サイズにはh-[280px]のような任意値ユーティリティを使います)。 - 具体的なインターフェイス文言を作成してください。完成したキャンバスでは、ダミーテキストやプレースホルダーのラベルを使わないでください。
- キャンバスはユーザーの PostHog テーマに従います。ドキュメントルートの
.darkクラスは実行時に切り替わります。デザイン・トークンのユーティリティのみで色を指定してください。サーフェスにはbg-background bg-card bg-muted bg-primary bg-success bg-warning bg-info bg-destructive、テキストにはtext-foreground text-muted-foreground text-card-foreground、境界線にはborder-borderを使用します。ハードコードした16進数やライトテーマ専用の色は使わないでください。 - ステータストークンは通常の規則とは逆です。トークン単体(
bg-success)は淡い背景色になり、-foreground(text-success-foreground)は視認性の高い色になります。色付きのテキストやアイコンには常に-foregroundユーティリティを使います。塗りつぶしのピルにはbg-success text-success-foregroundのように組み合わせます。デルタには Quill のBadge(variant="success"/"destructive")を優先して使うと、色を自分で選ぶ必要がありません。 bg-secondary、text-secondary、bg-accent、bg-popoverはキャンバスでは定義されていません。使用しないでください。- スタイルシートや
<style>ブロックで、プラットフォームトークン名(--background、--border、--card、--chrome、--input、--muted、--primary、--fill-*)の CSS 変数を宣言しないでください。 Quill はこれらをすべての要素に設定するため、独自の値は適用されず、テキストが読みにくくなることがあります。 独自の変数には--doc-mutedのようなプレフィックスを付けてください。トークン名との衝突はplatform_token_redeclaredによって検証時に拒否されます。 - recharts のストローク/塗りつぶしにはトークンの CSS 変数を使用します(
stroke="var(--primary)"、グリッド/軸にはvar(--border)/var(--muted-foreground))。 - recharts のデフォルト設定にはライトテーマ用にハードコードされたものがあり、ダークテーマのキャンバスでは正しく表示されません。プラットフォームのスタイルシートが修正するため、キャンバスコードで独自に対処しないでください。円グラフのスライスの輪郭線がその一例です。recharts は各セクターに白いストロークを付けますが、スタイルシートが輪郭線を取り除きます。積み上げ棒グラフのセグメント間に表示されるアンチエイリアスの隙間も同様です。また、デフォルトのツールチップも同様です。スタイルシートがページの背景色と反転させるため、テキストのコントラストは常に確保されます。
<Tooltip />をそのまま記述してください。 - 曲がった引用符、省略記号、矢印、絵文字などの Unicode グリフは、JSX 内にリテラル文字として記述してください。
\uXXXXエスケープは JSX のテキスト内でそのまま表示されます。
読み込み中、エラー、空の状態
読み込み中または更新中は、各データポイントをそれぞれのCard内にスケルトンとして表示します。テキスト/数値にはSkeletonText(linesとテキストサイズのclassNameを一致させます)、ブロック/チャートにはSkeletonを使います。
段階的にレンダリングします。各クエリがそれぞれの読み込み状態を管理します。クローム(見出し、日付ピッカー、内部にスケルトンを含むカードの枠)はすぐにレンダリングし、独立した各クエリはマウント時に同時に実行します。各カードは、そのカードのクエリが解決した時点でスケルトンをデータに置き換えるため、遅いクエリが影響するのはそのカードだけです。キャンバス全体を単一の共有loadingフラグや、独立したクエリをまとめたPromise.allで制御してはいけません。これでは、最も速い指標が最も遅い指標を待つことになります。更新時には各セクションの読み込み状態を再びtrueに設定し、空白やレイアウトの跳ねを表示しないようにします。初回表示に含まれないコンテンツ(非アクティブなタブ、折りたたまれたセクション、ドリルダウン)のクエリは、ユーザーが表示した時点まで実行を遅らせます。
クエリの失敗と空の結果は異なる状態です。一方を他方としてレンダリングしてはいけません。すべてのph.query/ph.loadInsightに.catchを設定し、エラー状態にします。エラー状態では、エラーメッセージと、スタータースキャフォールドと同様に更新nonceへ接続された「再試行」ボタンを目に見える形で表示します。ゼロ、空のチャート、「データがまだありません」というメッセージにフォールスルーさせてはいけません。エラーを黙って無視するクエリがあると、実際の障害(テーブルの欠落、認証エラー、不正なクエリ)がデータ不足のように見えてしまいます。空の状態は、クエリが成功し、行が返されなかった場合に限って使います。
データプロダクトのパターン
以下は出発点となる形です。リクエストと利用可能なデータに合わせて調整してください。
プロダクトダッシュボード
- 検証済みのPostHogデータからライブボードを構築し、静的なモックアップは使いません。
- まず
Headingを配置し、その後にコンパクトなKPI、トレンドチャート、有用な内訳テーブルをレスポンシブグリッドで並べます。 - 意味のある比較期間がある場合は、KPIに差分を示す
Badgeを表示します。 - 時系列には
LineChart、離散的なカテゴリにはBarChartを使います。すべての結果をテーブルにしてはいけません。 - すべてのKPI、チャート、テーブルに、それぞれ独自の読み込み中、空、エラーの状態を設けます。
- すべての数値を検証可能にします。インサイトに基づくカードには、保存済みインサイトを開く「PostHogで表示」機能を設けます(
generate-app-urlで生成したURLを使うph.openExternal)。アドホックなph.queryカードには、実行された正確なクエリを表示する「クエリを表示」DialogまたはCollapsibleを設けます。詳しくはquerying-canvas-dataの「検証可能性」を参照してください。
ウェブ分析ボード
リクエストまたは従来のパターン指定でweb-analyticsが求められている場合は、次の形式を使います。
- 訪問者数、ページビュー数、セッション数、セッション時間、直帰率をKPIカードで表示し、同じ長さの前期間との増減も示します。
- ユニーク訪問者数を時系列でプロットし、比較データがある場合は前期間を2本目の線で表示します。
- その後に、人気のパス、トラフィックソースまたはチャネル、デバイス、地域をコンパクトなテーブルで表示します。
- 国名の前に国旗の絵文字を付けます。リテンションやアクティブ時間のビューは、利用可能なデータに照らして有用な場合にのみ追加します。
querying-canvas-dataで説明されているウェブ分析用のクエリ種別を使います。HogQLで直帰率、セッション化、アトリビューション、ユニーク訪問者数を再実装してはいけません。236Kのように大きな値を表示用にフォーマットする場合も、計算用の生値とアクセシブルラベルは保持します。
インタラクティブエクスプローラー
各ディメンション、イベント、日付の選択には、制御されたQuill入力を使います。結果セットを小さく保ち、コントロールが変更されたら、依存するすべてのクエリを更新します。
チェックリストまたはランブック
チェックリスト、QA ランブック、ローンチ計画、オンボーディングの手順など、ユーザーが順に進めてチェックを付けていくリストを作成する場合は、まず
references/checklist-example.md にある、完全かつ検証済みのプロジェクトを基にしてください。重要な要素である、コンポーネントと分離した型付きコンテンツモジュール、各ステップに対応する共有の ph.state キー、デバウンス処理を伴う ref と state の併用更新パス、すべてのステップに設定された期待される結果、そして画面に表示される読み込み/保存エラー状態は、独自に実装すると破綻しやすい部分です。これらは維持し、コンテンツだけを置き換えてください。
日付範囲
データボードでは、日付の制御を独自に管理します。Popover 内に、Quill の DateTimePicker を(カスタムの Select やネイティブの日付入力ではなく)表示し、トリガーには Quill の Button を使ってください。PopoverContent には className="w-auto p-0" をそのまま指定し、DateTimePicker には value/onApply/onCancel 以外を追加しないでください(サイズは自動調整されるため、compact や幅を渡さないでください)。日付範囲が変わるたびに、すべてのクエリを再実行してください。dateRange への渡し方については、querying-canvas-data スキルを参照してください。
状態とアクション
再読み込み後も値を保持する処理(ユーザー/共有スコープを持つ ph.state)と、ボタンから PostHog に書き込む処理(タスクやアノテーションの登録などの ph.actions.invoke、またはフィーチャーフラグのロールアウト変更などの ph.tools.call)には、それぞれ固有の API 契約、機能宣言、ジェスチャーのルールがあります。使用する前に、querying-canvas-data スキルの「Runtime memory」「PostHog writes」「PostHog MCP tools」の各セクションを読んでください。アクションは、呼び出し中に自身を無効化する Button の onClick に接続し、返されたエラーを表示してください(これらは実際の PostHog 書き込みであり、サーバー側でスロットリングされます)。