3 Commits

Author SHA1 Message Date
Abhishek Sarkar
7f98b069ca docs: correct PRIVACY.md to reflect telemetry is on by default (opt-out) (#4631)
* docs: correct PRIVACY.md to reflect telemetry is on by default (opt-out)

PRIVACY.md stated telemetry is opt-in and "off by default" and that the app
"never starts sending anything before you do." The shipped behavior is the
opposite: `apps/web/src/state/config.ts` defaults `telemetry: { metrics:
true, content: true }` so fresh installs emit onboarding/ui_click events from
the first frame, and the first-run banner is a single-acknowledgement
informed-disclosure notice ("I get it"), not an opt-in gate. The daemon gate
(`if (telemetry?.metrics !== true) return`) therefore lets events flow before
any user decision.

Update the doc to describe the actual opt-out model: telemetry is on by
default, the banner is informed disclosure, and Settings -> Privacy (and the
banner footer) provide a one-click opt-out with per-category toggles. No code
change — this aligns the documentation with what ships.

* docs: clarify the consent banner footer only points to Settings

Per review on #4631: the first-run banner footer carries informational text
('Data sharing is on by default. You can turn it off any time in Settings ->
Privacy.') and does not itself expose an opt-out control — the toggle lives
only in Settings -> Privacy. Reword so the doc matches the shipped UI.
2026-06-23 08:54:57 +00:00
NJUHua
18edd90d31 feat(amr): forward Open Design device id to AMR for cross-product account linking (#4310)
* feat(amr): forward Open Design device id to AMR for cross-product account linking

To link an Open Design install to the AMR account a user registers after the
handoff (so paid AMR users can be traced back to Open Design), pass the Open
Design device id (installation/anonymous id) on the AMR handoff URL as
`od_device_id`. This gives AMR a stable, direct join key rather than relying
only on the one-shot entry id.

- `attributedAmrUrl` takes an optional `deviceId`; emits `od_device_id` only
  when provided.
- The ChatPane recharge handoff passes the device id ONLY when the user has
  opted into metrics (`telemetry.metrics === true`); otherwise it is omitted.
  AMR is Open Design's own official model service, so this is a same-owner
  cross-product link, but it still respects the telemetry opt-in. The handoff
  already opens with `noreferrer`, so the URL is not leaked via Referer.
- PRIVACY.md: add an "Open Design AMR" section disclosing that AMR is Open
  Design's official first-party model service and that information may be shared
  between the two same-owner products to provide and improve the experience.

Surface area: UI (handoff behavior) + docs (PRIVACY.md). No new capability or
CLI surface; analytics-forwarding + disclosure only.

* fix(amr): forward the canonical telemetry device id, not the bootstrap UUID

Review (nettee, elihahah666 — CHANGES_REQUESTED): the handoff sourced
od_device_id from analytics.anonymousId, which is the mount-time getAnonymousId()
bootstrap UUID — not the id telemetry keys on. Once /api/analytics/config
resolves, applyIdentity(installationId) re-registers device_id=installationId
for PostHog/Langfuse, and the two diverge before hydration and after a
Delete-my-data installationId rotation. AMR would then store a join key that
never matches the telemetry device id, weakening the cross-product link this
change exists to strengthen.

Extract amrHandoffDeviceId() which returns the canonical resolved device id
(getResolvedDeviceId(), = installationId once hydrated) when metrics consent is
on, falling back to config.installationId, never the bootstrap UUID; null
otherwise. Use it from the ChatPane recharge handoff and unit-test the gating:
consent off → null; consent on → resolved id; resolved not hydrated → falls back
to installationId; neither → null.

* fix(amr): prefer the freshly rotated installationId in the handoff device id

Review (nettee — non_blocking follow-up): amrHandoffDeviceId() preferred
resolvedDeviceId over installationId, but resolvedDeviceId is a module global in
the analytics client that only updates later, when the App-level setIdentity
effect runs applyIdentity(). config.installationId, by contrast, is the fresh
source-of-truth in the current render. During a Delete-my-data rotation the new
installationId is live while resolvedDeviceId still holds the deleted id until
that effect runs, so the handoff could forward the stale pre-rotation id and
break the exact cross-product join this helper exists to preserve.

Flip the precedence to installationId ?? resolvedDeviceId ?? null. In steady
state the two agree (the client seeds resolvedDeviceId from cfg.installationId),
so PostHog/Langfuse parity is unchanged; neither input is the mount-time
bootstrap UUID, so this does not regress the earlier fix. Extend the helper test
matrix with a rotation case where the two ids differ and assert the fresh
installationId wins.

---------

Co-authored-by: Mason <jinmeihong0201@gmail.com>
2026-06-16 03:54:54 +00:00
kami
30ad8b8ac3 improve privacy consent modal: policy link, clearer CTAs, mobile layout (#1921)
The first-run consent banner had no link to a privacy policy, an
affirmative button ("Help improve") that didn't read as a consent
choice, and a fixed bottom-right card that crowded content on phones.

- Add a "Read the privacy policy" link (external-link icon, accent,
  underlined) above the actions, plus a root PRIVACY.md it points to
  documenting the telemetry behaviour the modal discloses.
- Rename the CTAs to "Share usage data" / "Don't share" so both name
  the action; they stay equal-prominence per the EDPB/GDPR comment.
- Stretch the banner to a bottom-edge bar under 540px with a
  safe-area inset so it clears mobile browser chrome.
- Add PrivacyConsentModal tests; sync the new i18n key to every
  locale and update the consent-label assertion in App.connectors.

Refs #1756
2026-05-17 20:24:15 +08:00