> For the complete documentation index, see [llms.txt](https://faction-os.gitbook.io/faction-os-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://faction-os.gitbook.io/faction-os-docs/docs/runbooks/local-umami-analytics.md).

# Local Umami Analytics Verification

This runbook covers the optional local Umami setup used to verify the browser analytics sender in `apps/web`. Core FactionOS local workflows do not require Umami, hosted analytics, hosted identity, hosted storage, or a production app host.

Stable source references:

* `docs/hosted-services.md`
* `docs/environments.md`
* `docs/privacy-and-security.md`
* `docs/adr/0003-umami-optional-analytics.md`
* `apps/web/README_web.md`
* `packages/protocol/src/hostedAnalytics.ts`
* `apps/web/src/lib/hostedAnalytics.ts`

## Activation Boundary

The browser sender sends only when all of these conditions are true:

* Analytics is explicitly enabled.
* Provider is `umami`.
* Umami host URL is valid, browser-safe, and has no credentials, query string, or fragment.
* Umami website id is configured.
* Consent state is `granted`.
* Opt-out is not active.
* The event name is in the approved protocol event list.
* The payload contains no blocked sensitive labels.

Disabled, missing config, missing consent, denied consent, withdrawn consent, opt-out, malformed Umami URL, credentialed URL, query or fragment host URL, unknown event, and blocked-payload states return without fetching.

The sender posts minimized event envelopes to Umami `/api/send`. It does not inject a tracking script, import an analytics SDK, use a browser API key, add a recorder, enable heatmaps, enable session replay, capture console logs, create server-side ingestion, or add an app-owned analytics dashboard.

## Local Setup

Use a real self-hosted Umami instance outside this repository. Keep the app loopback-only for local verification.

Recommended local shape:

* Umami checkout outside the repo, for example `/home/aiwithapex/src/umami`.
* Docker Compose app plus PostgreSQL, following the upstream Umami install guide.
* App port bound as `127.0.0.1:3024:3000`.
* Website in Umami named `FactionOS local`.
* Website domain set to `localhost`.
* Browser config stored only in ignored private config such as `apps/web/.env.local`.

Private browser config example:

```bash
VITE_FACTIONOS_ANALYTICS_PROVIDER=umami
VITE_FACTIONOS_ANALYTICS_ENABLED=true
VITE_FACTIONOS_ANALYTICS_CONSENT=granted
VITE_FACTIONOS_ANALYTICS_OPT_OUT=false
VITE_UMAMI_HOST_URL=http://localhost:3024
VITE_UMAMI_WEBSITE_ID=<local-umami-website-id>
VITE_FACTIONOS_APP_VERSION=local
VITE_FACTIONOS_BUILD_VERSION=local
```

Do not create `VITE_UMAMI_API_KEY`. Do not put Umami API keys, Cloudflare ids, account ids, zone ids, tunnel tokens, bearer values, query strings, fragments, or other secrets in browser-visible `VITE_` values.

Restart the Vite dev server after changing `apps/web/.env.local`; Vite reads browser-visible env values at startup.

## Current Local Verification Snapshot

Last checked: 2026-06-07.

This development host had a local Umami instance at `/home/aiwithapex/src/umami`, cloned from `https://github.com/umami-software/umami.git` at commit `c0ea3aefb`. Container logs reported Umami `3.1.0`.

The external Umami Docker Compose app was bound to `127.0.0.1:3024:3000`. Running containers were:

* `umami-umami-1`
* `umami-db-1`

`curl http://localhost:3024/api/heartbeat` returned `{"ok":true}`.

The local website record was:

* name: `FactionOS local`
* domain: `localhost`
* website id: `29740ef2-0a78-4830-b61e-fa7010335ace`

The website id is not a shared credential and may not exist on another machine. Treat it as local verification state, not reusable project config.

The local Umami admin account was used with the default `admin` / `umami` credentials during setup. The service is loopback-only, but change the password before exposing the service or treating this instance as long-lived operational infrastructure.

## Browser Smoke

The ordinary full local restart path starts Umami first when the checkout exists at `~/src/umami` or `FOS_UMAMI_DIR`, then starts the local app:

```bash
npm run relaunch
```

For a narrower smoke when Umami is already running, start only the local app:

```bash
npm run dev
```

Open `http://localhost:5193/`, complete the first-run faction picker if it is visible, open Settings, and confirm Analytics reports:

* badge: `Ready`
* reason: `ready`
* copy says approved runtime events can be sent to Umami
* runtime note says no tracking script, beacon, dashboard, recorder, or server ingestion is active

Expected browser-originated events include:

| Event                       | Typical source                              |
| --------------------------- | ------------------------------------------- |
| `cockpit_view`              | App mount                                   |
| `settings_status_view`      | Settings analytics status render            |
| `hosted_guardrail_status`   | Settings analytics guardrail render         |
| `export_local_action`       | Session export attempt                      |
| `replay_local_action`       | Replay, share-link, or clear-buffer action  |
| `notification_local_status` | Notification toggle or test outcome         |
| `war_room_local_status`     | Bounded War Room health or lifecycle status |

`local_demo_view` is also an approved protocol event for future or adjacent demo use. Do not add new event names without updating the shared approved event list, allowlisted payload fields, no-send tests, and docs.

For automated browser smoke tests, use repo-local Playwright. Default headless Chromium user agents can be filtered by Umami as bot traffic; use a normal Chrome user agent when verifying persistence.

## Database Checks

From the local Umami checkout:

```bash
docker compose exec -T db psql -U umami -d umami -c "select event_name, url_path, page_title, hostname, created_at from website_event order by created_at desc limit 12;"
```

To inspect persisted event data keys:

```bash
docker compose exec -T db psql -U umami -d umami -c "select e.event_name, d.data_key, d.string_value, d.number_value, d.date_value, d.data_type from website_event e join event_data d on d.website_event_id=e.event_id order by e.created_at desc, e.event_name, d.data_key limit 80;"
```

The 2026-06-07 browser verification persisted these events:

* `cockpit_view`
* `war_room_local_status`
* `cockpit_view`
* `settings_status_view`
* `hosted_guardrail_status`

Expected data keys were allowlisted posture labels such as `action`, `appVersion`, `buildVersion`, `category`, `consent`, `feature`, `provider`, `reasonCode`, `routeId`, `status`, and `surface`.

## Dashboard Check

Log in to `http://localhost:3024` and open:

```
http://localhost:3024/websites/<website-id>/events
```

For the 2026-06-07 local verification, the Umami Events tab for `FactionOS local` showed `cockpit_view`, `settings_status_view`, `hosted_guardrail_status`, and `war_room_local_status`.

The Umami dashboard is Umami's own local dashboard. FactionOS does not ship an analytics dashboard.

## Focused Tests

Use these focused checks after changing analytics code, config parsing, or docs:

```bash
npx vitest run apps/web/tests/hostedAnalytics.test.ts apps/web/tests/SettingsHostedAnalytics.test.tsx packages/protocol/tests/hostedAnalytics.test.ts packages/protocol/tests/hostedConfig.test.ts
git diff --check
```

Broader changes that affect wired analytics callers should also run the affected component or store tests and `npm run typecheck --workspaces --if-present`.

## Troubleshooting

If Settings does not show `Ready`:

1. Confirm Vite restarted after `apps/web/.env.local` changed.
2. Confirm the browser values use `VITE_` names.
3. Confirm `VITE_FACTIONOS_ANALYTICS_ENABLED=true`.
4. Confirm `VITE_FACTIONOS_ANALYTICS_CONSENT=granted`.
5. Confirm `VITE_FACTIONOS_ANALYTICS_OPT_OUT=false`.
6. Confirm the Umami host URL has no credentials, query string, or fragment.
7. Confirm the website id is configured.

If `/api/send` returns HTTP 200 but the database shows no event rows, check the response body. Bot-filter responses may be accepted at HTTP level without creating event rows. Re-run the browser smoke with a normal Chrome user agent.

If direct curl or Node smoke tests appear filtered, prefer browser verification or provide a browser-like user agent. Curl and Node default user agents can hit Umami's bot-filter path.

If dashboard overview metrics look empty, open the Umami Events tab. These runtime events are custom events, not pageview rows in the default overview.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://faction-os.gitbook.io/faction-os-docs/docs/runbooks/local-umami-analytics.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
