Skip to content

fix(desktop): derive the hosted-community limit from the relay instead of hardcoding 5 - #4810

Open
TLXyloph wants to merge 6 commits into
block:mainfrom
TLXyloph:fix/issue-4160
Open

fix(desktop): derive the hosted-community limit from the relay instead of hardcoding 5#4810
TLXyloph wants to merge 6 commits into
block:mainfrom
TLXyloph:fix/issue-4160

Conversation

@TLXyloph

@TLXyloph TLXyloph commented Aug 4, 2026

Copy link
Copy Markdown

Intent

Resolve #4160: the desktop hardcodes the hosted-community per-owner limit at 5, so deployments that override BUZZ_MAX_COMMUNITIES_PER_OWNER show wrong gating and copy in both directions; expose the relay's effective limit on operator responses and have the desktop resolve it with a safe fallback.

What Changed

  • Relay — operator list/provision responses and both limit_reached 409 bodies (create and transfer) now carry max_communities_per_owner, the effective BUZZ_MAX_COMMUNITIES_PER_OWNER value, as a structured field beside the unchanged limit_reached: message prefix that 409 routing keys off. The env var is documented in .env.example and the TESTING.md config table.
  • Desktop — removes the hardcoded HOSTED_COMMUNITY_LIMIT = 5 in favor of a pure resolver (hostedCommunityLimit.ts) plus a useHostedCommunityLimit hook shared by all three hosted-community surfaces (onboarding, create flow, settings card): gating and copy read the relay-reported number, a rejection's number is adopted immediately, and a response that omits the field leaves a known-good limit in place rather than resetting to the default. Limit-reached copy is centralized in one helper, and transfer rejections now name the recipient — the relay rejects those on the transferee's quota, not the requester's. Note the soft dependency: the desktop reaches the relay through Builderlab, which must forward max_communities_per_owner; until it does, every surface falls back to 5 and behaves exactly as before.
  • Coverage — 22 node:test resolver/copy cases, a non-Postgres unit test that asserts and prints the 409/200 wire bodies under varying env values, two Postgres-gated operator handler tests (they self-skip without a database), and a 6-capture Playwright spec covering above/below-default limits, the no-limit fallback, create and transfer 409 adoption, and the gated create dialog — with mock-bridge support for the new limit and rejection payloads.

Risk Assessment

✅ Low: The wire change is purely additive with no in-repo consumers to break, the desktop change is fully backward-compatible through a documented default fallback (worst case it behaves exactly as before), the enforced and reported limits provably come from the same source, and the refactored shared loader is byte-identical to the code it replaced — the only open item is a cosmetic dead re-export.

Testing

Ran the change's own unit tests (22 desktop resolver/copy tests, relay provisioning and buzz-db limit tests) plus the full 4217-test desktop suite as a regression guard for the shared E2E bridge edit, then produced product-level evidence at both ends: a relay transcript showing max_communities_per_owner on the real 200 provision body and 409 limit_reached body tracking BUZZ_MAX_COMMUNITIES_PER_OWNER (7, 3) and falling back to 5 for 0/abc with the error string unchanged, and six screenshots from a new Playwright spec showing the settings card and add-community dialog gating and wording themselves from the relay's number — ungated at "5 of 7 used", gated at "3 of 3 used", unchanged "5 of 5 used" when the field is absent, re-gated by a create 409, and a transfer 409 worded about the recipient. Existing builderlab smoke specs and the 61-test onboarding integration spec still pass. Everything passed; the only gap is the Postgres-backed operator tests, which self-skip in this environment (no Docker/Postgres).

  • Evidence: Settings card: relay reports 7, owner with 5 communities is not gated (local file: /var/folders/sr/98ws056j0msc_fv3snynykbc0000gn/T/no-mistakes-evidence/01KZ78TRBV8WKJAXR1F2JGY931/screenshots/01-limit-above-default-ungated.png)
  • Evidence: Settings card: relay reports 3, owner gated with copy naming 3 (local file: /var/folders/sr/98ws056j0msc_fv3snynykbc0000gn/T/no-mistakes-evidence/01KZ78TRBV8WKJAXR1F2JGY931/screenshots/02-limit-below-default-gated.png)
  • Evidence: Settings card: response carries no limit, stock default 5 preserved (local file: /var/folders/sr/98ws056j0msc_fv3snynykbc0000gn/T/no-mistakes-evidence/01KZ78TRBV8WKJAXR1F2JGY931/screenshots/03-no-reported-limit-falls-back.png)
  • Evidence: Create 409 reporting 3: error copy names 3 and the card re-gates at 3 of 3 (local file: /var/folders/sr/98ws056j0msc_fv3snynykbc0000gn/T/no-mistakes-evidence/01KZ78TRBV8WKJAXR1F2JGY931/screenshots/04-create-rejection-adopts-limit.png)
  • Evidence: Transfer 409 reporting 9: copy names the recipient, counter adopts 9 (local file: /var/folders/sr/98ws056j0msc_fv3snynykbc0000gn/T/no-mistakes-evidence/01KZ78TRBV8WKJAXR1F2JGY931/screenshots/05-transfer-rejection-names-recipient.png)
  • Evidence: Add-community create dialog gated at the relay's limit of 4 (local file: /var/folders/sr/98ws056j0msc_fv3snynykbc0000gn/T/no-mistakes-evidence/01KZ78TRBV8WKJAXR1F2JGY931/screenshots/06-create-flow-gated.png)
Evidence: Relay wire contract under BUZZ_MAX_COMMUNITIES_PER_OWNER overrides

BUZZ_MAX_COMMUNITIES_PER_OWNER=<unset> -> HTTP 409 {"error":"limit_reached: owner already owns the maximum number of communities","max_communities_per_owner":5} BUZZ_MAX_COMMUNITIES_PER_OWNER=<unset> -> HTTP 200 {"community_id":"b8f7f4a0-0000-0000-0000-000000000000","host":"acme.communities.buzz.xyz","status":"created","max_communities_per_owner":5} BUZZ_MAX_COMMUNITIES_PER_OWNER=7 -> HTTP 409 {"error":"limit_reached: owner already owns the maximum number of communities","max_communities_per_owner":7} BUZZ_MAX_COMMUNITIES_PER_OWNER=7 -> HTTP 200 {"community_id":"b8f7f4a0-0000-0000-0000-000000000000","host":"acme.communities.buzz.xyz","status":"created","max_communities_per_owner":7} BUZZ_MAX_COMMUNITIES_PER_OWNER=3 -> HTTP 409 {"error":"limit_reached: owner already owns the maximum number of communities","max_communities_per_owner":3} BUZZ_MAX_COMMUNITIES_PER_OWNER=3 -> HTTP 200 {"community_id":"b8f7f4a0-0000-0000-0000-000000000000","host":"acme.communities.buzz.xyz","status":"created","max_communities_per_owner":3} BUZZ_MAX_COMMUNITIES_PER_OWNER=0 -> HTTP 409 {"error":"limit_reached: owner already owns the maximum number of communities","max_communities_per_owner":5} BUZZ_MAX_COMMUNITIES_PER_OWNER=abc -> HTTP 200 {"community_id":"b8f7f4a0-0000-0000-0000-000000000000","host":"acme.communities.buzz.xyz","status":"created","max_communities_per_owner":5}

BUZZ_MAX_COMMUNITIES_PER_OWNER=<unset> -> HTTP 409 {"error":"limit_reached: owner already owns the maximum number of communities","max_communities_per_owner":5}
BUZZ_MAX_COMMUNITIES_PER_OWNER=<unset> -> HTTP 200 {"community_id":"b8f7f4a0-0000-0000-0000-000000000000","host":"acme.communities.buzz.xyz","status":"created","max_communities_per_owner":5}
BUZZ_MAX_COMMUNITIES_PER_OWNER=7 -> HTTP 409 {"error":"limit_reached: owner already owns the maximum number of communities","max_communities_per_owner":7}
BUZZ_MAX_COMMUNITIES_PER_OWNER=7 -> HTTP 200 {"community_id":"b8f7f4a0-0000-0000-0000-000000000000","host":"acme.communities.buzz.xyz","status":"created","max_communities_per_owner":7}
BUZZ_MAX_COMMUNITIES_PER_OWNER=3 -> HTTP 409 {"error":"limit_reached: owner already owns the maximum number of communities","max_communities_per_owner":3}
BUZZ_MAX_COMMUNITIES_PER_OWNER=3 -> HTTP 200 {"community_id":"b8f7f4a0-0000-0000-0000-000000000000","host":"acme.communities.buzz.xyz","status":"created","max_communities_per_owner":3}
BUZZ_MAX_COMMUNITIES_PER_OWNER=0 -> HTTP 409 {"error":"limit_reached: owner already owns the maximum number of communities","max_communities_per_owner":5}
BUZZ_MAX_COMMUNITIES_PER_OWNER=0 -> HTTP 200 {"community_id":"b8f7f4a0-0000-0000-0000-000000000000","host":"acme.communities.buzz.xyz","status":"created","max_communities_per_owner":5}
BUZZ_MAX_COMMUNITIES_PER_OWNER=abc -> HTTP 409 {"error":"limit_reached: owner already owns the maximum number of communities","max_communities_per_owner":5}
BUZZ_MAX_COMMUNITIES_PER_OWNER=abc -> HTTP 200 {"community_id":"b8f7f4a0-0000-0000-0000-000000000000","host":"acme.communities.buzz.xyz","status":"created","max_communities_per_owner":5}
- Outcome: ⚠️ 3 infos across 1 run (24m51s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ⚠️ desktop/src/features/communities/hostedCommunityApi.ts:51 - The desktop reads max_communities_per_owner off the Builderlab response, but the desktop never talks to the relay's operator API — list_builderlab_communities posts to https://app.builderlab.xyz/api/goose/v1/buzz/communities/list (desktop/src-tauri/src/builderlab.rs:550), and Builderlab reshapes the relay payload (desktop sees id/name/slug/normalized_host; the relay emits community_id/host). Unless Builderlab (out of this repo) is updated to forward the field under this exact snake_case name, every desktop surface still resolves to the hardcoded 5 and issue Desktop hardcodes HOSTED_COMMUNITY_LIMIT = 5 while the relay enforces the env-configurable max_communities_per_owner() — UI drifts in both directions once BUZZ_MAX_COMMUNITIES_PER_OWNER is set #4160's repro is unchanged. Issue Desktop hardcodes HOSTED_COMMUNITY_LIMIT = 5 while the relay enforces the env-configurable max_communities_per_owner() — UI drifts in both directions once BUZZ_MAX_COMMUNITIES_PER_OWNER is set #4160 explicitly names this hop as the caveat it could not close; this branch neither closes nor documents it. Confirm the Builderlab-side change is landed/planned, or note the dependency in the PR.
  • ⚠️ crates/buzz-relay/src/handlers/community_provisioning.rs:82 - limit_reached_error appends (N) to an operator-API error string that the out-of-repo Builderlab service normalizes into the limit_reached code the desktop maps. If that normalizer matches the message by equality rather than prefix, it stops emitting limit_reached and the desktop falls through to error?.message ?? fallback — a raw relay string instead of the friendly copy. Meanwhile no in-repo client reads the embedded number: hostedCommunityErrorMessage looks up messages[error.code] first, so error.message (and the (N)) is never rendered when the code is known. As written this is wire-visible risk with no consumer; consider carrying the number as a structured field on the 409 body instead.
  • ℹ️ desktop/src/features/communities/hostedCommunityApi.ts:66 - HostedCommunityMutationResponse.max_communities_per_owner is declared and documented but never read — createHostedCommunity/transfer callers keep the load-time communityLimit and pass that into the error copy, so a limit changed since page load renders the stale number. Either wire it (careful: resolveHostedCommunityLimit falls back to 5, so an absent field on a mutation response would clobber a valid 7 from the list call — fall back to the current state instead) or drop the field.
  • ℹ️ desktop/src/features/settings/ui/HostedCommunitiesSettingsCard.tsx:117 - loadAccount here re-implements loadHostedCommunityAccount (hostedCommunityApi.ts:125) — same two invokes, same error gating — so this change had to add the resolveHostedCommunityLimit call in two places. Having the settings card consume the shared loader (which already returns communityLimit) removes the duplication and the drift risk for the next field added to the account payload.
  • ℹ️ desktop/src/features/communities/hostedCommunityApi.ts:82 - The new communityLimit parameter defaults to DEFAULT_HOSTED_COMMUNITY_LIMIT, so any call site that can surface limit_reached and forgets the argument silently renders "the limit of 5" — reintroducing the exact hardcoded-default defect class Desktop hardcodes HOSTED_COMMUNITY_LIMIT = 5 while the relay enforces the env-configurable max_communities_per_owner() — UI drifts in both directions once BUZZ_MAX_COMMUNITIES_PER_OWNER is set #4160 is about, invisibly to review. Today's omissions are safe (availability/identity/archive paths can't return limit_reached), but making the parameter required turns future omissions into compile errors.

🔧 Fix: harden hosted-community limit resolution and 409 wire contract
3 infos still open:

  • ℹ️ desktop/src/features/communities/ui/HostedCommunityCreateFlow.tsx:386 - The new hostedCommunityLimitReachedMessage helper (hostedCommunityLimit.ts:76) exists to own the limit-reached copy, but three UI surfaces still inline their own copy of the same sentence: HostedCommunityCreateFlow.tsx:386, HostedCommunityOnboarding.tsx:367, and HostedCommunitiesSettingsCard.tsx:608. They have already drifted from the helper — the inline versions use a typographic apostrophe (You’ve) while the helper uses a straight one (You&#39;ve) — and they bypass the helper's no-number fallback entirely. Routing all three through the helper removes the drift; since every surface holds communityLimit as a seeded number, output is identical apart from the apostrophe glyph, so pick one glyph (the typographic one matches the current UI) and put it in the helper.
  • ℹ️ desktop/src/features/communities/hostedCommunityLimit.ts:76 - hostedCommunityLimitReachedMessage's doc says it "names a number only when the caller resolved one from a server response", and limit_reached_copy_omits_the_number_when_unresolved pins that. But no product path can reach the number-less branch: every surface seeds communityLimit state with DEFAULT_HOSTED_COMMUNITY_LIMIT (HostedCommunityCreateFlow.tsx:49, HostedCommunityOnboarding.tsx:88, HostedCommunitiesSettingsCard.tsx:73) and always passes it, and the only call sites that omit the argument (identity bind/unbind, availability) cannot return limit_reached. So on any deployment where Builderlab does not forward the field, users are still told "the limit of 5" — the same fabricated number Desktop hardcodes HOSTED_COMMUNITY_LIMIT = 5 while the relay enforces the env-configurable max_communities_per_owner() — UI drifts in both directions once BUZZ_MAX_COMMUNITIES_PER_OWNER is set #4160 is about. That is a deliberate and reasonable back-compat tradeoff, but the "never invent a number" guarantee is not end-to-end; it is a tested-but-unreached branch.
  • ℹ️ desktop/src/features/communities/hostedCommunityApi.ts:166 - loadHostedCommunityAccount resolves with the implicit DEFAULT_HOSTED_COMMUNITY_LIMIT fallback, so a list response that omits max_communities_per_owner resets the limit to 5. That contradicts the invariant documented one file over on HostedCommunityMutationResponse ("an omitted field never clobbers a known one") and undoes the adopt-on-rejection logic: after a 409 reports 7 and the UI adopts it, the next loadAccount() (fired on connect-identity, successful transfer, successful create, or sign-in) drops it back to 5 if the list payload omits the field. This only bites if Builderlab forwards the field on mutation/409 bodies but not on list — plausible given it reshapes each endpoint separately, though narrow in practice. If the sticky semantics are intended, thread the current value in (e.g. setCommunityLimit(prev =&gt; resolveHostedCommunityLimit(resp, prev)) at the call sites); if list is meant to be authoritative, drop the "never clobbers" wording so the two paths do not read as contradictory.

🔧 Fix: centralize limit-reached copy and keep adopted limit sticky
2 issues (1 warning, 1 info) still open:

  • ⚠️ desktop/src/features/settings/ui/HostedCommunitiesSettingsCard.tsx:288 - The transfer path now threads the effective limit into a message that names the wrong party. The relay rejects a transfer with limit_reached: transferee already owns the maximum number of communities (operator.rs:444), but hostedCommunityErrorMessage maps code limit_reached to "You’ve reached the limit of N hosted communities." — so an owner giving away a community is told they are at their own limit, and the adjacent settings copy then advises "Transfer one to free up a slot," which is exactly what they just tried. The wrong-subject wording is pre-existing, but this change sharpens it: it now asserts the deployment's real number with confidence, and adopts that number into communityLimit state from a rejection that was never about this user. Because Builderlab collapses both relay details onto the single limit_reached code, the client cannot tell create-rejection from transfer-rejection today — distinguishing them needs a separate error code (or a transfer-specific fallback string passed at this call site, e.g. "That person has reached their limit of hosted communities."). Product-copy decision, so flagging rather than changing.
  • ℹ️ desktop/src/features/communities/ui/HostedCommunityOnboarding.tsx:319 - The same three-part limit plumbing is now copy-pasted across all three hosted-community surfaces: the useState(DEFAULT_HOSTED_COMMUNITY_LIMIT) seed, the setCommunityLimit((previous) =&gt; account.communityLimit ?? previous) line in loadAccount, and the const limit = resolveHostedCommunityLimit(response, communityLimit); setCommunityLimit(limit); adopt-on-rejection block — HostedCommunityOnboarding.tsx:90/105/319, HostedCommunityCreateFlow.tsx:51/66/249, HostedCommunitiesSettingsCard.tsx:75/90/288+370 (the settings card carries it twice). The prior rounds already centralized the copy and the account loader; this is the remaining duplicate, and it is the part that encodes the subtle next ?? previous stickiness invariant, so a fourth surface is the likely place it gets written as a plain setCommunityLimit(resolveHostedCommunityLimit(response)) and silently loses stickiness. A small useHostedCommunityLimit() hook returning { communityLimit, applyFromAccount, adoptFromResponse } collapses all four sites; it needs its own file since hostedCommunityLimit.ts deliberately stays value-import-free for node:test.

🔧 Fix: fix transfer limit copy subject and share limit hook
1 info still open:

  • ℹ️ desktop/src/features/communities/hostedCommunityApi.ts:9 - The re-export block re-exports five symbols from ./hostedCommunityLimit, but only hostedCommunityLimitReachedMessage is ever consumed through this module (the three UI surfaces). DEFAULT_HOSTED_COMMUNITY_LIMIT, readHostedCommunityLimit, resolveHostedCommunityLimit, and HostedCommunityLimitSubject are imported directly from ./hostedCommunityLimit at their only call sites (useHostedCommunityLimit.ts:8-9, and hostedCommunityApi.ts itself via the separate import on lines 3-7) — consumers pass limitSubject: &#34;transferee&#34; as a string literal and never name the type. Beyond dead surface, this cuts against the point of the round-3 hook: it publishes resolveHostedCommunityLimit on the module every hosted-community surface already imports, so a fourth surface can reach the raw resolver from @/features/communities/hostedCommunityApi and re-implement the next ?? previous stickiness by hand instead of using useHostedCommunityLimit. Narrow the re-export to hostedCommunityLimitReachedMessage.
⚠️ **Test** - 3 infos
  • ℹ️ crates/buzz-relay/src/api/operator.rs:1130 - The two Postgres-backed operator tests added by this change (list_and_provision_responses_report_effective_owner_limit, limit_reached_rejections_report_effective_limit) could not actually execute here — no Docker daemon and no local Postgres/Redis. They are not skipped loudly: operator_test_state returns None on connection failure and each test returns early, so cargo test -- --ignored reports them as passing after ~30s of connection attempts without asserting anything. Their end-to-end handler coverage is only real on a gate that has Postgres. I compensated with a non-Postgres unit test that asserts and prints the same 409/200 bodies under varying env values.
  • ℹ️ desktop/tests/e2e/hosted-community-limit-screenshots.spec.ts:50 - The new Playwright spec flaked twice during early runs: the app rendered a blank page and the test timed out waiting for open-settings (once on the 5th test, once on the 1st, in different runs). It never reproduced afterwards — 28 consecutive passes across repeat, cold-server and clean-rebuild runs — and the failures coincided with a cold Chromium install under load, so it reads as a first-load/boot race in this environment rather than a product issue. CI's retries: 2 covers it.
  • ℹ️ desktop/tests/e2e/hosted-community-limit-screenshots.spec.ts - new test file written by agent: desktop/tests/e2e/hosted-community-limit-screenshots.spec.ts
  • node --import ./test-loader.mjs --experimental-strip-types --test &#34;src/features/communities/hostedCommunityLimit.test.mjs&#34; — 22 resolver/copy tests added by the change
  • pnpm test (desktop unit suite, 4217 tests) after modifying the shared E2E bridge
  • cargo test -q -p buzz-relay --lib community_provisioning and cargo test -q -p buzz-db --lib relay_members
  • BUZZ_MAX_COMMUNITIES_PER_OWNER={unset,7,3,0,abc} cargo test -q -p buzz-relay --lib api::operator::tests::limit_reached_conflict_body_reports_the_deployment_limit -- --nocapture — new test printing the real 409 + 200 bodies per env value
  • pnpm build:e2e &amp;&amp; pnpm exec playwright test --project=smoke tests/e2e/hosted-community-limit-screenshots.spec.ts — new 6-capture spec covering limit above/below default, no-limit fallback, create-409 adoption, gated create dialog, transfer-409 recipient copy
  • pnpm exec playwright test --project=smoke tests/e2e/hosted-communities-settings-screenshots.spec.ts tests/e2e/add-community-screenshots.spec.ts — regression check on existing builderlab specs after the bridge change
  • pnpm exec playwright test --project=integration tests/e2e/onboarding.spec.ts — 61 tests, covers the hosted-community onboarding surfaces
  • cargo test -q -p buzz-relay --lib -- --ignored api::operator::tests::list_and_provision_responses_report_effective_owner_limit api::operator::tests::limit_reached_rejections_report_effective_limit — attempted; self-skipped with no Postgres
  • shasum -a 256 over the captured PNGs to confirm every screenshot shows a distinct state
⚠️ **Document** - 1 info
  • ℹ️ AGENTS.md:20 - The desktop only sees the relay's max_communities_per_owner if Builderlab (app.builderlab.xyz, an external service outside this repo) forwards the field through /v1/buzz/communities/*. That requirement is recorded only in a desktop code comment (desktop/src/features/communities/hostedCommunityLimit.ts:14-19); no repo-level doc states it, and AGENTS.md's Ecosystem table does not list Builderlab at all, so a Builderlab maintainer has no documented cue that the fix depends on their hop. Where to record this (AGENTS.md ecosystem table, a docs/ page, or an issue against the Builderlab repo) is a human call — I have no link or ownership info for that service, and until it forwards the field every desktop resolution silently falls back to 5.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…or responses

The relay derives the effective per-owner community limit from
BUZZ_MAX_COMMUNITIES_PER_OWNER (buzz_db::relay_members::
max_communities_per_owner), but clients had no way to learn the effective
value, so the desktop hardcoded 5 and drifted in both directions once a
deployment overrode the default (block#4160). Transmit the limit on the wire:

- ProvisionCommunityResponse gains max_communities_per_owner, populated at
  both construction sites (create_only and legacy convergence).
- GET /operator/communities includes max_communities_per_owner alongside
  the owned-community list.
- Both limit_reached rejections (create and transfer) embed the effective
  number after the routing prefix via a shared limit_reached_error()
  helper; the starts_with("limit_reached:") 409 routing is unchanged.
- Document BUZZ_MAX_COMMUNITIES_PER_OWNER in .env.example and
  cross-reference the desktop fallback from the server-side const.

Additive and backward compatible: no consumer of these responses uses
deny_unknown_fields, and the limit_reached: prefix is preserved.

Also fixes fresh_host_at_owner_limit_returns_limit_reached_conflict to
loop over the env-aware max_communities_per_owner() instead of the const,
so the test still provisions up to the effective limit under a live
BUZZ_MAX_COMMUNITIES_PER_OWNER override.

Refs block#4160

Claude-Session: https://claude.ai/code/session_012GSiyYe13zNJBFwxajcXeW
Signed-off-by: Samvrith <samvrith@gmail.com>
…d of a hardcoded 5

The relay enforces an env-configurable per-owner limit
(BUZZ_MAX_COMMUNITIES_PER_OWNER), but the desktop pinned
HOSTED_COMMUNITY_LIMIT = 5 into its gates and copy, so the UI drifted in
both directions the moment a deployment overrode the default: it kept
gating users below a raised limit and stopped gating below a lowered one
(block#4160).

- New pure resolver hostedCommunityLimit.ts: a positive-integer
  max_communities_per_owner from the server response wins; anything else
  falls back to DEFAULT_HOSTED_COMMUNITY_LIMIT (the old constant, now
  fallback-only), mirroring the relay's own env-parse fallback rules.
- loadHostedCommunityAccount() resolves communityLimit from the list
  response; HostedCommunitiesSettingsCard's standalone loader resolves it
  at its own ingestion site.
- Every gate and copy site in HostedCommunityCreateFlow,
  HostedCommunityOnboarding, and HostedCommunitiesSettingsCard reads the
  resolved limit; hostedCommunityErrorMessage() accepts the effective
  limit as an optional 4th parameter (existing 3-arg call sites keep the
  default) and the limit-bearing create/transfer error paths thread it.
- Colocated node:test coverage pins the resolver's fallback rules and the
  gating regression in both directions (limit 7 does not gate 5 owned;
  limit 3 gates 3 owned).

Until Builderlab forwards max_communities_per_owner from the relay list
response, the resolver falls back to the default of 5 and visible
behavior is unchanged; this change fixes the mechanism end-to-end on the
Buzz side.

Refs block#4160

Claude-Session: https://claude.ai/code/session_012GSiyYe13zNJBFwxajcXeW
Signed-off-by: Samvrith <samvrith@gmail.com>
@TLXyloph
TLXyloph requested a review from a team as a code owner August 4, 2026 23:13
… wire contract

Signed-off-by: Samvrith <samvrith@gmail.com>
…imit sticky

Signed-off-by: Samvrith <samvrith@gmail.com>
…hook

Signed-off-by: Samvrith <samvrith@gmail.com>
… wire contract

Signed-off-by: Samvrith <samvrith@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant