Public website interaction model #
The first-party collector is POST https://aesthetic.computer/api/visit-track.
The browser module and reviewed host catalog live in
system/public/aesthetic.computer/lib/visit-{tracker,model}.mjs.
This supplements Cloudflare request/byte totals, boots, piece logs and stored
replays. It does not export operational records to PostHog.
What the next 72-hour report can say #
| Measure | Definition |
|---|---|
| Visit | One visible top-level page load with a random in-memory ID; returning from a private route starts a fresh visit |
| Interacted visit | Visible-page trusted pointer/touch/keyboard/wheel input outside form fields, or focused gamepad input (button or stick beyond 0.5) |
| Engaged visit | An interacted visit with at least 10 seconds of accumulated visible time |
| Action visit | At least one occurrence of a reviewed action during that visit |
| Known automation | WebDriver, recognizable bot UA, explicit render query, or window.acAutomation = true |
| Unknown audience | A non-automated visit without interaction evidence |
“Likely human” means non-automated + interacted; it is not proof of personhood. An automation framework can generate trusted events. Untagged automation may remain. IPs are not counted as people. No cross-page retention, unique-user, cross-domain journey or conversion-attribution claims can be made from these ephemeral IDs. A returning person loading three pages produces three visits. In-page SPA navigation and Oskiewar's automatic round-URL changes preserve the visit ID; the surface dimension describes the broad landing category.
Visible-time lower-bound buckets are 0, 10, 30, 60, 180 and 600 seconds. This is foreground display time, not proof of attention. Polling gaps are capped at two seconds so sleep/suspension does not fabricate engagement. All actions are boolean per visit, not totals of clicks, rounds or downloads. Download clicks do not prove completed downloads. Canvas interaction does not prove a saved painting. Media starts count only after interaction.
Actions #
Common pages record link_followed, download_clicked, canvas_interacted
and media_started, with no destination URL, canvas content or media name.
Oskiewar additionally records round_started, round_completed (successful
replay upload), and match_completed (uploaded final score reaches five).
These are interaction-qualified, per-visit milestones; the existing replay
collection remains the authority for round totals. A failed upload will not
produce a completion milestone even if the player finished the round.
Product code can call window.acVisits?.action(name) for a reviewed action.
New actions must be added to the shared allowlist with a documented success
condition and a test. Do not infer publish/purchase/account success from clicks.
AC pieces running in the worker can send
{ type: "visit:action", content: { action: "reviewed_name" } } through their
existing api.send. BIOS forwards only the action name to the same collector;
no piece source, notes, command text, media identifiers or account data are sent.
The collector still requires visible-page interaction and respects private
routes, opt-outs and known automation. These hooks do not use PostHog.
| Action | Success condition |
|---|---|
canvas_interacted |
Trusted pointer/touch contact with a canvas, or inside AC's marked pointer-transparent display; overlaid DOM controls and keyboard input alone do not qualify |
note_played |
Notepat accepts a manual pad, keyboard or MIDI note and triggers its voice path; wrong song notes and Autopat do not qualify; this is not proof of audible output |
painting_edited |
No Paint commits an accepted proposal to the artwork and undo history; generated previews do not qualify |
recording_started |
The runtime's MediaRecorder emits start; recorder requests or permission prompts alone do not qualify |
painting_saved |
PNG upload tracking returns a successful media record with a code |
tape_saved |
ZIP/MP4/WebM upload tracking or tape draft finalization returns a successful record with a code; downstream transcoding may still be pending |
MIDI-only use still needs an independently recorded interaction in the visit. The new creation hooks cover Notepat, No Paint and the shared media upload paths, not every piece or every recording implementation. These per-visit flags describe occurrence, not note/stroke totals or unique people. Historical zeros before these hooks ship mean unmeasured activity, not absence of use.
Storage and privacy #
network-visits stores one document per property + random visit UUID. A
cumulative snapshot with Mongo $max and a unique _id preserves milestones
despite retries or out-of-order delivery. Automation can be upgraded to true,
never downgraded. A TTL on expiresAt expires rows after 35 days; there is no
permanent raw-event stream. Dates come from the server, not the client clock.
The property comes from the HTTPS Origin allowlist, never a submitted hostname.
An origin header and self-reported events are not cryptographic proof of human
activity. The collector rejects unreviewed values and oversized bodies and
uses a bounded, in-memory rate guard (240 requests/minute/source).
No stored IP, user agent, account/handle, full URL, query string, page text,
form value, key or pointer coordinate. The transient rate-limit digest is
process-salted, expires after one minute and never leaves memory. Requests omit
credentials and referrers. No tracking cookies or browser storage are used.
DNT, GPC, window.acVisitTrackingDisabled = true, private routes and embedded
frames suppress collection. The disclosure is /network-privacy.html.
New visit records include referrerHost: the browser-reported referring
hostname, stripped of credentials, path, query and fragment. Local hosts and
IP literals are excluded. Null means direct or unavailable, not necessarily
direct traffic. Older visit records without this field are unmeasured.
Render/test harnesses should set window.acAutomation = true before loading
the module, or append ?ac-automation=1. Existing social-preview,
offline-render and jev-vs-jev parameters also mark automation. Headless
browser QA must remain automated in production verification.
Readout #
On Lith:
cd /opt/ac/system
node --env-file=.env ../toolchain/analytics/visits-report.mjs --hours 72
The default scope is Studio. Use --scope clients for client properties or
--scope all for both. Scope applies to every table, time period and earliest
retained event. Classification is derived from the reviewed canonical domain,
including older records; clients never enter the default studio totals.
Optional --end 2026-09-26T20:00:00Z makes a report reproducible. Rows group by
property, automation classification and broad surface. Report automation
separately; subtract interacted from visits for unknown audience. The sum of
visible-time buckets is a lower bound, not exact duration. Compare with edge
requests to describe crawler/polling volume, but never subtract these different
instruments to invent a bot count. A report is grouped by visit start: later
milestones can update an earlier cohort. lastSeenAt is available for live use.
The earliest retained event indicates available history, not deployment time. Check the deployment/coverage record before interpreting a zero. Archived aggregate reports may be kept without visit IDs. No retrospective backfill is possible for the period before installation.
Reports include actionVisits (visits with any reviewed action) and cumulative
interacted30, interacted60, interacted180, interacted600 visible-time
thresholds. The analytics MCP exposes thresholds under depth, keyed by seconds.
Action totals overlap; do not sum them to count visits. Lith's daily rollup
retains these counts and each action flag for newly folded days. Previously
written daily rows are unchanged and may lack these fields; missing means
unavailable, not zero. Raw visit reports can still aggregate retained records.
AC Human Fishery #
The analytics MCP's human_fishery tool reads Silo's existing _firehose
(silo/server.mjs, MongoDB change stream → history + WebSocket dashboard),
filters to network-visits, and resolves recent, non-automated visits with
interaction. It adds no separate stream or raw event storage. Firehose
throttling/deduplication means this is a sampled operational feed, not a complete
audit log. Call with { "minutes": 5, "scope": "studio" }, then repeat
after at least 15 seconds. Use startedAfter (ISO UTC) to watch only new visits
after a deployment. The maximum lookback is 60 minutes and the maximum result
is 200 visits; truncated explicitly marks an incomplete snapshot.
Each fish has a temporary name derived from its random visit ID and the UTC day. Raw visit IDs stay on Lith. The tool exposes only the public property, broad landing category, arrival/last-report time, visible-time bucket and reviewed action flags. A fish is one visit, not a person. Same-page actions can accumulate, but separate page loads and domains cannot be connected. There is no stored event sequence: compare successive snapshots to see newly observed milestones, not the exact order or time in which actions occurred. The last report is the last changed snapshot, not continuous presence or departure. The tool reads existing data; it adds no browser identifiers or new retention.
Coverage #
Account activity and referrers #
POST /api/account-activity verifies a bearer token through the existing
authorization service. Identity is taken only from the verified account;
submitted user/handle fields are ignored. AC shell piece loads and reviewed
actions use a separate in-memory session and client sequence. Built-in public
piece names are retained; published/inline programs use published-or-code.
Sotce uses its own authentication tenant and the broad sotce category, without
diary page IDs or contents. Embedded shells and private routes are excluded.
Only signed-in activity after installation is available. Login does not replay
anonymous actions, and there is no join to anonymous visit IDs.
account-activity stores server receipt time, verified account subject, tenant,
property, session, sequence, piece, action and referral hostname. Actions dedupe
per piece load; receipt order can differ from client sequence. The endpoint has
no public read route, bounded requests and a per-account rate limit. Rows expire
after 35 days; each site's account deletion removes its tenant's rows. Separate
Sotce identities remain separate accounts. Existing Silo operational firehose
history has its own retention. These records are not sent to PostHog.
The private analytics MCP exposes:
account_activity({hours:24, handle:"@handle"}): verified account events, public handles where available, otherwise an account alias, and temporary session aliases. Counts are accounts, not unique people. Omithandlefor all recorded accounts. Results are bounded and report truncation.network_referrers({hours:24}): referral hosts grouped by property, with visits, interacted visits and engaged visits. Existing AC boot logs supply a separate historical referral table, stripped to hostnames. Do not add boot counts to visit counts; they measure different things. Neither table proves human identity or a complete marketing attribution chain.
Both default to studio scope and accept limit (up to 500). No campaign tags
are collected. Missing data before deployment cannot be reconstructed.
Use account_activity({hours:24, property:"sotce.net"}) or
network_referrers({hours:24, property:"sotce.net"}) to isolate Sotce; add
handle to follow a particular account with a resolvable public handle.
Sotce's authenticated feed additionally records sotce_page_viewed after two
foreground display seconds and sotce_page_visible_30s after thirty. Only the
displayed, loaded card qualifies: prefetched pages, flipped backs, transitions,
editors and hidden tabs do not. Time gaps are capped at one second. Returning
to a page after viewing another can produce another milestone; no page key,
number or content leaves the browser through this feed. These indicate display,
not verified reading or unique pages. Canvas and virtualized DOM views are covered.
sotce_page_touched requires a newly inserted touch (touchCreated: true),
excluding existing touches, the author's own page and failed writes.
sotce_question_submitted requires a successful saved question; it is the sole
allowed milestone within /ask, with no form content. /comment, /chat,
/write and /respond remain excluded. These four Sotce milestones can repeat
within a session and carry client sequence numbers. They remain best-effort
browser reports with server-verified identity; the existing sotce-touches
and sotce-asks collections are authoritative for saved operation totals.
Laer Klokken feature use #
/laer-klokken aliases to laklok. Both that canvas piece and the standalone
HTML sister (laklok.com/html/, recorded as laklok-vector) now send repeated
authenticated feature events. lib/laklok-activity.mjs is the reviewed catalog
and identifies which controls exist in each interface. It contains no message,
recipient, link, chosen theme or language values. Boot restores and repeated
clicks on the already-selected theme/filter do not count as changes.
The canvas worker uses account:action; BIOS passes only the action name to
the first-party account collector. The HTML client uses its existing Auth0
session, rechecks token expiry when sending, and posts to the same endpoint.
The server verifies identity and rejects Laklok events attributed to another
piece. These detailed counts require sign-in; anonymous visits keep their
existing broad measurements. Opt-outs, private routes and automation guards
still apply. Radio/send/edit/media/navigation events are named *_requested
and must not be reported as successful playback, delivery or completed loading.
Use feature_usage({hours:168}), optionally with handle:"@someone", for ranked
features, counts per account and UTC daily opens/action counts. Maximum lookback
is 840 hours (35 days), with at most 50 account rows. Top-feature totals cover
all matching accounts even when account detail is truncated. property can
restrict to a reviewed host; omitting it includes Laklok served through AC too.
Reports require featureVersion:1, so earlier piece opens do not fabricate
unused-feature rows. notRecorded means zero recorded uses of a control
supported by that account's observed interface, not proof the control was
visible or unused. Days without events are unobserved. Counts are best effort:
offline use, opt-outs, unloads and rate limits can leave gaps. Repeated Laklok
events are not deduplicated while a prior request is in flight; the client caps
concurrent sends at 20 and the server's per-account rate limit still applies.
Website installation #
The shared AC shell covers AC, notepat.com, nopaint.art, laklok.com and mime.ac
when those domains serve it. Static entry pages cover Whistlegraph, Jas,
KidLisp, Prompt, Aesel, Just Another System, Quiltnet and the public AC paper,
giving, bills, pop, NFT and language front doors. Oskiewar uses its standalone
shell. Sotce uses its function-generated HTML. Client domains on the same DNS
account (false.work, danzballet.studio, regarde.io, drvkforlife.com) are
collected under Clients, following explicit authorization. Public landing
pages only; draft/labs/builds hosts are excluded. Shopify uses
visit-shopify.mjs and the existing
Customer Privacy API
analyticsProcessingAllowed() decision. Denial or revocation stops collection;
regrant begins a fresh visit. Merchant settings and consent are never changed.
www aliases roll up to the same property. Reviewed public subdomains are
explicit; unlisted hosts are denied. Archived RDP painting pages and other
static documents without the script are not automatically covered.
Known deployment boundaries discovered September 23, 2026:
- menuband.app
/redirects to the App Store: no browser visit can fire on the redirect. Its hosted support/advanced pages are instrumented. App usage and App Store downloads are separate instruments. - Client regarde.io is on Cloudflare Pages, outside the Lith deploy; deployed and browser/database verified September 23.
- Client drvkforlife.com is on Shopify, outside the Lith deploy; live theme and consent-allowed browser/database collection verified September 23.
- Client false.work redirects to www.false.work on Squarespace. The Lith source is prepared, but live installation requires Squarespace access. Do not count it as covered. Danz is deployed and browser/database verified.
- wipppps.world currently serves an external site despite old Lith routing.
- aesthetic.direct and digitpain.com did not answer the initial HTTPS probe; local entry sources are prepared, but live coverage is not assumed.
- sotce.net failed this host's TLS probe, but the collector subsequently received a non-automated visit and interaction from Sotce. Do not interpret a failed local probe as proof that the property is globally unavailable.
The domain allowlist is not a deployment-completion list. Run the coverage
audit after shipping; external deployments require their own source/control
path. Cloudflare's account inventory and Porkbun's registrar inventory were
both consulted; neither alone is a complete list of public web properties.
visits-deployment.json preserves the initial front-door audit and the four
properties verified end-to-end through a browser and MongoDB. Measurement
began September 23 at 18:34 UTC; no pre-installation history is invented.
Verification #
node --test system/tests/visit-tracking.test.mjs
PLAYWRIGHT_CHANNEL=chrome node --test system/tests/visit-tracking-browser.test.mjs
The browser check exercises trusted versus synthetic input, form exclusion, engagement, duplicate installation, private SPA navigation and GPC. Production checks should use marked automation and verify database milestones as well as HTTP responses. Do not label a 204 alone as verified measurement.
visits-clients-deployment.json records the separate client rollout.
MIME's explicit media controls add mime_interact, mime_scroll_feed, and
mime_original_open. These count visits with the action, not total clicks.
Automatic re-locking when a card leaves the viewport does not count as a click.
They use the existing anonymous visit collector, opt-outs, automation flag,
retention, and Studio scope; no post ID or destination URL is added.
For bounded media loading probes, run node toolchain/analytics/media-speed.mjs --all-types --out report.json. Set MIME_CDP_URL to a dedicated Chrome's
loopback DevTools URL to include video first-frame timing. The public MIME
catalog supplies samples of shared AC media, not an exhaustive crawl of every
studio domain. The probe reads at most a 128 KiB prefix per asset request and
checks declared MIME against MP4/WebM signatures. Video playback stops at the
first presented frame or a 20-second timeout. Source fetch timings for programs
are not runtime/render-ready timings. Do not compare a prefix download to a
full archive download or interpret one run as a stable network percentile.
Native app launches #
POST /api/app-open (system/netlify/functions/app-open.mjs) takes
{app, version, platform, install, fresh} from our native apps on each launch.
install is a random UUID minted on first launch and kept in the app's own
defaults (not the Keychain), so deleting the app forgets it; fresh is true
on that first launch. A reinstall therefore looks like a new install; the App
Store report (app_downloads) is where redownloads and restores are told apart.
app-opens stores one row per app + install + UTC day with the open count,
platform, version and cf-ipcountry, and expires rows after 35 days.
Debug builds, dev Electron and acLaunchPingDisabled skip the ping.
Readout: node --env-file=.env ../toolchain/analytics/opens-report.mjs --days 7
on lith, or the app_opens tool in toolchain/mcp/analytics-mcp.mjs.
AC iOS 1.2 #
The store's 1.1 (4) predates launch telemetry. Version 1.2 (5) uses
/api/app-session instead of the older launch endpoint. Its cumulative
snapshots contain only schema/app/version/build/platform, random install and
session UUIDs, session start, foreground seconds and first-observed,
successful-load and canvas-interaction flags. A foreground after background
creates an open; an inactive/active system interruption does not. Background
push delivery never creates an open. Timers pause while inactive. A session
is attributed to its opening UTC day, including time across midnight.
Use ios_usage in the analytics MCP or ios-usage-report.mjs --days 7 on Lith.
Active installs are distinct app-local IDs; returning installs appear on at
least two days in the selected window. Engaged sessions loaded successfully,
received a trusted canvas touch and were active for at least ten seconds.
Foreground time alone does not establish attention. First observed includes
upgrades to this telemetry version, and backup restore may preserve an ID.
Apple's downloads report remains the source for acquisition counts.
Snapshots coalesce locally (128 sessions, seven days), survive process death,
and are acknowledged only after successful delivery. Server $max updates
make replay and reordering idempotent. Raw native-app-sessions expire after
35 days; metrics-daily.nativeUsage retains counts and refreshes the recent
eight days for delayed delivery. No identifiers enter daily rollups or MCP
responses. Settings → Apps → aesthetic → Share app usage disables sending
and discards queued snapshots; Debug builds are silent.
Signup #
On Lith, from /opt/ac/system, run:
node --env-file=.env ../toolchain/analytics/signup-report.mjs --days 35
The report compares the last seven days with the previous seven: new Auth0
accounts, current email verification, and those accounts with a handle. Handle
creation by older accounts is counted separately. Deleted or merged accounts
are absent from Auth0's current records. The users signup webhook is a
diagnostic, not the account-count source; it was stale at the October 3 baseline.
Web signup attempts record entry source, referrer hostname and cumulative
milestones through authentication, verification and handle completion in
signup-attempts. Login and handle-only attempts stay separate. Each attempt
has a random ID, expires after 35 days, and carries no account ID, email, handle,
prompt, full URL or token. DNT, GPC and marked automation are excluded. The
existing ac_handle_created product event remains in place.
Use weekly account counts and account-to-handle completion to assess signup
changes; use attempt milestones to locate friction. Attempts are not unique
people, and page visits are not unique visitors. Do not infer a pre-deployment
funnel or divide counts from different coverage windows. The October 3 baseline
is in reports/2026-10-03-signup-baseline.json. No scheduled report or growth
target is configured by this change.