Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
df1e856
docs(spec): posthog-analytics-identity-funnels-and-errors spec, plan …
denizmrtoglu Sep 21, 2026
793246f
feat(telemetry): resolve a stable anonymous install id
denizmrtoglu Sep 21, 2026
57b49a0
feat(telemetry): send to PostHog instead of Aptabase
denizmrtoglu Sep 21, 2026
14739bf
feat(telemetry): flush the PostHog batcher before quitting
denizmrtoglu Sep 21, 2026
b909aa3
feat(telemetry): opting out deletes the install id
denizmrtoglu Sep 21, 2026
243676e
feat(telemetry): sanitize exceptions before they can be sent
denizmrtoglu Sep 21, 2026
082e828
feat(telemetry): opt-in error reporting on its own channel
denizmrtoglu Sep 21, 2026
241fd30
feat(telemetry): forward renderer exceptions to the error channel
denizmrtoglu Sep 21, 2026
e14f46b
feat(settings): an opt-in row for error reports
denizmrtoglu Sep 21, 2026
0d3346e
feat(telemetry): version the disclosure notice
denizmrtoglu Sep 21, 2026
d8d4220
docs(privacy): PostHog, the install id, and the error opt-in
denizmrtoglu Sep 21, 2026
4dda951
docs(spec): posthog-analytics-identity-funnels-and-errors done
denizmrtoglu Sep 21, 2026
c67926f
docs(telemetry): the lazy-require comment names posthog-node
denizmrtoglu Sep 21, 2026
a781667
feat(telemetry): wire the PostHog project token
denizmrtoglu Sep 21, 2026
1bcb64f
feat(telemetry): derive country and region from the request IP
denizmrtoglu Sep 21, 2026
b42569a
feat(telemetry): six behaviour events on chokepoints, not buttons
denizmrtoglu Sep 21, 2026
37b9cba
refactor: telemetry is called analytics
denizmrtoglu Sep 21, 2026
0a28f1f
fix(analytics): pass disableGeoip:false — omitting it leaves geo OFF
denizmrtoglu Sep 21, 2026
c56358f
feat(analytics): session id and active-time-per-session
denizmrtoglu Sep 22, 2026
ea097e7
feat(analytics): send to Aptabase alongside PostHog
denizmrtoglu Oct 5, 2026
6823b39
chore(structure): regenerate after Aptabase restore
denizmrtoglu Oct 5, 2026
5efc38c
chore(tasks): extend the key-to-env task to cover PostHog
denizmrtoglu Oct 5, 2026
a0fe0fc
Merge kaanozhan/main into main
denizmrtoglu Oct 5, 2026
dc3ff2c
test: stub posthog-node where Aptabase is stubbed
denizmrtoglu Oct 5, 2026
9489ecb
Merge remote-tracking branch 'origin/main' into pr-165
Oct 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions .frame/PROJECT_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,84 @@ mainWindow.webContents.openDevTools();

## Session Notes

### [2026-09-21] IP-based location turned on, one day after turning it off

**Context:** The PostHog migration shipped with `disableGeoip: true` and
PRIVACY.md said, in two places, that location is never derived or stored. Asked
the same day whether country was visible in the dashboard, the answer was no —
by our own choice.

**Decision:** Reverse it. Geo lookup is on; PostHog resolves the request IP to
country/region on arrival and discards the IP. The alternative offered was
device timezone (`Europe/Istanbul`), which answers the same question without
touching a stated promise; it was declined in favour of the IP path.

**What this cost, recorded so it is not forgotten:** a promise published in the
morning was withdrawn in the afternoon. PRIVACY.md now has an **Approximate
location** section stating what is derived rather than a line saying it is not —
the doc is accurate again, but it is the second version of a claim about the same
subject in one day. `NOTICE_VERSION` went to 3, so every user is interrupted a
second time within days of the first card; the second card is what makes the
change honest and also what makes the first one look provisional.

**Rule reaffirmed (it held):** `NOTICE_VERSION` bumps when the disclosure
changes. This is exactly that case, and bumping was not optional — collecting
location under a card that says location is not collected would have been the
real failure.

**Open:** if location is ever narrowed or removed again, do not quietly drop the
PRIVACY.md section — a third revision of the same claim needs to say what changed
and why, or the document stops being read as a commitment.

---

### [2026-09-21] Analytics moved to PostHog — reversing the Aptabase decision

**Context:** `audit-q3-product-analytics` (July) chose Aptabase deliberately and
recorded a rejection of PostHog: *"PostHog's funnels/identity features exceed the
spec's out-of-scope line (no per-user tracking)"* (`plan.md:11`). It measured
activation as unique-users-per-event and accepted the imprecision. Two months of
using that dashboard showed the cost: Aptabase attaches no identifier at all, so
`app_started` cannot distinguish one person launching forty times from forty
people launching once, and there is no funnel, no retention and no per-user
feature usage. Every question worth asking about the roadmap turned out to be
user-level.

**Decision:** Reverse it. PostHog EU cloud, with a random per-install UUID as the
distinct id. The July constraint was right about the trade — identity is a real
privacy cost — and wrong about which side of it to take, because the alternative
was not "less data", it was "no answers".

Four choices shaped how the cost gets paid rather than avoided:

- **Default-on opt-out kept**, not switched to opt-in. On a developer tool opt-in
yields 5–15% participation, and none of the questions survive that sample. The
cost is paid by disclosure instead.
- **Anonymous only.** No self-identify field, no email. The PRIVACY.md line "no
email addresses or any personally identifiable information" stays true as
written.
- **The notice re-shows.** People acknowledged a system with no identifier;
`telemetryNoticeShown` became `telemetryNoticeVersion` so the changed text
reaches them once. Default-on is only honest if the change is surfaced.
- **Error detail on a separate opt-in channel**, not a redacted `message`
property on `error_occurred`. A free-form property would end the registry's
mechanically enum-only guarantee, and nothing would stop the next widening.

**What did not change:** the `telemetryEvents.js` registry and `validateEvent`
gate, the fail-closed opt-out, and the renderer-revalidated-in-main rule. Only
`track()`'s final send call moved. No event was added — the user-level views come
entirely from attaching the install id to the eleven events that already existed.

**Rules established:**
- Opting out **deletes** the install id; re-enabling mints a new one. An opt-out
that leaves a resumable identifier is not an opt-out.
- `NOTICE_VERSION` is bumped only when the disclosure itself changes — it is not
a release counter, and a bump interrupts every user once.
- The error channel never becomes an event. If exception detail is ever wanted in
the registry, that is a decision to re-open here, not a property to add.

---

### [2026-01-25] Project Navigation System

**Context:** When Claude Code enters a project, it needs to quickly capture the context.
Expand Down
Loading
Loading