●SUNSET — Two days until the image models shut down: imagen-4.0-generate-001, ultra, fast, and the Gemini 3 Image family stop on August 17●BREAKING — This is not a drop-in swap. The generate_images() method disappears entirely, and image generation moves to generate_content(), the same call used for text●HARD — On the cutoff date, calls fail with a hard error rather than a deprecation warning, so waiting it out is not an option●MIGRATE — The deprecations table points to gemini-3.1-flash-image as the recommended replacement●ROBOTICS — Gemini Robotics ER 2 is now publicly available, with stronger video understanding, task orchestration, multi-robot collaboration, and safety●LOGS — Developer logs for the Interactions API can now be viewed from the AI Studio dashboard●SUNSET — Two days until the image models shut down: imagen-4.0-generate-001, ultra, fast, and the Gemini 3 Image family stop on August 17●BREAKING — This is not a drop-in swap. The generate_images() method disappears entirely, and image generation moves to generate_content(), the same call used for text●HARD — On the cutoff date, calls fail with a hard error rather than a deprecation warning, so waiting it out is not an option●MIGRATE — The deprecations table points to gemini-3.1-flash-image as the recommended replacement●ROBOTICS — Gemini Robotics ER 2 is now publicly available, with stronger video understanding, task orchestration, multi-robot collaboration, and safety●LOGS — Developer logs for the Interactions API can now be viewed from the AI Studio dashboard
Before Adding New iPhone Widths, I Had Gemini Write Out the Branches I Already Had
Adding three new screen widths to four apps turned into an extraction job, not a rewrite. Here is why I let Gemini pull the branch table out of the source and kept every pass/fail decision in deterministic code.
I opened DefineManager.h, started typing one more ternary, and stopped.
Three widths to add: 420pt for iPhone Air, 402pt for iPhone 17 Pro, 440pt for the new Pro Max. A ten-minute job on paper. Except the same shape of branch was scattered across four apps, and I could not honestly say which edits would cover all of them.
As an indie developer, the apps I have shipped the longest are the ones with the most of these quiet accumulations.
The decision: write the table out before rewriting anything
There were two reasonable moves.
Drop the ternaries, move width-to-value pairs into a lookup table, and collapse the logic into one place
Touch nothing, extract the scattered branches into a table, and machine-check them for coverage
I picked the second one, for three reasons.
Rewriting four apps at once stacks all the risk into one week. If App Store review queues overlap, rolling back gets narrow fast
Extraction changes zero lines of shipping code. Being wrong costs nothing
I did not actually have the information needed to decide whether consolidation was worth it. Designing before seeing the full picture is the wrong order
The rewrite could wait until there was a table to look at.
29 ternaries, spread across four apps
The wallpaper and relaxation apps I publish on the App Store and Google Play switch padding and column counts based on screen width. After years of shipping, those checks had piled up in a constants header as ternaries — 29 of them.
Here are the widths in play, in portrait points.
Status
Width (pt)
Notes
Existing
375 / 390 / 393
Compact through standard
Existing
414 / 428 / 430
Plus and Pro Max family
New
402
iPhone 17 Pro
New
420
iPhone Air
New
440
New Pro Max size
Once I laid them out, the scary part was not the additions. Forgetting one does not crash anything. It ships a slightly wrong layout, quietly.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦You'll be able to decide where to start safely when a new device lands and your width checks are scattered across files
✦You'll be able to draw a clear line in your own pipeline between work an LLM should do and work that must stay deterministic
✦You'll be able to catch the quiet case where an existing threshold swallows a new width, instead of only checking for missing branches
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
My first attempt was the obvious one: paste the header in and ask which new sizes were missing. The answers read well, but asking twice about the same file produced answers at different levels of detail. That is a fine reviewer and a poor release gate.
So I split the work in two.
Extraction (enumeration): pull condition, threshold, and value out of semi-structured text. Absorbing notation drift is exactly what a language model is good at
Judgment (comparison): cross-check the extracted table against the device list. The same input must always produce the same verdict, so this stays in plain code
Naming the split removed most of my hesitation. Mixing "allowed to vary" with "must never vary" was the real mistake in the first attempt.
Pulling the branch table out with structured output
Extraction runs through structured output with an explicit response_schema. Free-form answers just move the ambiguity into your parser.
from google import genaifrom google.genai import typesclient = genai.Client(api_key="YOUR_API_KEY")BRANCH_SCHEMA = { "type": "object", "properties": { "branches": { "type": "array", "items": { "type": "object", "properties": { "file": {"type": "string"}, "line": {"type": "integer"}, "threshold": {"type": "integer"}, "comparison": {"type": "string", "enum": [">=", ">", "==", "<=", "<"]}, "value": {"type": "string"}, }, # Never let a field come back missing; the checker fails silently if it does "required": ["file", "line", "threshold", "comparison", "value"], "propertyOrdering": ["file", "line", "threshold", "comparison", "value"], }, } }, "required": ["branches"],}PROMPT = """Extract only the branches that switch on screen width.- Line numbers start at 1 on the first line of the source- Ignore conditions on anything else (OS version, locale, and so on)- If you cannot tell, omit the line rather than guessing--- source: {name} ---{source}"""def extract(name: str, source: str) -> dict: numbered = "\n".join(f"{i}: {l}" for i, l in enumerate(source.splitlines(), 1)) res = client.models.generate_content( model="gemini-3.5-flash-lite", contents=PROMPT.format(name=name, source=numbered), config=types.GenerateContentConfig( response_mime_type="application/json", response_schema=BRANCH_SCHEMA, temperature=0, ), ) return res.parsed # {"branches": [...]}
Number the lines yourself
I let the model count lines at first. On longer headers, that drifts. Prefixing each line with 1:, 2: on the caller side made the drift disappear. Anything you can count yourself, count yourself.
Say "omit rather than guess"
For a coverage check, one wrong row is worse than one missing row. A missing row shows up later as an intent with no matching branch. A wrong row impersonates a correct table. Telling the model to skip uncertain lines keeps the second stage honest.
Three of ten devices, 30%, land in a bucket the design never intended.
I had been bracing for "I forgot to add the new branch." The real hazard runs the other way: even when you forget, the existing thresholds quietly absorb the new width. 402pt falls into >= 393. 420pt falls into >= 414. No error, no warning. It builds, it passes review, and it ships with slightly cramped spacing.
A chain of >= always returns something for an unknown input. That is a safety valve most of the time, and a trap on exactly the day a new device appears.
The same table finds unreachable branches
The extracted table had a second use. Ternaries evaluate top to bottom, so thresholds that are not in descending order create branches nothing can reach.
import jsonbranches = json.loads(payload)["branches"] # the extraction result, as-isorder_errors = []for prev, cur in zip(branches, branches[1:]): if cur["threshold"] > prev["threshold"]: order_errors.append( f'{cur["file"]}:{cur["line"]} threshold {cur["threshold"]} ' f'is above the earlier {prev["threshold"]} (unreachable)' )for e in order_errors: print("ORDER:", e)print(f"branches: {len(branches)} order errors: {len(order_errors)}")
ORDER: DefineManager.h:43 threshold 430 is above the earlier 414 (unreachable)branches: 4 order errors: 1
Unreachable branches are hard to catch with tests, because no input reaches them. Coverage just drifts down without anyone noticing. Numbering the lines up front is what makes this report actionable.
Four rules I settled on for running it regularly
Use a small model. The job is absorbing notation drift, nothing more. Heavier reasoning models start returning inferences you never asked for
Commit the extraction output. The diff against last run becomes the review artifact for how a change moved the branches
Run the judgment step in CI every time. Extraction only needs to run when a human edits the header. The check is the part that should be able to fail
Regression-test the prompt. Editing extraction instructions changes output granularity. Keep one file whose correct table you have verified, and rerun it after every prompt change
The trap that bit me most often was how quiet a failed extraction is. Zero rows returned still exits cleanly. A single floor check — stop if the row count drops sharply from last run — removed that blind spot.
Supporting a new device makes you want to start by adding code. That is exactly what I did first. Writing down what already exists turned out to be the faster path.
Pick the one file where your width checks are densest and extract it into a table. Whether to consolidate afterward is a decision you can make with the table in front of you, and it will still be there tomorrow.
Share
Thank You for Reading
Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.