Skip to content

Let new albums publish without a production build #280

Description

@McCal-Codes

Adding an album costs a full production build today, and it does not need to. The photos are already fetched at runtime; only the manifest is baked into the deployment. Journalism already publishes with no build at all. Five galleries do not, and the reason is one line.

What happens now

  1. Photos go to src/images/Portfolios/, push to main.
  2. Vercel skips that build. From the logs: "The deployment was canceled because the Ignored Build Step command returned exit code 0." Correct, since nothing under sites/mcc-cal-vite/ changed.
  3. seo-auto-update.yml fires on src/images/Portfolios/**, regenerates the seven manifests and commits them into sites/mcc-cal-vite/.
  4. That commit is inside the root directory, so it triggers a full production build.

So it works. It just costs a build and a second commit every time, and if step 3 ever fails the photos sit in the repo with no manifest and nothing says so.

What already exists

  • scripts/cloudflare/add-shoot.js accepts journalism | concert | portrait | events | nature, converts to WebP q82, reads IPTC captions and keywords, uploads to R2 and writes portfolio_images. Resumable.
  • R2 is live. images.mcc-cal.com returns 200 for a journalism WebP at 213 KB.
  • portfolio_images holds 422 images across 18 collections.
  • RLS already allows anonymous read of every portfolio type (public_read, qual = true). No policy change needed.
  • VITE_R2_PUBLIC_URL, VITE_SUPABASE_URL and the anon key have been set in production for 72 days.
  • The runtime merge has a 5 second timeout and falls back to the static manifest, so an outage degrades to today's behaviour rather than an empty gallery.

The one line

src/components/portfolio/useManifest.ts:144

if (type.toLowerCase() === 'journalism') {

add-shoot.js will happily write rows for the other four types. The site never reads them back. The same line means the photojournalism alias misses the merge too, since that string is not journalism.

Three ways a naive change breaks quietly

Row cap truncation. The events gallery has 1,635 images. The current query is .select('*') with no pagination, and PostgREST's default max rows is 1,000. That returns 1,000 rows with no error and silently drops the rest. The setting is PostgREST config, not a database role setting, so it cannot be read from SQL and has to be checked under Settings, API, Max rows. Paginate with .range() regardless, or better, do not fetch every image row just to draw a grid: one row per collection with a cover and a count is a far smaller query, and the full list can load when a collection is opened.

Shape divergence. It is not two shapes, it is five.

Gallery Key Name field Image entry
journalism events eventName object: filename, path, description, caption, tags
events events eventName object: path only
concert bands bandName plain string
portrait collections collectionName plain string, plus nested albums and looseImages
nature collections collectionName plain string

Supabase always produces objects carrying a url. Concert, portrait and nature treat images as bare filenames today, so each has to accept both forms.

URL construction. imageUrl builds a jsDelivr path per gallery from folderPath plus filename. The Supabase source sets folderPath to the collection display name, not a real path, so any adapter that ignores a pre-built url will build src/images/Portfolios/Nature/Steel Strike 2026/foo.webp and 404. Journalism already solved this at JournalismPortfolio.tsx:50 with img.url ?? imageUrl.journalism(...). The other four adapters need the same.

And the rollups

featured-manifest.json and portfolio-manifest.json are aggregates built from the others at build time. An album that exists only in Supabase will not appear in either, so the featured and universal views would still silently omit new work after the per-gallery fix. Either compute those at runtime too or accept it as a documented gap.

Sequencing

Smallest surface first, so the generalisation is proven before it touches anything big.

  1. Nature. 7 collections, 61 images, simplest shape, nowhere near the row cap.
  2. Portraits and concert. Same string-image pattern. Portraits also has the nested albums and looseImages to handle.
  3. Events last. It is the one that trips the row cap and needs pagination.
  4. Rollups as a separate decision.

Alongside: narrow .select('*') to the columns actually used, and add a guard that fails if any gallery's row count comes back at the cap, since that is the failure with no symptom.

Verification

Per the usual rule, prove the failure fires before trusting the fix. Seed a collection past the row cap and confirm truncation is detected rather than silently rendered. Point a Supabase collection at a bad storage_path and confirm the fallback shows the static album rather than a broken grid. Confirm a collection present in both sources renders once, not twice.

Payoff beyond the build saving

The Supabase rows point at optimised WebP in R2. The static path points at the original JPEGs on jsDelivr, including the 25.5 MB file that 403s in #261. Finishing this routes the galleries at optimised images, advances the jsDelivr exit in #266, and lets Lightroom captions become real alt_text, which a manifest generated from filenames cannot do.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions