One label per runner, spelled <os>-<arch>. The aliases these jobs named
were removed, and a job naming a label no runner carries waits instead of
failing.
Co-authored-by: Hanzo Dev <[email protected]>
`bounties(uint256)` returns eighteen components. This board declared thirteen,
in an order that predates the reward asset being split from the stake asset.
Every component is a static type, so the short list did not revert: it read
thirteen words off the front of the answer and named them by the wrong list.
Position 1 is `rewardKind`, an enum in 0..3, which came back as
`token: 0x00…03`, and every address after it moved up one slot — funder read as
approver, approver as arbiter, worker as a deadline. The board rendered, and
credited the wrong people.
The events were stale in the same way. `BountyProposed` carries ten parameters,
not seven: `rewardKind`, `rewardTokenId` and `stakeToken` joined it when the
assets were split, and with a seven-parameter fragment `issueRef` — the only
dynamic member, so the one whose offset moves — decoded to noise.
`BountyFunded` and `BountyCancelled` name their third parameter `reward`.
`DisputeResolved`, `StakeSlashed` and `RewardRecovered` were not declared at
all; `ActivityKind` already had names for two of them, so the activity log had
been promising steps it could not read. All twelve lifecycle events are now
read.
The single `token` field is gone. A reward is `rewardKind` / `rewardToken` /
`rewardTokenId` / `reward` and a stake is `stakeToken` / `stake`, because a
reward may be an NFT while the worker's stake stays fungible. Both are read
through one `Asset`, so the board can show a stake beside a reward without a
second formatter. `parseReward` inferred the reward kind from whether the token
address was zero; the kind is recorded now, so the inference is deleted rather
than kept as a fallback, and `RewardType` takes the contract's name for it,
`AssetKind`. `Task` stops carrying a derived copy of the reward: the render site
reads it off the struct.
`src/abi.test.ts` pins the eighteen names and types, then encodes a bounty whose
every field differs and decodes it back through the fragment, so the order is
measured by what it does and not only by what it says. Swapping funder and
approver, dropping a component, or restoring the seven-parameter
`BountyProposed` each fail it.
Fixture rebuilt to the eighteen-field shape — in the old order it would have
enshrined the decode it was written under. Its Pars address and the README's
address tables were the superseded V1 set, which answers a different struct;
both now name the deployment in lp-0020.
Co-authored-by: Hanzo Dev <[email protected]>
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.
luxd serves chain APIs under one segment from constants.ChainAliasPrefix
(constants v1.6.4, node v1.36.181):
/v1/bc/<alias>/... -> /v1/chain/<alias>/...
/ext/bc/<alias>/... -> /v1/chain/<alias>/...
Observations keep their original addresses: a dated note or a recorded status
code is a fact about when it was written, not an instruction to follow.
github.com held commits this host lacked, which refuses the mirror push
("fetch first") and stops github receiving anything at all. Merging rather
than overwriting, so the mirror is a fast-forward again and neither side's
work is dropped.
Adds .hanzo/workflows/cicd.yml calling the canonical pipeline and drops the
GitHub-hosted workflows it replaces. Lands here because this is where the repo
is written — git.hanzo.ai is upstream and github.com is its push mirror, so the
same change made on github.com never reaches the runner fleet.
Moved to .hanzo/workflows. .github/workflows is read by both the forge and
GitHub, and the luxfi GitHub org has zero registered runners, so every build
here had two schedulers and one of them could never finish. Now it has one.
ghcr login switched from github.actor + secrets.GITHUB_TOKEN to
GHCR_USER/GHCR_TOKEN. On this forge GITHUB_TOKEN is minted by git.hanzo.ai and
ghcr.io answers `denied: denied` to it — the measured cause of every red build
of luxfi/cloud (17 of 17). GHCR_USER/GHCR_TOKEN are luxfi org-level secrets
here and carry write:packages.
Dropped cache-from/to: type=gha, which addresses the GitHub Actions cache
service and has nothing behind it on this forge.
Runner label normalised to lux-build-linux-amd64: lux-build, lux-build-amd64
and [self-hosted, linux, amd64] are three names for the same pool.
Co-authored-by: Hanzo Dev <[email protected]>
src/ui.tsx carried its own icon set: a local `svg()` factory and 37 glyphs drawn
by hand at strokeWidth 1.7 — Lucide is 2 — so every icon on the live 4-org hub
was a half-step lighter than the same icon in @luxfi/ui rendered beside it.
IconHalfCircle went further and painted a filled half-disc over an outline
circle, making the "in progress" chip the only duotone mark on a board that is
otherwise monochrome. And the reward row drew its NFT placeholder as the emoji
🖼️, which is not an icon at all — it is a colour font glyph, immune to
`currentColor` and sized by the text.
All 37 are now re-exported from `@luxfi/ui/icons` under the same `Icon*` names,
so not one view changed: `export { House as IconHome, Kanban as IconBoard, ... }`.
The emoji is Lucide `Image`. @luxfi/ui moves 7.4.0 -> 7.4.3, which is where
`./icons` lives.
Discord, X and GitHub keep their hand-drawn marks. A logo is not a UI glyph and
no icon set gets to redraw someone's mark.
Measured on the running preview, same page, same build pipeline:
before 22 <svg>, 0 lucide-classed, body 996 chars, 0 page errors
after 22 <svg>, 18 lucide-classed, body 996 chars, 0 page errors
`tsc --noEmit && vite build` clean.
Co-authored-by: Hanzo Dev <[email protected]>
Pin the lux-mainnet and hanzo-mainnet deployments to the CI images built from
e1a1430 (FIXTURE gate + title wrap + per-brand favicon). Applied + rolled out
to do-sfo3-lux-k8s / do-sfo3-hanzo-k8s; verified live 4/4 breakpoints.
Co-authored-by: Hanzo Dev <[email protected]>
Three prod UI fixes from the 4-res audit (work.lux.network + work.hanzo.network):
1. FIXTURE debug bar in prod. The LIVE/FIXTURE data-source pill (and the raw
RPC error string it surfaces, e.g. "Missing or invalid parameters") was
pinned bottom-left for every visitor and clipped past the mobile viewport.
Gate it to `vite dev` (import.meta.env.DEV) or an explicit VITE_SHOW_STATUS
flag, and cap the pill at calc(100vw-1.5rem) + overflow-hidden so it can
never overflow even in dev.
2. Bounty-card title truncation. Space-card names ("Ecosystem &
Partnerships", "Community & Social", "Content Creation") were ellipsis-
clipped at tablet/laptop. Drop `truncate`; let the title wrap.
3. Favicon. Replace the generic emerald green-check placeholder with each
brand's own square mark, injected per-build by the brand-html Vite plugin
as an inline data: URI (CSP-safe, no extra request). New purpose-built
16px-legible favicon.svg per brand (lux/hanzo/zoo/pars).
Board still reads $0 / 0 tasks because the work-market contracts are not yet
deployed on 96369/36963 (owner-gated) — untouched here.
Co-authored-by: Hanzo Dev <[email protected]>
Vite 8's Rolldown bundler needs @rolldown/binding-linux-x64-musl, but a committed
darwin-generated lock omits it and npm bug #4828 won't add it on install → 'Cannot find
native binding'. Copy only package.json and npm install fresh on the linux-musl build host so
the correct platform bindings resolve. Repo lock stays for local-dev reproducibility.
Co-authored-by: Hanzo Dev <[email protected]>
The @luxfi/ui migration bumped to TypeScript 7 (native) + Vite 8 (Rollup 4), both of which
ship platform-specific PREBUILT binaries as optionalDependencies. Two failures resulted in the
alpine CI build: (1) npm ci against a darwin-generated lock lacked the linux-musl variants, and
(2) the TS7 native compiler has no alpine/musl binary at all, so 'tsc --noEmit' can't run there.
Fix both the ecosystem-standard way (same as the dao-vote image): npm install resolves the
platform-correct optional deps on the linux build host, and the image builds the bundle with
vite directly (esbuild transpiles TS; the typecheck is a dev/CI-lint concern, verified locally).
Co-authored-by: Hanzo Dev <[email protected]>
The @luxfi/[email protected] migration regenerated the lock with npm 11, which dedupes the
[email protected] that @luxfi/ui's @expo/* deps require — but the Dockerfile builds on
node:22-alpine (npm 10) via 'npm ci', which demands those nested entries and failed
EUSAGE 'Missing: [email protected] from lock file'. Regenerate the lock with npm 10 so it
matches the build tool; no bypass of npm ci.
Co-authored-by: Hanzo Dev <[email protected]>
The Connect sheet's wallet method was Phantom-specific — window.phantom.ethereum only —
so eth_requestAccounts never fired for MetaMask/Rabby/Coinbase users (Zoo/Pars Connect
read as a no-op). Replace the narrow 'phantom' WalletKind with a generic 'injected' one
that resolves window.ethereum (any injected EIP-1193 wallet, Phantom included) and opens
the wallet's own connect prompt, so claim->sign is reachable on every brand.
Co-authored-by: Hanzo Dev <[email protected]>
Phase-1 built @luxfi/ui locally and consumed it via `file:`, which blocked the
merge. 7.4.0 is now published to npm WITH dist/ (real tarball, 192 dist entries)
via the KMS-gated release path, so switch the dep `file:` -> `@luxfi/[email protected]`
(exact, matching the sibling @hanzogui pins). package-lock now resolves the
registry tarball (integrity sha512-LfUYgZFP…).
Drop the local `body .font_body { line-height: 1.5 }` pin from src/index.css:
7.4.0's AppProvider now ships that Tailwind-Preflight reset itself (injected once
via useInsertionEffect), so every consumer — not just the work-board — inherits
it. One place, one way.
Verified against the npm package: build green (tsc + vite 8.1.5, 1622 modules);
visual-regression 25/25 goldens within 2% (21 zoo + 4 pars white-label), incl.
the task-detail sheet + ⌘K palette overlays that the pin originally fixed.
Co-authored-by: Hanzo Dev <[email protected]>
@hanzo/gui's GuiProvider wraps the app in a Tamagui `font_body` context that
pins an absolute line-height (23px). That cascaded into the work-board's
arbitrary-size text (text-[11px] field labels etc., which carry no line-height
utility), making each ~6.5px too tall and reflowing the bottom-anchored
task-detail sheet by ~33px on mobile. Restore the Tailwind-Preflight baseline
(html line-height:1.5) the work-board was authored against.
Visual-regression now 25/25 work-board goldens within 2% (21 zoo + 4 pars
white-label). task-detail modal panel height matches the pre-migration build
exactly (576px == 576px).
Co-authored-by: Hanzo Dev <[email protected]>
Set the Zoo (200200) and Pars (494949) addresses (bounty/escrow/reputation + karma,
+ Pars governor) to the newly deployed DAO-Safe-owned work-market, superseding the
smoke set the boards read today. Owner/governor unchanged. Lux + Hanzo untouched.
Co-authored-by: Hanzo Dev <[email protected]>
All four white-labels pinned to the single green sha e498092 (identical source to
d9d3a0e — only the CI matrix changed):
zoo sha-e498092 work.zoo.network (zoo-mainnet / zoo-k8s, @file)
pars pars-e498092 work.pars.network (pars / pars-k8s, @file)
lux lux-e498092 work.lux.network (lux-mainnet / lux-k8s, IngressRoute)
hanzo hanzo-e498092 work.hanzo.network (hanzo-mainnet / hanzo-k8s, @file)
lux-k8s's public ingress reads the kubernetescrd provider (Traefik IngressRoute,
group hanzo.ai/v1alpha1), not @file, so work.lux.network mirrors api.lux.network
(certResolver letsencrypt, Cloudflare-proxied). hanzo mirrors api.hanzo.network
(@file router, certResolver letsencrypt via DNS-01, grey). One brand per image;
zero cross-brand leak.
Co-authored-by: Hanzo Dev <[email protected]>
Mirrors the zoo/pars legs — VITE_BRAND selects the src/brands.ts profile and
VITE_RPC_URL bakes the brand's live CORS-enabled RPC into the bundle. Two more
immutable per-commit images from identical source:
lux -> ghcr.io/luxfi/work-board:lux-<sha> (work.lux.network, #7000FF)
hanzo -> ghcr.io/luxfi/work-board:hanzo-<sha> (work.hanzo.network, #ea580c)
One brand per build; zero cross-brand leak. amd64-only static SPA, luxfi ARC pool.
Co-authored-by: Hanzo Dev <[email protected]>
- brands.ts was importing .svg logos at top level; vite.config.ts imports
brands.ts (for build-time title/CSP), and the config bundler parses SVG
markup as JSX → 'Unexpected JSX expression'. Split the .svg imports into a
runtime-only src/brand-logos.ts (BRAND_LOGOS keyed by BrandKey); brands.ts
is now pure, bundler-safe config data. ui.tsx BrandMark/BrandLogo read
BRAND_LOGOS[BRAND_KEY].
- fixture.ts: add lux+hanzo empty fixtures (no live bounty yet) to satisfy
Record<BrandKey,...> after brands.ts grew to 4 brands.
Full build green (tsc + vite). Lands the in-flight ⌘K palette, 6-category
symmetrical grid, real brand logos, and proper accents (Zoo #6c5efb indigo,
Pars #1C3879 Persian blue, Lux #7000FF, Hanzo #ea580c — placeholder green gone).
Co-authored-by: Hanzo Dev <[email protected]>
Builder + brand agents paused on the weekly usage cap (resets Jul 21 6am PT).
Preserving in-flight work-board work so nothing is lost; next resume greens + finishes.
Co-authored-by: Hanzo Dev <[email protected]>
Render brand.spaces as a 1→2→3 column grid (1-col phone, 2×N tablet,
3×2 desktop for six categories) — driven by whatever spaces the brand
supplies, no hardcoded count, so it stays symmetrical as ae530aa6
expands Zoo/Pars to six. Cards are peaq-style: emoji tile + name +
"N open tasks · N contributors" (singular/plural aware), uniform
structure so the grid reads as one clean block.
Co-authored-by: Hanzo Dev <[email protected]>
One palette over bounties / spaces / views / quick actions (new
suggestion, connect). ⌘K / Ctrl-K toggles it on desktop (discoverable
via a Search pill in the topbar); a ≥44px search icon in the mobile top
bar opens the SAME palette as a full-screen bottom-sheet. Keyboard-nav
↑/↓ + Enter, Esc/⌘K to close; the rendered order and the selection
index share ONE grouped list so the highlighted row is always the row
Enter runs. Dependency-light (no kbar) — a subsequence fuzzy scorer.
App also composes the prior 64px icon-rail split into the shell.
Co-authored-by: Hanzo Dev <[email protected]>
min-w-0 on the leaderboard grid cells lets long addresses/amounts
truncate instead of forcing page-level horizontal scroll at 390px;
narrower snap widths keep board columns fully on-screen on phones.
Co-authored-by: Hanzo Dev <[email protected]>
Mobile-first (390px up, enhance to desktop):
- Sidebar → off-canvas drawer behind a hamburger (overlay + tap-close + safe-area);
static 256px sidebar only at md+. Mobile top bar carries hamburger + workspace + Connect.
- Kanban → horizontal scroll-snap columns (~86vw, peek next) on mobile; 4-col grid at xl.
- Leaderboard / All-Contributors tables wrapped in overflow-x scroll regions (min-w),
body never scrolls sideways. Tab row is horizontally scrollable.
- Task-detail + suggestion modals are full-bleed / bottom-sheet on mobile, centered cards at sm+.
- Overview stacks main + right rail on mobile; all view paddings responsive.
- index.css: overflow-x hidden, env(safe-area-inset-*), clamp() fluid type, thin scroll-x utility.
Design tokens: consolidated every surface hex to the Dework palette as CSS vars
(--bg #0e0e10 / --rail #141416 / --surface #17171b / --border #232328 / --hover #1e1e24),
set per-brand --brand accent at runtime. Deleted the now-unused theme.ts (dead code).
Leaderboards: Task Points = global Karma (formatAmount(karma)) with a transparent
tasks×100 proxy until Karma is canonical on-chain; dropped the separate Karma column.
Contract naming: ABI imports stay indirected via abi.ts (one-line swap for the
finished non-versioned Bounty/Escrow/Reputation/Karma set); scrubbed V1/V2 from
comments + README; README now documents the full platform.
Every view still renders from the fixture with no wallet/chain (screenshot-able).
Verified live at work.zoo.network (real Zoo data) + local at 390px and 1440px.
package: 0.2.0 -> 0.3.0
Co-authored-by: Hanzo Dev <[email protected]>
Extends the on-chain work-board into a full Dework-style community surface,
white-labeled per domain, on our own primitives. Every view derives from ONE
on-chain read (loadWorkspace) so header/board/leaderboard numbers can't drift.
Views (all reading live Bounty/Reputation over JSON-RPC):
- Overview: org header + Time-to-payment / Total-paid stats (from PaymentReleased
+ BountyProposed event timestamps), Discord role-gate banner, Bounties category
grid (Spaces), Projects, right rail (About/Contributors/Admins).
- Board: 4-lane kanban (To Do/In Progress/In Review/Done) with Dework empty-state
framing; task cards show title + skill tags + reward + open-to badge + assignee.
- Open Tasks: rows grouped by status with skill tags + reward + assignee.
- Leaderboards: Top Contributors + Top Reviewers (7d/30d/all-time; windows from the
event stream, all-time from Reputation) + All Contributors; global-Karma column.
- Community Suggestions: create modal + upvotes (local-first, IAM/on-chain-swappable).
- Combined Board (all Spaces) + Space tabs (Overview/Board/Open Tasks/Suggestions).
- Task detail: on-chain description + real activity log (created→funded→claimed→
submitted→accepted→paid) + apply/comment flow + status/assignee/reviewers.
- Explore: cross-space skill-filtered index + sibling-DAO links.
Data layer:
- chain.loadWorkspace assembles bounties + activity + stats + contributors (workers
∪ reviewers) in one pass; block timestamps batched.
- Reward model is a native/ERC-20/ERC-721/ERC-1155 union — parseReward reads
rewardType+tokenId when the finished Bounty struct carries them (NFT rewards
render with tokenURI metadata), defaults to native/ERC-20 today.
- Karma optional per brand (global soul-bound rep); degrades cleanly when absent.
Config-driven white-label: Spaces/skills/tagline/accent/socials/Karma/IAM added to
the existing VITE_BRAND brand map (zoo, pars); one canonical config, one image/brand.
Hash router (no dep); Dework dark palette; per-brand accent via --brand.
package: 0.1.0 -> 0.2.0
Co-authored-by: Hanzo Dev <[email protected]>
One repo, one Dockerfile. Brand-varying values (chain, addresses, owner
label, header/title, CSP RPC host) move into src/brands.ts, selected at
build time by VITE_BRAND (default zoo). The zoo profile reproduces the
prior hardcoded values exactly, so work.zoo.network is unchanged.
Adds the pars profile (chain 494949, BountyV1 0x316B..cf1F) plus CI and
k8s to stand up work.pars.network:
- docker.yml: a brand matrix builds zoo :sha-<sha> and pars :pars-<sha>
from the same Dockerfile on the luxfi ARC pool.
- k8s/pars/: Deployment + Service (ns pars) + Traefik route fragment.
Co-authored-by: Hanzo Dev <[email protected]>
This cluster's hanzoai/ingress (Traefik v3) routes every public zoo host through
the zoo-dynamic ConfigMap (@file provider) with TLS auto-issued by its built-in
ACME resolver over Cloudflare DNS-01 — exactly how api.zoo.network is served.
Kubernetes Ingress objects don't produce the :443 router here (the api-zoo-network
Ingress is vestigial too), so replace k8s/ingress.yaml with k8s/route.yaml, the
router+service fragment merged into zoo-system/zoo-dynamic. One ACME client
(Traefik), no cert-manager. Verified: https://work.zoo.network serves 200 with a
valid LE cert (CN=work.zoo.network).
Co-authored-by: Hanzo Dev <[email protected]>
The immutable image built by CI for commit ddbcc67, verified present in GHCR
and rolled out 2/2 on zoo-k8s (namespace zoo-mainnet).
Co-authored-by: Hanzo Dev <[email protected]>
The hanzoai/.github reusable is a private cross-org repo and both orgs are on
the free plan (no shared enterprise), so GitHub blocks the `uses:` at parse
time — every luxfi caller of it fails at 0s. Switch to the in-org self-hosted
build pattern proven green by luxfi/node/dex/genesis: runs-on lux-build-amd64,
GHCR login via GITHUB_TOKEN, metadata-action → immutable ghcr.io/luxfi/work-board:sha-<sha7>,
build-arg VITE_RPC_URL baked to the live Zoo RPC. amd64-only (deploy target is amd64).
Co-authored-by: Hanzo Dev <[email protected]>
On-chain bounty kanban (Vite+React+viem static SPA) that reads the live
BountyV1/EscrowV1/ReputationV1 work-market on Zoo (chain 200200) directly
over the CORS-enabled JSON-RPC. Split out of luxfi/dao into its own repo so
CI builds a clean image.
- .github/workflows/docker.yml: canonical hanzoai/.github reusable build,
bakes VITE_RPC_URL=https://api.zoo.network/v1/bc/C/rpc, pushes
ghcr.io/luxfi/work-board:sha-<commit> on self-hosted arcd runners.
- k8s/: Deployment (2 replicas) + Service (80->3000) + Ingress
(hanzoai/ingress class, host work.zoo.network, letsencrypt-prod).
Co-authored-by: Hanzo Dev <[email protected]>