本文へスキップ / Skip to main content
KINTO Tech Blog

約29分で読めます

Development

AIパイプラインで自社WebサイトをCompose Multiplatformアプリに変換してみた

AIパイプラインで自社WebサイトをCompose Multiplatformアプリに変換してみた cover

こんにちは、モバイルアプリ開発部の Yao です。

Web サイトの URL とアクセス認証情報を渡すと、サイトをクロールして API 仕様を逆生成し、Compose Multiplatform(CMP)の Android/iOS アプリを自動生成して、原サイトとのスクリーンショット比較で検収まで行う——そんなローカル AI パイプラインを作り、実際に自社サービス KINTO のステージング環境を動くアプリに変換してみました。結論から言うと、アプリファーストはもはや予算の問題ではなく、ツールチェーンの問題です。本記事ではパイプラインの設計、生成されたアーキテクチャ、実際のコード、原サイトとの並列比較スクリーンショットまで、その全工程を紹介します。

この記事でわかること

  • Web サイトは、それ自体が仕様書(ページとフロー)+ テストデータ(実 API トラフィック)+ 受け入れ基準(モバイルスクリーンショット)である
  • ローカル AI パイプラインが「クロール → 仕様のリバースエンジニアリング → コード生成 → スクリーンショット照合検収」を閉ループで回す
  • 成果:16 画面の仕様化、P0 の 4 画面をエンドツーエンド実装、API 31 オペレーション生成、テスト 107 本全グリーン、Android エミュレーター + iPhone シミュレーターで動作
  • DTO は録画済み JSON から機械的に導出し、バックエンドを想像で補わない
  • オフラインデモ(Recorded モード)は同一の Ktor クライアントを MockEngine に載せ替えるだけで、プロダクションのパース経路をそのまま通る

1. なぜアプリファーストは避けられないのか

まずデータから。日本では、スマートフォンでインターネットを利用する個人の割合が 2016 年の 57.9% から 2025 年には 74.3% へ上昇し、同期間に PC は 58.6% → 45.6% に下落しました(総務省 通信利用動向調査)。

利用時間の差はさらに顕著です。全年代のインターネット平均利用時間(平日)はモバイル機器が 116.8 分/日に対してパソコンは 50.0 分/日と 2 倍以上、休日には 133.5 分 vs 23.6 分と 5 倍超まで開きます(総務省情報通信政策研究所「令和6年度 情報通信メディアの利用時間と情報行動に関する調査」)。

さらにスマートフォンの上では、ユーザーはブラウザではなくアプリの中にいます。米国の成人はモバイルのインターネット利用時間の約 9 割をアプリに費やしています(eMarketer)。

米国市場では、この議論は 10 年以上前に決着しています。

  • Instagram:長年、Web 版は閲覧専用だった
  • Uber:Web クライアントはアプリを動かせない端末向けの軽量フォールバック
  • Venmo:2018 年に決済機能を Web サイトから丸ごと撤去

プッシュ通知はリテンションの生命線、ホーム画面のアイコンはブランドの恒久的な陣地、OS 統合(決済・生体認証・ウォレット・共有シート)は当然の前提。あちらでは、サービスにアプリが「要るかどうか」を問う人はもういません。

「PWA でいいのでは?」という反論には先に答えておきます。Web Push は確かに存在します(iOS も 16.4 からホーム画面追加済みの Web アプリに開放)が、ユーザーがまずページを手動で「インストール」する必要があり、配信の信頼性も OS 統合の深さもネイティブプッシュには遠く及びません。10 年経ってもギャップは開いたままで、リテンションの勝負は依然ネイティブ側で決まっています。

2. では、なぜ皆やっていないのか

古典的なコスト表が残酷だからです。

項目 内容
デザイン工程 コードを書く前に、Web の体験をモバイル向けに再設計
コードベース × 2 Swift と Kotlin で同じプロダクトを二度実装
チーム × 2 2 種類のスキルセットで採用
ロジックの二重化 同じバグを二度直す。同じ機能が別々の時期に出る。仕様合わせ会議が永遠に続く
その上に QA 倍増 + アプリストア運用

本当の急所は v1 ではありません。v1 以降のすべての機能が、永遠に二度ずつリリースされることです。この繰り返し課税が、「Web で十分」を先送りから既定方針へと硬化させてきました。

3. 計算式を変える 2 つの技術

最近 2 つのことが起き、しかも互いに増幅し合っています。

AI が労働コストを消した。 エージェントは既存の Web サイト——レンダリング済みページ、スクリーンショット、実 API トラフィック——を読み取り、その証拠からアプリを書けるようになりました。Web サイトはすでに仕様書でありテストデータであり受け入れ基準です。あとは誰かが読むだけ。エージェントは疲れずに読みます。

CMP が二重化コストを消した。 1 つの Kotlin コードベースから Android アプリと iOS アプリが生まれます。「すべての機能を二度リリースする」税は、規律ではなく構造によって消滅します。

ここで決定的なのは AI 側の形態です。魔法のプロンプト 1 発ではなく、

クロール → 仕様のリバースエンジニアリング → 仕様からコード生成 → スクリーンショットで照合検収

というパイプラインであること。以降はこれを実サイトに適用した記録ですが、その前に 2 つの設計判断——なぜローカルなのか、なぜ CMP なのか——を片付けます。

4. なぜ「ローカル」AI パイプラインなのか

クラウド型の変換 SaaS が不可能とは言いませんが、ローカルを選んだ理由は 2 つ、どちらもモデル性能とは無関係です。

① 権限と認証情報がローカルに留まる。 使うのは環境変数から読む認証情報だけ(gitignore 済みの .env と Playwright のログイン状態)。今回スコープに入れた 16 画面はすべて公開ページで、セッションなしでも到達できます(一部のパスには別系統のアクセス制限がかかっており、そちらの認証を渡していないので後述の 401 になります)。社外に預ける判断は重く、自分のラップトップで動くならただの社内ツールで済みます。

境界も明確です。オーケストレーション(今回は Claude Code)・スクリプト実行・認証情報はローカル、モデルはクラウドなのでページ内容と API サンプルはマシンを離れます。クロール対象は会員エリアを含まない公開相当のページに限り、API ログはマスキング済みです。

② ローカルのモバイル開発環境にそのまま繋がる。 Xcode、Android SDK、エミュレーターが同じマシンにあるので、クロール → 仕様 → コード生成 → ビルド → スクリーンショット検収を 1 回の実行で通せます。同じパイプラインがエミュレーターを操作して撮影し、原サイトと並べた差分を P0/P1/P2 に分類、深刻なものはループ内で修正(iOS の操作は現状手動)。クラウドでやるなら、2 つの OS のビルドツールチェーンとデバイスファームを自前で抱えることになります。

ローカルかどうかとは独立した、パイプライン自体の方針も 2 つ。

  • 証拠であって、推測ではない。 Playwright が収集するのはレンダリング済みの 60 ページ——HTML、スクリーンショット、実 API トラフィック(マスキング済みサンプル)。仕様はここから導かれます。
  • 成果物は契約、フェーズはゲート。 各フェーズは成果物を書き出してから次へ(artifacts/spec/app/report/)。決定的に実行できる工程はエージェント自身のスクリプトで、各ゲートを人間が見ます。

5. なぜ Compose Multiplatform なのか

私は KMM の時代から KMP / CMP を追いかけてきました(これまでに書いた記事はこちら)。今回ターゲットに CMP を選んだ理由は 3 つです。

一貫性。 KMP のデータ層とドメイン層が 1 つで両 OS に仕えるので、iOS と Android のビジネスロジックは乖離しようがない。実装が 1 つ、テストスイートも 1 つだからです。

効率。 2 倍ではなく約 1 倍の工数。v1 でも、それ以降のすべての機能でも。1 チームで両ストアに出荷。

性能。 Android では——日本はさておき世界のスマホの過半です——Compose は Google が Android の標準 UI ツールキットとして提供しているものそのもので、ファーストパーティアプリと同じ ART 上、同じ描画パイプライン上で動きます。つまり CMP アプリの Android 側は、プラットフォーム標準の外に別のレンダリング層を積みません(Compose 自身は View ツリーではなく自前のコンポジションを描画しますが、その先は Android のグラフィックススタックです)。iOS では CMP も Skia で描画します——独自エンジンを積むという点は他のクロスプラットフォームフレームワークと同じアーキテクチャ上の賭けです——が、Kotlin/Native が共有コードをマシンコードにコンパイルし、Android 側は標準ツールキットのまま。今回ベンチマークを取ったわけではないので優劣は断言しませんが、Android 側で追加の描画層を背負わないという構造上の差は残ります。

スライドに収まらないが実務で効く理由:

  • 既存チームには Android 側がタダ同然。 Compose はすでに Android の標準。CMP アプリの Android 半分は、いまの Android エンジニアが初日から読める慣用的なネイティブコード。Flutter は全員に Dart 乗り換えを要求します。
  • 段階導入できる。 データ層だけの共有も、UI 全体の共有も可。既存ネイティブアプリへの埋め込みも SwiftUI/UIKit との相互運用も可。移行の道筋であって「全面書き直しか、さもなくば」ではない。
  • 両陣営のファーストパーティが支える。 CMP は JetBrains 製。Google は KMP を公式サポートし、自社プロダクションで使用し(Google Docs が採用済み、Workspace 他アプリも追随中)、androidx の KMP 版(Lifecycle / ViewModel / Room / DataStore。Navigation の CMP 版は JetBrains 管理)を出荷。CMP の iOS 対応は 2025 年 5 月、1.8.0 で stable 到達。プラットフォームの所有者と言語の生みの親が同じスタックに収斂している——次の 10 年の投資先を示すこれ以上ないシグナルです。
  • プラットフォーム API に直接アクセス。 expect/actual で Keychain / EncryptedSharedPreferences をネイティブに呼ぶ。待たされるプラグイン層なし。
  • スタック全体が 1 言語。 Kotlin で端から端まで、Ktor でバックエンドまで。採用市場もツールチェーンも成熟。

そして本記事全体を貫く理由がこれです。

6. ケーススタディ:KINTO ステージング → 動く CMP アプリ

6.1 パイプライン全体像

5 フェーズ。各フェーズは成果物を書き出してから次へ進み、フェーズ間に人間のレビューゲートがあります。

パイプラインの 5 フェーズと、各フェーズが書き出す成果物

CRAWL — サイトをモバイル開発のリファレンスに変える

1 ページを 4 つの成果物として記録します。 モバイル(390×844 @2x、iPhone UA、isMobile/hasTouch)とデスクトップ(1440×900)のフルページスクリーンショット、networkidle 後のレンダリング済み DOM、そのページが実際に叩いた API、そして各エンドポイントのレスポンス実体。モバイル側が変換の正解データで、Phase 5 の検収でも同じ画像と並べます。サイトのモバイル表示がそのまま設計書になるので、「モバイル向けに作り直す」工程が消えます。

API はページ単位で記録します。 各エントリに「どのページが引き起こしたか」が付くので、画面 → 必要データ → エンドポイントが機械的に繋がります(見積りシミュレーションの ?step=1&memberType=corporate&term=7&bonus=0car-models/{carCode}/selectable-contract を叩く、という粒度で)。値を型トークンに置き換えたレスポンス形状(float / iso8601 / url / null)も保存し、これが DTO の型付けの根拠になります。実体サンプルはエンドポイントごとに最大 3 件、ハッシュで重複排除——複数サンプルの和集合が「どのフィールドが nullable か」を決め、同じファイルが後で MockEngine のリプレイと契約テストにも使われます。

URL をテンプレート化して画面を数えます。 パス 1 セグメントずつ、識別子らしいものを型に畳むだけです。

crawler/crawl.ts(抜粋)
function templatize(pathname: string): string {
  return pathname.split("/").map((seg) => {
    if (/^\d+$/.test(seg)) return "{id}";
    if (UUID_RE.test(seg)) return "{uuid}";
    // CAR-0000003908 → {carCode}、GRD-… → {grdCode}
    if (PREFIXED_CODE_RE.test(seg)) return `{${seg.split("-")[0].toLowerCase()}Code}`;
    if (isOpaqueToken(seg)) return "{token}";
    return seg;
  }).join("/") || "/";
}

これで同型ページは最大 3 インスタンスだけ辿れば済み、60 ページは 58 テンプレートに整理されて、これが 16 画面の候補になります。ページごとの outlinks はナビゲーショングラフに、img/table/iframe/繰り返しカードの構造シグネチャはコンポーネント一覧に、参照されている CSS とフォントは実ファイルとして取得してデザイントークンに回します。

Phase 2 で、サイトは機械可読な契約に変わります。app-spec.json の 16 画面それぞれに、ソース URL・優先度・コンポーネント・必要データ・明示的なモバイル適応プランが与えられます。本件のようにサイトがすでにレスポンシブ対応済みなら、この適応設計はモバイルビューポートのクロール結果から直接導出され、パイプラインに吸収されます

spec/app-spec.json(抜粋)
{
  "id": "Home",
  "priority": "P0",
  "components": ["TopBar", "HeroCarousel", "CarCard", "SectionHeader", ...],
  "dataNeeds": ["CarouselSlide", "RecommendedCarList", "CorpContent", ...],
  "mobileAdaptation": "Hero carousel → HorizontalPager with page indicator, 16:9 slides
     using the imageUrl_x2 asset. The desktop 3-column grid collapses to 2 columns ...
     51 images on this page: everything below the first two sections must be
     lazily loaded via AsyncImage."
}

念のため補足すると、この抜粋は実ファイルのままで、正しい仕様ではありません16:9 slides という横長前提が誤りで、CMS が実際に配信しているのは正方形素材でした。6.4 で触れるレターボックス不具合(実装は 16:10 の枠になっていました)の根っこは、この一行です。仕様そのものも検収ループの検査対象だということです。

API 契約は記録トラフィックから合成した OpenAPI で、そのことをファイル自身がヘッダーで宣言しています。

spec/api-spec.yaml(抜粋)
info:
  title: KINTO (staging) — reverse-engineered API
  description: |-
    Reverse-engineered from real traffic captured in artifacts/api-log.json.
    Nothing here is invented: every schema is the union of observed samples,
    a field missing from any sample is nullable.
    No mutation endpoints exist in this spec: the crawl observed GET traffic only.

デザイントークンも同じ証拠基準です。色・タイポグラフィ・余白はサイトの 24 個の CSS ファイル(2.3MB)から出現頻度順に抽出、WCAG コントラスト比は計算済み。ブランドカラーがモバイルで AA を満たさない箇所には、トークンファイルがアクセシブルな代替色を用意してテキストへの使用を強制します。

6.2 生成されたアーキテクチャ

Clean Architecture + MVI、すべて commonMain です。

生成されたアプリのレイヤー構成と、Koin が起動時に選ぶ Ktor エンジン

押さえるべきポイントは 1 つだけ。

メカニズムの全文がこれです。

di/CoreModule.kt
single<HttpClientEngine> {
    val config: AppConfig = get()
    when (config.dataMode) {
        DataMode.Recorded -> recordedEngine(get())   // MockEngine replaying the crawl
        DataMode.Live     -> platformHttpEngine()    // OkHttp / Darwin
    }
}

つまりオフラインデモはプロダクションのパース経路をそのまま通ります。実ペイロードをパースできない DTO は、ライブサーバー相手とまったく同じように Recorded モードでも失敗します。

もう 1 つ、どんなコードレビューでも擁護したいディテール:リクエストに合致するサンプルがないとき、リプレイエンジンは空の 200 ではなく 501 を返します。沈黙の成功を許すと、実際には何も流れていない画面が「実装済み」に見えてしまうからです。

ビジネスロジックは 100% commonMainexpect/actual は本当に避けられない箇所(セキュアなトークン保存 → Keychain / EncryptedSharedPreferences、プラットフォーム HTTP エンジン)にのみ現れ、各箇所に理由がドキュメント化されています。

6.3 AI が生成した CMP コードの実物

DTO は導出されるもので、幻覚ではない。 車種カタログのエンドポイントは、CMS 風味の混沌としたネーミングのフィールドを 80 個以上持つオブジェクトを返します。ジェネレーターは記録済みサンプル全体の和集合を取ります。全サンプルに存在するフィールドは非 null、どれか 1 つでも欠けていればデフォルト値付き nullable。

data/dto/GeneratedDtos.kt(抜粋)
@Serializable
data class CarCatalogEntryDto(
    val name: String,                                    // in every sample → required
    @SerialName("fuel_label") val fuelLabel: String? = null,  // absent in some → nullable
    @SerialName("year3_bonus0m_taxin") val year3Bonus0mTaxin: String,
    // … 80+ fields, mechanically derived from recorded JSON
)

このクラスを喜んで手書きする人間はいません。書く必要もありませんでした——そして、どのフィールドも推測されていません。判定はこの 1 行だけです。

crawler/tools/schema.ts(抜粋)
// そのフィールドが現れたオブジェクト数 === 観測したオブジェクト数 のときだけ required
const req = (s.propSeen?.get(k) ?? 0) === s.seen;

パイプラインの原理はここに凝縮されています:記録 → 全サンプルの和集合 → 型 → コード。どの矢印もモデルの判断ではなくスクリプトの計算なので、同じ記録を入れれば同じ DTO が出ます。モデルの仕事は、この機械的な出力の上に画面を組むことだけです。

API 面は型付きで、自分の出自に正直。 生成されたクライアントの全関数が、どの記録済みリクエスト由来かをドキュメント化しています。

data/remote/GeneratedApiClient.kt(抜粋)
/**
 * Contract terms and bonus-payment options available for a car model
 * Recorded as: GET /api/price/v1/web/car-models/{carCode}/selectable-contract
 */
suspend fun getSelectableContract(carCode: String): ApiResult<SelectableContractDto> =
    runCatchingApi {
        client.get(hosts.main + "/api/price/v1/web/car-models/$carCode/selectable-contract")
            .bodyOrThrow()
    }

すべての画面が同じ固定の MVI 形状。 sealed な Intent が入り、StateFlow が出て、ナビゲーションはワンショットの Effect。

ui/screens/home/HomeViewModel.kt(抜粋)
sealed interface HomeIntent {
    data object Load : HomeIntent
    data class CarClicked(val car: CarSummary) : HomeIntent
    // …
}

class HomeViewModel(
    private val getHomeFeed: GetHomeFeedUseCase,
    private val hosts: ApiHosts,
) : ViewModel() {
    private val _state = MutableStateFlow(HomeUiState())
    val state: StateFlow<HomeUiState> = _state.asStateFlow()

    fun onIntent(intent: HomeIntent) { /* exhaustive when */ }
}

この均一性は美学ではなく、画面生成を安全に並列化できる理由そのものです。テーマ・ナビゲーショングラフ・共有コンポーネントが契約としてコミットされた後は、独立したエージェントが 1 画面ずつ引き受けられます。

契約テストが、すべてを現実に釘付けにする。 クライアントと同時に生成され、オペレーションごとに 1 本、すべての記録済みサンプルをプロダクションのクライアントに通します。

data/GeneratedContractTest.kt(抜粋)
/** Contract terms and bonus-payment options — 3 recorded sample(s). */
@Test
fun `getSelectableContract parses every recorded sample`() = runTest {
    val results = ContractTestEnv.forEachSample("getSelectableContract") { env ->
        env.api.getSelectableContract(carCode = SAMPLE_CAR_CODE)
    }
    // サンプル 0 件のまま緑になる事故を防ぐ。件数は生成時に KDoc と同じ値で埋め込まれる
    assertTrue(results.isNotEmpty(), "no recorded sample was replayed")
    assertEquals(3, results.size, "recorded sample count changed")
    results.forEach { assertTrue(it is ApiResult.Success, "failed to parse: $it") }
}

件数のアサートは、あとから付け足した保険ではありません。forEachSample が空リストを返すと——オペレーション ID の綴りを 1 文字間違えるだけで起こります——forEach は何も検証せずに通ってしまい、「107 本グリーン」が何も意味しなくなります。生成器はサンプル数を知っているので、その数をテストに焼き込みます。

テスト環境は意図的にプロダクションのオブジェクトを使います——本物のクライアントファクトリ、本物の JSON 設定、本物のリプレイエンジン。スイート全体(生成契約テスト + mapper + 回帰)で 107 テスト、失敗 0。釘付けにしているのは記録された現実なので、後日ライブ API がずれれば、次の再クロール時に同じスイートが捕まえます——本番クラッシュではなく、赤くなったテストとして。

6.4 Web vs アプリ、並べて比較

検収ループは Android エミュレーター(API 35、1080×2400)上でアプリ自身のナビゲーションを操作し、実装済み画面を撮影します。全工程 Recorded モードなので、ライブ API には一度もリクエストを出していません(画像だけはサイトの CDN から読み込みます)。

ホーム:原サイトのモバイル版 vs CMP アプリ

ラインアップ:原サイトのモバイル版 vs CMP アプリ

車種詳細:原サイトのモバイル版 vs CMP アプリ

見積り:原サイトのモバイル版 vs CMP アプリ

各ペアは原サイトとセクション単位で照合します。上の 4 枚で実際に確認できるのは、ホームのヒーローコピーと CTA、PICK UP セクションの実 CMS バナー(後述の iOS スクリーンショットでは 14 枚ぶんのページインジケーターが見えます)、ラインアップの TOYOTA / LEXUS タブとカテゴリチップ、車種詳細の月額料金と納期目処、見積りのプラン選択と下部固定サマリーバーです。差分は P0/P1(コンテンツ欠落、構造・ナビゲーション破損)→ ループ内で修正、P2(ピクセルレベルの磨き込み)→ 記録、と分類処理します。

このスクリーンショットは差分も正直に写しています。ホームのヒーローは、原サイトでは背景ビジュアルに商品写真の装飾イラストが載っていますが、アプリ側はコピーと CTA だけのテキストブロックになっています。装飾素材がクロールの成果物から機械的に取り出せなかったためで、現時点では既知の差分(8 章の P2 リスト)です。

ループが実際に機能した例も 1 つ。ホームのカルーセルは当初バナーを 16:10 の枠にレターボックス表示していましたが、CMS が実際に配信しているのは 630×630 の正方形素材でした。スクリーンショット比較で発覚 → 修正 → 再撮影 → 解消。

そして同じコードベースが、画面コードの追加ゼロで 2 つ目のプラットフォームに乗ります。

同じホーム画面が iPhone シミュレーターで動く様子

6.5 正直な数字ボックス

項目 実績
仕様にリバースエンジニアリングされた画面数 16(優先度・コンポーネント・必要データつき)
現時点でエンドツーエンド実装済みの画面数 4——P0 セット:Home、Lineup、CarDetail、Estimate
生成された API オペレーション数 31、4 ホスト横断——すべて GET。ミューテーションは観測されず、想像で足してもいない
テスト(契約 + mapper + 回帰) 107 本、全グリーン
動作環境 Android エミュレーター + iPhone シミュレーター——ライブ API 不使用。画像のみサイトの CDN から読み込み
検収中のライブ API への接触 一度もなし(画像取得のみ CDN にアクセス)

7. AI を信頼できるものにした工学(デモの手品ではなく)

モデルの能力は必要条件であって、十分条件ではありません。実際に重さを支えたのは、その周りを固める工学でした。

  • 固定アーキテクチャという契約。 Clean Architecture + MVI、Koin、Ktor、Coil——生成の前に決定済み。エージェントは即興で形を発明せず、既知の形に中身を埋める。だから成果物はレビュー可能で、失敗は診断可能。
  • 手作業よりスクリプト。 決定的に実行できるものはすべて、エージェントが書いて自分でデバッグするスクリプト。スクリーンショット取得スクリプトも、アプリのコードと同じくパイプラインの産物。
  • 全コミットにゲート。 iOS ターゲットは全コミットでコンパイル必須——クロスプラットフォームの腐敗は最後ではなく数分で捕まる。
  • 1 画面 = 1 タスク = 1 コミット。 並列化は共有契約(テーマ、ナビゲーション、コンポーネント)のコミット後にのみ解禁。
  • 検収は事後ではなくループの内側に。 人間がレビューする前に、エージェントのスクリーンショットは原サイトのスクリーンショットと対決を済ませている。

この 5 つの根底にある姿勢は 1 つです。

8. 限界を、正直に

  • 16 画面中 12 画面はまだプレースホルダー。 画面単位で線形にスケールする作業であって、一発の魔法ではない。
  • 埋め込みコンテンツ(動画、地図、サードパーティウィジェット)はプレースホルダー + expect/actual の移行プランどまり。共通コードに WebView という逃げ道はない。
  • 見た目の既知の差分(P2)。 クロールで取得できないアイコン素材とヒーローの装飾イラスト、フォントウェイトの機微。見積り画面は固定バーぶんの content inset がなく、下端のコンテンツが隠れます(6.4 のスクリーンショット)。iOS のジェスチャーの質感は実機検証が必要。
  • クロールが捉えるのはハッピーパス。 エラー状態、深いページネーション、パーソナライズ、A/B バリアントは記録に含まれない。
  • 読み取り専用。 31 オペレーションすべて GET、会員エリアはスコープ外。見積りの「保存」「次へ」もアプリ内では書き込まず、選択内容を積んだ URL で原サイトに引き渡します——ないものは想像で補わず web に戻す。トランザクション系は次フェーズで、多くのプロダクトにとってはそちらが難しい半分。
  • 適用範囲は自社サイトのみ。 認証情報と権利を自分が持つサイト向けの道具であって、他社プロダクトのスクレイピング道具ではない。

9. ロードマップにとっての意味

コスト曲線は反転しました。かつてアプリ化で最も高くついた部分——モバイル再デザイン(サイトがレスポンシブ版を持つ場合)、ボイラープレート、二重のデータ層、仕様合わせの維持——が、いまや最も安い部分です。人間の労力は本当に判断が要る場所に集中します:どの画面が P0 か、ブランドは何を要求するか、何を Web に残すか。

現実的なチーム像:エンジニア 1 人 + パイプラインで、両プラットフォームで動くレビュー可能なビルドに到達(本記事の範囲では P0 の 4 画面まで。残り 12 画面は同じ手順の反復です)。ネイティブのスペシャリストは最後の 10%(ジェスチャーの質感、埋め込みプレイヤー、ストア向けの磨き込み)で合流。「2 チームで 1 年」とはまったく別次元の予算会話です。

まとめ

アプリファーストは、予算の問題であることをやめ、ツールチェーンの問題になりました。

あなたがすでに運用しているその Web サイトは、実は 3 つのものを兼ねています。

  1. 仕様書(ページとフロー)
  2. テストデータ(実 API トラフィック)
  3. 受け入れ基準(モバイルスクリーンショット)

AI パイプラインの仕事はこの 3 つを読み込むことだけ。Compose Multiplatform の仕事は、その結果をすべての端末で動かすことだけ。

プロダクトの居場所とユーザーの居場所のあいだの距離は、いまや年単位ではなく日単位です。埋めにいきましょう。

参考リンク

Facebook

Follow Us

XConnpassWantedly

KINTOテクノロジーズの最新情報をSNSで発信中!イベント・テックブログ更新情報もお届けします。