302 Commits
Author SHA1 Message Date
hanzo-dev 8153606ac8 wip: preserve agent work interrupted by session limit
(cherry picked from commit bac7cc5bf68eb03973ab0bec5d28fcc23b2a2ee8)

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
(cherry picked from commit e077a2285a3c1c682332f83260ac57a9cf1010d5)
2026-08-04 18:59:00 -07:00
hanzo-dev c4ac4eff3f wip: preserve agent work interrupted by session limit
(cherry picked from commit c256eb39aab2280121bfec766fdea3ea92f71091)

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
(cherry picked from commit 775d9ab84092ab7a42430a97a7e0f99573fc7839)
2026-08-04 18:59:00 -07:00
hanzo-dev 922ba7d676 wip: preserve in-flight telemetry + event bump before 8.x convergence
(cherry picked from commit 0045bac1727d1251991f1106b8f2bccb1acb34ac)

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
(cherry picked from commit 909388da5eb5ccdd1c87b5fb9b756b862733209d)
2026-08-04 18:59:00 -07:00
zeekayandClaude Fable 5 83e6eb4667 Read design tokens from the published package, not a vendored copy
Hanzo CI/CD / cicd (push) Successful in 5m16s
CI/CD / cicd (push) Successful in 5m16s
app/design/ carried a verbatim vendor of @hanzo/design's tokens, synced by hand
on 2026-07-24 because the package wasn't on npm. It is now (0.4.6), and the
vendored copy had already drifted a full border rework behind it — pure-black
ground, solid #1f1f1f borders, grey destructive, the pre-0.4.x palette the rest
of the fleet just moved off of. index.css now imports the real dependency and
the seven vendored token files are deleted; the two console-only tokens they
added (--border-card, --border-hairline) were unused. The Tamagui theme layer
in globals.css derives --colorN from the neutral ladder, which is unchanged, so
only the semantic surfaces adopt the rework — the intended unification.

tsc 0 errors, 3157 tests pass, next build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 15:53:28 -07:00
hanzo-dev b6e35ee042 legal: console is MIT OR Apache-2.0 (HIP-0137)
Hanzo-original work; BSD-3 is out of scope for hanzoai under HIP-0137.
LICENSE becomes the dual pointer, LICENSE-MIT / LICENSE-APACHE carry the
texts, and the upstream MIT copyright notices previously kept in LICENSE
are retained verbatim in LICENSE-MIT. README, LLM.md, NOTICE, Dockerfile
and package.json follow.
2026-08-04 01:28:13 -07:00
zooqueenandHanzo Dev 9da398459e deps: iam 0.21.6 — the SDK whose storage default survives prerender
Hanzo CI/CD / cicd (push) Successful in 4m54s
CI/CD / cicd (push) Successful in 5m24s
The previous commit dropped console's explicit `storage: sessionStorage` so the
session would be shared across tabs, and the build died on the next push:

  Error occurred prerendering page "/auth/callback"
  ReferenceError: sessionStorage is not defined

`^0.21.2` resolved to a build whose default was the bare global
`config.storage ?? sessionStorage`, evaluated in the IAM constructor. In a
browser that is merely the wrong lifetime; under Next's static export it runs in
Node, where the identifier does not exist at all, so the constructor threw and
the export exited. Passing `sessionStorage` explicitly had been masking it —
the callback page constructs the SDK at module scope, and the value it passed
was the client-side one Next never evaluates on the server.

0.21.6 resolves storage through a guarded probe (`typeof localStorage`, then an
actual write, since privacy modes expose the object and throw on setItem) and
falls back to an in-memory Storage. So prerender gets a real object, the browser
gets localStorage, and neither needs a caller to know which one it is.

The caret already permitted 0.21.6; the lockfile is what pinned 0.21.2, which is
why CI installed the broken one while a local `pnpm install` would not have.
Verified by running the export that failed: 13/13 static pages, /auth/callback
among them.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-08-03 17:18:58 -07:00
hanzo-dev 4656c316f4 build: react-native-svg — the dependency gui 8 needs and the bump forgot
`deps: console onto gui 8.x` moved @hanzo/gui 7.3.0 -> ^8.0.0. gui 8 pulls
@hanzogui/lucide-icons-2@8.0.0, whose ESM build does

    import { Svg, Path } from "react-native-svg"

while declaring react-native-svg in NEITHER `dependencies` NOR
`peerDependencies`. Nothing installs it, so webpack cannot resolve it, and
`next build` dies with a wall of "Module not found: Can't resolve
'react-native-svg'" — once per icon. The `react-native$: react-native-web`
alias in next.config.mjs does not cover it: `$` is an EXACT-match alias and
react-native-svg is a different package.

So console main has been UNBUILDABLE since that bump. That is why
ghcr.io/hanzoai/console has no v8.5.33, v8.5.34 or v8.5.35 — the tags were
cut, the images were never published, and universe's bump to v8.5.35 had to
be reverted to v8.5.32 to keep a pullable image. The three fixes riding those
tags (the 1px display line-box, the missing 404, @hanzo/iam 0.21.2) have been
finished in main and dark in production the whole time.

The estate already had the answer: every app on gui 8 carries this dependency
explicitly (hanzo.ai, hanzo.sh-std, app-std, console-std all pin 15.15.5),
and every app still on gui 7.3.0 does not need it. Console took the bump
without the companion. This restores the one established pairing.

Measured, not assumed:
  before  next build -> exit 1, "Can't resolve 'react-native-svg'"
  after   next build -> exit 0, 42 routes emitted
  pnpm install --frozen-lockfile -> exit 0 (the Dockerfile's exact command)

Lockfile regenerated with the DECLARED pnpm 11.17.0 (node 22), not the pnpm 9
on PATH — pnpm 9 renormalizes peer-suffix keys and churns 1061 lines. This
diff is +96/-0, purely additive.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-08-03 12:11:49 -07:00
hanzo-dev f64de84a4e deps: @hanzo/event 0.3.8 — the release that redacts credentials from event URLs
0.3.8 stops the client shipping a raw location on every event. The location is
stamped on all of them now, so an invite/reset/magic link — a JWT in the query,
an address in `?email=` — reached the warehouse in cleartext on the first click
and again on every later one. url, path and referrer now get the same redaction
the error plane has always applied to error text.

Console is the surface where that matters most and the one furthest behind: the
running bundle serves libraryVersion 0.3.0, six releases back, so it has no
error plane at all (sentry.ts did not exist yet) and never stamped a page onto
an event. The lockfile has been ahead of the image for a while — this bumps the
lockfile; the image has to be rebuilt and its pin moved for any of it to be
true in production.

Lock edited only where it names @hanzo/event, so an unrelated tree is not
re-resolved into a dependency bump. Integrity is the one npmjs serves for
0.3.8; `pnpm install --frozen-lockfile` accepts it on pnpm 11.17.0, the version
package.json pins and the Dockerfile installs via corepack.
2026-08-01 12:25:22 -07:00
hanzo-dev ecb9be3a8b deps: @hanzo/event ^0.3.5 for page-stamped autocapture
Autocapture ($click/$input/$change) reaches the wire through capture(),
which supplied no location, so every $click landed with an empty url and
path and was unattributable to a page -- the one thing a heatmap needs.
0.3.5 stamps the page in build(), the single point every event is built,
placed ahead of the caller's fields so pageview()'s explicit path still
wins on the route changes that fire before window.location catches up.

The lockfile moves with the range here. What ships is the lockfile, not
the range, which is why production was serving 0.3.3 while the declared
range already read ^0.3.4. Resolves 0.3.6 (the 0.3.5 runtime plus a
test), deduped with @hanzo/observe and @hanzogui/telemetry.
2026-08-01 10:53:23 -07:00
zeekayandClaude Opus 5 06d0681081 deps: unfreeze console's first-party pins; quarantine finance-ui 0.2.x
@hanzo/canvas was declared "^0.1.0". A caret on a 0.x version pins the
MINOR, so console was locked to canvas 0.1.0 while 0.2.1 was current —
the same trap that froze @hanzogui/shell on 7.5.1. @hanzo/ui@8.0.38
peer-depends on canvas ">=0.1.0", which 0.1.0 satisfies, so nothing
warned. Silent freeze.

Six exact pins (dash, gui, and the four @hanzogui/* entry points) could
never take a patch either. Floated them to carets; 8.x is a stable major
so a caret is the correct expression.

Moved on install: brand 1.4.4->1.4.5, canvas 0.1.0->0.2.1,
data 1.2.1->1.2.2, logo 1.0.13->1.0.14, ui 8.0.20->8.0.38.

@hanzo/finance-ui stays on 0.1.1, now written "~0.1.1" so the range says
so. 0.2.1 declares peer "@hanzo/ui": ">=8.0.0" — satisfiable, so the peer
check passes — but its source imports DataTable, LineChart, Column,
ChartPoint and Sparkline from @hanzo/ui, and no published @hanzo/ui
exports them (verified against 8.0.20, 8.0.38 and ui-shadcn 5.9.1).
It publishes raw src/, so those errors land in the consumer's tsc.
A satisfiable-but-false peer range is worse than an unsatisfiable one:
it fails at build time in the consumer instead of at install.

tsc --noEmit clean; next build succeeds; 3152 tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 15:49:51 -07:00
zeekay cdb6f9d091 deps: console onto gui 8.x — the shell fixes can finally reach production
Hanzo CI/CD / cicd (push) Failing after 1m7s
CI/CD / cicd (push) Failing after 1m7s
The console sat on @hanzo/gui 7.3.0 / @hanzogui/shell 7.6.3 while the gui line
shipped 8.0.0, so every shell fix landed on main and stopped there: cloud.hanzo.ai
and console.hanzo.ai kept serving a header from a package that could not move.

What actually pinned it was a phantom peer. Every published @hanzogui/shell
declared `@hanzo/iam@^0.13.1` — a package it never imported — and for a 0.x range
the caret pins the minor, so once IAM reached 0.21.2 the range was unsatisfiable
and the 8.x line was uninstallable. Fixed at the source and released as
@hanzogui/shell@8.0.1 with zero dependencies.

typescript goes ^7.0.2 -> ^5.8.2 in the same commit because it has to: Next 15
does not support TS 7, and on 7.0.2 `next dev` fails to a bare
"Cannot read properties of undefined (reading 'endsWith')" after silently
installing a 5.x behind your back. Same fix hanzo.ai just made.

Verified on the real 8.x tree at 1440x900 and 390x844: Products opens as FIVE
columns with all ten categories visible and nothing clipped (it was four columns
with WEB3 + APPS below the fold), one "Sign in" pointing at /signin, one h1,
the hero primary a white 999px pill, and Terms inside the viewport with no
underline and no horizontal overflow.
2026-07-31 14:35:28 -07:00
hanzo-dev 447825ef6d api: read the named total first; data2 is only the legacy fallback
The count of a list envelope now reads the named field before Casdoor's
untyped second slot: total, then data2, then the rows themselves. ONE
helper (envelopeTotal in lib/api/client.ts) owns the order; getList,
iamList, makeIamClient and AuditApi.list all go through it, so the data2
fallback lives in exactly one place and dies with the legacy emitters.

e2e fixtures still emit data2 on purpose — they pin today's live wire
and flip only when the fallback is deleted.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-30 12:32:55 -07:00
hanzo-dev 7a722f0a3c feat(console): the shared components report through the console's ONE client
@hanzo/ui 8.0.20 instruments itself — DataTable, PrimaryButton, SlideOver,
ConfirmDelete, the Field* editors, ComboBox, Segmented/SearchInput, MenuItemView,
OrgSwitcher, ThemeToggle, Toast and EmptyState now report what a user did. The
console renders all of them, so it gets the whole interaction vocabulary without
a line of app code — that is the point of instrumenting the component instead of
the app.

But those components emit through module-scope `track()`, which resolves an
AMBIENT client, and left alone that would have been a SECOND client: default host
api.hanzo.ai, no session cookie, its own anon id and its own batch. Cloud's
anonymous capability lane admits pageview and error only, so every component
event would have been DROPPED on arrival — a silent, plausible-looking nothing.

So `src/lib/event.ts` — the file that already declares itself the ONE console
client — registers `eventClient` as the ambient one. Now the provider's pageviews,
the error boundaries, and every shared component ride a single same-origin batch
that carries the session cookie, which is what makes their events CREDENTIALED and
attributable to the signed-in org. One client, one anon id, one stream, exactly as
that file's header always promised.

Also pins typescript back to 5.9.3. Under the 7.0.2 that main had taken, Next
15.5.19 stops honoring the tsconfig `paths` map and the build dies on every
alias — `Can't resolve '~/config'`, `'~/lib/event'`, `'~/components/ProductRoute'`
— i.e. main could not build at all, before and independent of this change. Adding
`baseUrl` does not help; only the TS pin does.

Build: 8.0.20 installs clean under pnpm 11 (8.0.19 could not — it shipped
`workspace:*`, fixed upstream) and `next build` completes.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-28 09:22:39 -07:00
zeekayandHanzo Dev 108977e0ca fix(deps): take @hanzo/iam 0.21.2 and delete the patch it supersedes
`patches/@hanzo+iam+0.13.6.patch` added a fallback that reads the token's
`exp` claim when the server omits `expires_in`, so session expiry is still
learned. It was written against 0.13.6; the dep has since moved to 0.21.1,
so patch-package refused it — and, because a version mismatch is a
warning-class failure there, `postinstall` still exited 0. The patch had
silently stopped applying with nothing failing: verified absent from the
installed 0.21.1, so the console was running without the fallback.

Fixed upstream instead (hanzo-js/iam 08476b4, released as 0.21.2) where it
belongs — one implementation, every consumer — so the patch is now
genuinely unnecessary, which is what patch-package itself reported.

Verified: 0.21.2 installed, the fallback present in node_modules, and
postinstall clean ("No patch files found"). typecheck 0 errors; 3136 tests
pass.

Co-Authored-By: Hanzo Dev <dev@hanzo.ai>
2026-07-28 00:04:04 -07:00
hanzo-dev 137432ad42 refactor(gui): import the ONE scale, stop declaring it
gui.config.ts declared the console's type/radius/space ladder. It is not the
console's ladder — it is the ladder every @hanzo/ui/product component is drawn at,
and the dedicated Hanzo Social app renders those same components. A private copy
would have forked the moment either side tuned a size.

It now ships with the components (@hanzo/ui/gui-config, 8.0.17); this file is the
console's import path onto it, so `~/gui.config` call sites are unchanged.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-27 23:30:51 -07:00
hanzo-dev 9bd12d7c28 refactor(social): render the shared surface, not the console's private copy
The whole social product lived HERE — a 601-line SocialModule plus its own copy of
the /v1/social contract in src/lib/api/social.ts. That is fine while the console is
the only host, and it stopped being true: social.hanzo.ai gets a dedicated app, and
the only way for a second app to render Publish was to copy 800 lines and let them
drift.

So the product moved to @hanzo/ui/product/social (SocialResource + createSocialApi)
and the console keeps only what is genuinely the console's:

- src/lib/api/social.ts is now the TRANSPORT binding — the four verbs on
  originV1Url('social/…'), through our own app/v1 user-bearer BFF — and re-exports
  the contract so call sites keep one import path. It binds
  @hanzo/ui/product/social/api, the React-free entry, so the console's data layer
  (and its node tests) never load a component tree.
- SocialModule.tsx is the mount: hand SocialResource the bound client. 23 lines.
- The tests split the same way the code did: the contract's normalizers and paths
  are tested in @hanzo/ui; what is tested here is what is ours — that a contract
  path resolves to this origin's /v1/social/… .

817 lines of module + client become 77. Nothing about the surface, the routes, or
the tenant isolation changes: the org is still resolved SERVER-SIDE from the bearer
owner claim, and social.hanzo.ai still boots straight into this product.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-27 23:20:42 -07:00
zeekayandHanzo Dev bc93b371f6 build: typescript ^7.0.2 (native compiler)
TypeScript 7 is the native Go compiler; the npm package is a shim resolving
a platform-specific native binary. The build is `next build`, so SWC does the
emit and tsc is typecheck-only — no built artifact changes.

Measured on this same config before and after: 0 errors on both tsc 5.9 and
tsc 7, so the compiler swap introduces nothing new.

Note: `pnpm install` exits non-zero here on an unrelated, pre-existing
postinstall failure — patches/@hanzo+iam+0.13.6.patch no longer applies
because the installed @hanzo/iam has moved past 0.13.6. That failure
reproduces with the previous package.json and is not caused by this change.

Co-Authored-By: Hanzo Dev <dev@hanzo.ai>
2026-07-27 23:07:47 -07:00
zooqueen a0ed4d8729 feat(shell): one account control, both switchers, at the foot of the rail
The console had THREE places to answer "who am I and which org am I in": an org
switcher at the top of the sidebar, an account popover at the bottom, and a third
menu in the phone drawer — with four ways to sign out between them. They are now
one control, mounted where the CTO asked for it, and it is the shared
`@hanzo/iam` UserMenu rather than a fourth thing built here.

Deleted: OrgSwitcher.tsx (a wrapper around the retiring shadcn @hanzo/ui), the
account popover and its two private helpers, the drawer's own theme/profile/
sign-out rows, and the wallet's duplicate sign-out. The shell is 160 lines
lighter and the wallet is a wallet again.

REACH. The SDK's own org state reads the token's memberships claim, which cannot
express what an admin console does. `@hanzo/iam` 0.21.1 takes an optional
`findOrgs`, so the switcher searches the console's EXISTING lazy, server-paged
cross-tenant list — the same `IamAdminApi.organizations` the full-page picker
uses, gated to a super admin, unchanged. A regular user is never asked for it and
sees their own org exactly as before. The reach was extended in the SDK, not
forked here.

MONEY. `adminOrgState` is a pure adapter and passes `org-scope.switchOrg` BY
REFERENCE — the console's one switch, which persists the scope and reloads so
every module refetches under the new `X-Org-Id`. No second switch, no header, no
billing call is added, so the ledger rule sits exactly where it sat.
`org-state.test.ts` pins the identity so a second switch cannot creep in later,
and the render spec asserts the write to `hanzo.console.org` really is what a
selection produces.

Two z-indexes joined the ladder they were ignoring, because the account control
needs them: a SlideOver was pinned at a literal 1000 and the rail flyout at 1000,
both ABOVE the popover rung, so on a phone the menu opened inside the account
sheet and the sheet swallowed it — present, measurable, and unclickable. They are
now `--z-modal` and `--z-dropdown` from `app/design/z.css`, whose own comment
already said a popover anchored in a sheet paints over it. The remaining literals
(tour, toast, detail pane) are the shell lane's.

Proven by rendering, not by status codes: e2e/account-menu.spec.ts opens the
control in the real signed-in shell and measures an opaque background, Geist, a
body fully inside the viewport at 1440 AND 390, rows padded and at 4.5:1, a hover
state that actually differs, zero uppercase nodes, the menu hit-testing to itself
over the sheet — and finds "Acme Industrial", a tenant that is nobody's
membership. tsc clean; 3093 unit tests green; next build and build:embed green.
2026-07-27 11:47:54 -07:00
zooqueen 1e647907a0 fix(console): one paper, one leading — overlay elevation and display type (v8.5.32)
Hanzo CI/CD / cicd (push) Successful in 5m42s
CI/CD / cicd (push) Successful in 5m42s
Two rendering contracts were silently not applying. Both found by measuring
computed styles in a real browser, not by reading code.

The product-guide headline had a 1px line box. PitchHero set
`style={{ lineHeight: 1.12 }}` — a correct ratio in plain React, whose unitless
allow-list includes lineHeight. React Native Web's does not, so under @hanzo/gui
it compiled to `line-height: 1.12px`: a 30px/900 headline in a 1px box, a 29px
overflow that dropped its descenders into the subhead and clipped the GET STARTED
eyebrow. It now wears `hz-display`, the class this app already added for exactly
this (PublicLanding, v8.5.24) — one way, one rule, every token and breakpoint.
Measured after: 30px on 33px leading at desktop, clean two-line wrap at 390px.

e2e/leading.spec.ts pins the invariant rather than the call site: no visible text
node on /models, /agents or /playground may compute a line-height smaller than its
own font-size. It fails on the unfixed tree and catches the next numeric lineHeight
anyone writes without their knowing about RNW's allow-list.

No overlay was wearing the elevation ladder. Gui compiles its shadow props to an
atomic rule injected at runtime as `:root ._bxsh-…` — specificity (0,2,0). The
design-token utilities were plain `.hz-paper` (0,1,0) and lost, so the command
palette, app launcher, floating chat and three menus rendered Gui's
`0 12px 24px rgba(0,0,0,.33)` instead of ring + top highlight + --hz-elevation-3.
On the true-black canvas that shadow is nearly invisible — the sheets did not lift
off the page. The utilities are now `:root .hz-x.hz-x` (0,3,0): deterministic in
either stylesheet order, no !important.

And every anchored overlay now wears ONE surface. Eleven Popover.Content sites
passed Gui's `elevate` while three wore `hz-paper` — one concept, two depths, plus
the same bordered/bg/borderColor triple repeated fourteen times. All fourteen now
spread ~/components/ui/paper, which holds the surface, the token elevation and the
opacity-only hz-menu-in entrance in one place.

Verified by rendering: scope switcher, network picker, model selector, save-prompt
popover and the ⌘K palette all opaque, correctly anchored, ring visible, nothing
occluded. 3,086 unit tests pass; leading spec green.
2026-07-27 09:22:05 -07:00
hanzo-dev f9272ec880 Merge remote-tracking branch 'origin/main' into chore/pnpm
# Conflicts:
#	package-lock.json
2026-07-27 08:15:06 -07:00
hanzo-devandHanzo Dev fc5fe3c616 build: move console to pnpm, one lockfile, one pinned package manager
npm could never run `npm ci` here. The Dockerfile said so in a comment: @hanzo/gui
pulls a react-native tree whose platform and optional packages resolve differently
between npm versions, so a lockfile written by one npm failed under another, and
the build ran `npm install` — resolving the tree fresh every time and using the
committed lockfile as a suggestion. A lockfile nobody installs from is decoration,
which is how it drifted far enough that `npm ci` was already dead on main before
this change (react-native-worklets missing from the tree it claimed to describe).

pnpm records every platform in the lockfile, so the build installs exactly what is
committed and fails loudly rather than quietly resolving something else. Both
Dockerfiles now `corepack enable && pnpm install --frozen-lockfile`, corepack takes
the version from `packageManager`, and package-lock.json is gone. .gitignore is
reversed accordingly: pnpm-lock.yaml is the tracked one and every other manager's
lockfile is ignored, so a stray `npm install` cannot leave a second source of truth.

pnpm-workspace.yaml carries the two settings this needs, both documented in place.
Install scripts are denied unless named, and esbuild and sharp are named because the
app does not build without them. The release-age gate is excluded by SCOPE for
@hanzo/* rather than by version, because pnpm rewrites a per-version entry on every
bump, and a file that rewrites itself during install makes --frozen-lockfile fail in
CI — the exact determinism this change exists to get.

Verified on pnpm 11.17.0: frozen-lockfile install clean, tsc 0 errors, 3056 tests
passing, `pnpm build` compiles, and `pnpm build:embed` emits the real bundle cloud
fail-hards on (367KB index.html, 5.5M _next). node_modules stays self-contained —
every symlink is relative into node_modules/.pnpm — so the runner stage's
COPY --from=build of node_modules still resolves.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-27 08:13:46 -07:00
hanzo-dev fcdd98a80a event: 0.3.4 — the console error plane now resolves its own DSN
0.3.4 carries the product -> DSN registry, so `product: 'console'` resolves the
hanzo-console project with no env var and no build argument. This is the release
that actually makes console.hanzo.ai report errors.

Lockfile edited SURGICALLY (4 lines: the range, version, resolved, integrity).
Do NOT regenerate it here: both `npm install` and `npm install --package-lock-only`
drop the optional-peer block `node_modules/expo/node_modules/react-native-worklets`,
which `npm ci` then needs — that removal is what broke CI once already and had to be
restored by hand. npm ci verified green against this lockfile.

tsc clean; 3048 tests pass.
2026-07-27 08:13:45 -07:00
hanzo-dev cbcf2466a6 event: wire the error plane (0.3.1 -> 0.3.3 + dsn)
The console reported ZERO errors. Two independent reasons, both silent:

  1. @hanzo/event was pinned ^0.3.1 — the version with NO envelope code at
     all. captureError() collected and dropped.
  2. createAnalytics() was never passed a dsn. The error plane authenticates
     independently of the event stream; with no dsn it is inert by design
     (fail-safe), so even 0.3.3 would have stayed dark.

This file also asserted the failure into existence: it documented /v1/event as
being lensed server-side into 'error tracking'. There is no such fan-out. That
claim is why nobody looked. Corrected to state the two-plane reality and the
no-dsn => inert contract explicitly.

dsn comes from NEXT_PUBLIC_HANZO_EVENT_DSN, the convention hanzoai/app and
hanzoai/hanzo.ai already use. Publishable by design — it ships in the bundle.

Still dark until a DSN is set: NEXT_PUBLIC_HANZO_EVENT_DSN is set on zero CRs
fleet-wide, and no 'console' Sentry project exists yet
(/v1/sentry/console/envelope/ -> 404). This makes the surface correct so the
plane lights up on config alone, with no further code change.

tsc clean; 3048 tests pass.
2026-07-26 23:35:31 -07:00
hanzo-devandHanzo Dev 8f011bea82 Merge the forge into GitHub — converge the two mains
Same drift as cloud and iam: git.hanzo.ai held 133 commits GitHub did not, so the
sync could never fast-forward. Merging the forge in from this side needs no forge
credentials and makes that push a fast-forward again.

Two files conflicted, and both resolve to GitHub's side because GitHub is the newer
one: parseBlueprint and fetchOssApps were lifted into @hanzo/ui/oss and are
re-exported from here, while the forge still carries the inline copies. Taking the
forge's would put a second implementation of the compose reader back in the tree.

The merge also brings the forge's "unify Apps and command search" refactor, which
deletes AppLauncher and folds it into CommandPalette. That deletion arrives with the
CommandPalette and dashboard changes that stop calling useAppLauncher, so nothing is
left dangling — the merged tree has no reference to it.

tsc reports the same 24 errors as clean main and vitest the same 3028 passing with
the same 2 files failing to collect; both are the stale local @hanzo/ui (8.0.8
installed, ^8.0.11 required, which is where the /oss subpath lives). The merge adds
nothing to either count.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-26 23:02:53 -07:00
zeekay b286d48c7f fix(chrome): the console wears the ORG's identity, and the switcher is the account control's peer
Two defects in the cloud console's chrome, both fixed in the SHARED control
(@hanzo/ui 8.0.11) so every surface inherits them, not just this one.

The top-left mark showed the house H whenever an org had set no logo — a
customer's console showing OUR brand. It now renders `OrgMark` unconditionally:
the org's own logo when IAM carries one, else the org's MONOGRAM, the treatment
the account widget already gives a person. Never the house glyph, never the org
name as running text.

The org switcher was a caption beside a control. Its trigger is now the peer of
the account row — 44px tall, a 30px mark, the same type, the same hit area — and
`SidebarWorkspace` is a COLUMN so it stretches the sidebar's width the way the
account row does (a row container had shrunk it to its text).

One org-identity source: `useOrgLogo` (a URL) becomes `useOrgIdentity` (name,
display name, logo — one cached read), fed to BOTH the mark and the switcher's
new `current` prop, so the two slots can never disagree and a user with no
cross-tenant list still gets their own logo. The dead `BrandLogo` component,
a second copy of the same logo-else-mark decision, is gone.

`e2e/org-identity.spec.ts` measures it off the rendered boxes: without the change
the mark paints an SVG with no monogram and the switcher has no trigger to find.
2026-07-26 19:32:06 -07:00
zeekay 6116dae865 release(console): v8.5.31
Hanzo CI/CD / cicd (push) Successful in 6m19s
CI/CD / cicd (push) Successful in 6m20s
2026-07-26 17:17:30 -07:00
zeekay 9b7d8da187 feat(console): unify Apps and command search 2026-07-26 17:15:33 -07:00
hanzo-dev 52f7c9e245 fix(console): reachable footer links, ONE typeface, ONE sign-in on the anon landing
Three defects found by a rendered-DOM audit (CDP + hit-testing) of the live
cloud.hanzo.ai at 390x844 and 1440x900. Measured before AND after, per defect.

FOOTER legal links were CLIPPED off-screen at 390px. The link clusters are Views
(`flex-shrink: 0`), so they held max-content width and their own `flex-wrap` never
engaged: Terms painted at x 397->435 on a 390px viewport, while
`html,body{overflow-x:clip}` keeps `documentElement.scrollWidth` at 390 — the
overflow is CLIPPED, not scrollable, so a legally-required link could not be
reached by any gesture. `ConsoleFooter`'s `flexShrink` already fixes that in this
line; production is BEHIND it (live still renders the hero as a SPAN, so it
predates the same commit), so rather than re-fix it this locks the geometry: at
390 the row wraps to two lines, Terms lands at x 149->187, every link hit-tests
to itself, and nothing on the page is painted past the right edge.

HEADER chrome rendered in a SYSTEM font while the body rendered Geist.
`@hanzogui/shell` sets its own stack as an INLINE style on its root
(`fontFamily: CHROME.font` = `ui-sans-serif, system-ui, -apple-system, "Segoe UI",
…`, which names no Geist) and its subtree inherits it — its buttons re-declare
`font-family: inherit`. Live: wordmark `Noto Sans:11:SYSTEM`, nav
`Noto Sans:9:SYSTEM`, hero `Geist:26:custom` — mixed typography on one screen.
Geist loads fine (self-hosted woff2), so this is a CASCADE problem and the font
loading is untouched. One rule in globals.css pins `[data-hanzo-shell]` and its
descendants to `var(--font-sans)`; `!important` is required because nothing else
beats an inline declaration, and `code/pre/kbd/samp` keep the mono face so the two
font invariants stay orthogonal. After: nav `Geist:9:custom`, Meet-Hanzo
`Geist:10:custom`, CTA `Geist:7:custom` — the body's own face. Deleting ONLY that
rule from the CSSOM on the same build reverts the header to `Noto Sans:9:SYSTEM`
with the old stack, so the rule is demonstrably the fix, in isolation.

The desktop logged-out header carried TWO "Sign in" affordances. `HanzoHeader`
renders its OWN account link whenever `account` is nullish
(`account ?? <DefaultAccount/>`) and `landingSurface` already relabels the primary
CTA "Sign in", so live read `[Get API key] [Sign in -> /signin] [Sign in ->
href="#"]` — the duplicate was also a dead link. `PublicLanding` now declines the
control explicitly (`account={false}`: not nullish, so the default never renders,
and React draws nothing — including the mobile sheet's identity row).

Also: `@hanzogui/shell` was pinned `^7.6.4`, which is not published (latest is
7.6.3) — `npm install`, which the Dockerfile runs, fails ETARGET on it, so no
image could build. Relaxed to `^7.6.3`, which still admits 7.6.4 the moment it
publishes; this tree compiles and passes against 7.6.3.

Verification: `next build` ✓ ("Compiled successfully", types + 20/20 static
pages); `tsc --noEmit` clean; `vitest` 3024/3024; `e2e/landing-chrome` 3/3 against
the PRODUCTION build on `next start`. Font evidence is CDP
`CSS.getPlatformFontsForNode` (real family + custom-vs-system) — never
`document.fonts.check()`, which answers true on a page with zero @font-face rules.
2026-07-26 16:24:20 -07:00
zeekayandHanzo Dev f59f158eb1 chore: commit outstanding working-tree changes
2 files changed, 26 insertions(+), 2 deletions(-)

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-26 12:58:37 -07:00
zeekay 0f945b4b7a landing: a primary CTA that reads primary, a real h1, and Terms you can reach
Measured on cloud.hanzo.ai / console.hanzo.ai in a real browser.

The hero "Get started" computed to rgb(36,36,36) on rgb(204,204,204) -- dark
grey on black, LESS visual weight than the nav's white Sign in pill, so the
page's own primary action read as disabled. Beside it "Learn more" was
`chromeless`: no edge at all, not legibly a button. Both are now the house pill
pair -- white primary with dark text, hairline-bordered secondary, both
rounded-full. Measured after: rgb(242,242,242)/rgb(5,5,5) and a
1px rgb(56,56,56) border, radius 999px.

"The AI cloud, one platform" was a styled span; the primary product page had NO
h1 at all. It renders `h1` now -- same classes, byte-identical typography.

The footer's link clusters are Views, which are `flex-shrink: 0`, so they held
max-content width and their `flex-wrap` never engaged: at 390px "Terms" sat at
x 390->426 while documentElement clipped at 390, i.e. a legally-required link
that could not be scrolled to. They shrink now: body.scrollWidth 426 -> 390,
Terms lands at x 147. Those links were also the only underlined links on any
Hanzo surface -- now consistent.
2026-07-25 18:24:30 -07:00
Hanzo AI 5c110fa071 fix(console): the brand typeface never loaded in production — self-host it (8.5.26)
Every rule in the app asks for Geist. On live console.hanzo.ai `document.fonts.size`
was **0**: the two `@import url('https://cdn.jsdelivr.net/npm/geist@1.3.1/…')` were
refused by the browser as cross-origin (ERR_BLOCKED_BY_ORB), so every customer read
the entire product in system-ui. The import ORDER was already fixed once for this
same symptom — the remaining cause was the CDN itself, which is a dependency we do
not control sitting on our own critical render path.

Now served from public/fonts. One VARIABLE file per family spans weights 100-900, so
eighteen static cuts collapse to two requests (56K + 58K) and any weight the design
reaches for already exists — no second place to add a face.

Verified on the real `build:embed` export, not asserted: fonts 0 -> 2,
"Geist 100 900: loaded", document.fonts.check('16px Geist') true, woff2 served 200.
(Geist Mono reports unloaded on the landing page because nothing there sets mono —
correct lazy behaviour.)
2026-07-25 14:37:30 -07:00
Hanzo AI c12aa4b146 fix(console): the sign-in button says "Hanzo" on a Lux console — name the active brand (8.5.25)
The heading directly above it already reads `config.brandName`, resolved from the host,
so the same screen said "Lux Cloud" and then offered "Log in with Hanzo" — a white-label
leak on every non-Hanzo console (lux.id, zoolabs.id, pars). The button now reads the
same value the heading does: one brand fact, one source, no second place to drift.

Rescued from feat/hanzo-appbar, which is otherwise superseded — its app-switcher landed
on main long ago and it carried a /zach personal page that is not a product surface.
Branch deleted; this was the only unmerged value on it.
2026-07-25 14:03:48 -07:00
Hanzo AI d8d3562048 fix(console): hero overprinted itself on mobile — unitless line-height for display type
LIVE BUG, found by rendering the production landing at 390px: the hero headline
wraps to two lines on a phone and the lines OVERPRINTED each other — the front
door of console.hanzo.ai, unreadable on mobile. Desktop was fine (one line), which
is exactly why it survived: it is invisible until the text wraps.

Cause: a Gui font-size token ships a line-height tuned for ONE line. Nothing set a
line-height for the wrapped case, so the line boxes collided.

The fix must live in CSS, not a style prop. React Native Web reads a bare numeric
`lineHeight` in a style object as PIXELS — I tried `lineHeight: 1.1` there first
and it made the crush WORSE (1.1px leading), which is the tell. Unitless in real
CSS is relative to the element's own font-size, so ONE rule holds at $11 and $13
and every breakpoint: `.hz-display` in globals.css, worn via className (which
forwards to the DOM node on web) — the same mechanism as .hz-mono/.hz-tnum.

Verified by RENDERING, not by reading: 390/768/1280/1680 all pass with no
horizontal overflow, and the mobile screenshot shows 'The AI cloud, / one platform'
cleanly on two lines. tsc 0 errors; build:embed green. → v8.5.24
2026-07-25 13:36:18 -07:00
Hanzo AI 467cb4bbb8 release(console): v8.5.23 — brand+voice chrome, dead-CTA fix, monochrome, e2e proof
Cuts the semver for the waves already on main:
- chrome: white-label org logo, floating chat circle -> topbar brand-H + voice mic
  opening the right sidebar, freely resizable Developers dock, live Create-key
- fix: the landing's 'Open Console' was a self-link (read as 'login is broken');
  both header CTAs now resolve to the one sign-in surface (landing-surface.ts)
- monochrome: one semantic map (ui/tone.ts) across 3 passes; 15+ per-module colour
  ladders deleted; categorical scale split out (lib/theme/ramp.ts); vendor brand
  identity deliberately preserved. Exposed + fixed two real bugs: health identified
  by colour equality (degraded counted as healthy) and drifted vendor hues.
- proof: voice unit tests, chrome render spec, landing-surface + tone contracts.

tsc 0 errors; vitest 3013 passed / 8 skipped; build:embed green.
2026-07-25 12:27:54 -07:00
zeekayandHanzo Dev 75e89cb483 feat(console): complete monochrome pass — neutralize remaining $blue tokens, orange→amber
Follow-up to the design-token adoption: the remaining chromatic accents were
Tamagui theme TOKENS ($blue10/$blue11 links + icons, $blue3/$blue4 badge fills,
$orange10 severity), which the first pass's hex-focused sweep missed.

- $blue10 → $color11, $blue11 → $color12, $blue3/$blue4 → $color3/$color4
  (neutral ladder) across StorageModule, SearchModule, ContactModule, Tracker,
  embeddings, providers, platform-hub, StartupsModule and 8 more.
- $orange10 → $yellow10: severity/priority/waitlist states keep a sanctioned
  amber CAUTION hue (the design's allowed semantic), not a decorative orange.

Console chrome now uses zero chromatic accents — only the neutral ladder plus the
three permitted semantics (green/amber/red). tsc clean, next build ok.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-24 18:02:58 -07:00
zeekayandHanzo Dev b5544ba59b feat(console): Lux Network SuperAdmin board (Web3), brand-scoped lux
Multi-network validators (Lux primary/testnet/devnet + Pars/Osage/L2 coming-soon),
node/pod memory, 16 lux-* service health — real VM-hub telemetry via the gated
proxy, honest 0.0000-uptime note (tracker bug), zero cross-brand leak. Only on
console.lux.cloud. 10/10 shaper + 174 registry tests, tsc 0 errors. 8.4.156→8.4.157.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-24 17:53:39 -07:00
zeekayandHanzo Dev 2d16a512fb feat(console): monochrome redesign — adopt hanzoai/design tokens, purge chromatic accents
Wire the Hanzo Design System (hanzoai/design) into the console as the styling
source of truth and convert the whole surface to true-black MONOCHROME
(Linear/Vercel-grade), keeping only the genuine semantic hues (green live/success ·
amber caution · red error).

- Vendor the design token layer (color/type/space/radius/elevation/motion/z) into
  app/design/ — @hanzo/design is unpublished, and its README contract is copy-1:1 —
  and import it before globals.css so the Tamagui theme derives from the design
  neutral ladder.
- colors.ts: retire the per-product/category rainbow. Product + category icons now
  read ONE neutral (design --neutral-300); legacy chromatic prefs still resolve but
  only to greyscale. Public API + persisted keys unchanged.
- Charts CHART_PALETTE → monochrome descending-lightness ramp (was a purple-led
  rainbow); grid/axis neutralized.
- Primary actions (Deploy Endpoint / landing CTA) → theme-aware white-on-black
  monochrome (was purple #7c5cff).
- Purge #a371f7 / #c084fc / #3aa0ff / #7c5cff / #8b5cf6 / #5E6AD2 house accents
  across 30 modules → design neutrals; router / mission-control / scope / hero /
  railway / crm accents monochromized; hero + template-tile gradients → neutral.
- Tests enforce the monochrome guarantee (every swatch greyscale; legacy keys never
  reintroduce a hue). 2953 unit tests green, tsc clean, next build ok.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-24 17:52:29 -07:00
Hanzo AI d7a9659686 feat(console): brand-forward chrome — white-label org logo, kill the chat circle for a small H + voice, resizable Developers dock + live Create-key
- SidebarBrand: the selected org's OWN logo leads the top-left chrome (useOrgLogo,
  cached, shared with BrandLogo); the Hanzo H is only the fallback (hanzo org's
  logo IS the Hanzo mark). White-label — a tenant sees their brand front and center.
- FloatingChat: removed the big floating circle that covered page content. The
  assistant now opens from the topbar: a SMALL brand-H (openChat → the right sidebar
  / dock on desktop, the sheet on phones) + a mic 'talk to Hanzo' (startVoice).
- Voice (new src/lib/voice.ts): a feature-detected Web Speech wrapper (SpeechRecognition
  STT + speechSynthesis TTS), SSR-safe, strict-clean. Wired INTO the one chat binding
  (AiApi.ragChatStream) — a mic in the composer (renders only when supported) + a
  voiceSignal from the topbar; a completed utterance sends a turn and the reply is
  spoken back. No new backend, nothing leaves the browser.
- Workbench: the Developers dock is now FREELY drag-resizable (top handle → any
  height, persisted per-user) instead of a binary toggle; the maximize button is a
  tall/compact preset. Overview tab: a live 'Create key' button mints the real hk-
  Cloud API key inline (KeysApi.create), shown once with a copy affordance.
- AuthorsModule: dropped the public 'earn 20%' number from every hero/CTA/badge; the
  real rate stays as ONE muted 'Your rate' dashboard detail (per CTO — no public %).
- SystemStatusBadge: degrade 'Checking…' → neutral 'Status' after the first probe
  returns nothing (the go:embed build prunes /system-status), never a permanent spinner.

tsc --noEmit clean; npm run build:embed ✓ (static export + 30 handlers restored).
2026-07-24 17:11:45 -07:00
zeekay 7560573efb chore(cloud): bump @hanzogui/shell ^7.5.1 — polished mega-menu (single Products, complete hints, 3×2 flagship, focus ring, GCP-leak removed) 2026-07-24 00:09:46 -07:00
zeekay f1aa569730 feat(cloud): unified @hanzogui/shell HanzoHeader + rich Products mega-menu on cloud.hanzo.ai public landing (hanzo brand) 2026-07-23 23:08:22 -07:00
zeekay 457b2daa6a fix(auth): guard the PKCE callback to exchange the code exactly once
console.hanzo.ai login was stuck on /auth/callback with 'Sign-in failed.' The
OAuth code is single-use and @hanzo/iam removes the PKCE verifier before the token
fetch, so React StrictMode's double-invoke (reactStrictMode:true) — or any
handleCallback identity change re-running the effect — fired a SECOND exchange that
found the code consumed / verifier gone and threw, surfacing the failure even though
the first exchange succeeded. Add a useRef run-once guard so the exchange fires once
per page load. Also drop the vestigial @hanzo/iam-js-sdk dep (unused; one way = @hanzo/iam).
2026-07-23 15:09:02 -07:00
hanzo-dev 68e4bb5e87 console: keep the shell inert on navigation + drop in-product OSS upstream notices
Shell re-render (decomplect the route subscription):
- Dashboard no longer calls usePathname(); the route subscription is confined to
  the leaves that depend on it (SidebarNav for the active highlight, a new
  BreadcrumbsBar). A navigation click now re-renders only the swapped page content
  and those leaves — the topbar and sidebar chrome stay put (no flicker/lost state).

Upstream notices:
- Remove the per-product "Built on open source — forked from X" surfaces from the
  UI (the content-column note, the interstitial OSS-card clause, the overview
  "Upstream" fact). OSS attribution belongs in the repo LICENSE/NOTICE, not the
  product surface; permissive/copyleft licenses require the notice in source, not
  in-product. Upstream provenance stays catalog metadata for NOTICE generation.
- Delete the now-unused ProductUpstreamNote component.
- Drop the LibreChat upstream from the chat entry: the in-console chat is a native
  widget over /v1/chat/completions, not a LibreChat fork.

v8.4.154.
2026-07-23 01:57:34 -07:00
Hanzo AI 2d05c108e5 refactor(admin): repoint Block Storage board to /v1/admin/block-storage (8.4.153)
Follows the cloud endpoint rename (/v1/admin/storage → /v1/admin/block-storage,
cloud 9a51bffbc) so /v1/admin/storage stays free for the operator's S3
object-buckets view. Client (storage-fleet.ts) + the ADMIN_AGGREGATE_HEADS /
ADMIN_V1_HEADS allow-lists + the e2e mock all move to the block-storage head; the
registry entry id was already block-storage. The real admin.hanzo.ai Block Storage
view lives in hanzoai/admin apps/operator (this console board is the super-admin
twin on console.hanzo.ai).

NOTE: main is RED from a PRE-EXISTING unrelated error — src/lib/event.ts:55 passes
`ingestKey` to createAnalytics but the installed @hanzo/event AnalyticsConfig has no
such field (another agent's 8.4.152 analytics work; needs a @hanzo/event bump).
This repoint is green on its own; the event.ts RED blocks the shared build:embed
gate until that lane fixes it.
2026-07-22 23:47:44 -07:00
hanzo-dev 5a8de26c0c feat(console): telemetry on the canonical @hanzo/event 0.3.1 (/v1/event)
Upgrade @hanzo/event ^0.2.0 -> ^0.3.1 — the ONE telemetry client that POSTs
every signal (pageview · product event · identify · error) as one batched
stream to the ONE Hanzo Cloud front door /v1/event, lensed server-side into
web analytics, product insights, and error tracking (subsumes @sentry). The
0.2.0 client posted the deprecated /v1/analytics + /v1/tracker.

- ONE shared client (src/lib/event.ts): createAnalytics({ product:'console',
  host:'' (same-origin), ingestKey }). host:'' posts to the console's own
  /v1/event so the first-party session cookie rides along (go:embed cloud
  native; standalone BFF forwards as the signed-in user); the client NEVER
  sends an org — Cloud stamps the tenant from the validated session.
- The provider references the shared client; the three existing error
  boundaries (product, dashboard, global) report React render errors via
  reportError() to the same stream — including the provider-less global-error
  boundary, the reason the client is shared. Auto error capture (window.onerror
  + unhandledrejection) + beacon-on-unload are on by default.
- Consent + PII: PII-free by construction (anon id + the stable owner/name
  actor id, never an email; org never sent) and honors an explicit GPC /
  Do-Not-Track opt-out — the consent layer for logged-out/public views.
  Logged-out pageviews + errors ingest with an optional publishable key
  (NEXT_PUBLIC_EVENT_INGEST_KEY).
- Product moments: + AGENT_CREATED, CHAT_STARTED/CHAT_MESSAGE_SENT,
  SIGNUP_COMPLETED (atop the existing PROJECT_CREATED, API_KEY_CREATED,
  PRICING_VIEWED/PLAN_CLICKED/CHECKOUT_STARTED, APP_CREATED/DEPLOY_STARTED,
  FIRST_ACTION).
- proxy-allow: add the `event` head so the standalone BFF forwards /v1/event.

tsc clean; vitest 2933/2933; next build + build:embed green.
2026-07-22 22:46:48 -07:00
Hanzo AI 4fbd373d5d feat(admin): Block Storage board — realtime DO fleet + datastore fill (8.4.151)
The admin.hanzo.ai realtime block-storage view, so we can watch the analytics
datastore fill and scale DO storage before it runs out. One read:
StorageFleetApi.snapshot() -> GET /v1/admin/storage (the global-admin-gated
aggregate; storage added to ADMIN_AGGREGATE_HEADS + ADMIN_V1_HEADS).

- StorageFleetModule (Observe, admin:true): fleet KPIs (volumes / provisioned /
  used / monthly $), the analytics datastore highlighted with a green/amber/red
  fill bar + near-full badge, near-full alerts, and the full volume list.
- Honest by construction: DO gives capacity + attachment but NOT fill %, so a
  volume's used/pct render an em-dash "—", never a fabricated number; the
  datastore card shows only when a filesystem source (system.disks) reported fill.
- e2e (storage-fleet.spec.ts): renders the datastore (200 GiB), fleet KPIs
  (295 volumes / $1,309), a 91% near-full alert, and the honest "—" — passes.

Ships to admin.hanzo.ai via the next hanzoai/cloud release embedding console@main.
Pairs with cloud GET /v1/admin/storage (DO volume inventory + system.disks fill).
2026-07-22 22:32:37 -07:00
hanzo-dev 507b7b04a0 chore(console): v8.4.150 — unified Code hub 2026-07-22 18:47:24 -07:00
d520665cff fix(console): go:embed org-switcher + Observe→Status — IAM-admin & PaaS use cloud-native /v1/* (8.4.149) (#167)
console.hanzo.ai/cloud.hanzo.ai serve the go:embed console inside the cloud binary,
whose webui.go serves the SPA index (HTTP 200 HTML) for any non-/v1/ path. build-embed
stashes the Next BFF route handlers, so the OrgSwitcher (/admin/iam) and Observe->Status
(/paas) client calls fell through to the SPA shell and threw 'Invalid response from
server (HTTP 200)' -> missing switcher + 'Could not reach the platform'.

Cloud already serves the equivalents natively at /v1/iam/* and /v1/paas/*, so in the
embed (IS_EMBED) the IAM-admin client uses client.ts iamList/iamOne/iamMutate and the
PaaS inventory uses cloudProxyV1Url('paas/...'). Standalone console2/admin.hanzo.ai are
UNCHANGED (their /v1 BFF deliberately excludes iam/* and paas/*). Scoping unchanged.

tsc clean (2 files); vitest 109 baseline + 3 new embed-path assertions.

Co-authored-by: hanzo-dev <dev@hanzo.ai>
2026-07-22 18:33:47 -07:00
Hanzo AI c3b4e4c8c6 chore(console): 8.4.148 — Developers workbench full tab set 2026-07-22 03:17:27 -07:00
Hanzo AI 2d67180feb console: app launcher renders the full canonical surface set (adds bot/chat)
AppLauncher's cross-surface tiles now come from the ONE @hanzo/ui surfaces list
(otherSurfaces('console') = every surface but this one), each with a distinct
icon — replacing the hardcoded team+billing pair. Bumps @hanzo/ui to 8.0.6.
2026-07-21 23:57:34 -07:00
hanzo-dev 195b62916a fix(auth): patch @hanzo/iam to derive token expiry from the JWT exp (kills the login loop)
console.hanzo.ai silent-SSO-looped: the hanzo-cloud IAM app issues tokens with no
configured lifetime, so the token response omits expires_in. @hanzo/iam@0.13.6
storeTokens() only writes hanzo_iam_expires_at WHEN expires_in is present (no else),
so isTokenExpired() returns true forever → getValidAccessToken() returns null → a
freshly-minted token reads as expired → endless authorize redirects (~128/min).

patch-package adds the missing else to all 6 SDK bundles: when expires_in is absent,
fall back to the access token's own JWT exp claim (RFC 7519) — authoritative for IAM's
JWTs, correct on the initial exchange AND every refresh. + patch-package postinstall so
CI applies it on npm ci. (SDK repo is badly diverged from the published 0.13.6, so a
republish would regress; patching the exact published bundle is the zero-regression fix.)
2026-07-21 22:46:19 -07:00