Problem
pages/privacy.js is listener-scoped and says, twice, that WXYC collects no personally identifiable information:
Our website, Android app, and iOS app collect anonymous analytics data to help us understand performance and usage trends. This data does not include personally identifiable information. — §2
No personally identifiable information is collected or used for marketing or advertising. — §3
That was true when the only apps were the listener apps, where users are anonymous. It is false for the two DJ apps, which authenticate a named individual against dj.wxyc.org, hold a session token and a JWT in encrypted device storage, and act on a per-DJ record (/djs/bin). A DJ's username, email, and DJ id are personally identifiable by any reading of the term.
This is not a hygiene issue. Both app stores require the linked privacy policy to match the store's data-safety declaration:
- Google Play Data Safety —
wxyc-dj-android cannot be submitted for review with a declaration that contradicts its linked policy.
- Apple App Store privacy labels — the same for
wxyc-dj-ios, which is already built.
Both apps link to this one page, so this single file is a submission blocker for both.
Desired end state
pages/privacy.js distinguishes the listener apps from the DJ apps and states accurately, for the DJ apps, what identifying data is collected, why, where it is stored, and that it is not used for advertising. The page remains a single document — the goal is one policy that covers four apps honestly, not four policies that can drift.
Where
pages/privacy.js — the whole file is ~60 lines of JSX with <h2>-numbered sections. Sections 2 (Information Collection), 3 (Data Usage), and 4 (Third-Party Services) all need amending; section 1 (Introduction) currently enumerates "our website, Android app, and iOS app" and needs to name the DJ apps too.
- The
Last updated: line (currently March 18, 2025) must be bumped.
What the DJ apps actually do — write the policy from this, not from assumption
Confirmed against wxyc-dj-ios and wxyc-dj-android as built:
- Authentication. A DJ signs in with a dj.wxyc.org username or email, by password or by a mailed one-time code. Backend-Service issues a session token; the app exchanges it for a JWT.
- Storage. Both tokens live in device-local encrypted storage only (iOS Keychain,
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, no iCloud sync; Android encrypted token storage). Not synced, not backed up off-device.
- Identifiers in the JWT. DJ id, email, role.
- Per-DJ data. The bin (
/djs/bin) is a per-DJ record on Backend-Service.
- Telemetry — and this is the part most likely to be got wrong. The DJ apps' error reporting is deliberately stricter than the listener apps', and the policy should not over-declare.
wxyc-dj-ios ships Sentry under a contract that is enforced at compile time, not by review convention: sendDefaultPii off, no screenshots, no view hierarchy, no session replay, no tracing, and error context restricted to types that structurally cannot carry free text. URLs are stripped of query and fragment before an event leaves the device. See wxyc-dj-ios/docs/adr/0007-ios-anonymous-error-reporting.md for the full contract and the three leak channels it closes. Read that ADR before writing the telemetry sentences — declaring "we collect crash reports containing personal data" would be inaccurate in the other direction and would create a Data Safety declaration the app does not match.
- Analytics.
wxyc-dj-ios sends analytics into dj-site's PostHog project (444958) with personProfiles = .never. Org-level context is in WXYC/CLAUDE.md.
Constraints
- One page, four apps. Do not fork a separate DJ policy. Two policies drift, and the drift surfaces as a store rejection months later.
- The listener apps' existing claims stay true and should stay on the page — the fix is to scope them, not delete them.
- The DJ apps are internal tools for station staff, not consumer products; the policy should say who the DJ apps are for.
- Deployment: this site is on GitHub Pages, so the change is live on merge. Nothing to coordinate with an app release.
Acceptance criteria
Related
Problem
pages/privacy.jsis listener-scoped and says, twice, that WXYC collects no personally identifiable information:That was true when the only apps were the listener apps, where users are anonymous. It is false for the two DJ apps, which authenticate a named individual against dj.wxyc.org, hold a session token and a JWT in encrypted device storage, and act on a per-DJ record (
/djs/bin). A DJ's username, email, and DJ id are personally identifiable by any reading of the term.This is not a hygiene issue. Both app stores require the linked privacy policy to match the store's data-safety declaration:
wxyc-dj-androidcannot be submitted for review with a declaration that contradicts its linked policy.wxyc-dj-ios, which is already built.Both apps link to this one page, so this single file is a submission blocker for both.
Desired end state
pages/privacy.jsdistinguishes the listener apps from the DJ apps and states accurately, for the DJ apps, what identifying data is collected, why, where it is stored, and that it is not used for advertising. The page remains a single document — the goal is one policy that covers four apps honestly, not four policies that can drift.Where
pages/privacy.js— the whole file is ~60 lines of JSX with<h2>-numbered sections. Sections 2 (Information Collection), 3 (Data Usage), and 4 (Third-Party Services) all need amending; section 1 (Introduction) currently enumerates "our website, Android app, and iOS app" and needs to name the DJ apps too.Last updated:line (currentlyMarch 18, 2025) must be bumped.What the DJ apps actually do — write the policy from this, not from assumption
Confirmed against
wxyc-dj-iosandwxyc-dj-androidas built:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, no iCloud sync; Android encrypted token storage). Not synced, not backed up off-device./djs/bin) is a per-DJ record on Backend-Service.wxyc-dj-iosships Sentry under a contract that is enforced at compile time, not by review convention:sendDefaultPiioff, no screenshots, no view hierarchy, no session replay, no tracing, and error context restricted to types that structurally cannot carry free text. URLs are stripped of query and fragment before an event leaves the device. Seewxyc-dj-ios/docs/adr/0007-ios-anonymous-error-reporting.mdfor the full contract and the three leak channels it closes. Read that ADR before writing the telemetry sentences — declaring "we collect crash reports containing personal data" would be inaccurate in the other direction and would create a Data Safety declaration the app does not match.wxyc-dj-iossends analytics into dj-site's PostHog project (444958) withpersonProfiles = .never. Org-level context is inWXYC/CLAUDE.md.Constraints
Acceptance criteria
Last updated:bumpednpm run build) and reads as one coherent documentRelated
wxyc-dj-ios/docs/adr/0007-ios-anonymous-error-reporting.md— the authoritative telemetry contract for both DJ apps