Skip to content

Repository files navigation

DryRun icon

DryRun: Say the Hard Thing

You already know what you need to say. Then it comes out soft.

A rehearsal app for difficult workplace conversations. The counterpart pushes back, your hedging underlines as you type, and when you finally make a clear point they go quiet for three real seconds.

Download on the App Store · Demo video (1:50) · Website · Privacy policy

Built for RevenueCat Shipaton 2026.


For judges, in one table

Live app App Store, iPhone, iOS 18.2+
Demo video 1 minute 50 seconds
Shared code 89% — 4,633 of 5,198 lines in commonMain
Tests 101 — 61 Kotlin, 40 Worker
Billing RevenueCat KMP SDK, one entitlement, one file
Architecture ARCHITECTURE.md
How it was built BUILDING-WITH-CLAUDE.md
Categories entered Ship Kotlin Everywhere · Design · HAMM · Influencer (Career Coaching)

The three things worth looking at first:

  1. coach/HedgeDetector.kt — deterministic hedge detection, phrase-level, no model call
  2. counterpart.ts — the silence beat, earned rather than random
  3. billing/Plus.kt — the entire RevenueCat surface

What it does

You pick a conversation you are dreading, or describe your own. The other person answers in character: defensive, tearful, charming, or simply unmoved. They do not let a vague opener slide.

Three things happen that do not happen in a normal chat app.

Your hedging underlines as you type. Just, maybe, I think, sorry to bother you, does that make sense? are marked the moment you write them, with a running count. Nothing is blocked. You just get to watch yourself flinch.

When you make a clear point, they say nothing. The typing indicator starts, then stops. Three real seconds of dead air. Most people fill it and talk themselves back out of the thing they just said.

Afterwards you are scored on four things, quoted back word for word: whether you named the specific thing, said what it cost, left them room to answer, and held the point under pushback.

Run the same conversation twice and it shows you the line that changed.


Architecture

graph TB
    subgraph device["iPhone / Android"]
        UI["Compose Multiplatform UI<br/>10 screens, one implementation"]
        subgraph common["commonMain — 4,633 lines, 89%"]
            HD["HedgeDetector<br/>deterministic, no network"]
            SL["SilenceLog<br/>offered vs filled"]
            CS["CounterpartState<br/>clamped ±1 per turn"]
            RC["RunComparison<br/>the line that changed"]
            FT["FreeTier gate"]
            PL["Plus.kt<br/>RevenueCat KMP"]
        end
        subgraph plat["expect / actual — 4 seams only"]
            KV["KeyValueStore"]
            LN["LocalNotifier"]
            CK["Clock"]
            SK["Store key"]
        end
    end

    subgraph edge["Cloudflare Worker — TypeScript"]
        AUTH["Auth + rate limit<br/>KV namespaces"]
        SAFE["Safety<br/>input and output"]
        CP["Counterpart state machine<br/>shouldOfferSilence()"]
        PROV["llm/provider.ts<br/>single interface"]
    end

    GROQ["Groq<br/>gpt-oss-120b roleplay<br/>gpt-oss-20b scoring"]
    STORE["App Store / Play<br/>StoreKit"]
    RCB["RevenueCat<br/>entitlement 'plus'"]

    UI --> HD
    UI --> common
    common --> plat
    UI -- "HTTPS, transcript only" --> AUTH
    AUTH --> SAFE --> CP --> PROV --> GROQ
    CP -- "reply, state, silence" --> UI
    PL <--> RCB
    RCB <--> STORE
    KV -. "device local only" .- DB[("Rehearsals, runs, scores<br/>never leave the phone")]
Loading

The rule the diagram encodes: anything that gets scored is computed deterministically, not asked of a model. Hedge counts and silence counts are produced on the device and sent to the scorer as ground truth, overriding whatever the model thinks it saw.

Full detail, including the request flow and the state machine, is in docs/ARCHITECTURE.md.


Project layout

shared/src/commonMain/    4,633 lines — all logic, all UI
  coach/                  HedgeDetector, SilenceLog, CounterpartState, RunComparison
  billing/                Plus.kt — the only file importing RevenueCat
  ui/                     10 Compose screens + theme
  data/                   DryRunApi (Ktor), DryRunStore
  models/                 Rehearsal, Turn, RunRecord, FeedbackReport
shared/src/iosMain/         220 lines — 4 actuals + entry point
shared/src/androidMain/     276 lines — 4 actuals
shared/src/commonTest/       61 tests
iosApp/                      28 lines of Swift
androidApp/                  41 lines
worker/src/                 Cloudflare Worker, 40 tests

Running it

Backend

cd worker
npm install
cp .dev.vars.example .dev.vars   # add GROQ_API_KEY and MINT_SECRET
npm test                          # 40 tests
npx wrangler dev

Android

./gradlew :androidApp:installDebug

iOS

open iosApp/iosApp.xcodeproj      # then run on a simulator or device

Tests

./gradlew :shared:testAndroidHostTest   # 61 Kotlin tests
cd worker && npm test                    # 40 Worker tests

There is no API key in this repository. The app runs against a deployed Worker that holds the only model credential; the client never sees one.


Privacy, and one deliberate absence

What you type is about a real colleague, sometimes by name. So:

  • Rehearsals, runs and scores are stored on the device only
  • Message content is sent to the Worker for the length of a request and is never written to any database
  • There is no share button anywhere in the app. On purpose. A share sheet here is a privacy hazard dressed up as growth
  • Delete everything on the home screen erases all of it, with no cloud copy to survive it

Full policy: PRIVACY.md.


How this was built

The product decisions, the store work and the judgement calls are mine. Claude Code did most of the typing.

An honest account of the split, including three bugs that shipped anyway and how each was actually found, is in docs/BUILDING-WITH-CLAUDE.md.


License

MIT.

About

You know what to say. Then you soften it. A rehearsal app for hard conversations: the counterpart pushes back, your hedging underlines as you type, and the silence is real. Kotlin Multiplatform, live on the App Store.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages