A managerial view of the Excuse Caddie project — what's in good shape, what's lacking, and what the next 6 months should look like. Updated 2026-05-13.
For the day-to-day backlog see app/ROADMAP.md and webb/ROADMAP.md. This document is the level above that — strategy, not tickets.
| Surface | Version | Status |
|---|---|---|
| iOS app | 1.5.0 (build 6) | In Apple review (submitted 2026-05-13) |
| Android app | 1.5.0 (versionCode 7) | Build pending, not yet on Play Store |
| Website | live | excusecaddie.xyz, Next 16 |
| Backend | live | Supabase + Redis (voting, leaderboard, generated counter) |
Shipped in 1.5.0: rebrand to Excuse Caddie, sounds on Mulligan, on-device share card (works offline), tighter home layout, 270 excuses, ASO-optimized store listings, new logo, all dependabot advisories cleared.
- Single excuse pool, two surfaces. Same 270 excuses power the website and the app. Source of truth lives in both
webb/lib/excuses.jsandapp/src/excuses.js(mirrored manually for now — see "drift risk" below). - No accounts, no ads, no tracking. The product position is clean. App Privacy declarations match what the code actually does (Device ID for functionality, Product Interaction for analytics, neither linked to identity).
- Offline-first. Excuse generation, sound, and share card capture all work without internet. Only the leaderboard ticker needs a network call.
- Brand consistency. After the 1.5.0 rename pass, "Excuse Caddie" is the name everywhere except the App Store URL slug (which only updates on the next listing rename in App Store Connect).
- Repo hygiene. Root is clean (
app/,webb/,design/,LICENSE,README.md). Design assets are organized. ROADMAPs exist for both surfaces. - Security. Zero dependabot advisories across
app/,webb/, andapp/backend/.
- Zero automated tests. No unit, integration, or smoke tests anywhere. Means every refactor is "hope and click around."
- CI is just dependabot auto-merge. No
npm test, no lint, no typecheck in PRs. Bad code can land onmainundetected. App.jsis a 1074-line monolith. Components, styles, and logic all in one file. Hard to navigate, easy to introduce regressions during refactors. Should split intocomponents/(~6 files) andscreens/Home.js.- Excuse pool drift risk.
app/src/excuses.jsandwebb/lib/excuses.jsare duplicated by hand. Already drifted twice this version. Should be one shared file consumed by both surfaces (npm workspace, or a generated JSON file). - No analytics beyond a row count. We know how many excuses were generated. We don't know which ones, retention, daily active users, what country, where the funnel breaks. Hard to make product decisions blind.
- No app review prompt. The single biggest organic-growth lever for a free app, missing. Queued for 1.5.1.
- No localization. English-only. The brand humor translates poorly to non-English markets — needs adaptation, not just translation. Sweden is the obvious first non-EN market.
- No widget / lockscreen surface. "Excuse of the day" as a widget would be a daily-touch retention loop. Currently the only way to use the app is to actively open it.
- Backend is a small black box.
app/backend/has minimal docs. If the Redis instance or Supabase project goes down, recovery instructions don't exist in the repo. - Store assets are static and English-only. Six screenshots, no localized variants, no A/B testing.
Theme: ship what we built, prove the loops work.
- Get 1.5.0 approved on iOS. Apple review is in flight; usually 24–48 h.
- Submit 1.5.0 to Google Play. Android build + the closed-test-to-production form (answers written, awaiting submission).
- Ship 1.5.1 with
expo-store-review. Trigger after 5 Mulligan taps. This is the highest-leverage change of the year — a free app with 0 review prompts gets ~10× fewer reviews than one with a well-placed prompt. - Set up basic CI. GitHub Action that runs
npm installandnpm run buildon every PR forwebb/. Forapp/runnpx expo-doctorandnpx tsc --noEmit(once we add types) or at minimum anode -e "require('./src/excuses')"sanity check. - Add Vercel Analytics or PostHog to the webb. Free tier, anonymous, lets us see DAU and which excuses get shared.
Theme: don't let the codebase rot.
- Split
App.js. Target structure:app/src/ App.js (~150 lines, just composition) screens/Home.js (~400 lines) components/CTAButton.js components/ThumbButton.js components/SharePill.js components/TopTicker.js components/StoryShareCard.js components/PaperGrain.js components/CountUp.js - Single source of truth for excuses. Move
excuses.jsto a rootshared/folder, symlink or copy via aprebuildscript so both surfaces consume it. Eliminates the drift bug we hit twice in 1.5.0. - First real tests. Smoke tests only — does the bundle build, does the API return 200, do the React components render without throwing. Aim for a 30-minute investment, not a coverage chase.
- Settings screen in the app. One toggle: mute the Mulligan sound. (Currently sounds are unconditional; not everyone wants noise.)
- Backend runbook. A two-page doc in
app/backend/README.mdexplaining how to recover if Supabase or Upstash dies. Where the env vars live, how to rotate them, how to restore from backup.
Theme: pick the first growth surface.
Pick one of the following based on what 60-day analytics show. Do not do all three.
- A) Localization (Swedish). If analytics show meaningful Swedish traffic on the webb, port the 270 excuses to a Swedish variant. Native humor, not Google Translate. Submit to App Store as a separate localization on the same binary.
- B) Widget. If retention is the weak metric, ship an iOS WidgetKit + Android Glance widget showing the "excuse of the day." Two new platform surfaces, but high daily-touch.
- C) Per-category browse in the app. If session-length is the weak metric, port the webb's
/c/[category]pages into the app (8 category pills below the card). Lets users self-serve a "more like this" loop.
Theme: become a small business, not a project.
- Watch app. Tap to roll on the wrist on the tee.
- Apple Vision / spatial preview. Niche but listing-eligible.
- Partnership / sponsored excuse. "Brought to you by [golf brand]" once per session. The first non-ad monetization path that doesn't poison the experience.
- iMessage sticker pack with the top 20 excuses as illustrated stickers.
- Cross-app deep-link from webb → app. If someone lands on
excusecaddie.xyz/42from mobile, prompt to open in the app. Catches the share-loop and increases install conversion.
- User accounts, login, profiles — the no-account vibe is the brand
- Ads or in-app purchases — the app is and stays free
- Subscriptions — same
- Daily push notifications — friction at install (the "Allow notifications" popup) outweighs the retention benefit for a humor app
- Cross-app tracking SDKs — undermines the privacy claim in store listings
Two principles when adding anything to this list:
- Brand fits or it doesn't ship. The voice is dry, cocky, golf-clubhouse. Any new feature that requires a different voice (notifications, gamified XP bars, onboarding tutorials) usually means the feature is wrong for the product, not that the voice is wrong.
- Friction kills humor. Anything that takes more than one tap to land a punchline is a regression. The Mulligan button → excuse loop is the product. Defend it.