Skip to content

Put the accounts on the statistics dashboard, and leave our own out - #174

Open
Alexgodoroja wants to merge 2 commits into
mainfrom
feat/stats-accounts
Open

Alexgodoroja wants to merge 2 commits into
mainfrom
feat/stats-accounts

Conversation

@Alexgodoroja

Copy link
Copy Markdown
Collaborator

What changed

Builds on #173, whose commit is the first of the two here: merging this
lands both, so #173 can be closed rather than merged separately.

The dashboard could already read account figures from the accounts app, but
nothing was linked to it — the deployed relay Worker has APP_STATS_TOKEN and
no APP_STATS_URL, so the panel was never drawn — and the panel it would have
drawn was three numbers at the bottom of the page.

Accounts are the one exact count of people the dashboard has; everything else
on it is a keyed hash guessing at a person. So they now sit directly under the
funnel, which ends at a first keystroke, and are laid out to be read in one
pass:

  • Four headline figures, each against the period before. Accounts there
    are, how many signed up in the range, how many opened the app, and how many
    of those had signed up earlier. The last one is the only number that says
    whether anything is sticking.
  • A day-by-day line of sign-ups and use, for every day since the first
    account, whatever range is selected. Accounts arrive a few a day, so a day is
    the smallest step that says anything and the range picker would reduce it to
    one point.
  • What accounts did in the app (machines linked, sessions registered,
    commands sent, vaults, invites, feedback), how many separate days each
    account has been in it
    , and which sign-up weeks came back.

The account days are only known from the day the app started recording them, so
an older account has days missing through no fault of its own. It is left out
of the "how much they use it" bands rather than counted as a bounce, and the
panel says which day the recording starts and how many accounts the bands are
over.

Leaving our own accounts out

Of the 43 accounts in production today, 26 are ours: founders, colleagues, and
the addresses the end-to-end tests sign up with. They are the most active
accounts there are and were always going to use the product, so left in they
make a quiet week look like a good one.

STATS_EXCLUDE on the accounts app names them — a whole address, or a domain
and its subdomains, with a +tag ignored on both sides. Unset, nobody is
excluded. The dashboard reports how many accounts it left out, in the panel's
own sentence and in the page footer, so the figure can be checked rather than
taken on trust.

It is configuration rather than a list in the source because it names
individual people and this repository is public.

What those accounts do in the app is counted apart from what customers do:
app_events gains an internal column (migration 014), and appEvents()
reports only the customers' side. The rows written before the column existed
cannot be split any more, so the column arrives defaulting to TRUE — it
backfills what is already there as ours and then defaults to FALSE — which
means the change can only ever understate what customers did, never overstate
it, and no row is thrown away to get there.

The address is read to decide all of this and for nothing else. The store
applies the predicate while it still holds the email and answers with a
boolean, so nothing outside it is ever handed an identifier; the route's answer
still contains no uid and no address, and the tests assert it.

Incidental

  • A chart takes points and a step now rather than the trend type and a range,
    which is what lets the account days be drawn by the same code.
  • A tooltip on a point that covers a whole UTC day names that day rather than a
    local time in the middle of it. The 7d trend's step is six hours, so it keeps
    its hour.
  • The Worker's half of the accounts link moved to worker/account-stats.ts,
    where it is testable. A Worker deployed ahead of the app now fills in the
    figures the app does not send yet instead of reporting the whole answer as
    the wrong shape.

Verification

  • npm run check at the root: three typecheck projects clean, 175 tests,
    installer, Docker entrypoint, deploy guard, QEMU manifest, landing SEO,
    mobile controls and Homebrew formula all pass; npm run build:web
    succeeds.
  • app: tsc -b --force clean, 947 tests pass, oxlint adds no warning in
    any file touched here, check-protocol passes.
  • Postgres, not just the memory store: the conformance suite and
    store-postgres.test.ts run against a real Postgres wire protocol
    (PGlite over a socket), which applies migration 014. Every account activity and app events case passes on both stores. Five failures
    remain in authorization codes, migrations and the organization race
    tests — the same ones fail on this branch's base commit, so they are the
    harness, not this change.
  • Rendered the dashboard headlessly against the real production account
    rows
    (read-only, through the Cloud SQL proxy) with the exclusion list
    applied: 17 accounts, 26 excluded. Checked at 1440px and 390px, on every
    range, plus the two degraded states — accounts not linked (the whole block
    including its heading is absent) and accounts linked but unreachable (the
    existing note naming the three variables). No console errors.

Deploy notes

  • Migrate before the Worker, as usual: 014 must be applied before the
    accounts app starts writing internal. deploy-app.yml already does this in
    that order.
  • STATS_EXCLUDE is a secret on the shell-online-app Worker
    (npx wrangler secret put STATS_EXCLUDE). Until it is set, nobody is
    excluded and the dashboard says nothing about exclusions.
  • The relay Worker needs APP_STATS_URL added to its (private) production
    config and a deploy. APP_STATS_TOKEN is already set. Without the URL the
    accounts block does not appear at all, which is the behaviour today.
  • The relay deploy is the one that carries web/**, so the dashboard changes
    reach nobody until it goes out.

🤖 Generated with Claude Code

philip638 and others added 2 commits September 16, 2026 14:07
… its own line

The scheduled download check installed on three fresh GitHub runners every
half hour, and the dashboard counted it: six installs, five installer
fetches, three "ok" outcomes and three or four new machines per run, which
is a day of ninety installs and fifty-six machines that never came back.
Both install scripts now take SHELL_ONLINE_INSTALL_CHECK=1, which puts a
check user agent on every request and reports nothing; the workflow sets
it, and the Worker counts that agent, and monitors in general, as crawlers.
HTTP libraries and PowerShell are tools rather than desktop browsers, so a
Node script is not a person and a Windows install is a run.

The installs tile drew the sessions line, since the trend had no installs
series; it now has installs and started sessions, crawlers left out of
every line. "New" is measured per surface from the day its people were
first counted, and until a whole range has passed since then the split is
replaced by the day it becomes meaningful. Machine rows keyed the old way
are dropped once. Prefetched pages are not views, and the 24h range says
people are counted by UTC day.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The dashboard could already read account figures from the accounts app, but
nothing was linked to it and the panel it drew was three numbers at the bottom
of the page. Accounts are the one exact count of people the dashboard has --
everything else on it is a keyed hash guessing at a person -- so they now sit
directly under the funnel, which ends at a first keystroke, and are laid out to
be read in one pass:

- four headline figures, each against the period before: how many accounts
  there are, how many signed up, how many opened the app, and how many of those
  had signed up earlier;
- a line of sign-ups and use for every day since the first account, whatever
  range is selected, because accounts arrive a few a day and a day is the
  smallest step that says anything;
- what accounts did in the app, how many separate days each of them has been in
  it, and which sign-up weeks came back.

Our own accounts are dropped before any of it is counted. They are the most
active accounts there are and were always going to use the product, so left in
they make a quiet week look like a good one. STATS_EXCLUDE on the accounts app
names them -- whole addresses, or domains and their subdomains -- and the
dashboard says how many it left out, so the figure can be checked rather than
taken on trust. The list is configuration rather than source because it names
individual people and this repository is public.

What those accounts do in the app is counted apart from what customers do:
app_events gains an internal column, and the rows written before it existed are
attributed to us, so the change can only ever understate what customers did.
The address is read to decide that and for nothing else; the store answers the
dashboard with a flag and never an identifier.

Also: a chart now takes points and a step rather than the trend type and a
range, which is what lets the accounts days be drawn by the same code; a
tooltip on a point that covers a whole UTC day names that day instead of a
local time in the middle of it; and the Worker's half of the accounts link
moved into worker/account-stats.ts, where a Worker deployed ahead of the app
fills in the figures the app does not send yet instead of reporting an error.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants