If I can build a viz extension, can I build an app?

·10 minute read·
AIClaude CodeWeb DevelopmentViz ExtensionsData
If I can build a viz extension, can I build an app?

This is not a Tableau post. Except it sort of is.

Over the last couple of months I've used AI assisted coding to build:

Each build more complex than the last. It had me wondering what else I could build.

Custom Viz extensions are not a special Tableau artifact. They are an HTML page, some JavaScript, some CSS, a config dialog, and a small API contract with Tableau that hands it data and takes back a rendered thing.

Apps are also HTML pages. They too have some JavaScript, some CSS, a settings screen, and a contract with a database instead of with Tableau.

So: if I can build a viz extension, can I build an app?

 

The lock screen

The actual problem

If I'm going to build an app, I want a genuine reason to do so. And I definitely had one.

I wanted to get back to the gym and get back in shape.

I don't think I'm old yet, but also it has been a while since anyone checked my ID at the bar. It has also been a while since I've been a regular at the gym.

I wanted an app to encourage, train and log my progress.

However, none of the main Workout Apps were doing it for me.

  • They're built for ~25 year olds, or regular gym goers.

  • They are very generic loaded with exercises I'd never do.

  • They enforce the program. Miss a day, swap an exercise, travel for a week, and the app either nags you or quietly falls apart.

  • Logging a set is complicated. Once the data is logged, I can't easily get to it.

  • The best ones often have expensive subscriptions with the best features behind the pay wall

  • I can't suggest edits to the developers

None of these are real problems. These apps are great for a wide range of users. They just weren't landing with me.

I needed an app specifically designed for my age, fitness levels and focusing on my goals - that works the way I want it to work.

So I set about building an app designed just for one person.

Since it's just for me, I'll make it blue.

 

Today screen focused on a single user

What it is

A mobile-first installable web app. One user, one phone, one passcode. It suggests today's session from whichever program I'm on, and then it gets out of the way.

Three programs, run in order:

  • Foundation, six weeks, machines and dumbbells only. Loads start deliberately below what I can do, because strength comes back in weeks and tendon capacity takes months, and the gap between those two is where returning lifters get hurt.

  • Step It Up, eight weeks, barbell work back on the table.

  • Definition, eight weeks, chest and midsection with conditioning at the end of every session.

Behind that, an exercise library of 101 movements with instructions, swap options, travel alternates, and demonstration videos.

 

101 exercises suitable for this one user. It flags exercises with high-stress on joints because I guess I'm old

Built in phases

I didn't build a finished app. I built Phase 0, then Phase 1, and so on. Each phase was small enough to finish, and each one ended with something that actually worked.

Phase 0: Scaffold the app. There's nothing working here yet. We are just getting everything into place. Next.js, a passcode in a cookie, a Neon Postgres connection. Nothing you can see. One deliberate exception: I built the numeric keypad in Phase 0, long before anything needed it, because the whole logging experience depends on never invoking the iOS keyboard.

Phase 1: the data model. Thirteen tables, then the exercise library, then the three programs, then read-only screens to browse them all. No logging yet, just the ability to look at what exists.

Phase 2a: enrolment and session generation. Pick a program, and the app produces today's session.

Phase 2b: the logger. The screen everything else exists to serve. This is where the app either works in a gym or doesn't.

Phase 3: offline sync. I didn't think of this one. The cell reception at my gym is good, but the agent advised adding it for travel weeks or other situations where I might not be able to get online.

Phase 4: the Coach. I want this app to work for me. Can it consistently design new programs for me as I work though sessions.

 

3 personalized programs for a single user

The details that only exist because I built it for me

Prefill is the whole design. Set one arrives filled with what I did last session. Sets after that fill with what I just did this session, not with what the program says. A repeated 3x8 at last week's weight costs three taps in total.

No operating system keyboard anywhere. Big steppers with press and hold acceleration, plus a custom pad for a real jump in weight.

The rest timer is derived from a timestamp, never counted down. A countdown drifts when the phone is in your pocket and stops dead when the screen locks. This one recomputes from the start time every tick, so coming back after four minutes shows the truth.

It suggests and never enforces. That's a house rule in the code, not a preference. Superset alternation, drop set advice, program order: all of it is a suggestion with an override sitting underneath it.

Travel mode. I've always broken streaks when I had to travel. The hotel doesn't have a gym, or it does but it is only a set of dumbells and a broken running machine. Telling the app I'm traveling has it swap out all the workouts for bodyweight or basic-gym versions all with a single click.

 

Not just an interface, but the data is being written to a Postgres database for future querying

Iterate

This is the part that matters most, and it's the same thing I found building extensions: the value isn't in the first build. It's what you do in the second, third etc. versions

Take the app to the gym. Use it and, note what needs work. Come back and describe it and have it fixed before the next session. Not a backlog. Not a sprint. A conversation on the sofa afterwards.

Real things I brought back:

Every rest timer was 30 seconds too long. No big deal really - but because I'm building this just for me - why not fix it. This is not a fix I'd have bothered making if it meant opening a codebase and hunting for where the number lived. But since we are using Claude to write the code, I just tee'd it up as a to-do list for the next version.

I wanted to read weights in kg. Some machines in the gym use lbs, others kg. The first version of the app just accepted lb. Add it to the list for v2.

The supersets weren't actually supersetting. We built supersets in the program - but the UI didn't handle them any differently to regular exercises. Describing the exact behaviour (exercise A to exercise B with no rest. Rest after B, then back to A) and we got it in queue for the next version

"How am I actually meant to do this one?" Written cues are fine until you're standing in front of a cable machine at an angle you've never used. So every exercise got a link to a demonstration video. 82 of the 101 got one. The other 19 didn't, because I had the research agents skip anything they couldn't source from somewhere credible rather than return a plausible link.

Warm-ups became part of the session. I was skipping them, because a warm-up you have to remember is a warm-up you don't do. Now it's the first screen of every workout, it's already counted in the session length, and it's opinionated about what's in it.

Plus a pile of smaller ones: being able to cancel a workout I'd started by mistake, swapping in any exercise from the library mid-session, and building a one-off custom session for a day that doesn't fit the plan.

None of these were in the plan. All of them are obvious the moment you're standing in a gym with the phone in your hand.

 

After some iterating a very nice and easy to use UI.

 

Two agents having an argument

I had Claude build the exercise library and the programs. Then I had a separate agent audit them against strength and conditioning standards, with no knowledge of how the first one had built them.

It came back with a list of disagreements, and most of them were real.

It was fun to watch the 2 agents duke it out over whether it was right to program a barbell back squat alongside a barbell Romanian deadlift. Or whether I (the user) was ready for a particularly heavy routine.

I found assigning agents specific roles with one agent as the main project-lead really effective at catching all kinds of errors in judgment, code and UI/UX.

 

Completing the Tableau circle

Here's why this belongs on a Tableau blog.

I'm not just building an app. I'm building a data source.

Every set writes a row: exercise, date, set number, whether it was a warm-up or a working set or a drop set, weight to two decimal places, reps, duration, which side. Alongside that, a bodyweight time series, a cardio log, and a one-tap rating for each session.

And crucially, the prescription is copied onto the session at the moment it's generated rather than looked up later. So the record keeps both numbers: what the program told me to do, and what I actually did. Editing a program next year cannot rewrite last year's history.

The data sits in a Postgres database I set up as part of the build with a plan to connect Tableau to that database.

So the loop closes. Viz extensions taught me that a Tableau artifact is just a web page. That let me build an app. The app generates data. The data comes back into Tableau, where I started.

 

Rating the workout will help inform the coach for future sessions.

What I'd tell you if you're thinking about it

Pick something only you need. The reason this app has been fun to build is that its user base is one person and I know exactly what he wants.

Phases, not a plan. Ship Phase 0 with nothing visible in it. It feels like no progress and it's the reason the rest went fast.

Use it early, then iterate. One real session produced more useful direction than any amount of planning did. Build the smallest thing you can actually use, use it, and bring back a list. Iteration is the whole advantage of AI-assisted coding, and you only get it if there's something real to react to.

The scary part isn't the code. It's hosting, domains, databases, deployment. That stuff turned out to be the most helpable part of the whole build.

Four viz extensions ago I'd never loaded a .trex file. Now there's a database in the cloud filling up with my bench press numbers.

Go build something only you need.