Skip to content

docs(#270): publish the real listing-media requirements, scaffold assets/, fix the broken quickstart - #275

Open
ZacxDev wants to merge 2 commits into
mainfrom
zach/270-listing-media-docs
Open

docs(#270): publish the real listing-media requirements, scaffold assets/, fix the broken quickstart#275
ZacxDev wants to merge 2 commits into
mainfrom
zach/270-listing-media-docs

Conversation

@ZacxDev

@ZacxDev ZacxDev commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Closes the CLI-side half of #270: the documentation and the assets/ scaffold. The attach-order reorder is a separate lane and is not in here.

The issue's premise was half wrong, and this PR follows the corrected version

The issue says the platform's dimension requirements "are not published". They are — they are named constants in civitai/civitai src/server/schema/blocks/app-listing.schema.ts, enforced server-side at attach by validateListingImage, with rejection messages that name the bound and the measured value. Re-read for this PR and byte-identical on main and release, so these are what an author hits today:

kind aspect (w ÷ h) minimum maximum byte cap formats
icon 0.9 – 1.1 128px shorter side 4096px longer side 1 MiB png / jpeg / webp
cover 1.3 – 2.4 640px wide 4 MiB png / jpeg / webp
screenshot 0.4 – 2.6 320px shorter side 2 MiB, ≤8 per listing png / jpeg / webp

One correction to those numbers, because they cannot go in the docs as written. The icon's 1 MiB (MAX_LISTING_ICON_SIZE_BYTES) and the CLI's 2 MiB (maxIconBytes) measure different bytes, and conflating them would have documented a cap that does not apply to the author's file. The icon rides the inline data-URI path: the server decodes it, downscales to ≤1024px and re-encodes to PNG, then stores metadata.size = png.byteLength (listing-meta.service.ts) — and that is what validateListingImage compares against 1 MiB. The CLI's 2 MiB is the source file, mirroring INLINE_ICON_MAX_DECODED_BYTES. Cover and screenshot take the full-res path where the CLI sends sizeBytes: len(data), so there the two caps do describe the same bytes. The README documents the CLI's caps for the file you pass, and the aspect/dimension table separately as server-side. AGENTS.md item 25 records why.

What changed

  • assets/ is scaffolded by all three templates, with a README of the requirements and no placeholder images. A placeholder passes format + byte validation and uploads cleanly — that is how a stub icon reaches a public listing, whereas a missing file fails loudly at the one step that can still fix it.
  • README: new TOC-linked Listing media requirements section, splitting what the CLI enforces locally (format + byte cap) from what the platform enforces at attach (aspect + minimum dimension), plus the icon rescale, the "covers/screenshots are not rescaled" fact, and the ~16 MP source that fails with the unhelpful "That icon couldn't be read". "square-ish" / "landscape hero" are gone from every doc surface. The quickstart's step 7 now says the artwork is yours to supply, and ./shot.png became ./assets/screenshot-1.png.
  • AGENTS.md item 25 — these bounds stay prose. Stale guidance costs one round-trip carrying the server's current bound; a stale local gate refuses valid images and cannot be argued with. Please don't promote the table into internal/cmd or internal/validate.
  • Template READMEs get a short Store-listing media section pointing at assets/README.md.

Verification

Executed the quickstart rather than reading it, with the binary built from this branch:

$ civitai app create my-app          # → 36 files, page-money
$ ls my-app/assets/                  # → README.md  (and no images — the point)
$ civitai app listing set-icon ./assets/icon.png
Error: no such file: ./assets/icon.png      ← exit 2, the loud failure we want

Then, after saving a real 512×512 icon as assets/README.md instructs, the same command clears every local check and reaches the network (run against an offline base URL so nothing touched prod):

Error: Get "http://127.0.0.1:1/api/v1/blocks/submissions?blockId=my-app": dial tcp: connection refused

On origin/main (1eb4095) that same command exited 2 with no such file on a fresh scaffold of all three templates — that is the bug, reproduced and then removed. Positive controls on the local gate, which is what the README now claims it does: a non-image gets unrecognized image (want png, jpeg, or webp), and a 2.9 MB icon gets larger than the 2.0 MB max for a icon.

Red/green matrix for the new guards — red at origin/main (1eb4095) with the test files copied into a clean worktree of that ref, green at HEAD:

origin/main HEAD
new + touched tests 12 RUN / 0 PASS / 12 FAIL 12 RUN / 12 PASS / 0 FAIL
full suite 2719 RUN / 2715 PASS / 0 FAIL / 4 SKIP

The 4 skips are pre-existing environment-gated guards (CIVITAI_ANTIPATTERN_SCAN_DIR, CIVITAI_CHECK_SUBMISSIONS_CAP, CIVITAI_CHECK_PUBLISHED_PINS, CIVITAI_CHECK_SCAFFOLD_RUNTIME). No timeout panics.

Mutation-verified, each mutant killed by its own message, not a neighbour's:

mutation failure
plant assets/icon.png in a template template "page-vite" scaffolds an IMAGE at assets/icon.png
README icon cap 2 MiB → 3 MiB README says "…≤ 3 MiB…" for icon, but the CLI enforces 2 MiB
rename assets/README.md.tmpl away has assets/ but no assets/README.md (entries: [NOTES.txt])
drop "server-side" from the README section the Listing media requirements section never says "server-side"
drop "no images, on purpose" from a template README does not explain that the directory ships with no images on purpose

The one direction I did not mutate is changing maxIconBytes itself, because that lives in internal/cmd/app_listing.go, which another lane is editing. The guard derives its expectation from the constant, so both directions diverge by construction, but only the README side is empirically demonstrated.

Other gates: gofmt -s -l . clean, go vet ./... clean, golangci-lint v2.12.2 (the exact CI pin, run from nixpkgs) → 0 issues, and the offline half of scaffold-currency reproduced locally (app init --template page-moneyTestScanDirFromEnvno anti-patterns found). The npm halves of scaffold-currency / template-page-vite / template-page-money are untouched by adding one markdown file.

Two things I found and did not fix

  1. internal/cmd/app_listing.go still says "a square-ish image" in set-icon --help (and "a landscape hero image" in set-cover). Deliberately left alone — another lane holds that file for the attach-order reorder. Follow-up owed once it merges: replace both Short strings with what the CLI actually enforces plus a pointer to the README section, so --help stops being the last surface repeating the phrase this PR removed everywhere else.
  2. humanBytes labels MiB values as MB — the CLI prints 2.0 MB for 2*1024*1024. The README's "2 MiB" is the correct label. Cosmetic, and the fix belongs in download.go, which is shared with the download UI, so it is not smuggled in here.

Also worth knowing: assets/ is not excluded from civitai app submit, so whatever an author leaves there ships in the source bundle (measured: 37 files with an icon and cover present). Left that way on purpose — a static app may legitimately serve images from assets/ — and stated in assets/README.md rather than silently changing packaging.

Refs #270

🤖 Generated with Claude Code

ZacxDev added 2 commits August 7, 2026 16:54
…ents, scaffold assets/

The quickstart handed authors `civitai app listing set-icon ./assets/icon.png` —
a path no template creates, so the one copy-pasteable command in that step could
not work (measured: exit 2, `no such file: ./assets/icon.png`, on a fresh
scaffold of all three templates).

- Every template now scaffolds `assets/` with a README of the requirements, and
  deliberately NO placeholder images: a placeholder passes format + byte
  validation and uploads cleanly, which is how a stub icon reaches a public
  listing, whereas a missing file fails loudly at the step that can still fix it.
- README: new "Listing media requirements" section (TOC-linked) splitting what
  the CLI enforces locally (format + byte cap) from what the platform enforces
  server-side at attach (aspect + minimum dimension), with the current bounds
  read from `civitai/civitai`
  `src/server/schema/blocks/app-listing.schema.ts` (identical on main and
  release). Also records that icons are re-encoded/downscaled to <=1024px
  server-side while covers and screenshots are not.
- AGENTS.md item 25: those bounds stay prose. Stale guidance costs one
  round-trip carrying the server's current bound; a stale local gate refuses
  valid images. Also records that the CLI's 2 MiB icon cap and the platform's
  1 MiB icon cap measure DIFFERENT bytes (source file vs the server's re-encoded
  PNG), so the two must not be conflated.

Refs #270
Both surfaces were individually fine and broken together: the README named
./assets/icon.png, no template created assets/, and every test was scoped to one
side. So these guards pin the RELATIONSHIP.

- internal/cmd/listing_media_docs_test.go
  - TestREADMEListingMediaPathsExistInEveryScaffoldedProject: every relative path
    the README hands an author must have its parent directory present in a
    project the CLI actually scaffolds, for EVERY template. It asserts the
    DIRECTORY, never the file — demanding assets/icon.png would be demanding the
    placeholder hazard. Positive control: >= 4 extracted invocations, and a
    failure if every path collapses to the project root.
  - TestREADMEListingByteCapsMatchTheCLIConstants: code<->prose drift guard. The
    aspect/dimension bounds are platform constants the CLI does not vendor, so
    nothing can check them; the BYTE caps are this CLI's own gate, so the README
    quoting a number the binary does not enforce is checkable and is checked.
- internal/scaffold/assets_dir_test.go: every template scaffolds assets/README.md
  and NO image file (extension ledger, so logo.png is caught as well as
  icon.png), plus the per-kind bounds an author sizes artwork from.

Red at origin/main (1eb4095), green at HEAD: 12 RUN / 0 PASS / 12 FAIL there vs
2719 RUN / 2715 PASS / 0 FAIL / 4 SKIP (pre-existing env-gated) here.

Mutation-verified, each killed by its OWN message: planting assets/icon.png ->
"scaffolds an IMAGE at assets/icon.png"; README icon cap 2 -> 3 MiB ->
"README says ... but the CLI enforces 2 MiB"; assets/README.md renamed away ->
"has assets/ but no assets/README.md"; dropping "server-side" from the README
section and "no images, on purpose" from the template README -> their own
framing errors.

Also records in assets/README.md that the directory is not excluded from
`civitai app submit`, so its contents ride along in the source bundle (measured:
37 files with an icon and cover present). Not excluding it is deliberate — a
static app may legitimately serve images from assets/.

Refs #270
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant