The Scheduler's hourly alarm now runs two jobs in order, updaters then
posters, matching the container; a failure in either schedules the
retry. POST /post runs the posters by hand and wake?job=posters through
the alarm. The posters read process.env, which Workers populate from the
bindings: SITE_URL and CLOUDFLARE_ACCOUNT_ID are vars, the Browser
Rendering token and the social credentials are secrets. Without social
credentials the posters only render and save the public images, which is
the shadow mode until cutover.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The screenshot module posts the deployed screenshot page's URL and the
viewport to Browser Rendering's REST endpoint and gets the image back, so
the same code runs under Node and in a Worker. It waits for network idle
as the puppeteer version did. Puppeteer, the local HTTP server, and the
browserless configuration are gone.
Posts render once per media type a client needs: PNG for the public copy
and Twitter, JPEG for Bluesky, which limits image size. That replaces the
sharp conversion in the Bluesky client, which now takes the media as
given and includes the aspect ratio when the size is known (from the
viewport for screenshots, from the PNG header for stage images).
Configuration mirrors splatoon3.ink: SITE_URL, CLOUDFLARE_ACCOUNT_ID, and
CLOUDFLARE_BROWSER_RUN_API_TOKEN.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The post generators read site data through publicStorage and their state
(last post times per client, the previous Salmon Run shift, known stages)
through privateStorage, the same two storages the updaters use, so they
run unchanged under Node and in a Worker. The public copy of each image
goes to twitter-images/ in public storage and test screenshots to
test-screenshots/. Data access is async; the base class keeps its
structure. Posts are created per run with the storage and the social
clients they post through.
Tests cover the base class (posting once per data time per client,
skipping clients that already posted, a failing client not blocking the
other, image-only mode) and the schedule, Salmon Run, new stage, and
Splatfest posts.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Updater.update() now returns what happened: per-phase timings (fetch,
localize, process, publish, images), which languages had to be fetched,
which locale documents changed, and how many images were downloaded.
Subclasses add their own details (stages seeded and new stages, festival
rankings fetched). updateAll() carries each summary in its results, so the
Worker's "Updaters finished" log entry contains the whole picture per
updater. FilesystemStorage.writeJson now skips unchanged content like
BucketStorage does, so "locale documents written" means the same locally.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
GET /list?prefix= returns the keys under a prefix in the public bucket.
Wrangler can fetch, put, and delete single objects but not list them,
and this is the only way to inspect the shadow bucket from a terminal.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
One skill image is no longer served by SplatNet's CDN. The updater's
image step falls back to a bundled copy: under Node, cdn.node.js reads
the PNG from the repo; in the Worker, cdn.worker.js imports it as bytes
through a wrangler "Data" module rule. A "#cdn-images" import condition
in package.json selects the right one, so the PNG in src/common/cdn stays
the only artifact and nothing is generated or copied by hand.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
workers/updater runs src/app/updater, the same code npm run splatnet runs
locally, against the Worker's R2 bindings through BucketStorage. The
Scheduler Durable Object from the proof of concept owns when the run
happens: an alarm at :00:10 that re-arms itself, with retries after a
failed run; the only cron trigger is a minute-30 watchdog that re-arms a
lost alarm. Operator endpoints (/run, /wake, /arm, /status) are behind a
bearer token. Handlers and the Durable Object are wrapped with
@sentry/cloudflare so the updaters' @sentry/core reports are delivered
when SENTRY_DSN is set.
updateAll() now returns a per-updater result (name, ok, duration, error)
that both the local runner and the Worker can report on, and the SplatNet
client reads its user agent lazily since Workers populate process.env
from the bindings.
Deployed in shadow mode against the dev bucket: a full run of all eight
updaters on Cloudflare matched production's data semantically within the
same hour, and steady-state runs take about seven seconds.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>