A browser strips a tab out of a URL before it parses it, so /<tab>/host arrived
as //host: another site, drawn as this app's own section.
Co-authored-by: Hanzo Dev <[email protected]>
A section points at a path of the app or an https address on another host;
a protocol-relative, script or bare address is not drawn, and the home mark
falls back to the app's root. The scope renders in exactly one place, the bar
or the phone's sheet, so there is one selection and one set of reads. A
notification is marked read or archived only when its id is a single plain
segment, and a reason the server names is looked up in a Map. The scope's
keys are per workspace, not per person, and the README says the host clears
them when the person changes. `.auth/` is ignored.
Co-authored-by: Hanzo Dev <[email protected]>
hanzo.ai and the Hanzo App each drew their own copy of the workspace shell,
and the copies had drifted. The frame is here now, once: the rail of
sections, the sidebar with the workspace switcher at its top and the account
at its foot, the bar, the pane, and the column beside it that a room fills
through Beside.
The rail is the sidebar shut on a laptop (an explicit toggle kept per device,
never hover) and the tab bar on a phone. The far-left strip that stands beside
an open sidebar is opt-in through `strip`, off by default. The account menu is
Profile, Notifications, Appearance, Security, Sign out, and carries no
balance. Scope puts project and environment in the bar, kept per workspace
and never carried across one. Notifications reads the reader's own through
the host's call; the package holds no credential.
The frame follows the host's theme, so its inks are the host's own rungs
(FRAME in theme.ts). Its layout lives in shellStyles.ts beside the chrome's,
injected by an insertion effect so the grid is in place before first paint.
@hanzogui/shell 8.3.19
Co-authored-by: Hanzo Dev <[email protected]>
Every plane hangs inside the header, and the header carries backdrop-filter,
which makes it a backdrop root: a descendant's own blur samples only what the
bar paints, never the page. So the 72% glass plane showed the hero straight
through the menu in Chromium and WebKit alike. The plane is the opaque panel
now, the bar wears that same ground while a plane or the sheet hangs from it,
and the veil dims deep enough to recede without its blur. The scrolled bar is
still glass, where the blur is real. No API change.
@hanzogui/shell 8.3.18
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]>
The bar linked Privacy, Terms and Cookies. The Acceptable Use Policy, the
Data Processing Addendum and the subprocessor register — what a procurement
review asks for first — were reachable only from inside another document.
hanzo.ai/legal indexes all of them; the bar now names it.
Co-authored-by: Hanzo Dev <[email protected]>
Hovering a column lights it and steps the others back, and below the
rail's width the four sections are peers rather than one tall column
with three stubs under it.
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 platform is what they were, so the menus name it once and the column
under it lists what the platform actually offers.
Co-authored-by: Hanzo Dev <[email protected]>
Cloud and Team are what the platform does rather than products beside
it, so one name carries all three and one door opens the console.
Co-authored-by: Hanzo Dev <[email protected]>
The brand pill pads by 12 and handed all 12 back, which put the NAME itself
exactly on the page's column. That is right for body text and wrong for a
wordmark: set in bold beside the window's edge it reads as flush, and the
gutter looks like it was forgotten.
Four of the twelve are kept. The lit pill still grows outward into the
gutter, and the name now sits a hair inside the column instead of on it.
An open menu is a layer over the page: the plane wears GLASS, the grounded bar
wears the same, so from the top of the screen to the bottom of the menu there is
one ground, one blur and no edge.
App, Bot, CLI, Cloud, Dev, IDE, Platform, Team, each with the line its product
page says under the name. Chat and Base stay products and leave the estate
menu. Every console address is platform.hanzo.ai.
Every door that opened console.hanzo.ai opens platform.hanzo.ai — the same
console, simplified for the people who use it — and the menu row wears the
product's name, Hanzo Platform.
App, Bot, CLI, Dev, IDE, Platform, Team — then Cloud and Chat. CLI and Platform
become flagship products (hanzo.ai/cli and platform.hanzo.ai both serve), and
Team is released rather than beta, so a signed-out reader meets all seven.
The order is stated once, where the menu derives it, so the product table stays
a table of what exists and a new product sorts to the end until it is ranked.
The bottom bar reads left to right as the page does: whose it is, what
governs it, then the mark closing the column.
The brand wears the chevron every other trigger in the bar wears. It was
the one control that opens a menu and did not say so, so a reader looking
for the products read past it.
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.
Node's require never selects a package's react-native condition and
cannot load an ES module whose sources say a bare require, so the
extractor resolves that entry itself and translates such a file to
CommonJS as it loads. Directories are walked with the filesystem's
glob, negative numbers become AST through valueToNode, the swallowed
load error is named under DEBUG, and the optimize fixture is its
source again with the suite restoring it after each run.
Co-authored-by: Hanzo Dev <[email protected]>
Native sources say a bare require, which Metro provides and an ES
module cannot; marking dist/native CommonJS like dist/cjs lets Node
load it as written, so the optimizer's native pass runs again. The
.cjs sibling answers there too, the CLI imports its commands, the
loader names its css file by the emitted name, and the extractor
tolerates a dependency whose tsconfig extends a preset it lacks.
Co-authored-by: Hanzo Dev <[email protected]>
A relative import without its extension survives the emit unchanged
and nothing but a bundler can load it, and the .cjs and directory
shims at package roots pointed at emits no build produces. The exports
map is the one entry; the worker each emit runs is the worker of its
own kind.
Co-authored-by: Hanzo Dev <[email protected]>
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 CJS pass ran only where a manifest named a require entry, so an
ESM-only package could drift into the tree and a package marked
commonjs could carry ESM files. Now every package gets all three emits
from tsgo; a .cjs.ts sibling answers for the one file that needs
import.meta, and the CommonJS output is parsed as a script so a module
that cannot load there fails the build by name.
Co-authored-by: Hanzo Dev <[email protected]>
Four defects in the shell's nav, all measured on hanzo.ai.
THE GUTTER RODE A BOX INSIDE THE PLANE, not the plane. A host must
style `[data-hanzo-plane]` to break a full-bleed drape out of its page
rail — hanzo.ai does — so the host's gutter REPLACED the local nav
card's and STACKED on these two. The local card opened its first column
at x=80 at every width; MeetHanzoMenu opened at 121 / 127 / 138 / 152 at
1024 / 1180 / 1440 / 1920. Two menus off one bar, drifting further apart
the wider the window. One gutter now, on the element the host can see.
Both start at 80.
THE BACK ROW WAS CENTRED. `<button>` with display:flex and no
justify-content takes Chrome's UA `button{justify-content:center}`, and
this was the only sheet row not wearing `sheetRow` — which states
space-between and never met it. `textAlign:'left'` was already there and
cannot help: text-align does not place flex items. "‹ Home" moved from
x=346.6 to x=49, which is where Documentation, API Reference, SDKs and
CLI already were.
THE PALETTE THREW AWAY MARKS IT WAS HANDED. `TRY_HANZO_GROUPS` carries a
`glyph` on every row and `PaletteRow` already had an unused `icon` prop;
the rows in between dropped it. Ten rows now wear the same marks the
mega-menu wears — one product, one icon, read from the registry rather
than a second copy. `anyMark` holds the column so glyph-less page rows
keep their title x.
A HEAD WAS THREE RANKS. 13/600/.92 on the drape and the local nav plane,
11/600/dim in the palette, 11/400/.45 in the mobile sheet. One rank now,
on all four, and 10px of air under it rather than 6.
(cherry picked from commit d4a4d6f8ecba353771adc30af1df2ab8d6f0f7e9)
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.
UserAvatar named its shape and its crop in `rounded-full object-cover`.
This package ships no stylesheet and its hosts are not all Tailwind, so on
hanzo.ai those two class names resolved to nothing: a photo rendered as a
square and squashed to the box instead of cropped to it. The values are
inline now, which every host honours, and `className` stays the caller's.
Hanzo Base returns to the estate with it. `stage` is what holds a product
back — every surface filters the list through `sees(stage)` — so clearing
the flag took a released product out of the mega-menu and the footer for
everybody, against what stage.test.mjs asserts.
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.
Its entry carried no `flagship` flag, so it was never in HANZO_FLAGSHIP — and
the doors come from there. Every Hanzo surface's Try Hanzo menu and every
command palette offered App, Cloud and Dev and said nothing about the product
the estate leads with. Nothing errored; the door was simply absent.
An omission rather than a decision: the note above that list names Team, Bot and
Studio as the ones held back until they are ready, and Chat is neither
unfinished nor unannounced — it carries a `page` on the marketing site and is
the door most readers open the palette to find.
A light host flips this chrome's inks and could not flip what they sit on. The
inks are `var(--white-92, …)` — a name, with white only as the fallback — so a
host that defines those names inverts them. The plane under them was the bare
literal #0a0a0a, which no variable reaches, and the menus came out black on
black: measured 1.06:1 with the whole menu present and invisible.
`panel` is now `var(--chrome-panel, #0a0a0a)`, written the way every ink beside
it already was. Undefined still means #0a0a0a, so a host that themes nothing
gets today's chrome unchanged; a host that themes the inks can theme the ground.
The sibling-dimming rule had the same shape and the same fault: it stated
rgba(255,255,255,0.46) flat, with `!important`, so on a light plane the whole
menu went white-on-white the moment a pointer entered and nothing could reach
it. It takes `--white-45`, which is that value to within a rounding step and is
already a name a light host redefines.
@hanzogui/config/v5 starred both @hanzogui/themes/v5 and ./v5-base, and
v5-base re-exports a dozen names it takes from themes/v5 — createThemes,
tokens, the palette helpers, the theme types. ESM calls a doubly-starred name
ambiguous and omits it rather than picking one, so every shared name silently
left the module: `import { tokens } from '@hanzogui/config/v5'` compiled and
resolved to undefined.
It ships as a patch in at least one consumer, which is the wrong home for it —
a fix in a patch file is a fix exactly one app gets, and only until its
overrides drift. So v5 now names the nine things themes/v5 carries that
v5-base does not, and everything they share arrives through v5-base, which is
the module that already curates it.
8.3.1.
Chat, Base and Studio were flagships, so the footer PRODUCTS column and
the mega-menu listed eight. The five that are sold are App, Bot, Cloud,
Dev and Team; the others keep their pages and their registry entries.
The footer wore the lockup next to the copyright line, which already
sets the name in type, so the row read Hanzo twice.