@fytkit/sdk 3.5.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,29 @@ All notable changes to `@fytkit/sdk` are documented in this file.
4
4
  Format: [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/).
5
5
  Versioning: [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 4.0.0
8
+
9
+ To be published by hand (release-please, which runs on GitHub Actions, does not cut this release; the manifest records 4.0.0 so the next automated release bumps from it). Create the tag and GitHub release `@fytkit/sdk@4.0.0` when it is published, or release-please proposes 4.0.0 again.
10
+
11
+ ### ⚠ BREAKING CHANGES
12
+
13
+ * `appConfig.get` is removed, with no deprecation window: the `appConfig` namespace is gone from `FytkitClient`, and `GET /api/v1/app-config` and `POST /rpc/appConfig/get` answer 404. Use `app.bootstrap` instead. Its colours become the app theme's curated palette (`primaryColor` → `data.theme.palette.primary`, and so on), `appName` → `data.studio.name`, the links → `data.links`, `realtime` → `data.realtime`; the icon URLs, `buildStatus`, `buildVersion`, the social sign-in ids (`auth`), `id` and `trainerId` are gone. Field-by-field migration: [docs/developer/reference/app-bootstrap.md](https://github.com/cbarlow1993/fytkit-web/blob/main/docs/developer/reference/app-bootstrap.md#migrating-from-appconfigget) ([#535](https://github.com/cbarlow1993/fytkit-web/issues/535))
14
+ * `MessagesRealtimeSource` reads the Realtime settings from `app.bootstrap()` (`{ status: "ok", data: { realtime } }`) instead of `appConfig.get()`. A `FytkitClient` still satisfies it; a hand-written source must rename `appConfig.get` to `app.bootstrap` ([#535](https://github.com/cbarlow1993/fytkit-web/issues/535))
15
+
16
+ ### Features
17
+
18
+ * `app.bootstrap({ etag? })`: the app's launch read. The studio's display name and logo, the effective app theme (template, theme, palette, derived variants and the oldest app release that paints it), every template's minimum app version, feature overrides, the studio's minimum app version, the terms, privacy and help links, the Realtime settings and the payment policy, in one request that needs the client id alone. Resolves to `{ status: "ok", data, etag }`, or to `{ status: "unchanged", etag }` when the `etag` you stored is still current (the server answers 304 with no body). New types `AppBootstrapData`, `AppBootstrapOptions`, `AppBootstrapResult` and `AppNamespace`; `FytkitClient.clientId` names the client id the client was created with, to key per-studio caches by ([#535](https://github.com/cbarlow1993/fytkit-web/issues/535))
19
+ * `app.bootstrap`'s `data.paymentPolicy` and `resolvePaymentMode(policy, { platform, storefront, conditionsMet? })`: whether the app shows "Subscribe" (a link out to the studio's checkout) or "What your plan includes" for the storefront the device's store reports (StoreKit's alpha-3 `Storefront.countryCode` on iOS, Play Billing's alpha-2 country code on Android; case-insensitive). The policy is Fytkit's reviewed list of storefronts where linking out is allowed, with any conditions the app must meet first; every other storefront, and any unmet or unknown condition, is plan info. New types `PaymentMode`, `PaymentPlatform`, `PaymentPolicy` and `ResolvePaymentModeOptions` ([#538](https://github.com/cbarlow1993/fytkit-web/issues/538))
20
+ * `resolveJoinCode(code, { baseUrl?, fetch? })`: resolves a studio's join code (the code on its Fytkit Coach QR code or join link) to the studio's client id, name, logo URL and app theme, with no client id or session. An unknown or replaced code rejects with `{ kind: "not_found", refusal: { reason: "join_code_inactive" } }`; new types `JoinCodeResolution`, `JoinCodeTheme` and `ResolveJoinCodeOptions` ([#529](https://github.com/cbarlow1993/fytkit-web/issues/529))
21
+ * `resolveJoinCode`'s `studio` gains `coaches` (at most 3; today the studio's own public coach profile alone: `name` and the public profile image as `avatarUrl`, null when there is none; never staff names or emails) and `programCount` (published programmes, any access level; drafts and archived ones never count), for a welcome screen's "Coached by" row and programme count. Additive. New type `JoinCodeCoach` ([#600](https://github.com/cbarlow1993/fytkit-web/issues/600))
22
+ * `FytkitRefusalReason` gains `join_code_inactive` ([#529](https://github.com/cbarlow1993/fytkit-web/issues/529))
23
+ * `auth.resendVerification()`: mails a new email-verification link to the signed-in member's own address (a session is required; there is no email input), for a "Send it again" link on a confirm-your-email screen. Resolves to `{ status: "sent" }`, or to `{ status: "already_verified" }` with nothing sent when the address is verified already. Throttled per account (one send a minute, five an hour): over the limit it rejects with `{ kind: "rate_limited" }`. Fytkit Coach members get the app-link URL, as at sign-up. A bearer call, so a 401 refreshes or signs out as usual. Additive. New type `ResendVerificationResult` ([#615](https://github.com/cbarlow1993/fytkit-web/issues/615))
24
+ * `auth.confirmEmailChange({ token })`: applies an email change from the token in the confirmation link, for an app that opens that link itself (Fytkit Coach's app link). Needs no session; idempotent while the address still holds ([#532](https://github.com/cbarlow1993/fytkit-web/issues/532))
25
+ * `sessionLogs.create` and `sessionLogs.addSet` take an optional `clientRequestId` (a UUID) so an offline queue or a retry can replay a write safely: a replay with the same key returns the stored log or set instead of creating a duplicate log or failing the set with `conflict`, and a key reused for another log or set is `conflict`. On a replayed `create`, `sets` holds the log's current sets, so its type is now the set array instead of `never[]`. Calls without a key are unchanged ([#603](https://github.com/cbarlow1993/fytkit-web/issues/603))
26
+ * `bodyMetrics.record` and `nutritionLog.logFood` take an optional `clientRequestId` (a UUID) for the same offline replay: a replay with the same key returns the stored weigh-in (with the warning `update` would give it) or food log entry, with their current values, instead of recording a second one, and writes no second timeline event. A `logFood` key reused for another `loggedFor` day is `conflict`; a weigh-in's values can all be edited, so a `record` replay returns the stored weigh-in whatever it sends. Keys are scoped to the trainee. `nutritionLog.updateFood` and `nutritionLog.setTargets` take no key: they set absolute values, so a replay is already safe. Calls without a key are unchanged (W17, W18)
27
+ * `sessionLogs.delete({ id })`: deletes one of the member's own session logs, in progress or finished, with its sets, personal records and any staff review, comments and reactions; its timeline events go too, and the streak, personal records and program enrollment completion are recomputed. Resolves to `{ data: { id, deleted: true } }`; another member's log or an unknown id is `not_found`, and the call is refused while a member is impersonated ([#602](https://github.com/cbarlow1993/fytkit-web/issues/602))
28
+ * member coaching loop Phase 2: the `bodyMeasurements`, `progressPhotos`, `timeline`, `health` and `habits` namespaces, `progress.getVolume`, `progression.getE1rmTrend`, `bodyMetrics.update`, and the refusal reasons `staff_entered`, `checkin_sourced`, `consent_required`, `consent_outdated`, `too_many_pending`, `photo_limit_reached`, `invalid_uploads` and `poses_differ`, with the `coaching_habits` entitlement refusal and the `first_party_account` error code ([#461](https://github.com/cbarlow1993/fytkit-web/issues/461)) ([#487](https://github.com/cbarlow1993/fytkit-web/issues/487))
29
+
7
30
  ## [3.5.0](https://github.com/cbarlow1993/fytkit-web/compare/@fytkit/sdk@3.4.0...@fytkit/sdk@3.5.0) (2026-10-10)
8
31
 
9
32
 
package/README.md CHANGED
@@ -48,6 +48,42 @@ await fytkit.auth.signOut();
48
48
  off();
49
49
  ```
50
50
 
51
+ ## Joining a studio and launching
52
+
53
+ A Fytkit Coach style app starts from a studio's join code (its QR code or
54
+ join link) and reads the studio's launch settings once per launch:
55
+
56
+ ```ts
57
+ import { createFytkitClient, resolveJoinCode, resolvePaymentMode } from "@fytkit/sdk";
58
+
59
+ // Once, when the member scans or types the code: keep the client id.
60
+ const studio = await resolveJoinCode(scannedCode);
61
+ const fytkit = createFytkitClient({ clientId: studio.clientId });
62
+
63
+ // Every launch: revalidate the stored settings with their ETag.
64
+ const stored = await loadStoredBootstrap(); // { data, etag } | null, your storage
65
+ const result = await fytkit.app.bootstrap({ etag: stored?.etag });
66
+ if (result.status === "ok") await saveStoredBootstrap({ data: result.data, etag: result.etag });
67
+ const settings = result.status === "ok" ? result.data : stored?.data;
68
+ // settings.studio (name, logoUrl), settings.theme (palette, variants),
69
+ // settings.featureOverrides, settings.minAppVersion, settings.links,
70
+ // settings.realtime, settings.paymentPolicy
71
+
72
+ // "Subscribe" (a link out to the studio's checkout) only where the store allows it.
73
+ const { mode } = resolvePaymentMode(settings.paymentPolicy, {
74
+ platform: "ios",
75
+ storefront: storefrontCountryCode, // StoreKit's Storefront.current?.countryCode
76
+ });
77
+ ```
78
+
79
+ `app.bootstrap` resolves to `{ status: "unchanged" }` (a 304, no body) while
80
+ the `etag` you sent is current. It replaced `appConfig.get` in 4.0.0:
81
+ [`app.bootstrap`](../../docs/developer/reference/app-bootstrap.md), with the
82
+ field-by-field migration. [`resolveJoinCode`](../../docs/developer/reference/resolveJoinCode.md)
83
+ needs no client. `resolvePaymentMode` is plan info ("What your plan includes")
84
+ for every storefront the policy doesn't list:
85
+ [Subscribe or what your plan includes](../../docs/developer/reference/app-bootstrap.md#subscribe-or-what-your-plan-includes).
86
+
51
87
  ## Tracking a workout
52
88
 
53
89
  Log a workout ad-hoc (no program enrollment needed), record sets, complete
@@ -143,7 +179,11 @@ fresh sign-in is `{ kind: "forbidden", code: "reauth_required" }`. None of
143
179
  these sign the trainee out — only a 401 on an authenticated call means the
144
180
  session is gone. A wrong password on `signIn` (for example, signing in again
145
181
  after `reauth_required`) is a plain `auth_required` and leaves the current
146
- session alone. Enrolling twice in a program is `{ kind: "conflict" }` with the
182
+ session alone. An account that also runs or works at a trainer on Fytkit can't
183
+ sign in, sign up or complete two-factor through an app, and an existing app
184
+ session can't change its password or email or close it:
185
+ `{ kind: "forbidden", code: "first_party_account" }`; point it to Fytkit's own
186
+ sign-in. Enrolling twice in a program is `{ kind: "conflict" }` with the
147
187
  existing `enrollmentId`.
148
188
 
149
189
  ## Refusals
@@ -174,7 +214,7 @@ Every reason and its data: [`docs/developer/reference/errors.md`](../../docs/dev
174
214
 
175
215
  `subscribeToMessages` keeps a conversation screen current over Supabase
176
216
  Realtime: it fetches a token (`messages.realtimeToken`) and fytkit's public
177
- connection settings (`appConfig.get`), joins the trainee's own private
217
+ connection settings (`app.bootstrap`), joins the trainee's own private
178
218
  channel (and joins it afresh after Realtime closes it), renews the token
179
219
  before it expires, and reports when to poll instead. Events carry no message content: `onChange` means "read the
180
220
  conversation and the unread count again".
@@ -227,13 +267,14 @@ await fytkit.preferences.updateNotifications({ streakAlerts: false }); // partia
227
267
 
228
268
  ## What's included in this preview
229
269
 
270
+ - `resolveJoinCode` (a studio's join code to its client id, name, logo and app theme, with no client) and `app.bootstrap` (the launch read, revalidated with its ETag), with `resolvePaymentMode` ("Subscribe" link-out or "What your plan includes", per storefront)
230
271
  - `auth.signUp` / `signIn` / `signOut` / `getSession` / `onAuthStateChange`
231
272
  - `auth.requestPasswordReset` / `confirmPasswordReset` / `confirmEmail`
232
273
  - Two-factor: `auth.verifyTotp` / `verifyBackupCode` / `requestEmailOtp` / `verifyEmailOtp` (sign-in), `enableTotp` / `verifyTotpSetup` / `disableTwoFactor` / `regenerateBackupCodes` / `getSecurityStatus` (management)
233
- - Account security: `auth.changePassword` / `changeEmail` / `listSessions` / `revokeSession` / `revokeOtherSessions` / `closeAccount`
234
- - Data namespaces: `workouts`, `programs`, `recipes`, `nutrition`, `nutritionLog`, `profile`, `preferences`, `articles`, `enrollments`, `sessionLogs`, `progression`, `progress`, `bodyMetrics`, `checkIns`, `messages`, `media`, `appConfig`, `subscription`
274
+ - Account security: `auth.changePassword` / `changeEmail` / `confirmEmailChange` / `listSessions` / `revokeSession` / `revokeOtherSessions` / `closeAccount`
275
+ - Data namespaces: `workouts`, `programs`, `recipes`, `nutrition`, `nutritionLog`, `profile`, `preferences`, `articles`, `enrollments`, `sessionLogs`, `progression`, `progress`, `bodyMetrics`, `bodyMeasurements`, `progressPhotos`, `checkIns`, `messages`, `timeline`, `health`, `habits`, `media`, `subscription`
235
276
  - Workout tracking: program session logs and ad-hoc workout logs (`sessionLogs.create` with `workoutId`), set logging, per-workout history (`sessionLogs.list({ workoutId })`)
236
- - Progress: `progress.getSummary` / `getAdherence` / `getStreak` (stored streaks), `progression.getPrSummary` / `listPrHistory` (stored PR history) / `getHistory`, `bodyMetrics.list` / `record` / `delete`
277
+ - Progress: `progress.getSummary` / `getAdherence` / `getStreak` (stored streaks) / `getVolume` (last week's hard sets per muscle group), `progression.getPrSummary` / `listPrHistory` (stored PR history) / `getE1rmTrend` (estimated 1-rep max per session, per lift) / `getHistory`, `bodyMetrics.list` / `record` / `update` / `delete`
237
278
  - Coach feedback on logged sessions: `sessionLogs.getFeedback` / `markFeedbackSeen` / `unseenFeedbackCount`
238
279
  - Check-ins: `checkIns.current` / `submit` / `update` / `list` / `get` / `markReplySeen` / `requestPhotoUpload` (weekly or fortnightly check-ins a coach reviews; needs a plan with `coaching_checkins`)
239
280
  - Messages: `messages.list` / `send` / `requestAttachmentUpload` / `markRead` / `delete` / `unreadCount` / `realtimeToken` (the trainee's conversation with their coaches; starting one needs a plan with `coaching_messages`, answering a coach does not; `requestAttachmentUpload` reserves a photo to send with a message)