CCB is a Facility-run training platform for Coaches, Admins, and Athletes. The product runs on the storage below, and on nothing else. This page is the complete inventory.
Why CCB uses storage
CCB stores only what it needs to authenticate Users, authorize anonymous Athletes, keep Work-Phase submissions recoverable across a network drop, and remember a small set of interface choices. None of it is sold, shared with advertisers, or used to build a profile of you off the platform.
Strictly necessary
Required for sign-in, anonymous Athlete access, Session authorization, and Work-Phase submission recovery. Disabling these will sign you out, remove anonymous Athlete access, or drop in-flight submissions on a network drop.
CookieStrictly necessary
ccb.session (dev) | __Secure-ccb.session (prod)
Stored under ccb.session (dev) | __Secure-ccb.session (prod).
Keeps a signed-in User authenticated in the CCB platform. The cookie carries a signed JWT and is read-only by JavaScript.
Owner
Collective Cadence Burden
Retention
7 days from sign-in
Required for
Account access for Coaches and Admins
Facility and Coach authorization
Cross-Session Athlete history
CookieStrictly necessary
athlete_token_{sessionId}
Stored under athlete_token_{sessionId}.
Proves that this browser joined a specific Session as an anonymous Athlete. The token is HMAC-bound to the Session and is read-only by JavaScript.
Owner
Collective Cadence Burden
Retention
Up to the configured Athlete token lifetime
Required for
Athlete Session state
Private recap access
Anonymous Athlete realtime authorization
Local storageStrictly necessary
pendingReportFreeze
Holds a Work-Phase Checksum payload that the Athlete Node could not deliver to the server after three retry attempts. The queued payload is replayed on the next page load or connectivity event.
Owner
Collective Cadence Burden
Retention
Removed on the next successful POST; removed on station advance; cleared on a malformed entry
Required for
Work-Phase Checksum delivery across a transient network drop
Recovery of an explicit Athlete tap on a single Station
Local storageStrictly necessary
pendingRacePracticeResult
Holds a Race Practice station result that the Athlete Node could not deliver after three retry attempts. The queued value is scoped to a single station so it cannot replay against a different station.
Owner
Collective Cadence Burden
Retention
Removed on the next successful POST; removed when the Athlete advances to a new station
Required for
Race Practice result delivery across a transient network drop
Recovery of an explicit Athlete result entry on a single station
Session storageStrictly necessary
ccb.joinAttempt:{sessionId}
Stored under ccb.joinAttempt:{sessionId}.
Caches the Join-attempt token (a short-lived HMAC-signed bearer) so that retries during a single page render resolve to the same anonymous Athlete identity. The storage key is the server-provided Session identifier; the QR bearer token is never embedded in the key.
Owner
Collective Cadence Burden
Retention
Until the form tab is closed; cleared on cap-rejection and on a malformed entry
Required for
Retry idempotency — the same cached token resolves to the same `athleteId` across retries, which is what the atomic join's single-row guarantee depends on
Cap-rejection re-mint flow — the cache is cleared on cap-rejection so the next attempt mints a fresh token (and a fresh `athleteId`) for the retry
Local storageStrictly necessary
ccb.consent
Holds the visitor's per-purpose consent decision (analytics, diagnostics) so it persists across page loads. No Visitor identifier, no fingerprint, no tracking payload — only the per-purpose state, the policy version, the decision timestamp, and the surface that produced the decision.
Owner
Collective Cadence Burden
Retention
Until the visitor clears browser storage, calls the in-product 'withdraw all optional consent' action, or the consent-policy version is bumped (obsolete records are treated as 'unknown' and re-prompted)
Required for
Remember the visitor's affirmative consent decision across page loads
Avoid re-prompting a visitor who has already made a decision
Distinguish 'unknown' (no decision yet) from 'declined' (explicit rejection) — the conservative default never silently converts a rejection to an acceptance
Local storageStrictly necessary
ccb.consent.pending
Fail-closed marker for an in-progress consent change. Holds the canonical structured PendingConsentMutation at schemaVersion 1 (kind, mutationId, consentVersion, requestedDecision, effectiveDecision, surface, requiresAccountSync, createdAt) so a half-applied change restores the conservative posture on the next mount rather than being lost. Carries no Visitor identifier. The canonical persistence status surface drives the visitor's recovery path; the marker does not auto-replay.
Owner
Collective Cadence Burden
Retention
Cleared on successful write completion (mutationId-scoped); also cleared synchronously for anonymous visitors after the local writes; an obsolete-consentVersion record is normalized (decline preserved, accept demoted to unknown); a malformed record is treated as no record. The marker is NOT automatically replayed on the next page load — recovery requires an explicit Retry, or authentication restoration followed by Retry, to commit the change to the server.
Required for
Restore a half-applied consent change on the next page load (the visitor never silently loses a decision they just made)
Order-safety — stale completions from a prior write cannot clear the current marker (mutationId-scoped)
Authenticated acceptance fail-closed — the bootstrap applies effectiveDecision (previous state) until the server confirms, so an unconfirmed acceptance cannot enable Analytics or browser Sentry on reload
Session-expiry recovery — an authenticated unauthenticated failure transitions to waiting_for_auth (not unsynced) and the visitor sees a sign-in-again prompt, not a misleading Retry
Storage-failure recovery — a transient localStorage failure transitions to error with the storage_unavailable error kind and a functional Retry that re-attempts the local writes (anonymous) or the local writes followed by the account-sync Server Action (authenticated)
Reconciliation on cold start — a pending mutation read on mount informs the coordinator to surface the canonical recovery UI rather than treat the local write as final
Functional preferences
Browser storage that remembers a UI choice the visitor made (theme, language). The product continues to work without them; the visitor just loses the preference and re-picks on the next load. CCB does not gate these on consent because the preference belongs to the visitor in the first place.
Local storageFunctional preference
ccb-theme
Holds the visitor's light / dark / system theme choice so it persists across page loads. Removing the value falls back to the system preference; the product continues to work.
Owner
Collective Cadence Burden
Retention
Until the visitor clears browser storage or switches theme
Affects
Remembered theme choice across page loads (preference, not a hard requirement)
Session storageFunctional preference
ccb-deck-lang
Holds the investor-deck language selection for the current tab. Removing the value falls back to English; the product continues to work.
Owner
Collective Cadence Burden
Retention
Until the browser tab is closed
Affects
Remembered deck language within the current tab (preference, not a hard requirement)
Error reporting
CCB reports unhandled browser errors to Sentry so the team can fix bugs quickly. The reporter does not write any cookies or browser storage in the current configuration, and every event passes through a PII-stripping filter before it leaves the browser. The filter covers four surfaces: the request cookie jar, the top-level extra object, the top-level user object, and the top-level data object on each breadcrumb. On those surfaces, it removes the identifiers that pin an error to an individual User, Athlete, Session, or Facility, redacts emails and magic-link URLs from string values, and deletes auth-shaped cookie keys. Other event surfaces (tags, contexts, exception values, request headers and URL, nested extra or breadcrumb objects, breadcrumb messages) are not touched by the filter and should not be relied on to remove sensitive material. CCB never sets those fields with personal data.
Errors onlySentry
Sentry receives unhandled browser errors and unhandled promise rejections. No session replay, no performance tracing, no product analytics, and no default PII.
PII filtering: CCB-owned beforeSend hook strips emails, magic-link URLs, auth-cookie values, and athleteId / sessionId / boxId / userId from the four surfaces it touches: top-level request.cookies, top-level event.extra, top-level event.user, and the top-level data object on each breadcrumb. Other event surfaces (tags, contexts, exception values, request headers / URL, nested objects, breadcrumb messages) are not covered — CCB never writes personal data there.
No advertising or tracking
CCB does not use advertising or marketing trackers, and does not share any data with advertising networks. The two optional purposes CCB does offer — first-party product analytics and browser error reporting — are both consent-gated. The visitor can turn each one on or off independently at any time from the Your privacy choices panel below. A future change in posture (new third-party integration, new optional purpose) will require a consent boundary — see Changes to this page below.
First-party analytics (cookieless, consent-gated)
The public CCB pages can send a single anonymous event per page view, CTA click, and outbound-link click to a first-party endpoint at POST /api/track. The endpoint is cookieless: no identifier, no persistent client ID, no session stitching, no fingerprint, no advertising profile is built from it. The wire shape accepts only name, path, slug, cluster, cta, href, and ts; any other field is dropped at the schema stage. Magic-link URLs, QR-join tokens, and email addresses that happen to appear in the wire fields or in the browser's Referer header are redacted to [redacted] before the line is written. The redactor is the same PII strip list used by the Sentry beforeSend filter.
The endpoint writes the redacted event to the application's standard log stream. It is not sent to a third-party analytics provider, and the Sentry error reporter is not invoked by the analytics path.
Analytics is opt-in. The tracker is inert until the visitor turns the “Analytics” switch ON in the Your privacy choices panel below. Until then the browser sends nothing to POST /api/track. Turning the switch back OFF removes the tracker without a page reload and suppresses further events for the rest of the session.
The full story of what CCB collects, why, and the rights you have over it is on the Privacy Policy.
Browser error reporting (consent-gated)
The browser-side Sentry SDK is opt-in. The SDK is inert until the visitor turns the “Diagnostics” switch ON in the Your privacy choices panel below. Until then unhandled browser errors are not reported. Turning the switch back OFF tears the SDK down for the rest of the session. The same teardown runs whenever the diagnostics state transitions to unknown — for example after a consent-policy version bump, after clearing the consent record, or any reconciliation that returns unknown. The PII filter described in the error reporting section above is unchanged.
Removing browser storage
You can remove any of the above from your browser at any time. The trade-off: clearing the session cookie signs you out, clearing the anonymous Athlete cookie removes that browser's proof of join for any in-progress Session, and clearing the recovery queues drops any Work-Phase or Race Practice submission that the browser had not yet delivered to the server.
Changes to this page
This page is updated whenever CCB introduces a new cookie or storage key, expands a storage purpose, or integrates an analytics, diagnostics, or marketing tool that requires consent. The current product has no third-party analytics or marketing integration. First-party analytics and browser diagnostics are consent-gated as described above.
Contact
Questions about CCB's storage choices can be sent to the team via the Contact link in the site footer.