querying-canvas-data について
`querying-canvas-data` は、PostHog Canvas でデータをクエリして表示する作者向けのスキルです。クエリの選択、結果の扱い、段階的な読み込み、検証に加え、実行時の機能宣言とエラー処理を扱います。
主な機能
- 01保存済みインサイトを優先して読み込む
- 02型付きクエリまたは HogQL を使用
- 03データの検証リンクを用意
- 04セクションごとに段階的に読み込む
- 05日付範囲と SQL 変数を扱う
- 06タスクデータと実行時状態を管理
活用シーン
- 01Canvas ダッシュボードに指標カードを作成
- 02日付ピッカーに応じてクエリを更新
- 03ライブのタスク状況ボードを作成
- 04クエリ結果を検証可能にする
MCP 対応
PostHog MCP ツールでインサイトを見つけて保存し、Canvas から読み込む方法を推奨します。Canvas の PostHog クエリ機能の宣言方法も説明します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ posthog/ skills --skill querying-canvas-dataインストール後、Claude Code で/querying-canvas-dataを入力して読み込まれたか確認します。
同梱ファイルreferences/canvas-sdk.d.ts
詳細
キャンバスデータのクエリ
ph ブリッジは、キャンバスが PostHog と通信する唯一の手段です。import { ph } from "@posthog/canvas-sdk" でインポートします。プラットフォームから提供されるモジュールのため、dependencies への追加は不要です。同じオブジェクトは window.ph グローバルとしてもインストールされており、既存のキャンバスで使用されています。新しいコードではインポートを優先してください。型定義されたインターフェースは
references/canvas-sdk.d.ts です。初期化はしないでください。認証情報はホスト内に保持され、サンドボックスから PostHog にアクセスするために fetch()、posthog-js、手書きのクライアントを使用することはできません。外部リクエストとリソースを使用するには、PostHog 以外のオリジンを capabilities.network.origins で宣言する必要があります。また、動作するのは公開済みのキャンバスのみです。編集モードのプレビューでは、すべての直接ネットワークアクセスがブロックされます。外部スタイルシートも対象です。リモートスクリプトは引き続きブロックされます。
データ階層 — すべての指標の根拠として保存済みインサイトを使用する
- 推奨 — インサイトを保存し、参照から読み込む。 PostHog MCP のインサイトツールを使い、インサイトクエリタイプ(TrendsQuery、FunnelsQuery、RetentionQuery、PathsQuery、または WebOverviewQuery / WebStatsTableQuery などのウェブ分析用クエリタイプ)で指標を計算するインサイトを作成・保存します(生の SQL は使用しません)。数値を確認して
short_idを記録し、await ph.loadInsight(shortId, { dateRange })で表示します。これらは検証済みのクエリであり、数値は PostHog UI と完全に一致します(セッション化、ユニークユーザー、内訳、直帰率)。クエリを捏造したり、イベント名やプロパティ名を推測したりしないでください。まず MCP を使って見つけ、保存してください。 - 次善策 — アドホックな型付きノード:インサイトを保存する方法が本当に適さない場合は、
ph.query({ kind: "TrendsQuery", series: [...], dateRange: {...} })を使用します。 - 最後の手段 — インライン HogQL:どのインサイトタイプでも指標を表現できない場合に限り、
ph.query("SELECT …")を使用します。その場合、SQL と日付範囲は自分で管理します。
特にウェブ分析ボードでは、ウェブ分析用のクエリタイプを使用してください。生の HogQL では、直帰率、セッション化、チャネルアトリビューション、ユニーク訪問者数が微妙に誤った値になります。
どの階層を使用する場合も、公開前にプロジェクトの capabilities で宣言してください。capabilities.posthog.insights にすべての ph.loadInsight の short id を、captureEvents にすべての ph.capture のイベント名を追加し、ph.query を使用する場合は inlineQueries: true を設定します。宣言されていない呼び出しは実行時にホストによって拒否され、宣言されていないリテラルがあると検証に失敗します。
検証可能性 — すべての主張を PostHog で確認できること
閲覧者が検証できない数値は、信頼できない数値です。キャンバスに表示される、データに基づくすべての数値 — KPI、チャート、テーブル、明示された結論 — には、該当する階層に応じた検証手段を用意する必要があります。
- インサイトに基づくメトリクスから、PostHog の実際のインサイトにリンクします。 保存済みのインサイトから読み込んだメトリクス(推奨される階層)には、クリック時に
ph.openExternal(insightUrl)を呼び出す「PostHog で表示」手段を表示します。作成時にgenerate-app-urlMCP ツール(パステンプレート/insights/{id}とインサイトの短い ID を使用)で URL を生成し、返された URL をソースにそのまま埋め込みます。自分で URL を組み立ててはいけません。ph.openExternalが開くのはhttps://*.posthog.comの URL のみで、ユーザー操作からしか呼び出せません。そのため、ボタンまたはリンクに割り当て、読み込み時やレンダリング時には呼び出さないでください。また、インサイトの保存済みクエリテキストをソースに埋め込んではいけません。キャンバスのソースはすべてのキャンバス閲覧者が読めますが、インサイトへのアクセスは PostHog によって制御されます。閲覧者が自身の権限でクエリを確認するための場所が、このリンクです。 - アドホッククエリでは、実行された正確なクエリを開示し、その場で確認できるようにします。
ph.query(型付きノードまたはインライン HogQL)で計算された数値については、その根拠となるクエリ — HogQL テキスト、または JSON として整形した型付きクエリノード — をカードに関連付けたモーダルまたは折りたたみ式の開示領域に表示します(React キャンバスでは Quill のDialogまたはCollapsible、HTML キャンバスでは<details>要素を使用します)。表示するクエリは、ph.queryに渡すものと同じ定数またはビルダーから生成してください。そうすれば、表示されるクエリと実行されるクエリが食い違うことはありません。これは閲覧者がすでに実行している内容を開示するだけです。ph.queryは、サインイン中の閲覧者として実行されます。
これらは任意の仕上げではありません。これらを備えていない、PostHog のデータを提示するキャンバスは未完成です。表示手段はコンパクトにしてください。インサイトに基づくカードごとに小さなリンクアイコンを置く、アドホックカードごとに「クエリを表示」の開示領域を設ける、またはキャンバスで実行されるすべてのアドホッククエリを、根拠となる数値と対応付けて一覧表示する共通モーダルを設けます。
ステータスボードでは、refresh にキャッシュの有効期間を秒単位で設定します。30 から 86400(1 日)までの整数を指定してください。この範囲外の値や小数を指定すると、実行時にエラーになります。
await ph.loadInsight(shortId, { refresh: 30 })
await ph.query(queryNode, {}, { refresh: 30 })
結果の形式 — 正しく読み取らないと、すべての値が 0 として表示されます
- トレンド形式の結果(インサイトのクエリタイプ。
ph.loadInsightまたは型付きノード経由):resultsは行ではなく、系列オブジェクトの配列です。各系列には、data: number[](期間ごとの値)、days: string[](ISO 形式)、labels: string[]、count(合計)、aggregated_value(単一値の合計)、label、および任意のcompare_label: "current" | "previous"が含まれます。KPI の合計にはresults[0].count(または.aggregated_value)を使用します。折れ線グラフではresults[0].dataをresults[0].daysに対してプロットします。countは期間ごとの値を合計するため、複数の日にアクティブだったユーザーを重複して数えます(math: "dau"のユニークユーザー系列の場合)。期間内のユニーク数を KPI に表示するには、クエリにtrendsFilter: { display: "BoldNumber" }を設定し、代わりにaggregated_valueを読み取ってください。比較期間がある場合は、compare_label === "previous"で前の系列を検索してください。インデックスで検索してはいけません。この場合、columnsは空です。 - SQL の結果:
{ columns: string[], results: rows[][] }— 各行はcolumnsの順に並んだセル値の配列です。
段階的に読み込む — 各セクションのデータが届いたら、そのセクションを描画します
PostHog のクエリはそれぞれ数秒かかることがあり、ボードでは通常、複数のクエリを実行します。すべてのクエリが完了するまで描画を待たせないでください。
- マウント時に独立したクエリを並行して実行し、無関係なクエリを逐次的な
awaitで連鎖させないでください。ホストは一度に 8 件のデータリクエストを実行し、残りは最大 32 件のキューで待機するため、セクション数が実行枠を超えるボードでも、セクションごとに読み込まれます。キューよりも幅広いボードでは、追加リクエストはエラーメッセージに理由を添えて拒否されます。ランタイムは、諦める前にバックオフ後に各リクエストを再送します。この規模のボードでは、クエリを統合してください(すべての行を返すクエリを 1 つ実行し、クライアント側で分割します)。状態はセクションごとに 1 つずつ維持してください。 - 各クエリに独自の
{ loading, error, data }状態を持たせ、結果が届いたらすぐに、各カード、グラフ、テーブルのスケルトンをデータに切り替えてください。loadingフラグを共有したり、独立したクエリ全体を 1 つのPromise.allでまとめたりすると、最も速い指標も最も遅い指標を待つことになります。キャンバスは一度にすべて表示するのではなく、段階的に埋まっていく必要があります。 - 静的な枠組み(見出し、日付ピッカー、内部にスケルトンを表示したカード枠)はすぐに描画してください。各セクション内の値だけを、そのクエリの結果が届くまで待たせます。
- 初回描画に不要なクエリは後回しにしてください。タブの背後にあるコンテンツ、折りたたまれたセクション、ドリルダウンのクエリは、マウント時ではなく、ユーザーが表示したときに実行します。
useState とともに useEffect でデータを読み込み、クエリ内で集計してください。生のイベントデータを一括取得してはいけません。クエリの失敗と空の結果は、異なる状態として扱ってください。.catch でエラー状態を設定し、目に見える形で描画してください(メッセージと再試行)。ゼロ、空のグラフ、「データなし」のメッセージに置き換えてはいけません。エラーを握りつぶすと、テーブルの欠落や認証の失敗といった実際の問題が、データの欠落に見えてしまいます。空の状態は、クエリが成功し、行が返されなかった場合にのみ使用してください。
日付範囲
- キャンバスの日付ピッカーの範囲を、そのまま
dateRangeに渡してください。ph.loadInsight(shortId, { dateRange: { date_from: win.start.toISOString(), date_to: win.end.toISOString() } })— 保存済みインサイトは、時間に関する SQL なしでその範囲に再スコープされます。型付きノードにも同じdateRangeを指定します。範囲が変わったら、すべてのクエリを再実行してください。 - 保存済みの SQL インサイトは
dateRangeを無視することがあります(範囲は SQL 内に記述されています)。これも、インサイトクエリタイプを優先する理由の 1 つです。範囲が{variables.…}プレースホルダーに由来する場合は、代わりにvariables(以下を参照)を通じて指定してください。dateRangeはそこには反映されません。 - インライン HogQL は、他に手段がない場合に限って使用してください。
now()やハードコードされた INTERVAL を埋め込んではいけません。Unix 時刻の境界値(Math.floor(win.start.getTime() / 1000))を計算し、半開区間のtimestamp >= toDateTime(fromUnix) AND timestamp < toDateTime(toUnix)を記述してください。前期間は直前の同じ長さの期間とし、toStartOfDay/toStartOfHourでバケット化します。
SQL 変数
HogQL に {variables.name} プレースホルダーを含む保存済み SQL インサイトでは、各呼び出し時に変数の値を指定します。キーには変数の UUID ではなく、コード名を使用します。
await ph.loadInsight(shortId, { variables: { product: 'surveys', month: '2026-07-01' } })
こうすることで、保存済みインサイトを製品ごとに一度ずつ読み込む同じ製品別インサイトとして使い、ボード全体を 1 つの保存済みインサイトで埋められます。各タイルでインサイトに保存されたデフォルト値を解決する必要はありません。
- まず、インサイトのクエリからコード名を確認してください(MCP 経由の
insight-get)。ホストは、インサイトで使われていない変数や、値が反映されなかった変数を拒否します。保存済みの値に黙ってフォールバックすることはありません。そのため、変数の不一致は、別の製品の数値としてではなく、目に見えるエラーとして表れます。 - 変数は読み取りキャッシュキーの一部であるため、製品が N 個なら読み込みも N 回になります。同じインサイトを N 回読み込むより、すべての製品を行として返すインサイトを1 つ使い、クライアント側で分割してください。
- 値の型は PostHog での変数定義(String/Number/Boolean/Date/List)によって決まります。インサイトが期待する形式で渡し、複数選択の List 変数には配列を指定してください。
ライブタスクデータ
タスクの受信トレイ、キュー、またはステータスボードを作成する場合は、ph.query を通じて
system.tasks と system.task_runs をクエリしてください。作成時に posthog:tasks-list を呼び出してその応答を埋め込まないでください。スナップショットが生成される一方、システムテーブルを使えば、レンダリングされたキャンバスをライブ状態に保てます。
テーブルはサインイン済みの閲覧者として実行されます。プロジェクト単位でスコープが設定され、Tasks リソースへのアクセスが必要です。system.tasks に含まれるのは、公開中のスペースで登録された内部向けではないタスクのみです。プライベート、個人用、未登録、内部向けのタスクは含まれません。ソフト削除されたタスクは必ず明示的に除外してください。
キャンバスで最新のステータスが必要な場合は、タスクを最新の実行に結合します。
const data = await ph.query(`
SELECT
t.id,
t.task_number,
t.title,
t.repository,
t.created_by_id,
t.created_at,
t.updated_at,
latest.status AS latest_run_status
FROM system.tasks AS t
LEFT JOIN (
SELECT
task_id,
argMax(status, tuple(created_at, id)) AS status
FROM system.task_runs
GROUP BY task_id
) AS latest ON latest.task_id = t.id
WHERE t.deleted = 0
ORDER BY t.updated_at DESC
LIMIT 100
`)
これはインライン HogQL なので、capabilities.posthog.inlineQueries: true を宣言してください。リンクの表示には、タスク URL を組み立てるのではなく、ph.navigate.toTask(id) を使ってください。
テーブルで表現できないフィルターを適用できると約束しないでください。channel_id はクエリできないため、キャンバスのクエリを現在のスペースに限定することはできません。また、現在の閲覧者に絞り込むには、既知の数値ユーザー ID が必要ですが、キャンバスのランタイムではその ID は渡されません。「このスペース」や「自分のタスク」に関する依頼でこの制限が関係する場合は、プロジェクト全体の公開タスクを黙って表示するのではなく、制限を明示してください。
ランタイムメモリ — ph.state
キャンバスごとの永続的なキー・バリューストレージです。使用するすべてのスコープ(["user"]、["shared"]、またはその両方)を capabilities.posthog.state で宣言してください。宣言されていないスコープは検証に失敗し、ホストはランタイムでの使用を拒否します。スコープを渡さない場合のデフォルトである "user" は各閲覧者専用です。"shared" はキャンバスごとに 1 つの値を持ち、チーム全体から参照できます。
const draft = await ph.state.get('draft') // user scope by default; null when unset
await ph.state.set('draft', { text }) // JSON value, capped at 64 KB serialized
await ph.state.set('draft', null) // null deletes the key
await ph.state.set('board', { columns }, { scope: 'shared' }) // team-visible
const entries = await ph.state.list({ scope: 'shared' }) // [{ scope, key, value, updatedAt }]
- マウント時にエフェクト内で状態を読み込み、解決するまではスケルトンを表示してください。書き込みは後勝ちになるため、再読み込みするか、自分の書き込みを信頼してください。マージは行わないでください。
- スコープあたり 256 個のキーを使用できます。大きなデータは PostHog(インサイト、ウェアハウス)に保存し、そこへの参照を保持してください。
- 状態はチーム全体に見えるアプリケーションデータです。シークレットや閲覧者の個人識別情報(PII)は決して保存しないでください。
ユーザーがキャンバスの現在の進捗や設定について尋ねた場合、ソースだけから推測しないでください。ソースを読んだ後、キャンバス ID を指定して canvas-state-retrieve を呼び出してください。これは、公開チャンネルまたはユーザーの個人チャンネルにあるキャンバスについて、共有状態と、認証済みユーザー自身のユーザースコープの状態を返します。ユーザーが値の変更を求めた場合は canvas-state-set を使ってください。最初に読み込み、無関係なキーを保持したうえで、キャンバスのソースが想定するスコープを使用してください。
キャンバスのディスカッションには汎用コメントツールを使用します。scope=canvas とキャンバス ID で絞り込んで comments-list を呼び出し、コメントを読み取ってください。同じスコープとアイテム ID を指定して comments-create を呼び出し、ルートコメントまたは返信を作成します。スレッドはキャンバスに紐づくため、タスク ID は省略可能です。そのタスクがキャンバスを生成または公開した場合に限り、item_context.taskId にタスク ID を指定してください。それ以外のタスクは API に拒否されます。アクセス権はキャンバスのスペースによって決まります。そのスペースを閲覧できるユーザーは、そのコメントの読み取りと書き込みができます。
PostHog への書き込み — ph.actions
ph.actions.invoke(verb, payload) は、閲覧者として PostHog に書き込みます。すべての verb を
capabilities.posthog.actions に宣言してください。宣言されていない、または登録されていない verb は検証で失敗し、ホストによって実行時に拒否されます。呼び出しは明示的なユーザー操作(閲覧者がクリックするボタン)に紐付ける必要があります。ホストは読み込み時またはレンダリング時の呼び出しを拒否します。
結果またはスローされたエラーを画面に表示し、呼び出し中はボタンを無効にしてください。呼び出しはすべて、実際に PostHog へ書き込みます。
verb の正となる情報源はレジストリです。verb を組み込む前に、canvases-actions-retrieve ツールを使って一覧を取得してください。各エントリには verb、summary、destructive、usage が含まれており、ペイロードと結果の形式、呼び出しによって実際に行われる処理、必要な確認メッセージが記載されています。成功メッセージで何を伝えてよいかも含め、verb の usage に厳密に従ってください。対応する製品の MCP ツールやスキルから verb のペイロードを推測しないでください。MCP ツールの呼び出し(今、あなたが自分の認証情報で行うもの)とキャンバス verb(後で、公開されたキャンバスで閲覧者が行うもの)では、ペイロードの形式、認証、動作が異なります。呼び出し方は次のとおりです。
const { result } = await ph.actions.invoke('tasks.create', { title, description })
PostHog MCP ツール — ph.tools
ph.tools は、閲覧者自身の権限で PostHog MCP ツールを実行します。ツールは PostHog MCP サーバー上で実行されるため、入力には、そのツールを自分で呼び出すときと同じものを渡します。機能フラグのロールアウトなど、PostHog MCP ツールですでに対応できる操作をボードに追加する場合に使用してください。
- すべてのツールを
capabilities.posthog.toolsに宣言してください。キャンバスで使用できるのは、許可リストに含まれるツールのみです。canvases-actions-retrieveツールのtoolsリストに、各ツールのread_onlyとdestructiveが記載されています。info <tool>でツールの入力スキーマを確認してください。 ph.tools.read(tool, args)で実行できるのは読み取り専用ツールのみです。読み込み時またはレンダリング時に実行できます。ph.tools.call(tool, args)はデータを変更します。クリック時にのみ呼び出してください。ホストは閲覧者にツールと引数を表示して、確認を求めます。読み込み時またはレンダリング時の呼び出しは拒否します。- どちらもツールの JSON 結果を返し、失敗した場合はツールのエラーメッセージを伴ってスローします。エラーメッセージを表示し、呼び出し中はボタンを無効にしてください。
機能フラグへの書き込みには、そのフラグの現在の version が必要です。読み取ってから、その値を使って書き込み、書き込みによって返された version を保持してください。version が競合した場合は、次の書き込みの前にフラグを再度読み取ってください。
const flag = await ph.tools.read('feature-flag-get-definition', { id: flagId })
const updated = await ph.tools.call('feature-flag-set-release-condition-rollout', {
id: flagId,
condition_index: 0,
rollout_percentage: 25,
version: flag.version,
})
ライブのサードパーティデータ — ph.connectors
ph.connectors.call(provider, tool, args, { refresh? }) は、閲覧時に、閲覧者自身の接続を使ってサードパーティサービスからデータを読み取ります。ユーザーごとに最新の状態を保つ必要があるデータ(未解決のプルリクエスト、今日の会議、割り当てられた課題など)に使用してください。作成時にこのようなデータを自分で取得してソースに埋め込んではいけません。公開した時点でそのスナップショットは古くなり、すべての閲覧者に作成者のデータが表示されます。
- プロバイダーは、
github(閲覧者個人の GitHub 接続を介したネイティブプロバイダー)か、閲覧者が MCP ストアで接続した任意のサーバーを指定するmcp:<server host>(例:mcp:mcp.calendly.com)です。canvas-connectors-retrieveツールを使って、プロバイダー、ツール、引数スキーマ、ツールごとのusageを確認できます。現在のユーザーが接続していないサーバーを調べるには、mcp_hostsを指定します。カタログ項目のis_read_only: trueのツールのみを呼び出してください。 MCP ツールには、明示的な読み取り専用のヒントと、ローカルの読み取り動詞チェックに合格する名前が必要です。 capabilities.connectorsですべてのプロバイダーとツールを宣言してください。例:[{ "provider": "github", "tools": ["list_pull_requests"] }]。宣言されていないph.connectors.callのリテラルは検証で拒否され、実行時にもホストによって宣言されていない呼び出しは拒否されます。- 呼び出しは
{ status, result, detail, truncated, connect_path }を返します。statusに応じて処理してください。ok—resultにツールの出力が含まれます。ネイティブツールはドキュメントに記載された形式を返し、MCP ツールは{ content, structured_content, is_error }(MCP コンテンツブロック)を返します。not_connected/needs_reauth— この閲覧者には利用可能な接続がありません。ph.connectors.connect(provider)を呼び出す「Connect GitHub」(またはサーバー名)のボタンを表示してください。呼び出しはクリック時に行います。ホストが適切な設定ページを開きます。これを空のデータとして扱ってはいけません。blocked、write_blocked、tool_missing、upstream_error—detailを表示し、再試行できるようにしてください。
truncated: trueは、結果が 256 KB を超え、プレビューまで切り詰められたことを示します。クライアント側でページングするのではなく、呼び出しの範囲を絞ってください(limitを小さくする、リポジトリを 1 つにするなど)。- 呼び出しはマウント時に開始できますが、コネクタのデータを読み取る前に、ホストが閲覧者にアクセス許可を求めます。許可はキャンバスのバージョン、プロバイダー、ツールの組み合わせごとに適用されます。拒否された場合、呼び出しは失敗するため、エラーと再試行ボタンを表示してください。
- 結果はキャンバスのバージョンごとに
refresh秒間キャッシュされます(デフォルトは 60、範囲は 30~86400)。アカウント、組織、プロジェクトを変更すると、結果と許可の両方が消去されます。 - コネクタの結果は、コンポーネントの状態または
ph.stateの"user"スコープに保持してください。コネクタを使うキャンバスでは、共有状態を宣言できません。検証と API は、この組み合わせを拒否します。
const [prs, setPrs] = useState<{ loading: boolean; status?: string; rows?: PullRequest[] }>({ loading: true })
useEffect(() => {
ph.connectors
.call('github', 'list_pull_requests', { repository: 'example/app', state: 'open' }, { refresh: 60 })
.then((res) => setPrs({ loading: false, status: res.status, rows: res.result?.pull_requests ?? [] }))
.catch((error) => setPrs({ loading: false, status: 'error', rows: [] }))
}, [])
副作用
ph.capture(event, properties?, distinctId?)— インタラクションの分析イベント(ファイア・アンド・フォーゲット)。セッションリプレイ、$session_id、ユーザーのアトリビューションはホストが自動的に処理します。記録を初期化したり、セッション ID を設定したり、独自のキャプチャ処理を実装したりしてはいけません。ph.openExternal(url)— PostHog の HTTPS URL と、https://github.com/<owner>/<repo>/pull/<number>のリンクを、ユーザーのクリック時に開きます。GitHub の PR リンクは確認ダイアログなしで開きます。ファイル、コミット、チェック、フラグメントへのリンクは許可されますが、認証情報、カスタムポート、クエリ文字列、その他のドメインは許可されません。サンドボックスではtarget="_blank"による移動がブロックされるため、ブラウザーへのフォールバックではなくブリッジを使用してください。ph.navigate.toTask(id)/.toNewTask()/.toCanvas(id)/.toNewCanvas()— キャンバス自体のチャンネル内でアプリ内を移動します。ph.navigate.toNewTask({ prompt, repository })— ユーザーのクリック時に、新しいタブでプロンプトが事前入力されたタスクフォームを開きます。promptは最大 16,000 文字、repositoryはowner/repo形式の名前です。どちらも省略可能です。リポジトリを設定すると、このタスクではスペースを変更せずにクラウドモードが選択されます。閲覧者がプロンプトを確認して送信します。フォームを開いただけでは実行は開始されません。古い公開済みアーティファクトにph.navigateが含まれているか確認し、デプロイ後に見つからない場合は再ビルドを依頼してください。