-
-
A photo of the whiteboard becomes the workout and a giant class timer on the gym's own TV, through a Fire TV Stick.
-
Amazon Nova Pro on Bedrock reads the whiteboard photo into a structured workout and timer; the coach reviews it before it goes live.
-
Run the class from the phone, the Fire TV remote's Play/Pause or 'Alexa, pause'. Every TV follows within a fraction of a second.
-
WiFi down: the timer keeps running from the TV's own clock. Power cut: the stick boots straight back into the class screen.
-
Luis, coach at a functional-fitness box in Spain, on camera: a one-month trial, then 15.90 euros a month.
-
The class screen: today's schedule with the class running now and the next one, readable from the back of the room.
-
Filmed on a real Fire TV Stick (3rd gen, Fire OS 7) on a real TV.
-
For Time with the workout beside the clock: huge digits, phase colours and a 3-2-1 lead-in on every screen.
-
Copilot: the coach's day in one message becomes a plan for every TV, by three Nova agents on Amazon Bedrock AgentCore. Coach approves.
-
How it's built now: Fire TV, phone and web console via CloudFront; ECS API; copilot on Bedrock AgentCore; Nova; RDS, S3. One CDK stack.
-
The copilot in the coach app: type or dictate the day, check every step, one tap updates every TV. Already up? It says so.
-
After class, Nova reads the scores board and ScreenDesk ranks it (RX first): today's leaderboard on every TV. Songs on request by QR.
-
This week's board: most workouts scored in 7 days, then podiums. The member who never misses a class tops it. Built in SQL, no AI.
-
The TV markets the gym: results become a story to post, a free-class QR brings in leads, and Nova drafts the coach's welcome.
At a glance
- The problem: in small gyms the coach rewrites the whiteboard every morning (about ten minutes, every day), it can't be read from the back of the room, and the class clock is a small LED box on the wall.
- ScreenDesk: a Fire TV Stick on the TV the gym already owns becomes the class screen. It shows the schedule with the class running now, promos, today's workout read from a photo of the whiteboard by Amazon Nova, and a giant class timer in sync on every screen. After class, Nova reads the scores board and the TVs show today's leaderboard; members ask for songs from a QR on the screen, as the coach we interviewed asked for.
- Fire TV as a device, not just a player: one multi-modal flow in which voice, the D-pad and the coach's phone drive the same screen. The phone's camera feeds Amazon Nova, the remote's Play/Pause pauses the class on every TV, Left/Right page the slides, Menu shows the screen's status, and "Alexa, pause" / "Alexa, next" arrive through a MediaSession. The screen never sleeps, keeps running offline and reopens itself after a power cut. Kotlin + Compose for TV, built and tested on a real Fire TV Stick.
- AWS: a copilot of three Strands agents on Amazon Bedrock AgentCore Runtime turns the coach's day, typed or dictated in one message, into a plan for every screen: a planner on Amazon Nova Pro orchestrates a programming coach and a promo writer on Nova 2 Lite, and nothing reaches a TV until the coach applies it. Nova Pro also reads the whiteboard photo. The whole stack (AgentCore, ECS on Fargate, CloudFront, RDS, S3) lives in one AWS CDK app, live in us-east-1.
- Validated: a coach at a real box in Spain asked for a one-month trial and named his price on camera: €15.90 a month.
- Open source (mini challenge): the voice + remote layer is now a standalone Apache-2.0 library, firetv-voice-controls (on JitPack, with 6 tests run on a real Fire TV Stick), and an agent skill contributed to Amazon's devices-agent-skills repository.
- Try it: the v1.13.1 APKs talk to the live backend (the copilot opens first in the coach app, with voice dictation), and the coach side also runs in any browser at https://d3n061nhp7r00e.cloudfront.net/media/coach.html (no Android phone needed); see Testing instructions. The friction log has 21 entries, and the product feedback covers every tool we used.
Inspiration
I train at a small neighbourhood box. On the wall there's a TV that is either off or showing a spreadsheet someone screenshotted, a whiteboard you can't read from the rowers, and an interval timer only the coach knows how to program. By day I'm a software engineer building safety-critical, real-time systems, where the first design rule is that a lost link must never mean a blank screen: every node keeps operating on its last known state and resynchronises the moment the link comes back.
ScreenDesk is that idea moved from the control room to the gym floor. I applied the offline-resilience architecture of a mission-critical system to a $40 screen in a neighbourhood gym, because in the middle of a WOD the system can't fail just because the WiFi drops. Each Fire TV caches what it should show, runs the class timer from its own corrected clock instead of waiting for network ticks, and quietly catches up when the router comes back. The coach never notices, and the athletes never stop.
Then I took it to the people it is for. Luis, the coach at my box, told us the worst part of his day is writing the board every morning, about ten minutes, for a text so small that "the guy with glasses comes over and can't read a thing". After seeing ScreenDesk, he asked for a one-month trial and named his price: €15.90 a month.
What it does
- Pair a TV in seconds. Install ScreenDesk on a Fire TV: it shows a 6-digit code and a QR. The coach scans it with the ScreenDesk Coach app and the screen belongs to the gym.
- A living class screen. Today's schedule highlights the class running NOW and the NEXT one, from the TV's own clock. It rotates with promos, a booking QR and today's workout. Everything is designed to be read from 3 metres away on a 55" TV, inside the overscan-safe area.
- Whiteboard → WOD with Amazon Nova. The coach snaps the whiteboard. Amazon Nova Pro on Amazon Bedrock turns it into a structured workout (format, time cap, movements with reps and loads), and ScreenDesk picks the matching timer. The coach reviews the draft and taps Show on screens + start timer.
- Plan the whole day in one message (Copilot). Typed or dictated on the phone: "18:00 Fran cap 10, 19:00 Olympic Lifting is cancelled, 2x1 smoothies until Sunday, take down the nutrition challenge": three AI agents prepare the workout with its timer, the promo, the schedule change and the slide to take down. The coach sees exactly what will change (the schedule as a diff), unticks anything, and applies it to every TV at once.
- Today's results, from the scores board. After class the coach snaps the board where members wrote their scores. Amazon Nova transcribes every name, score and division exactly as written; ScreenDesk ranks them in code, never in the model (times up with capped athletes after every finisher, rounds + reps and loads down, RX first), and every TV shows the leaderboard with medals for the podium. The photo is not kept. One more tap puts This week's board on the screens: who scored the most workouts in the last 7 days, then podiums, so the member who never wins a WOD but never misses one gets to see their name at the top.
- Members ask for the music (what Luis asked for). A QR on the screens opens a one-field page on any phone: no app, no account. The request lands in the coach's queue; one tap opens it in Spotify (a plain search link, no account linking) and On now puts "♪ Eye of the Tiger · asked by Ana" in the header of every TV. Nothing a member types reaches a screen until the coach puts it on, and the public endpoint is rate-limited per phone and per gym.
- The TV markets the gym. Today's leaderboard becomes a ready-to-post Instagram/TikTok story with a caption Nova writes in the coach's language (it may only quote scores that are on the board), so members' own results bring in their friends. And a "Try a free class" QR turns visitors into leads: they leave a name and a phone or email (with consent, deleted after 90 days), and Nova drafts the coach's welcome message in their language with real classes from today's schedule, ready to send from the coach's own WhatsApp.
- Write a promo from one sentence. "2x1 on monthly passes until Friday" becomes a headline, subline, call to action and a colour palette that reads well on a dark screen.
- A giant class timer, controlled from the phone. AMRAP, EMOM, Tabata and For Time, with a 3-2-1 lead-in. Huge digits, red for work and green for rest, round X/Y, and the numbers pulse in the last three seconds of every interval. It counts in out loud too: 3-2-1 beeps before every change, a tone to go, a lower one to rest and a buzzer at the end, on the same millisecond on every TV. The coach switches the sounds for the whole gym from the phone (Up/Down on a TV's remote mutes just that screen). Start, pause, resume and stop from the phone, or press Play/Pause on the Fire TV remote to pause the class mid-set; every TV in the gym follows within a fraction of a second.
- Coach from any browser. The same controls also run as a web console, with nothing to install: pair a TV by its code, start a timer preset, turn a whiteboard photo into a workout, write a promo. It suits the front-desk computer, or a coach with an iPhone.
- The remote runs the screen too. Between classes, Left/Right page the slides (a slide picked by hand gets a full turn on screen), and Menu shows which screen this is, whether it's online and what the remote can do. Pressing Home on one TV never pauses the class on the others (Fire OS sends a pause on Home; we tell it apart from a person's command, see friction log #20).
- Hands full of chalk? Use your voice. "Alexa, pause", "Alexa, resume" and "Alexa, stop" reach the class timer (and "Alexa, next" pages the slides) through a MediaSession, Fire TV's documented path for voice transport controls. A voice command says what it wants, so "pause" never resumes a class that is already paused.
- Speaks the gym's language. A Fire TV set to Spanish shows the class screen in Spanish (AHORA/PRÓXIMA, TRABAJO/DESCANSO, RONDA 3/8), and the song request page follows each member's phone. Nova already keeps the whiteboard's own language, so a box in Spain gets a Spanish screen end to end.
- Never a black screen. If the gym's WiFi drops, the TV keeps rotating the last content it received and the timer keeps running. It reconnects on its own within seconds of the network coming back. A screen removed from the app goes back to its pairing code.
- Back by itself after a power cut. Gym TVs are often switched off at the wall overnight. A paired stick reopens the class screen once Fire OS has booted, so nobody has to find the remote before the first class.
Validation with a real box
We showed ScreenDesk to Luis and Pablo, coaches at a functional-fitness box in Spain, and filmed the conversation at their whiteboard (it opens the demo video):
- The pain is real and daily. Writing the board takes "about ten minutes, every single day". It's unreadable from the back of the room, and the class clock is a small LED display on the wall.
- They would use it. "Put a screen right here that shows the time clearly, with the WOD coming up on it."
- They would pay. A one-month free trial, "and once everyone's used to it, you charge me €15.90 a month".
- What they asked for next: motivation videos between classes, a QR code so members can request songs, and promos for the drinks sold at the front desk. The song QR is now in ScreenDesk (1.10), built the way they described it: members scan, the coach decides what plays.
The same visit also exposed our weakest point: Nova 2 Lite couldn't read their real hand-written board, so whiteboards now go to Nova Pro (see Challenges). We would rather find that out in a box than in front of a customer.
How we built it
- Fire TV app: Kotlin + Jetpack Compose for TV on Fire OS. It needs no Google Play Services: QR codes are rendered with ZXing and the type is the system Roboto Condensed. The app caches its identity and playlist in SQLite (SQLDelight), so it boots straight into content even offline.
- Coach app: Kotlin + Compose Multiplatform (Android today). It covers camera and gallery capture, downscaling photos to Bedrock's limits, and the timer remote.
- Web coach console: one HTML file with no dependencies, served by CloudFront from the media bucket next to the API, so it calls the same REST endpoints from the same origin. It downscales photos in the browser before uploading them.
- Shared core: a Kotlin Multiplatform module used by the TV, the phone and the backend: wire contract, the timer engine and the clock sync. The timer engine is a pure function from "server state + server time" to what's on screen. Each TV estimates its offset from the server clock from ping messages, so every screen in the room shows the same second, and nothing ticks over the network. 20 unit tests pin every boundary of the four modes, pauses in every phase, clock skew and an invariant sweep.
- Backend: Spring Boot 3.5 (Kotlin) with REST plus a WebSocket hub. JWTs come in two scopes: coach, and a revocable device token. PostgreSQL with Flyway. Integration tests run against a real Postgres (Testcontainers) over real HTTP and WebSockets.
- AI: Amazon Bedrock Converse API: Amazon Nova Pro reads whiteboards, Amazon Nova 2 Lite writes promos, each selected per feature by configuration. Neither has a structured-output mode, so each feature declares one tool whose input schema is the JSON we want and forces the model to call it. We validate the result and retry once, telling the model exactly what was wrong. Every result is a draft the coach approves. On the results board the model only transcribes: the ranking (times, caps, rounds + reps, loads, divisions) is a pure, unit-tested function in the shared module, because a leaderboard that puts the wrong athlete first in front of the whole gym is worse than none.
- Copilot agents: three Strands agents (Python) in one ARM64 container on Amazon Bedrock AgentCore Runtime, one session per gym per day. The planner (Nova Pro) can only propose: each of its tools adds a step to a plan, and the two specialists (Nova 2 Lite) are tools too. The backend validates every step with the same rules as the rest of the app twice, when it is proposed and when it is applied in one transaction. Live runs found three ways the agents went wrong (a renamed class, a misremembered benchmark WOD, an EMOM with no length); each now has a rule or a deterministic check, and a test.
- AWS: one AWS CDK stack in Kotlin.
- ECS on Fargate behind an ALB that only accepts CloudFront's origin-facing IPs, with CloudFront for HTTPS/WSS without buying a domain.
- RDS PostgreSQL (encrypted), and S3 for photos (private, Origin Access Control).
- Secrets Manager for the DB, JWT and demo credentials; CloudWatch alarms; a Bedrock policy scoped to exactly the two Nova models we call.
- Amazon Bedrock AgentCore Runtime for the copilot (the container is built for ARM64 and pushed to ECR by
cdk deploy). Its role can only be assumed by AgentCore in this account and can call the same two Nova models and nothing else; the API may invoke only that runtime. - The stack itself has unit tests, and
scripts/deploy.ps1deploys it and builds release APKs in one command.
Challenges we ran into
- One clock for every screen. Ticking a timer over the network makes TVs drift and freeze on a WiFi blip. Making the server the owner of state and each TV the owner of time (corrected by a measured offset) solved both.
- Forcing JSON out of a model without structured outputs: see the friction log, entries #3 and #5.
- Calling Nova 2 Lite from us-east-1. It's only served through the US geo inference profile, and least-privilege IAM for that needs non-obvious resources (friction log #2 and #4).
- Real handwriting. Synthetic boards come back perfect. The real board of the box (four blocks, hurried marker, dim light) made Nova 2 Lite return "Unreadable", or worse, invent a plausible movement. A multi-block prompt now recovers the structure (METCON, 12' cap, 5 rounds). Nova Pro read most of the WOD at ~3× the tokens, so whiteboards now go to Nova Pro (≈ $0.003 per board). The coach's review step still keeps a wrong draft off the TVs (friction log #15).
- Agents that do too much. The copilot's first live run renamed the 18:00 class to "Fran" and deleted a cancelled class instead of showing it; the programming agent wrote Fran with push-ups; "EMOM 12" came back with no length, so no timer. The fix was not a longer prompt: the planner can only propose, benchmark WODs come from a definition table, the numbers that drive the timer are checked against the coach's own words in code, and every step passes the app's validation twice. Planned a second time on the demo gym, it added Fran again and took down the wrong promo; now what is already up is never added twice, and a slide only comes down when the coach's own words name it.
- Making a TV un-killable. A pre-submission audit found that one over-long link could be cached and crash the QR renderer on every boot. Content is now bounded on the server, the renderer can't throw, and a supervisor restarts the TV's lifecycle on any unexpected error.
Accomplishments that we're proud of
- The offline story: pull the plug on the router mid-class and the timer keeps counting and the slides keep rotating; plug it back and the TV catches up within seconds.
- Tested on a real Fire TV Stick (3rd gen, Fire OS 7.7.1.6, 32-bit ARM). Device profiling with
dumpsys gfxinfocut janky frames in slide transitions from 38 % to 17 % (p99 from 250 ms to 61 ms). - Voice and power-cut recovery checked on the same stick: its MediaSession pauses, resumes and stops the class end to end (pause twice stays paused), and a reboot brings the class screen back with nobody touching the remote.
- A coach who would pay for it, on camera, at his own whiteboard.
- The test suite: shared core, backend (real Postgres + WebSockets) and infrastructure-as-code all tested, including tests written to prove specific bugs we found are gone.
- A design that reads from the back of the box: giant tabular digits, phase colours, condensed headlines, live NOW/NEXT.
What we learned
- With a TV on a wall, robustness is the user experience. Nobody will ever press "retry" on it.
- Models with forced tool use plus strict validation plus a single, specific retry are far more dependable than prompting for JSON.
- AWS CDK assertions make infrastructure reviewable like any other code.
- An agent is safest when its tools can only propose: the coach approves, and the server checks everything again.
What's next
- Reading real hand-written boards even better: a set of real phone photos from partner boxes as a regression suite for the whiteboard model.
- The rest of what Luis asked for: motivation videos between classes and front-desk promos.
- Member check-in, and personal records (PRs) built from each day's results.
- Multiple screens per gym showing different content (lobby vs. training floor).
- Publishing on the Amazon Appstore for Fire TV (the submission kit is ready: listing, images, privacy page and a self-check against the Fire TV test criteria, in
docs/APPSTORE.md), then a pilot with local boxes.
Business model
SaaS per gym, sold to independent gyms, functional-fitness boxes and studios. The hardware is a Fire TV stick on the TV they already own.
Who it's for. About 9,900 CrossFit affiliates worldwide, roughly 5,000 of them in the US (Fitness Avenue, 2026), and 64,550 health and fitness clubs in Europe (EuropeActive, 2025). The independent boxes and studios among them are the first market. Gym programming suites already put the workout on a screen (SugarWOD's BoxTV, for example), but only after it has been typed into their software (SugarWOD). ScreenDesk starts from the board the coach already writes, needs no programming platform, and runs on a stick the gym can buy anywhere.
- Free: 1 screen, schedule and timer.
- Pro, €15.90/gym/month after a one-month free trial (the price the first coach we interviewed named): AI whiteboard, results and promos, the copilot, song requests, unlimited slides, multiple screens, timer remote.
- Multi-location: shared content and scheduling across sites.
Infrastructure is mostly fixed cost: the whole AWS stack is ≈ $65/month (estimate in docs/AWS_INTEGRATION.md) whatever the number of screens. A TV costs one idle WebSocket. Unit economics are therefore driven by Bedrock calls, which only happen when a coach asks for a draft. Measured: about $0.003 per whiteboard with Nova Pro and $0.0006 per promo with Nova 2 Lite, and about $0.01 per copilot plan, so even a gym photographing every class and planning every day spends well under a euro a month on AI.
Testing instructions (for judges)
- Repository: https://github.com/tapaderuza/screendesk
- Fire TV app: turn on Settings → My Fire TV → Developer options → ADB debugging, then sideload
tvApp-release.apkfrom https://github.com/tapaderuza/screendesk/releases/tag/v1.13.1 withadb connect <fire-tv-ip>:5555andadb install tvApp-release.apk, and open ScreenDesk from Apps. - Coach app (Android phone): install
adminApp-release.apkfrom the same release. It opens on Create your gym: tap I already have an account and log in with the demo gym:- email
[email protected]; the password is in the notes of the v1.0.0 release in the private repository (shared with the judges) - backend (already built into the APKs): https://d3n061nhp7r00e.cloudfront.net (health: https://d3n061nhp7r00e.cloudfront.net/actuator/health)
- the demo gym is shared by every judge, and goes back to its starting slides (with yesterday's leaderboard and two song requests waiting) every day at 06:00 UTC (your paired TV stays paired). For a private sandbox, fill in Create your gym → Create account instead (your gym starts empty).
- email
- No Android phone? Web coach console: open https://d3n061nhp7r00e.cloudfront.net/media/coach.html on any phone or computer and sign in with the same demo gym (or create your own). It pairs a TV from its QR (scan it with the phone's camera: the console opens with the code filled in) or its 6-digit code, and covers steps 2, 4, 5, 8, 9 and 10 below: timer presets with pause/resume/stop, whiteboard photo → workout, promo from one sentence, the copilot, song requests and today's results.
- Try it:
- Pair the TV: scan the QR on its screen with any phone camera (the web console opens with the code filled in: sign in, Pair TV), or in the coach app Screens → Scan the QR (or type the 6-digit code).
- Timer → Tabata → Start on screens. Watch and listen: the 3-2-1, the work/rest colours, and a beep before every switch. Timer sounds on the TVs (coach app or console) turns them off for every screen at once; Up/Down on the remote mutes just this TV. Press Play/Pause (or the centre button) on the Fire TV remote: the class pauses on every screen; press it again to resume. Or hold the remote's microphone button and say "Alexa, pause", then "Alexa, resume".
- Between classes, press Left/Right on the remote to page the slides, and Menu to see which screen this is and whether it's online. "Alexa, next" pages them too.
- Slides → Whiteboard → From gallery with a clear, well-lit photo of one workout block, or, with no whiteboard at hand, Try a sample: For Time (or EMOM, in Spanish), then Show on screens + start timer. The draft is always reviewed before it reaches the TV.
- Slides → Promo → "Write it for me" with a one-line offer.
- Turn off the Fire TV's WiFi: content and timer keep going. Turn it back on: it reconnects by itself.
- Unplug the stick and plug it back in: once Fire OS has booted and run its own start-up apps (one to three minutes), ScreenDesk reopens on the class screen by itself. (Remove the screen in the coach app, or uninstall, to stop it opening on boot.)
- Copilot. The coach app opens on Copilot ✨: tap Use the example → Plan it, or 🎤 Dictate and say the day in your own words. (Web console: Copilot · plan the day → Use the example → Plan it.) In about 10 seconds the agents propose a schedule change, a slide to take down, a workout with its timer and a promo, with each agent's model and time underneath. Untick anything, then Apply: every TV updates at once. The demo gym is shared: if another judge already applied the example, the copilot says there is nothing to change (and why) and offers Try another day.
- Songs 🎵 → Put the request QR on the screens. Scan the QR on the TV with any phone, type a song and send it. It appears in the coach's queue (two requests are already waiting): Spotify opens it, On now shows it in the header of every TV for five minutes. (Web console: Song requests.)
- Slides → Results 🏆 → Try a sample → Show on screens. Nova reads a hand-written scores board (RX and Scaled, two capped athletes) and the TVs show the ranked leaderboard. Or snap your own board. (Web console: Scores board → today's results.)
- Slides → Results 🏆 → Share today's leaderboard 📸 (web console: Scores board → Share): the story image and Nova's caption (on a phone, the share sheet opens). Then Put this week's board on the screens 📅: the TVs show who scored the most workouts this week (the demo gym has five days of results).
- Leads 🙋 → Put the free-class QR on the screens (web console: Free-class leads), scan it, leave a name and a phone or email; then Message ✨ on the lead: Nova's welcome message with real classes from the schedule, and a button that opens WhatsApp or email with it.
Demo video
Link: https://www.youtube.com/watch?v=eCapcQmJmUQ
2:59, recorded on a real Fire TV Stick and in a real box, narrated in English; the coaches speak Spanish with English subtitles.
| Time | Scene |
|---|---|
| 0:00 | Luis at his whiteboard: ten minutes every morning, and nobody can read it |
| 0:13 | The box today: a small LED clock, and the workout across the room |
| 0:18 | ScreenDesk in one sentence |
| 0:25 | Filmed on a real Fire TV Stick: paired in seconds, schedule, timer, workout beside the clock |
| 0:34 | The class screen: NOW/NEXT schedule, workout, promos, booking QR |
| 0:46 | Whiteboard photo → Amazon Nova on Bedrock → workout + timer on every TV |
| 1:08 | Tabata timer: red work, green rest, last-3-seconds pulse, run from the phone |
| 1:20 | Backend killed mid-AMRAP: the TV keeps the class running and reconnects by itself |
| 1:34 | After a reboot (what a power cut does), the stick opens ScreenDesk by itself, back on the class screen |
| 1:39 | The Fire TV remote: Left/Right page the slides, Menu shows the screen's status (filmed on the real stick) |
| 1:47 | Luis's verdict: one-month trial, then €15.90 a month |
| 2:08 | Today's results from a photo of the scores board, and song requests by QR (what Luis asked for) |
| 2:22 | This week's board, today's results as a story to post, and a free-class QR that turns visitors into leads, with Nova's welcome message |
| 2:36 | The copilot on the phone: the day in one message, three agents on Amazon Bedrock AgentCore plan it, Apply, and the real Fire TV switches to Fran's countdown |
| 2:52 | How it's built on AWS |
Full docs in the repo: README.md, docs/AWS_INTEGRATION.md (AWS Builder), docs/PRODUCT_FEEDBACK.md, FRICTION_LOG.md (21 entries).
Built With
- amazon-bedrock
- amazon-bedrock-agentcore
- amazon-cloudfront
- amazon-cloudwatch
- amazon-ecs
- amazon-nova
- amazon-rds-relational-database-service
- aws-cdk
- aws-fargate
- aws-secrets-manager
- claude-code
- compose-for-tv
- compose-multiplatform
- fire-os
- fire-tv
- jetpack-compose
- kotlin
- kotlin-multiplatform
- ktor
- postgresql
- python
- spring-boot
- sqldelight
- strands-agents
- websockets
Log in or sign up for Devpost to join the conversation.