hanzoai/ci keeps the reusable at .github/workflows/build.yml — the one path
both planes resolve, since github.com reads a reusable only from there and the
forge reads it too. The v1 tag moved with it, so a caller still naming
.hanzo/workflows/build.yml asks for a blob that is not in the tree:
InsertRun: read hanzoai/ci@v1:.hanzo/workflows/build.yml: object does not exist
The caller itself stays at .hanzo/workflows/cicd.yml on purpose: github.com has
no runner for these labels, so a caller placed there could never be scheduled.
Co-authored-by: Hanzo Dev <[email protected]>
README and the docs site listed a rival PaaS's deploy badge and a
Postgres-hosting link alongside AWS/Azure/Northflank/Render/Vercel/
Elestio; remove that one option (and its dedicated deployments/
railway.mdx page + nav entry), keep the rest. The WEBAPP_URL
RAILWAY_STATIC_URL fallback and its turbo.json cache-key entry are
real env-var integration and stay untouched.
Co-authored-by: Hanzo Dev <[email protected]>
All ten vendored Zen binaries had name ID 0 rewritten to 'Copyright 2026
Hanzo AI, Inc.' alone, dropping 'Copyright 2024 The Geist Project
Authors' — and Zen is drawn from Geist, so OFL section 2 binds that line
to every copy. Name ID 13 still pointed at the OFL while the notice it
governs was gone.
Replaced with the authored bytes from @hanzo/font 1.9.2. Every other
name record, every table and all 974 glyphs were already identical, so
nothing renders differently; only the copyright record changes.
The three Hanzo wordmark SVGs asked for Inter, which the app has not
shipped since it moved to Zen, so every one of them silently fell
through to the system UI face and rendered the mark in whatever the
viewer's OS supplies. They now name Zen.
The atoms package also carried seven Inter woff2 subsets left behind by
an old next/font build. Nothing in the tree referenced them and they
went out in the npm tarball as dead weight.
@hanzo/font 1.9.1 exports Zen. The name table, zen.css and every stylesheet
call the family Zen, so ZenSans named a family that does not exist; twenty
files imported it and would not resolve against the version they depend on.
The SDK examples bound it to `inter` and `poppins` — index.tsx held two
names for one Zen — so a customer copying them read the sans as Inter. The
atoms theme set --font-sans from --font-inter, which nothing defines, while
--font-zen-sans is the property the package publishes.
The OG route still fetched /fonts/cal.ttf for its display weight after that
file was deleted, so every share card would have rendered with one of its three
faces missing — a break the previous commit introduced. Zen SemiBold.
And the embed theme's stored default was fontSrc "https://cal.com/cal.ttf",
written into every branded booking page, so a customer's own page fetched its
face from our upstream's host. It is same-origin now.
Three surfaces the earlier pass missed, and the embed is the one that matters
most: packages/embeds/embed-core runs inside a CUSTOMER's page and its
stylesheet carried @font-face src url("https://cal.com/cal.ttf") — so every
page anyone put our embed on made a request to our upstream's domain and told
that host about the visit. An embed has no business fetching a face at all; the
headings inherit the host page's stack.
The OG route fetched Inter-Regular.ttf and Inter-Medium.ttf and registered them
as "inter", so every share card was set in a face the product stopped using —
satori reads ttf, which is why the variable file could not serve it. Zen's
static cuts, carried beside them.
shared-globals declared Cal Sans and Inter var; one Zen variable file covers
both roles. The transactional emails linked and @imported fonts.googleapis.com
for Roboto and Inter.
Three entry points called next/font/google for Inter and bound it to
--font-sans, so the product rendered in a Google-hosted face and made the
request to fetch it before anything painted. They take @hanzo/font's ZenSans,
which is local; the --font-sans contract the app already sets from the font
object is unchanged, so nothing downstream moves.
The display face was Cal Sans — the upstream product's own, which we have no
licence to ship under our name. Both apps take Zen's matching cuts and the
seven CalSans files are gone.
The platform SDK examples are sixteen more next/font/google imports, and they
matter for the reason the docs examples do: a customer scaffolds from them, so
whatever face they open with becomes the face of somebody else's project.
`interFont` is `sansFont` — a binding named after a face it no longer holds is
the thing the next reader has to be told about.
These favicons still shipped the upstream artwork of whatever the surface was
scaffolded from, so a customer with a few of our products open read someone
else in every tab. They are @hanzo/logo favicon.{ico,svg,png} now, one source
for the geometry.
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.
This repo is a fork, and its SECURITY.md still carried the upstream's contact —
so a researcher who found a hole in what WE ship was told to mail a vendor who
cannot fix it, and who would then know about a live vulnerability in our
production before we did.
Fourteen repos were in that state: datastore -> [email protected], git and
its two worktrees -> [email protected], auto -> [email protected],
calendar -> [email protected], dns -> [email protected], python-sdk ->
[email protected], plus bot, gui-gates, gui-geist, playground and
playground-oauthcb pointing at brand domains with no mailbox behind them.
All of them now carry one policy and one address, [email protected], matching
what cloud and gateway already used. The policy says out loud to report here
rather than upstream, because the next fork will inherit the same file and the
same instinct.
github.com has no runner for hanzo-build-linux-amd64, so a caller under
.github/workflows is a gate that can never be scheduled. This is the ~7-line
caller on the plane that can (git.hanzo.ai git-runner fleet), pinned @v1.
Co-authored-by: Hanzo Dev <[email protected]>
The repo carries the upstream LICENSE but no NOTICE, so nothing states which
project the code derives from or who holds copyright on it. Added in the same
form the other forks here use.
Co-authored-by: Hanzo Dev <[email protected]>
* fix(bookings): refund all seat payments when cancelling a paid seated booking
A paid seated booking creates one Payment row per seat on the same bookingId (each seat's payment_intent.succeeded webhook flips only its own payment via handlePaymentSuccess). processPaymentRefund only refunded payment.find((p) => p.success) — the first successful payment — so fully cancelling a multi-seat paid booking with an auto-refund policy refunded a single seat and left the other attendees charged with no error surfaced.
Refund every successful, not-yet-refunded payment instead.
Tests: unit regression in processPaymentRefund.test.ts (all payments refunded, verified red before the fix); integration test in handleCancelBooking.test.ts proving a full cancel of a seated multi-payment booking hands all payments to the refund path.
* fix(bookings): make seat refunds resilient and drop dayjs in test
Address CodeRabbit review on #29685:
- processPaymentRefund now attempts every seat's refund independently and throws an aggregate error, so one provider failure no longer skips the remaining seats.
- Add a regression test for that resilience, and use native Date instead of dayjs for fixed-offset test timestamps.
When a booking question is optional and left unanswered, the field value
stored in customInputs can be null. The previous guard only checked
for empty strings, so null (and undefined) would coerce to the literal
strings "null"/"undefined" inside the template literal, polluting calendar
event descriptions.
Fixes#29688
Fixes an issue where cancellation emails were sending ICS files with
METHOD:REQUEST instead of METHOD:CANCEL. As per RFC 5546, calendar
clients only process cancellations correctly when METHOD:CANCEL is used,
otherwise cancelled events remain in attendee calendars with active
reminders.
- Use METHOD:CANCEL for cancelled booking ICS files
- Keep METHOD:REQUEST for non-cancelled booking invitations
- Preserve STATUS:CANCELLED and sequence handling
- Add tests to verify the generated ICS method for cancellations
* refactor: derive category metadata from single Record source of truth
* test: add test for category order and entries sync
* fix: check for duplicates
Adds `paths:` YAML frontmatter so each rule only loads into Claude Code
context when the agent is working on relevant files, keeping context lean
in a large monorepo.
- architecture-page-level-auth: fires on page.tsx and layout.tsx
- data-prisma-migrations: fires on packages/prisma/**
- testing-playwright: fires on playwright dirs, *.e2e.ts, and api e2e dirs
- api-thin-controllers: fires on *.controller.ts files only
Co-authored-by: Claude Sonnet 4.6 <[email protected]>
generateUsernameSuggestion built its numeric suffix with
String(rand).padStart(4 - rand.toString().length, "0"). padStart's first
argument is the target total length, not the number of pad characters, so the
expression only padded single-digit numbers: rand=1 became "001" but rand=42
became "42" and rand=999 stayed "999". Suggested usernames therefore had
inconsistent suffix widths, and the magic 4 didn't match the 3-wide output the
existing test pins ("johnny001").
Pull the suffix into a small helper that always pads to 3 digits, so collisions
and the returned value share one definition and every suggestion is uniformly
zero-padded. Add unit tests for the first-attempt path and for two- and
three-digit suffixes; the two-digit case fails on the old code.
Co-authored-by: Bandhan Majumder <[email protected]>
- Remove two console.log statements dumping private hashed link data
in update.handler.ts (fixes production data leakage)
- Replace console.log(e) with structured logger.error in
getScheduleByEventTypeSlug.handler.ts
- Fix @ts-expect-error by typing reduce accumulator as
Record<string, unknown> in update.handler.ts
- Narrow Prisma include to select in duplicate.handler.ts:
team and webhooks now fetch only required fields
Fixes#29500
Co-authored-by: lucadapice <[email protected]>
Co-authored-by: Claude Sonnet 4.6 <[email protected]>
Co-authored-by: Bandhan Majumder <[email protected]>
* fix: load more users on table scroll
Signed-off-by: Chayan Das <[email protected]>
* refactor: move user listing logic to UserRepository
Add a dedicated listUsers method with search and cursor pagination support.
Add repository tests covering filtering and pagination.
Ensure user listing metadata remains consistent with the returned results.
Signed-off-by: Chayan Das <[email protected]>
---------
Signed-off-by: Chayan Das <[email protected]>
* refactor: use userRepository class methods for signup handlers
* feat: add user repo modules for DI
* feat: build user repo container
* test: integration test for user repository
* fix: use DI container in selfhosted
* refactor: tests, module, container and handlers
---------
Co-authored-by: Bandhan Majumder <[email protected]>
- disable update button until form is valid
- add required field validation
- add translation key for password complexity message
Signed-off-by: Chayan Das <[email protected]>
getUserVerifiedPhoneNumber incorrectly called getUserVerifiedEmail
on the repository, causing verified phone numbers to never be found.
Fixes#29450
Co-authored-by: Bandhan Majumder <[email protected]>
* api: me: fix return 401 instead of 409 for unauthorized requests in the me API route
Signed-off-by: Chayan Das <[email protected]>
* undo import changes
Reorder imports for better organization.
---------
Signed-off-by: Chayan Das <[email protected]>
Co-authored-by: Bandhan Majumder <[email protected]>