Monorepo for Aesthetic.Computer aesthetic.computer
core docs POSTHOG.md
11 kB

PostHog #

PostHog is additive product analytics. It does not replace the existing operational or traffic systems.

System Ownership
Lith + Silo/Mongo Boot health, piece-run logs, errors, bundle performance, piece-hit counters, access logs, and storage operations
Google Analytics Existing broad site traffic
PostHog Minimized journeys, funnels, cohorts, and experiments

The browser integration is inert until Lith receives POSTHOG_PROJECT_TOKEN. POSTHOG_API_HOST may be https://us.i.posthog.com (default) or https://eu.i.posthog.com. The project token is public browser configuration; personal API keys and OAuth tokens never belong in the repository or HTML. Set POSTHOG_SERVER_ENDPOINT_EVENTS=true separately to enable anonymized, batched endpoint-volume events. This second switch makes the higher-volume server stream an explicit rollout decision. POSTHOG_OSKIEWAR_EVENTS=true separately enables category-only Oskiewar server milestones.

Initial capture is deliberately narrow:

Event Properties
$pageview ac_route, with query, hash, published handles, and source removed
ac_piece_opened built-in piece or null, piece_kind, minimized route
ac_prompt_succeeded the safe destination fields from ac_piece_opened; never prompt text
ac_piece_interacted safe piece fields plus input_kind (pointer, touch, or keyboard)
ac_handle_created no properties; emitted only after the first successful handle claim
ac_media_created media_kind and anonymous account_state; never filenames, codes, or content
$identify Auth0 sub; public handle when available; never email
ac endpoint completed endpoint, method/status/latency buckets, analytics class, aggregate count

Autocapture, session replay, exception capture, performance capture, heatmaps, surveys, and product tours are off. Embedded, packed, local, and preview/icon renders do not initialize PostHog. Do Not Track is respected.

Start with ac_piece_opened to ac_piece_interacted, and use ac_prompt_succeeded as the successful prompt-navigation milestone. Break down by piece_kind and built-in piece. Validate that published pieces have piece = null before adding any dashboard or experiment. Use ac_handle_created and ac_media_created as activation milestones; neither event contains handles, filenames, codes, source, prompts, or media content.

The server event is an anonymous aggregate, not a person event. It uses one Lith-level distinct ID, disables person profiles and GeoIP, and combines equal dimensions into ten-second batches. Never use it for unique-user counts or person funnels; sum its count property for request volume.

Oskiewar #

Oskiewar uses the browser client plus anonymous aggregates from Lith and the session server:

Event Meaning
ac_oskiewar_match_started Browser play began after character selection
ac_oskiewar_live_started First valid state reached a live room
ac_oskiewar_spectator_joined A viewer entered a live or waiting room
ac_oskiewar_round_stored Lith stored a new validated demo
ac_oskiewar_match_completed A stored round completed the match
ac_oskiewar_live_viewed Browser received its first live state
ac_oskiewar_replay_viewed Browser received a stored demo
ac_oskiewar_round_followed Browser followed the next-round transition

Properties are allowlisted categories: source_system, surface, input_family, opponent_type, phase, viewer_state, round_position, duration_bucket, and result. Not every event uses every property. Custom properties never include round or series IDs, raw URLs, fighter handles, replay content, commands, scores, IPs, user agents, or raw errors. Oskiewar routes collapse to /oskiewar or /oskiewar/round before browser capture.

Server milestones use fixed ac-oskiewar-*-aggregate distinct IDs, disable person profiles and GeoIP, and combine equal categories into ten-second batches. Sum properties.count; do not use these aggregates for unique-person analysis. Browser milestones remain ordinary person events.

Endpoint map #

Run the inventory from the repository root:

node toolchain/analytics/posthog-inventory.mjs > /tmp/ac-posthog-inventory.json

The current tree contains 166 function source files, resolving to 165 unique names: 161 statically detected handlers and four helpers or scripts. This is not the same as the number of public routes. Lith also provides aliases, media routes, host rewrites, operational routes, static files, and piece/index fallbacks. lithRouteFamilies in the generated JSON maps those surfaces.

Every function is classified by shared/posthog-policy.mjs:

Policy PostHog treatment
minimized-browser-or-aggregate Browser journey and/or bounded server count
aggregate-status-only Bounded server count only
inventory-only Mapped for context; emits no event
existing-lith-silo-only Remains in Lith/Silo/Mongo
disabled / review-required Fails the inventory test until reviewed

The server count contains only function name, HTTP method, status class, duration bucket, analytics class, and count. It never reads or sends path, query, request or response body, IP, user agent, user ID, authorization header, raw error, or stack. Messaging, MCP, local-machine, admin, and existing operational telemetry classes do not emit endpoint events.

Adding a function source requires an analytics classification. Run:

node --test \
  system/tests/product-analytics.test.mjs \
  system/tests/posthog-server.test.mjs \
  system/tests/posthog-inventory.test.mjs

The inventory test fails when the source/handler counts change or any function lands in review-required. Update the reviewed policy and counts together.

Validation #

Before enabling the server switch, configure only the browser token and verify:

  1. Production $pageview events contain minimized ac_route values.
  2. /@handle/..., /$code, and prompt-source routes become /@published, /$code, and /prompt; no query or hash survives.
  3. Published pieces have piece = null.
  4. Identified profiles contain Auth0 sub and optional public handle, never email.
  5. Replay, autocapture, exception, performance, survey, and tour data remain absent.

For Oskiewar, verify that both a random round URL and a short legacy round URL produce ac_route = /oskiewar/round, then confirm every Oskiewar event's custom properties use only the categories above. Enable POSTHOG_OSKIEWAR_EVENTS=true on Lith and the session server only after this browser check.

Then enable POSTHOG_SERVER_ENDPOINT_EVENTS=true and verify ac endpoint completed has only the documented properties. A useful HogQL request-volume check is:

select
  properties.endpoint as endpoint,
  sum(toInt64(properties.count)) as requests
from events
where event = 'ac endpoint completed'
group by endpoint
order by requests desc

For product behavior, create a funnel from ac_piece_opened to ac_piece_interacted and break it down by piece_kind. Endpoint aggregates answer system-usage volume; Lith/Silo answer failures and diagnostics; neither should be substituted for the other.

Rollback requires no code or data migration: unset POSTHOG_PROJECT_TOKEN to disable browser and server analytics, or unset only POSTHOG_SERVER_ENDPOINT_EVENTS to retain browser journeys. Unset POSTHOG_OSKIEWAR_EVENTS to stop only Oskiewar server milestones.

Product context #

  • The front door is a prompt-driven creative computer. imnew registers, verification establishes the account, handle claims a public identity, and a piece name opens a program.
  • The browser runtime lives in system/public/aesthetic.computer; pieces are in disks/; Lith serves the site and API; Silo is the data and storage console.
  • Built-in, published, and KidLisp pieces are different content classes. Raw source, prompts, paintings, chat, and private account fields are content, not analytics properties.
  • Local tools include Slab/host tooling and the prox, fleet, paper, frame, chat, DM, mail, and calendar MCP surfaces. Their existence and public product role are useful context. Prompt/session content, contacts, messages, mail, calendars, fleet host details, secrets, and local files are not analytics inputs.

Any local-tool analytics must be a separate opt-in proposal with a minimized schema such as { tool_family, operation_class, outcome, duration_bucket }. Never include arguments, file paths, hostnames, handles, recipients, message content, artifact contents, or raw errors by default.

Self-driving #

PostHog's setup command is:

npx @posthog/wizard self-driving

Do not run it unattended. It can connect GitHub, enable replay and error tracking, configure signal sources, and schedule scouts. Before activation:

  1. Verify production events and the privacy contract above.
  2. Decide whether AI data processing is acceptable.
  3. Resolve repository routing: Tangled is authoritative and GitHub is currently documented as a read-only mirror, while PostHog requires a writable GitHub repository to open PRs.
  4. Grant only the selected GitHub repository, require human review and merge, and leave deployments outside PostHog.
  5. Review every proposed signal source. Replay, error capture, support, Slack, and local MCP content stay off until each has its own privacy review.

Self-driving creates a branch and PR for an actionable report; a human still reviews and merges it. Start with one manually reviewed report, not broad autonomous production mutation.