Architecture
WallCab separates the public website and renderer from a deliberately narrow cache service. The Next.js application remains useful when the cache or every upstream provider is unavailable.
Apple Shortcut / browser
|
v
Next.js wallpaper route
| |
cache lookup daily resolver
| |
| active-day KV lesson
| | |
| provider fallback
| |
+--------> Sharp renderer
|
PNG response now
|
shared-image KV upload
Deterministic daily content
The normalized interest set and UTC date select one category for the day. The order is random-looking but deterministic, and complete cycles give every selected interest a turn. Vocabulary uses rotating advanced semantic prompts plus Datamuse frequency and syllable metadata to discover an uncommon term; Free Dictionary then enriches its definition and pronunciation. The other categories rotate through advanced, category-specific Wikimedia searches before selecting a concept and page summary. Themes and device sizes never alter the day’s lesson.
Every provider candidate passes structural and editorial validation independently. One malformed search result cannot reject an otherwise usable provider response. WallCab rejects excessive text, unsafe titles, missing source links, overly common vocabulary, absent pronunciation, duplicates, and content that cannot fit the composition. The reviewed advanced fallback record for that date and category is used as a complete unit when validation fails.
The accepted lesson and its provenance are stored in Workers KV until the next UTC rollover. This keeps provider traffic bounded to one resolution per category per day and makes external versus fallback observable through response headers, the preview, and the status APIs.
Background pipeline
The five photography themes use Openverse searches restricted to CC0 and public-domain images with mature content disabled. The server validates the returned source host, redirect destination, content type, byte length, image dimensions, and attribution. Those photography backgrounds change at the UTC rollover.
Six WallCab Original themes use bundled SVG artwork: AMOLED, Minimal, Abstract, Gradient, Black & White, and Grid. Their artwork remains fixed while the lesson changes each day. When no eligible remote photograph is available, the selected photography theme uses a bundled procedural SVG fallback. This keeps rendering functional without making an unverifiable license claim.
An optional custom image replaces only the background layer. The browser first resizes it to stay below the Vercel request limit. After Turnstile verification, the server validates the decoded format and dimensions, removes metadata, applies orientation, and creates a 1320×2868 WebP. Raw custom images live in a private Workers KV binding; there is no public object URL. If the opaque ID is missing or expired, rendering continues with the URL's selected built-in theme.
Image composition
The Node.js renderer uses Sharp with an SVG overlay:
- the clock and Dynamic Island zone remains quiet;
- the lesson occupies the middle and lower fields;
- vocabulary terms include a bounded IPA pronunciation line;
- titles, definitions, quotations, and optional personal notes have separate hard line and block bounds;
- a contrast gradient protects readability;
- lesson and image credits are always visible;
- adaptive indexed-palette compression targets 2.2 MiB.
The renderer version is part of the cache key. A composition change can therefore roll forward without colliding with an earlier file.
Cache boundary
The Cloudflare Worker exposes a signed public GET and HEAD endpoint. Private lookup and upload routes require a timestamped HMAC signature covering method, path, and body hash. KV records expire at the next UTC rollover.
On a hit, the Next.js route returns a temporary 307 to the signed Worker URL. On a miss, Next.js sends the newly rendered image immediately and schedules the cache upload after the response. If the Worker is unavailable, image generation still succeeds. Active-day lesson and background records reduce repeated upstream requests even when a complete rollover manifest is unavailable.
Personalized requests are intentionally different. The optional note is validated to 80 characters and changes only the SVG text layer. Its raw value never appears in an internal cache key, and the completed personalized PNG bypasses the shared Worker image cache. Daily lesson and background inputs can still use their normal active-day caches.
Custom-object routes share the same timestamped HMAC boundary and expose private KV data only to the renderer. Separate KV metadata records creation time, a hash of the deletion token, and a daily-bounded last-use timestamp. A Worker cron removes items after 30 inactive days and cleans old orphaned keys. User deletion removes the source object, metadata, and matching active-day PNG variants.
Anonymous run analytics
Only valid image-body requests from the public wallpaper route produce usage events. The lightweight website preview, source-status route, HEAD requests, invalid input, rate-limited traffic, cron work, and cache probes are excluded. The Next.js route schedules the event after its response and sends it through the existing timestamped HMAC boundary to a write-only Analytics Engine binding.
Each event is a standalone run record: timestamp, random per-request ID, success or failure, cache delivery mode, external or fallback content mode, resolved category, theme, device preset, and response status. The strict Worker schema rejects extra fields. IP addresses, user agents, complete URLs, selected-interest lists, notes, custom-background IDs, and persistent client identifiers never enter the analytics dataset. Read credentials belong only to a separate private administration deployment and are not present in this repository or the public runtime.
Optional support email
The support button is rendered only when SUPPORT_URL is a validated live
Razorpay Payment Page. Payment UI, mandatory contact collection, receipts, and
settlement remain on Razorpay.
After capture, Razorpay sends a signed webhook to a Node Route Handler. The
handler bounds the raw body before reading it, verifies the HMAC before JSON
parsing, and accepts only payment.captured with a valid Razorpay account
envelope, domestic INR, and a fully captured payment. It passes the validated
optional name, email, amount, and payment ID directly to Resend. The payment
ID becomes the idempotency key, so a Razorpay retry cannot create a second
thank-you within the provider retry window. No supporter database or
application log is created; a provider failure returns 503 so Razorpay can
retry.
Trust boundaries
Provider data, uploads, personal notes, and webhook bodies are untrusted input. Remote image fetches use an explicit hostname allowlist, bounded redirects, media and size validation, timeouts, and Sharp decoding. Enumerated query values are validated before they reach the renderer; notes are bounded, control characters are rejected, and text is escaped before SVG composition. Turnstile tokens are verified server-side against the exact action and hostname. Razorpay webhook signatures use a private HMAC secret and the untouched bounded body. The Shortcut URL contains an unguessable custom ID and, when chosen, the personal note; the deletion token remains in browser storage and the fragment of a private deletion link. Users are warned not to put sensitive information in a URL-visible note.