Designing Random Carousel Tiles, From Idea to Boolean Flag
TL;DR
- The ILiv carousel held static, cohort-specific content that needed constant admin attention.
- Plan: admins upload a CSV of random tiles, deduplicated, with no cohort ID required.
- Made cohort ID, start date, end date and weekday optional in the schema to accommodate them.
- The “always show random tiles” control lives in the cohort schema, not a URL parameter, so it persists.
- Random content shows automatically when nothing is prescribed; the flag adds it alongside prescribed content.
A full design thread on one feature, from the initial sketch through to the flag that controls it.
The problem
Our carousel tile system is filled with static, cohort-specific content, which means somebody has to keep feeding it. When a cohort finishes its prescribed content, or when scheduling leaves a gap, the carousel has nothing to show.
The goal is a pool of random content that keeps the carousel populated without manual intervention.
The upload path
A new admin endpoint at /admin/content/carousel, permission-secured, accepting a CSV of carousel data which gets validated and uploaded. Crucially, without requiring a cohort ID — that’s what makes the content general-purpose rather than tied to one group.
Deduplication on upload so the pool stays varied. I built the endpoint without this initially; it’s a known gap rather than an oversight, and worth doing before the pool gets large.
Schema changes to make it possible. The tiles live in the existing MongoDB collection, which meant making cohort ID, start date, end date and weekday optional. That’s the change that carries risk — those fields were required for a reason, and relaxing them means existing consumers could receive documents shaped differently than they expect. I added test checks specifically to confirm existing functionality still held.
The return type became a union of iCohortContent and iCarouselContentBase to carry both shapes.
One consequence worth recording: validation happens through JOI at the application layer rather than the database layer. That’s a deliberate choice, and it means anyone changing the application logic can bypass validation without the database catching it. A real if minor exposure.
When should random content appear?
The design question I spent the most time on. Should “always show random tiles” be a transient URL parameter set by admins during upload, or a persistent property of the cohort?
I went with the cohort schema. A URL parameter describes one upload; a schema field describes how the cohort behaves. Since the behaviour should hold whenever the carousel renders — not just after an upload — it belongs to the cohort.
The behaviour splits into two cases, which took a while to articulate cleanly:
- No prescribed content available — whether because the cohort finished it or because scheduling left a gap — random content appears automatically. No flag needed. An empty carousel is never the right answer.
- Prescribed content exists — the boolean decides whether random tiles appear alongside it, in front of the prescribed content.
That split is what makes the flag meaningful. It doesn’t control whether random content can appear; it controls whether it competes with content someone deliberately scheduled.
Rotating selection handles which tiles show: for each day of a cohort’s cycle, a revolving mechanism picks tiles so nothing repeats until the pool is exhausted. Clients fetch and cache the result, keeping server interaction light.
Implementation
Adding the flag touched the update and creation validation schemas and the ICohort interface, where it’s optional and defaults to false — so existing cohorts behave exactly as they did.
I left the cohort posting logic alone. It posts content without needing awareness of other cohort operations, and that independence is worth preserving.
Naming got more debate than it probably deserved. random tiles beat random content because tiles are what an admin actually sees in the interface, and the interface is where the setting gets used.
Where it stopped
Integration tests failed, and not on anything I’d changed — the failures trace to rate limiting. That’s tomorrow’s problem and it’s unrelated to this feature, which is at least a clean place to pause.
The carousel design itself is done and coherent: content that’s always fresh, admin workload substantially reduced, and existing behaviour untouched unless someone opts in.
ryer.io