Codex vs Claude

Generated Placeholder Assets: Borrowing Against a Design You Have Not Made

David Guzenburg/ / 10 min read

Placeholders do not ship because someone decided to ship them. They ship because nothing in the system knew they were placeholders.

toolingquality gatesCIconventions

A placeholder is a debt instrument, and most teams never record it

An agent that can fill every empty src attribute with a plausible icon removes a real source of friction. Screens get built. Reviews happen against something that looks like the product. Nobody waits three days for a designer to draw a bell.

The claim worth arguing with is that this is free. It is not; it is borrowing. You have taken an asset you do not intend to keep and put it somewhere production reads from, on the strength of an intention to replace it later. That intention is not stored anywhere. Placeholders do not ship because teams decide to ship them. They ship because nothing in the system knew they were placeholders.

What "context-aware" actually buys you

The generation is genuinely better than a grey box. An agent that has read your components knows the icon set is 24×24, knows the nav uses line icons rather than filled ones, and can produce something in that register rather than a stock clip-art cart. For a demo two days from now, that is the difference between feedback about the flow and feedback about the icons.

What it does not buy you is consistency across a set. Generate ten icons in ten turns and you get ten interpretations of your stroke weight. Whether that matters depends entirely on whether anyone is going to look at them side by side, which in a navigation bar they will.

Generate sets, not icons

If you are going to generate, generate the whole row in one turn with an explicit shared specification, and regenerate the whole row when you change it. A set produced together is coherent in a way ten independently produced icons never are.

Make the placeholder announce itself

The fix is not vigilance. It is making the artefact self-identifying, so that noticing it requires no attention.

Two markers, both mechanical. A filename convention that no production asset would ever match, and a manifest entry. The filename does the work in code review and in a file listing; the manifest does the work in automation.

Every file in this directory is generated and temporary.

Convention:  assets/placeholder/<name>.png
Referenced:  only from *.stories.tsx and *.fixture.ts

Nothing under src/ may import from this directory. The check in
scripts/no-placeholders.sh enforces it and runs in CI.

The check that makes the rule real

A convention nobody enforces is a comment. The enforcement here is four lines, and it belongs in the same gate as your other pull-request checks — the argument for putting agent-produced work behind mechanical gates rather than reviewer attention is made at length in CI gating for agent pull requests, and this is one of the cheapest instances of it.

#!/usr/bin/env bash
# Fail if production source references a placeholder asset.
set -euo pipefail

hits=$(grep -rn --include='*.ts' --include='*.tsx' --include='*.css' \
         -e 'assets/placeholder/' src/ \
       | grep -v -e '.stories.' -e '.fixture.' || true)

if [ -n "$hits" ]; then
  echo "placeholder assets referenced from production source:" >&2
  echo "$hits" >&2
  echo >&2
  echo "Replace them, or move the reference into a story or fixture." >&2
  exit 1
fi

echo "no placeholder references in src/"

Note what this check does not do. It does not try to detect whether an image looks generated, which is unreliable and would fail on real assets. It checks a path convention, which is exact. Mechanical checks should assert things that are true by construction, not things that require judgement.

Watermark the ones that matter

For anything that will be seen by someone outside the team — a demo build, a staging environment, a screenshot in a ticket — a path convention is invisible. Burn a marker into the pixels instead: a low-contrast diagonal band, or a corner dot in a colour your palette does not contain.

It looks unfinished, which is the point. A stakeholder who sees a marked placeholder asks when the real icon lands. A stakeholder who sees a clean generated icon assumes the icon is done and asks about something else, and now the debt is invisible to the only person who would have flagged it.

The licensing question you have not asked

Generated imagery raises ownership and provenance questions that vary by jurisdiction, by model provider terms, and by what you intend to do with the asset. I am not going to pretend there is a settled answer, and anything that tells you there is should be treated with suspicion.

What is tractable is process. Know which assets are generated, by which model, on which date, from which prompt. If that record exists, a question about provenance is answerable in an afternoon. If it does not, the answer involves someone opening every image in the repository and guessing.

Brand marks are a different category

Never let an agent generate a logo, a wordmark, or anything resembling a third party's trade dress, even as a placeholder. A generated icon that happens to look like a well-known mark is a problem that outlives the placeholder, and the usual "we were going to replace it" defence does not help.

When generated placeholders are simply the right call

Internal tooling that ten people use. A spike you will delete. A fixture set for visual regression tests, where what matters is that the image is stable, not that it is beautiful. A design exploration where the icons are scaffolding for a conversation about layout.

In all of those the debt is real and the repayment is trivial, because nobody outside the team will ever see the asset. The discipline in this article costs about twenty minutes to set up once, and it is what lets you say yes to generation freely, because the failure mode is now caught by a script instead of by someone remembering.

Empty states carry the most debt

Icons are the obvious placeholder. Empty-state illustrations are the expensive one, because they are larger, more distinctive, more likely to be seen by a customer, and much more likely to be the thing an agent generates enthusiastically because it makes a screen look finished.

They are also the assets most likely to be wrong in a way nobody catches. An empty state carries tone. A generated illustration for "no results found" that reads as slightly melancholy, or slightly cartoonish, or simply unlike everything else in the product, is a brand decision made by an agent with no brief. Nobody reviews it because it is one image in a pull request about search.

Track the debt where the team already looks

A manifest is necessary and insufficient: it lives in the repository, and planning does not happen in the repository. If the placeholder set is going to be paid down, it has to appear somewhere a person plans from.

Two arrangements work. Either the CI job that enforces the path convention also prints a count, so every build says 14 placeholder assets outstanding and the number is visible in a place people already read. Or the manifest generates one tracking issue per asset, closed when the real one lands. The count is lighter and tends to get ignored once the number stops changing; the issues are heavier and actually get scheduled.

import json, pathlib, sys

man = json.loads(pathlib.Path("assets/manifest.json").read_text())
open_items = [k for k, v in man.items() if v.get("status") == "placeholder"]

for k in sorted(open_items):
    print("placeholder: %s (added %s)" % (k, man[k].get("generated", "?")))

print("%d placeholder assets outstanding" % len(open_items))

# Fail the build once the pile stops being temporary.
sys.exit(1 if len(open_items) > 20 else 0)

The ceiling in the last line is the part that does the work. A count with no limit is a metric; a count with a limit is a constraint. Pick a number the team can live with and let the build enforce it, in the same spirit as any other budget you defend mechanically.

Deleting the directory is the real test

The honest way to find out whether your placeholder discipline holds is to delete the placeholder directory and build. If the build fails with a clear list of what depended on it, the system is working. If the application builds and the missing images show up as broken icons in staging, the convention was decorative.

Run that as a scheduled job rather than a one-off. It is cheap, it fails loudly, and it is the only check in this article that verifies the other checks are still connected to anything.

The three-week rule

A placeholder that has been in the repository for three weeks is not a placeholder. It is an asset, and the sooner you treat it as one the fewer surprises you get.

The practical version: put an age check in the same script that counts them. Anything older than a set threshold either gets replaced or gets promoted — consciously re-marked as final, with someone's name on the decision. Promotion is a legitimate outcome. Plenty of generated icons are fine forever in an internal tool. What is not legitimate is the third state, where an asset is nominally temporary and functionally permanent, because that is the state in which nobody owns it.

Age is also the most honest debt metric available here. A count tells you how many; an age distribution tells you whether the pile is moving. Twelve placeholders added last week is a project in progress. Twelve placeholders averaging four months is a policy that has stopped working, and the number alone would not have told you.

Empty states, one level of care up

Because empty-state artwork is the highest-risk category, it earns one extra rule: it does not get generated without a named reviewer. Not a process, not a committee — a person whose name goes in the manifest entry and who has actually looked at the image in context, in both themes, at the size it renders.

The reason is that the failure is tonal rather than technical, and no script detects tone. An illustration that is slightly too whimsical for a billing error page will pass every check in this article. Someone has to look, and making that someone explicit is the difference between a review and a diffusion of responsibility.

What this looks like when it works

An engineer building a settings screen asks for the six icons it needs. They arrive in one turn, coherent, into the placeholder directory, with a manifest entry each. The screen is reviewable that afternoon. CI prints 18 placeholder assets outstanding. Nothing under src/ imports them, because the check would have failed.

Six weeks later a designer produces the real set. The manifest says exactly which files to replace and where each is referenced. The count drops to twelve. Nobody had to remember anything, and no generated icon ever reached a customer, which was the entire objective and is not achievable by intending it.

The uncomfortable version of this argument

Everything above assumes placeholders get replaced. Often they do not, and the honest position is that a generated icon in an internal admin tool is simply the finished icon, and the elaborate temporary framing is self-deception that costs a manifest entry.

If that describes your situation, say so. Mark them final on the way in, skip the count, keep the provenance record because the licensing question does not care what you called the file, and drop the rest. A policy nobody follows is worse than no policy, because it produces the appearance of control.

The discipline in this article earns its cost when the assets are customer-facing and a designer is genuinely coming. It is overhead when they are not. Deciding which case you are in takes thirty seconds and almost nobody does it, which is how teams end up with ceremony around assets that were never going to change and no tracking at all on the ones that mattered.

Takeaway

Generated placeholder icons are worth using and worth tracking. Put them under a path no production file may import, enforce that with a four-line CI check rather than reviewer attention, watermark anything an outsider will see, record model and prompt per asset, and generate whole sets in one turn so they share a stroke weight. The point is not to generate less — it is to make shipping one an explicit decision rather than an oversight.

Keep reading
Codex vs Claude

Constructing SVG Icons in Code: viewBox, currentColor and the Rest Is Detail

What to check first in agent-written SVG: the coordinate contract, colour inheritance, non-scaling strokes, the labelled-versus-decorative decision, and a twenty-line linter that replaces the review comment you keep repeating.

Codex vs Claude

The Favicon Set Is Four Files: Cutting the Package an Agent Gives You

Generated favicon packages encode a decade of obsolete device workarounds. The set that is actually requested, an SVG icon that follows the colour scheme, the iOS transparency trap, and a CI check for paths that quietly return HTML.

Codex vs Claude

One Agent, Many Surfaces: They Share a Model and Nothing Else

Terminal, editor, desktop, browser and hosted runner are different tools sharing an account. What differs, why consistency has to come from the repository, verifying permission denials per surface, and choosing a surface by the shape of the task.

Codex vs Claude

Mockup to Code: The Component Matches the Picture, Which Is the Problem

An image carries layout and proportion and omits breakpoints, states, content variance and semantics. Why generated UI is full of bracketed pixel values, and the token-extraction step that fixes it.

← Kernel Sandbox or Application Permission Layer?  ·  Default-Deny Egress: The Control You Turn Off in the First Hour →

All codex vs claude articles  ·  Every article