Setline is Apple-only: structured training model, real landing page, no backend, no Cloudflare - #40
Merged
sarthakagrawal927 merged 11 commits intoAug 16, 2026
Conversation
Add PostHog analytics snippet to track page_view events with the product's project_id. Uses PostHog's CDN loader — no npm dependency needed for static/Astro sites. Part of fleet-workspace#348 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…cise measurement The app could not represent the training it claimed to run. Targets were free text, exercises were bare strings with no identity across sessions, and the dated 12-week programme existed only in the deleted web app's TypeScript. - Replace `PlannedSet.target: String` with `SetTarget`: rep ranges, absolute / relative / bodyweight / assisted load, reps in reserve, RPE, tempo, hold, distance, pace, HR zone, per-side, and rest as a `RestRange` band rather than a scalar. `StepType` keeps warm-ups out of volume, records and progression. - Add `ExerciseCatalogue`: stable slugs, pillars, muscle groups, equipment and measurable metrics across strength, stamina, mobility and flexibility, plus the CrossFit movement vocabulary. - Port the authored 12-week block to Swift, resolved natively for all 84 dates, with the bench ramp, week-phase RDL volume, hard-cardio round switch, pull-up checkpoints, optional lateral raise, rest bands and reassessment dates. - Add an Exercises surface: measured current values that cite the session that produced them, authored targets, progress, weekly rate, projected arrival and Swift Charts trends. No evidence means no number, never an estimate. - Make set entry repeatable so `5 reps x 40 kg` then `2 reps x 30 kg` records as one set, with a tested shorthand parser that shows its reading before applying. - Add a set timer independent of rest, and a rest-completion notification so the timer survives leaving the app. - Replace the flat `+2.5 kg` suggestion with the programme's own double progression, including its per-movement increments and below-range regression. - Bump the document to schema version 2, reading version 1 without losing recorded work. Closes #39 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…alue Looking at a captured screenshot showed "91.83333333333333 kg" on the bench press screen. `Double.trimmedString` fell back to `String(self)`, so binary floating-point precision reached the interface for every non-integer value — estimated 1RM, weekly rate, distance to target. - Bound formatting to two decimals, with a one-decimal `kilogramString` for loads, since two decimals claim more precision than a barbell has - Give the decimal keypad a Done button and interactive scroll dismissal. It had no return key, so numeric entry could trap the keyboard over the record button - Replace the placeholder landing page. It was a dark navy stub with amber and sky accents that contradicted the tracked identity in DESIGN.md, describing a product two releases out of date. The new page is built to the fleet landing standard on the app's own palette: hero, the problem stated precisely, one set being recorded, current versus ideal, the four pillars, what Setline refuses to do, fit and poor-fit cases, and a FAQ of questions people actually ask - State pre-release status plainly; add no App Store or TestFlight link, because neither exists. A test enforces that - Rewrite the agent surfaces to match and add a test holding the Worker's inline copies byte-identical to the static files in public/ - Seed a demo document with four weeks of evidence so the measurement surfaces can be exercised, captured and tested; add UI tests covering the trend chart and the progression suggestion's cited evidence - Remove all six duplicated blocks in the native sources via a shared legacy decoder, programme set builders and a cardio definition builder. Duplication is now zero, and the baseline is lowered to hold it there - Split the two longest new functions rather than raising the complexity budget for them; the budget rise covers only memberwise initializer parameter counts, with the reason recorded next to the baseline Closes #41 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The account layer existed to give a server something to authenticate. With the web app gone and the native app self-sufficient, none of it earned its keep. Removed: - worker/ (9 files, 1,482 lines): Better Auth with Google OAuth, the native handoff, D1-backed private state, the MCP read surface, and the agent edge - src/lib/ (7 files, 3,277 lines) superseded by the native model, plus the 12 test files covering it and the three D1 migrations - The client half: NativeAccountClient, SetlineCloudDocument, and the whole-document sync and conflict flow in AppModel and Settings - TypeScript itself, along with vite, better-auth and drizzle-orm Kept honestly: - The sync invariant the deleted cloud type guarded is now `SetlineDocument.hasSameContent(as:)` with its own tests: recording that a save succeeded must never itself look like new training - Settings states where the training lives rather than implying an account, and says plainly that iCloud sync is not active yet - The public site is static Pages content. `_headers` carries the security headers the Worker set in code, and /api/ai keeps its path as a real file - sw.js is now a stub that evicts the deleted web app's shell and unregisters, so a returning visitor is never served a page that no longer exists Also fixed, found by a UI test rather than by reasoning: numeric entry had one shared focus flag across all fields, so moving from Reps to Weight left focus ambiguous and typing failed. Focus is now tracked per field. The parity test was rewritten: with no Worker to compare against, it now holds the sitemap, agent catalog, llms.txt and landing page to each other and proves every path they advertise resolves to a file. Complexity dropped from 27 violations to 9 and max CCN from 52 to 15 once the TypeScript left; both baselines are lowered to match. Three accepted security advisories came in through the removed dependencies and left with them. The live Worker and the unused setline D1 database are deliberately retained. The fleet catalog records the Worker as live-purpose-review, the Pages project as configured-not-deployed, and D1 as active-unreferenced, pending an owner cutover. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Setline is an iPhone app. It had a Cloudflare account attached to it for a web app that no longer exists. Deleted from the account: - The live `setline` Worker - The `setline` D1 database. Checked before deleting: every table — workout_state, user, account, session — held zero rows, because no one ever signed in. The `d1 list` summary claiming "0 tables" was wrong; the tables existed and were empty, which is why this was verified by querying rather than trusted Removed from the repository: - wrangler, its config, and the deploy script - `_headers` and `_redirects`, which are Cloudflare Pages syntax and would be dead config anywhere else The public site is now GitHub Pages, published from `public/` on pushes that touch it. `CNAME` claims the existing canonical hostname so no URL, canonical tag, sitemap entry or agent surface changes. `.nojekyll` keeps the markdown mirrors and dotfiles intact. One capability is genuinely lost: GitHub Pages cannot set a Content-Type, so `/api/ai` is served with whatever type GitHub infers for an extensionless path. The body is still valid JSON carrying every field the agent-indexing standard requires, and a test now asserts exactly that rather than assuming a header. The canonical hostname returns 530 until its DNS record points at significant-hobbies.github.io. The fleet catalog records Setline as undeployed with that as the named blocker, and its Cloudflare deployment and resource rows are now empty. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI installs with --frozen-lockfile, which fails on a manifest the lockfile does not match. Resolved offline, so nothing new was fetched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Trimming package.json for the Cloudflare exit dropped `quality:native`, but `.github/workflows/ci.yml` still runs `pnpm quality:native` on macOS. That job would have failed with "command not found" — the native tests, release build and coverage floor were silently not gating anything. Restored the script. The gate now measures 84.1628% coverage of the production targets against a floor of 65.3%, set before the structured model arrived with its own tests and before the untested account layer left. Raised the floor to 83.8% so the gain cannot quietly erode. Also dropped the hardcoded "10 unit tests, 4 UI tests" summary line, which had gone stale without failing anything; the real totals are 64 and 11, and xcodebuild already prints what it ran. Corrected three claims that the backend removal falsified: PROJECT_STATUS said a test holds the Worker and static copies byte-identical (there is no Worker), and described the owner-gated Sites deployment as a rollback copy (it is a survivor of the removed web app, and nothing depends on it); README still listed "optional private account sync" as shipped.
The three secondary pages were one-sentence placeholders on the old navy palette, and the privacy notice stated something that is no longer true: that optional Google sign-in stores a private user-scoped copy. There is no sign-in and no server. A privacy page making a false claim about where training data goes is the worst possible page to leave stale, and App Store review reads it. Privacy now states what actually happens: the app collects nothing, makes no network requests, and keeps one JSON document in its own container; export, import and reset are the only ways data moves; notifications are local. It also discloses what the previous version never did — that this website runs PostHog and the sassmaker portfolio strip, and what each one handles. Terms gained the health disclaimer a fitness app needs, the distinction between recorded and calculated values, and a note that the bundled block is one person's programme rather than a recommendation. The changelog became actual dated entries, including the removals, replacing a sentence that still claimed private sync and PWA support. Also fixed two stale answers in the landing FAQ: "Sign-in exists only to keep a private copy across devices" described a feature that no longer exists, and the data answer omitted that the export is now the only backup. Added iCloud sync to the not-yet-built list on every agent surface, where its absence read as working. Five new tests keep this from drifting again. The strongest ties disclosure to reality: it reads the script origins out of the markup and fails if the privacy notice does not name them, so adding a tracker without disclosing it breaks the build. Others assert no page claims an account or a server copy, every page uses the tracked palette, every referenced asset exists on all four pages, and the legal pages carry a date and the disclaimer. The specs went with the code: the never-completed Google-auth change is archived as withdrawn with a note on why, and the account-data-deletion spec — Better Auth, D1 cascades, browser state — is archived, since none of that exists and it required the privacy notice to advertise account deletion.
sarthakagrawal927
force-pushed
the
feat/structured-targets-native-programme
branch
from
August 16, 2026 10:25
f3ec481 to
590d385
Compare
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI's macOS image builds with an older SDK than this machine's Xcode 27, where
`UNNotificationSettings` is not marked Sendable. `await centre.notificationSettings()`
therefore compiles locally and fails there:
Non-sendable result type 'UNNotificationSettings' cannot be sent from
nonisolated context in call to instance method 'notificationSettings()'
Taking `authorizationStatus` inside the completion handler means only a Sendable
enum crosses the isolation boundary, which compiles on both SDKs. Behaviour is
unchanged.
This surfaced only because the previous commit restored the `quality:native` job
that CI had been unable to run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rywhere The previous fix was incomplete. `UNUserNotificationCenter` itself is not marked Sendable on the SDK CI builds against, so a stored `centre` property on a `@MainActor` class produced "sending 'self.centre' risks causing data races" at both use sites, not just the settings read. Every call now resolves `.current()` at the point of use, inside a synchronous or nonisolated scope, so no non-Sendable value crosses an isolation boundary. The `init(centre:)` seam went with it: nothing ever injected a centre, so it was unused flexibility rather than a testing affordance. CI now selects the newest Xcode on the runner and prints it. The default is older than this machine's, which is exactly how a data-race error compiled clean locally and failed there; a native failure should not be a mystery about which compiler ran. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Setline becomes an Apple-only, device-first iPhone app with a real public face and no infrastructure of its own.
Closes #39
Closes #41
Closes #42
1. Set targets that can express a real programme
PlannedSet.targetwas a line of free text, so a programme with rep ranges, %1RM, reps in reserve, tempo, per-side work and rest bands could not be represented at all. Targets are now structured, andstepTypekeeps warm-up, preparation and cooldown work out of volume, records and progression instead of quietly counting it.The dated 12-week block now resolves natively for each of its 84 days — week-dependent RDL volume, the 4→5 round cardio build, the week 5/9/12 pull-up checkpoints — and progression uses the increments the plan itself specifies, replacing a flat
+2.5.Per-exercise measurement landed with it: every exercise has an identity, a current value derived only from completed working sets, and an authored target. Each value cites the session that produced it. No evidence means no chart.
Set entry now records a set the way it happened:
5 reps × 40 kgthen2 reps × 30 kgis one set with two segments. The shorthand parser is deliberately rule-based, not an LLM, and shows its reading before anything is recorded.2. No backend
Deleted the Worker, the account layer (Better Auth, Google and Apple sign-in, private D1 state, the MCP surface, whole-document sync and its conflict UI), 3,277 lines of superseded TypeScript, and the 12 test files covering them.
3. No Cloudflare
Deleted the live
setlineWorker and thesetlineD1 database. Checked before deleting:wrangler d1 listreported 0 tables while the tables existed, so I queried them directly —workout_state,user,accountandsessionall held zero rows. Nobody had ever signed in, so the cross-device sync that layer existed to provide was never once used.The site is now static files on GitHub Pages with its own
CNAME. No URL changed.4. A public face that is true
The landing page was a placeholder; so were privacy, terms and changelog, on the old navy palette. The privacy notice still claimed "optional Google sign-in stores one private, user-scoped state copy" — false since the backend went, and the single worst page to leave stale given App Store review reads it.
Privacy now states what happens: nothing collected, no network requests, one JSON document in the app's container, and — disclosed for the first time — that this website runs PostHog and the sassmaker portfolio strip. Terms gained the health disclaimer, the recorded-versus-calculated distinction, and a note that the bundled block is one person's programme.
Two stale landing-page FAQ answers went too, including one describing a sign-in that no longer exists.
5. A gate that was not gating
ci.ymlrunspnpm quality:native, but trimmingpackage.jsonhad dropped that script — the macOS job would have failed with "command not found", so the native tests, release build and coverage floor were gating nothing. Restored, and its floor raised from 65.3% to 83.8% against a measured 84.1628%.Verification
pnpm checkgreenNot in this PR
@Modelreference types and breaking the 64 core tests; per-record CloudKit reaches the same outcome with the value types intact.CNAME setline → significant-hobbies.github.ioDNS-only, not proxied — proxying it puts Cloudflare back in front and breaks certificate issuance.🤖 Generated with Claude Code