Files
zeekayandHanzo Dev 55b2193acf feat(gui): GuiProvider owns telemetry, so a gui app reports without wiring it
Every @hanzo/gui app — web, Tauri desktop, native — now gets pageviews,
unhandled errors, React render errors and interaction capture on the one front
door (POST api.hanzo.ai/v1/event) with no app-side code. The desktop shell
proves it: zero telemetry imports, and the shipped bundle carries the client,
the endpoint and a `desktop` product tag resolved from the Tauri runtime rather
than from a URL that spells the same app three different ways.

Turning it on for everyone is only safe because it cannot become the SECOND
thing emitting. GuiProvider mounts `<TelemetryProvider owner="gui">`, a posture
that collects only while nothing else is: an ancestor provider is seen through
context, a descendant one through its claim on the ambient slot (React runs
child effects before parent effects), and the claim is subscribed rather than
sampled, so a lazy route takes the stream and an unmount hands it back. Losing
the claim rebuilds the client with `enabled: false` — inert in every path, not
merely quiet — so a stray capture() cannot wake it.

The one shape context cannot reach is a raw @hanzo/event <AnalyticsProvider>
BELOW GuiProvider, which claims nothing; hanzo.ai, chat and hanzo.industries
ship exactly that and need `telemetry={false}` until they drop their wiring.
A test pins that gap so it can never be a surprise.

Also here: EVENT_INGEST_KEY becomes the name the package reads, which is the one
KMS already carries and the one @hanzo/event already reads — a surface that
resolved no key sent unattributed beacons the door refuses, silently. The
@hanzo/event floor moves to ^0.3.11 (0.3.4 was resolving, below the release that
made errors actually reach Sentry). And the package builds through hanzogui-build
like its 168 siblings, so it ships esm + cjs + native instead of ESM only —
without which @hanzo/gui's own CJS output could not require it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-04 22:59:33 -07:00

36 lines
1.1 KiB
TypeScript

// @vitest-environment node
//
// SSR safety, proven where it matters: a Node environment with no window, no
// document and no localStorage. The provider must render its children, collect
// nothing, and never throw — importing telemetry into a server component has to
// be a non-event.
import { describe, expect, it } from 'vitest'
import { renderToString } from 'react-dom/server'
import { TelemetryProvider } from '../src/TelemetryProvider'
import { createTelemetry, hasDom, track } from '../src/telemetry'
describe('server rendering', () => {
it('has no DOM to speak of', () => {
expect(typeof window).toBe('undefined')
expect(hasDom()).toBe(false)
})
it('renders children and collects nothing', () => {
const html = renderToString(
<TelemetryProvider>
<p>hello</p>
</TelemetryProvider>
)
expect(html).toContain('hello')
})
it('is inert: a client built on the server is disabled', () => {
expect(createTelemetry().enabled).toBe(false)
})
it('does not throw when the ambient API is called on the server', () => {
expect(() => track('server_side_call', { a: 1 })).not.toThrow()
})
})