月曜の朝、Rork のプロジェクトを開いたら、モデルメニューの並びが変わっておりました。Claude Opus 5.5 と GPT-6 Sol が加わり、先に入っていた GPT-6 Astra と合わせて三つ並びです。変更履歴には 9 月 22 日の日付で、どちらも Pro と Max のプランに含まれ、無料プランでは解放するプランの名前が表示される、と書かれています。
新しいモデルが増えた日に私が必ずやることが一つあります。壁紙アプリで長く育ててきた「3 列の一覧画面」を、新しいモデルでもう一度作らせてみることです。サムネイルが並び、押すと開き、引っ張ると更新される——それだけの画面ですが、道具の癖がいちばん早く手触りとして分かるのです。
今回はその一画面を Opus 5.5 と GPT-6 Sol で作り分け、effort の段数をどう置いたか、どこで差が出たか、そして私がどちらで続けることにしたかを書き残します。
最初に決めるのはモデルではなく、画面の仕様です
最初にお伝えしたいのは、モデルを選ぶ前に、頼む画面の仕様を先に書き切っておくということです。仕様が揺れたまま二つのモデルを比べても、出てきた差がモデルのものか指示のものか、あとから分けられません。
私が両方に渡した指示は、次の一つだけです。文言を変えずに、そのまま二回使いました。
ギャラリー画面を 1 つ作ってください。
- 正方形のサムネイルを 3 列で並べる(画面幅に合わせて自動で大きさを決める)
- サムネイルを押すと詳細画面 /wallpaper/[id] へ移動する
- 下に引っ張ると一覧を読み直す
- 画像は expo-image を使う
- データは仮の配列で 12 件。あとで API に差し替えるので、読み込み部分は 1 か所にまとめる
- 検索・お気に入り・タブなど、ここに書いていない機能は追加しない最後の一行が、いちばん大事な一行でした。書いていない機能を足さない、と先に言っておかないと、モデルは親切に増やしてくれます。増えた分を読んで消す時間のほうが、書く時間より長くなるのです。
Opus 5.5 で作る — effort を Medium に置き直した理由
Opus 5.5 の effort は、Low・Medium・High・Extra High・Max の 5 段階です。最初に私は、迷わず Max を選びました。段数が多いのだから上が良いはずだ、という思い込みです。
結果は芳しくありませんでした。生成には長く待たされ、出てきた画面には「お気に入り」のハートと空の検索欄がついておりました。仕様の最後の一行を書いていたにもかかわらず、です。effort を上げると、モデルは指示の外側まで考えてしまう——私の手元では、そう見えました。
復元ポイントに戻し、同じ指示を Medium で出し直しました。今度は 3 列の正方形と、引っ張って更新と、詳細画面への遷移だけが返ってきます。読み込みは reload という一つの関数にまとまっており、API に差し替える場所も迷いません。
ここで私は一つの線を引きました。仕様を書き切った画面には、effort は下から当てます。 段数を上げるのは、自分が仕様を書けていないときだけです。
GPT-6 Sol で同じ指示を出す — 3 段の effort と、差が出た場所
GPT-6 Sol の effort は Low・Medium・High の 3 段階で、Opus 5.5 より段数が少ないぶん、選ぶ手が止まりません。Opus 側と条件を揃えるため、こちらも Medium で同じ指示を出しました。
出てきた画面の骨格は、驚くほど似ておりました。FlatList の numColumns で 3 列を作り、画面幅から一辺の長さを計算し、expo-image でサムネイルを描く。ここまでは同じです。
差が出たのは、細部の二か所です。一つは列の隙間の付け方で、Sol は columnWrapperStyle に gap を置き、Opus はセルごとに margin を持たせていました。もう一つは空の状態で、Sol は「まだ壁紙がありません」という表示を頼まずに用意し、Opus は何も置きませんでした。どちらが正しいというより、頼んでいない部分の埋め方に、それぞれの性格が出たのだと思います。
Low でも試しましたが、引っ張って更新の refreshing が更新のたびに切り替わらない版が返ってきて、一度直しを頼む必要がありました。3 段の真ん中に置いておくのが、この画面ではちょうどよいのだと感じています。
生成された一覧画面の骨格 — 自分で読める範囲まで
二つのモデルの出力を突き合わせ、私が最終的に手元に残した形をそのまま置きます。コードを書かない方も、三つの場所だけ目で追っていただければ十分です——COLUMNS の数、SAMPLE の配列、そして onPress の行き先です。
import { useCallback, useState } from "react";
import { FlatList, Pressable, StyleSheet, Text, useWindowDimensions, View } from "react-native";
import { Image } from "expo-image";
import { useRouter } from "expo-router";
type Wallpaper = { id: string; title: string; thumb: string };
const COLUMNS = 3; // 列数はここだけを変えます
const GAP = 4;
// 画像の置き場所は環境変数から読みます(EXPO_PUBLIC_ で始まる名前だけがアプリに渡ります)
const IMAGE_BASE = process.env.EXPO_PUBLIC_IMAGE_BASE ?? "";
// あとで API に差し替える仮のデータ
const SAMPLE: Wallpaper[] = Array.from({ length: 12 }, (_, i) => ({
id: String(i + 1),
title: `Sample ${i + 1}`,
thumb: `${IMAGE_BASE}/thumb-${i + 1}.jpg`,
}));
export default function GalleryScreen() {
const { width } = useWindowDimensions();
const size = (width - GAP * (COLUMNS + 1)) / COLUMNS; // 画面幅から一辺を決めます
const router = useRouter();
const [items, setItems] = useState<Wallpaper[]>(SAMPLE);
const [refreshing, setRefreshing] = useState(false);
// 読み込みはこの 1 か所だけ。API に替えるときはここを書き換えます
const reload = useCallback(async () => {
setRefreshing(true);
try {
await new Promise((resolve) => setTimeout(resolve, 600));
setItems((prev) => [...prev].reverse());
} finally {
setRefreshing(false);
}
}, []);
return (
<FlatList
data={items}
numColumns={COLUMNS}
keyExtractor={(w) => w.id}
contentContainerStyle={{ padding: GAP }}
columnWrapperStyle={{ gap: GAP, marginBottom: GAP }}
refreshing={refreshing}
onRefresh={reload}
renderItem={({ item }) => (
<Pressable
accessibilityLabel={item.title}
onPress={() => router.push({ pathname: "/wallpaper/[id]", params: { id: item.id } })}
style={{ width: size, height: size }}
>
<Image
source={{ uri: item.thumb }}
style={StyleSheet.absoluteFill}
contentFit="cover"
transition={150}
/>
</Pressable>
)}
ListEmptyComponent={
<View style={styles.empty}>
<Text>まだ壁紙がありません</Text>
</View>
}
/>
);
}
const styles = StyleSheet.create({
empty: { padding: 32, alignItems: "center" },
});なぜこの形で残したのかを、ひと言ずつ添えます。列の隙間は Sol 案の gap を採りました——セルごとの margin は、端の列だけ幅がずれる原因になりやすいからです。空の状態の表示も Sol 案から残しました。try / finally は私が足した部分で、読み直しに失敗しても refreshing が回りっぱなしにならないようにしています。
一辺の長さを useWindowDimensions から計算しているのは、iPad や横向きでも 3 列が崩れないようにするためです。壁紙アプリを iPad に広げたときに、固定の幅で並べていた画面から直したことがあり、それ以来この一行だけは最初から入れております。
どちらで続けるかを決めた基準
二つを並べた結果を、表に置きます。
| 項目 | Claude Opus 5.5 | GPT-6 Sol |
|---|---|---|
| effort の段数 | 5 段(Low / Medium / High / Extra High / Max) | 3 段(Low / Medium / High) |
| この画面で置いた段 | Medium(Max は機能が増えた) | Medium(Low は refreshing が直らなかった) |
| 頼んでいない部分の埋め方 | 置かない | 空の状態の表示を用意する |
| 含まれるプラン | Pro / Max | Pro / Max |
| 私の使い分け | 仕様が固まった画面の書き起こし | 仕様に穴がある画面の叩き台 |
無料プランの方は、メニューでこの二つを選ぼうとすると解放するプランの名前が表示されます。払う前に、いま止まっている理由が料金なのか指示の書き方なのかを分けておくと、あとで悔いが残りません。私は今回の一画面で、指示の書き方のほうに原因があったことを二度確かめました。
Rork は生成のたびにコードを書き換えますので、どちらのモデルで続けるにしても、変わった箇所だけを読む習慣が要ります。私の手順は「Rork が書き換えた箇所だけを読む — 再エクスポートのたびに回している差分確認の手順」に書き残しております。Rork Max の SwiftUI 側で同じ「どこまで任せられるか」を確かめた記録は、「Rork Max の SwiftUI ネイティブ生成能力を30機能で検証する」のほうにあります。
まずは、ご自身のアプリでいちばん小さな一覧画面を一つ選び、上の指示文の「書いていない機能は追加しない」の一行だけを写して、Medium 同士で作り比べてみていただければと思います。段数を上げるのは、そのあとで構いません。