@hanzo/event 0.3.39 and 0.3.40 carry a Hanzo key whose project no
longer exists, so the ingest door answers every beacon from gui with
403 ingest_key_unknown. 0.3.41 carries the current keys. The telemetry
package asks for it, and an override lifts the copy @hanzo/observe
pins to the same version.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
apps/team moved to hanzo-apps/team with its history, and builds there
against the published @hanzo/gui. It leaves the workspace list, the
lockfile, and the Written-at-Hanzo list in every NOTICE (ten names
become nine).
Co-authored-by: Hanzo Dev <[email protected]>
151 packages of Tamagui-derived code are on npm with no license text in
them. Checked four tarballs at 8.3.5 — @hanzogui/core, @hanzogui/web,
@hanzogui/shell and @hanzo/gui — and not one contains a LICENSE file.
MIT asks for one thing: "The above copyright notice and this permission
notice shall be included in all copies or substantial portions of the
Software." Shipping the code without it does not meet that.
The cause is mechanical rather than deliberate. npm includes a LICENSE
that sits in the package directory, and ours sits at the monorepo root,
where it reaches no tarball. So the license is now in each of the 153
publishable packages, and NOTICE beside it — that one npm will not take on
its own, so 152 manifests name it in `files`.
Verified by dry-run pack: 1.3kB LICENSE, 2.8kB NOTICE.
Already-published tarballs are immutable, so this corrects on the next
publish and not before.
The loader and the Next plugin only existed to drive webpack; Next runs
on Turbopack and Vite has its own plugin. kitchen-sink and the
integration app serve through vite, the optimizer suite drops the
webpack name, and its fixture packages join the workspace so a fresh
checkout links @my/ui.
setTelemetry(undefined) deleted @hanzo/event's page-client registry — the map
that makes "one stream per page" true. It reads as test hygiene, and that is
where it came from, but setTelemetry(undefined) is also what TelemetryProvider
calls from its cleanup effect: every unmount, every consent change, every prop
change that rebuilds the client.
So the guarantee this package exists to hold was being dropped by this package.
The next mount gets a client that has never seen the page and counts the view
again — one page load, two pageviews on the wire, with nothing on either row to
say they came from different clients. owner.test.tsx is entirely about not
emitting twice and could not see it, because the delete happened inside the very
call the suite used to reset between tests.
The reset belongs to the page, so it belongs to the package that owns the
registry: @hanzo/event 0.3.40 names it resetClients(), and the suites call it in
beforeEach, where "each `it` is its own page load" is true and says so.
Pins the double as a test: mount, unmount, mount, expect one pageview.
PUBLISHABLE_KEY is a BUILD-time variable, and a static export is built once and
served on every brand — so a build-time key is either absent or wrong. Absent
is what the fleet actually has: hanzo.ai's is commented out in .env.example and
both Zoo sites shipped with none, which means every one of those surfaces
mounts a client that collects and sends nothing. An analytics client that
reports nowhere looks exactly like one that reports fine.
A brand's domain already names its org, and an org has exactly one publishable
key. @hanzo/event has derived it from the hostname since org.ts was written,
for precisely this case, and nothing ever called it. Now the key falls back to
`keyForPage()` after config and env, so a page served from a domain we
recognise reports correctly with nothing configured, and one served from a host
no brand claims still reports NOWHERE — filing one brand's visitors in
another's project is the defect the derivation exists to prevent.
Bound once, not twice. `describeTelemetry` restated the same expression to
answer `hasIngestKey`, so the moment this line gained a third source the two
disagreed about the same client: it had a key and its own description said it
did not. That is what the new test caught first.
Every published manifest now states the same exports: types, the
native emit where the package builds one, ESM under import and
CommonJS under require, with src, types and dist in files. The nine
packages that read import.meta do so in one here.ts each, with a
here.cjs.ts sibling for the CommonJS emit; the stragglers on bun,
esbuild, plain tsc or hand-written shims build with the runner like
everything else, and the root-level .cjs shims that pointed at emits
no build produces are gone, as is the vite-plugin-cjs mirror.
Co-authored-by: Hanzo Dev <[email protected]>
The project files inherited the root paths map, so a package's typecheck
read its siblings' source instead of their published declarations and
failed on files that were never its own. An ESM-only manifest now names
no dist/cjs path under any condition, the native emit names its renamed
map, and the runner has a test of its own again.
Babel and esbuild were never here for TypeScript. Babel carried the static
optimizing compiler and one plugin that appended extensions to esbuild's
emitted imports; source now names its own extensions and the compiler rewrites
them on emit. A sixty-line runner replaces the tool: an ESM emit with
declarations, a CommonJS emit where a manifest states a require entry, and a
native emit where a .native sibling takes its sibling's name. The compiler
packages keep Babel as their own substrate; nothing else runs over the code.
Eighteen packages still carry type errors the previous tool never checked.
Ranges drift once nothing moves them: a caret that has not been rewritten
since it was written pins whatever was current that day, and an exact pin
cannot even take a patch. Both are updated here.
One line per package, so a surface reads the same components, the same
tokens and the same face as every other one.
A version written into a manifest is written into every manifest that wants it,
which is how eslint came to exist in a hundred and four forms across this estate
and how a package came to resolve twice at run time. The catalog moves the number
out of the manifest: a manifest names the package, the workspace names the
version.
Four are held below their latest because the newer release cannot be used, and
the reason is recorded rather than the version alone: @noble/hashes 2 renamed
every subpath the web3 stack imports, @splinetool/runtime 2 depends on a package
that was never published, @dynamic-labs 5.4.1 the same, and react-resizable-panels
3 points its types condition at the bundle.
Every dependency on a @hanzo/@luxfi/@zooai package now names the same version
across the repo: a catalog entry where pnpm supports one, an exact pin where it
does not, and a range only where a peer genuinely means one.
pnpm itself is pinned too — the estate ran eighteen different versions, five of
them too old to read a catalog.
One word stood for several: the MCP server, one address that accepts requests,
the single way in, and the boundary a credential is validated at. Each site now
names its own sense — endpoint, entry point, the MCP server, the edge, address,
relay — or, where none of those fit, takes the word the surrounding code already
uses.
Prose and comments only, 1:1 line for line. Identifiers keep their names: CSS
classes, DOM ids, struct fields, enum variants, config values and the doc
comments that must open with the identifier they document.
gui's whole CI lane has been red since Aug 5 and the cause was `oxfmt --check`
inside `bun run lint`, which the `Test` step reaches about eighty seconds in.
Nine authored files had drifted from the format — mechanical, no behaviour — and
two more were machine-written.
Those two are the real finding. The generated per-app gui config moved from
`.hanzogui/` to `.gui/` and `.gitignore` did not follow it, so every app's
generated config sat untracked-but-unignored. The formatter checks what git does
not ignore, so a gate that exists to keep AUTHORED code consistent was failing on
an esbuild bundle. `.gui` is ignored now, beside the old name.
`bun run check` and `bun run lint` both exit 0.
The rename (bc8f4b7dc0) landed on main unpublished — 8.1.2 on npm still reads
the old spelling. Bump so the rename actually ships to consumers.
Co-authored-by: Hanzo Dev <[email protected]>
The value is a pk- prefixed PUBLISHABLE key (the Stripe vocabulary, and what
the build-time gate already tests for). EVENT_INGEST_KEY hid that.
One substitution covers all three spellings, since the framework prefixes wrap
the same token: EVENT_INGEST_KEY -> PUBLISHABLE_KEY, NEXT_PUBLIC_* and VITE_*
follow. KMS already carries deploy/PUBLISHABLE_KEY (env prod) with the same
value, written and read back first, so no build can reach a name that does not
exist yet.
Co-authored-by: Hanzo Dev <[email protected]>
Native was gated silent alongside SSR (`permitted = hasDom() && …`), so a mobile
app mounting <TelemetryProvider> built a disabled client and sent nothing. React
Native is not SSR: it has `fetch`. Gate emit on `canEmit()` (a DOM *or* React
Native) instead of a DOM, so the event/identify/group/error streams send on
mobile while the DOM-only planes (History pageviews, session replay) stay off.
- env: `isReactNative()` (navigator.product) + an unconfigured RN app reports
as `mobile`, mirroring Tauri→desktop.
- telemetry: `canEmit()` = hasDom() || isReactNative(); the gate reads it.
- useRouteTracking: gate on `hasDom()` not `typeof window` — RN has a window but
no History, and reaching for window.location/history there threw.
- TelemetryProvider: guard the cross-tab `storage` listener behind hasDom() (RN
has no window.addEventListener) while keeping in-app consent live everywhere.
- useScreenTracking(navigationRef): the RN counterpart to History pageviews,
duck-typed against React Navigation so the package takes no dependency on it.
- @hanzo/event floor → ^0.3.16 (RN-safe init) + a native.test.ts proving emit.
Co-authored-by: Hanzo Dev <[email protected]>
d4e5c36b moved this package onto hanzogui-build, so main has emitted both formats
and declared `require: ./dist/cjs/index.cjs` since. None of that reached anyone:
the published 8.1.0 predates it and is still the ESM-only tsc build, with no
`require` condition and `import.meta` in dist/env.js.
That one gap is what kept `require('@hanzo/ui')` broken against npm no matter what
@hanzo/ui itself published — its dist/index.cjs requires this package, and CJS
resolution finds no condition to take. Publishing the build main already produces
is the whole fix; there is no source change here.
A single-package patch, not the fleet-wide release.ts stamp — the registry is
already non-uniform (shell is at 8.1.4) and one package's packaging defect is not
a reason to restamp a hundred others.
Built with hanzogui-build, NOT --skip-native: the exports map declares a
react-native condition, and that file only exists when the native build runs.
esbuild rewrites import.meta to `const import_meta = {}` in the CJS output and
env.ts's existing try/catch degrades correctly, so the source needed nothing.
Verified: 35 exports load via bare `node -e "require(dist/cjs/index.cjs)"`, 35 via
the ESM entry, 41 tests green, typecheck clean.
Co-authored-by: Hanzo Dev <[email protected]>
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]>
Three fixes that landed on the pre-transplant lineage as @hanzogui/telemetry
0.1.3 and never reached this one. 8.0.0 was published from this tree, so the
latest published package regressed all three.
1. A render error with NO `fallback` was never reported. React's order is
getDerivedStateFromError -> re-render -> commit -> componentDidCatch;
re-throwing from render() aborts that sequence before the commit, so
componentDidCatch never ran. Re-throw from componentDidCatch instead:
the error propagates to the next boundary exactly as before, but AFTER
it has been observed. Silent, and only for the apps that correctly let
their own boundary own the UI.
2. `const m = import.meta` is a parse-time SyntaxError in a CommonJS
consumer -- the babel plugins that rewrite import.meta for a CJS target
match only the member expression, so a bare one survives the transform
and takes the whole module down. Read it as a member.
3. useRouteTracking now reads the url pushState/replaceState was handed
rather than reading window.location back, so a SPA route change is the
navigation the app requested instead of an assumption about when the
host reflects it.
Carries the regression test: it fails against the unpatched provider
(expected {message,handled} -> received undefined) and passes here.
Ported from origin/telemetry/one-surface 7223b27e85 -- the four source files
are byte-identical to that commit. Version resolved to 8.0.1: the source
commit bumped 0.1.0 -> 0.1.3 on the other lineage, this lineage's last
published telemetry is 8.0.0.
Co-authored-by: Hanzo Dev <[email protected]>
telemetry and shell set no explicit rootDir. TS6+ stopped inferring the
common source directory, so tsc fails TS5011 — and because the check runs
in the prepare script it only fires at publish time, never during
`bun run build`. Both now name ./src.
bento-get is upstream's package on npm (owner: nwienert), so publishing it
returns 403. Its name carries no `tamagui` string for the rebrand to
rewrite, which makes it the one publishable package here we do not own; it
is now private.
Co-authored-by: Hanzo Dev <[email protected]>
Aligns @hanzogui/* and the four unscoped gui packages with @hanzo/ui@8,
which is already on the 8.x line. @hanzo_network/* and @hanzo/booker keep
their own version streams — they are not part of this framework's lineage.
The lockfile is regenerated deliberately: bun resolves workspace:* from it
at publish time, so bumping versions without re-resolving would have
published 8.0.0 packages whose deps pin 2.6.0 — versions never published
under this scope, so every install would 404.
Co-authored-by: Hanzo Dev <[email protected]>
This repo could never sync. There was NO merge-base with tamagui — 372 commits
here against 15,791 there, unrelated histories — because it began as a
copy-paste rebrand with a flattened layout. Every "sync" since was a hand
re-application that drifted, which is why the tree sat on 7.3.1 while upstream
shipped fixes we then rediscovered by hand.
This commit's PARENT IS TAMAGUI. A future sync is `git merge tama/main` then
`node scripts/rebrand.mjs` — a real merge, because the history is now shared.
174 packages: 166 upstream, our 8 restored (chrome, shell, telemetry, recipes,
get, font-geist-{sans,mono}, @hanzo/booker), bento-get gained. 170/170 build.
Our patches to files UPSTREAM ALSO OWNS are carried, which the package-level
delta missed and which was where the value actually was:
- useMedia's server snapshot (`initState ?? getMedia()`). Upstream still
returns undefined there, so every Tamagui Dialog kills a prerender — the
exact crash hanzo.app spent a day on.
- data-slot on web host nodes, absent upstream entirely.
Both applied ONTO upstream's newer files (it rewrote useMedia +150/-63 and
createComponent +160/-55), marked `PATCH (ours, carried across the transplant)`.
Seven traps are encoded in scripts/rebrand.mjs, each found by a gate:
skip itself and lockfiles; special-case the double-branded names in BOTH scoped
and bare form; map path strings, not just paths; drop a level for the flattened
group; check disk rather than a stale index.
Rust build artifacts are NOT committed. An earlier draft of this commit swept
apps/desktop/src-tauri/target/debug in via `git add -A` — 3,859 files including
a 272MB .a, over GitHub's 100MB cap, so the push could never have landed. They
are gitignored now.
Co-Authored-By: Hanzo Dev <[email protected]>