block / block/buzz

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

Open
#4,160 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

## Problem

The per-owner community limit is enforced by the relay from an **env-configurable** value but gated in the desktop UI by a **hardcoded constant**. The two agree today only because both were edited by hand in the same PR. The moment a deployment sets `BUZZ_MAX_COMMUNITIES_PER_OWNER`, the client and the server disagree and the UI is wrong in one of two directions.

**Server (authoritative, env-configurable):** `crates/buzz-db/src/relay_members.rs:414` `max_communities_per_owner()` → `effective_owner_limit(env BUZZ_MAX_COMMUNITIES_PER_OWNER)`, falling back to `MAX_COMMUNITIES_PER_OWNER = 5` (`relay_members.rs:406`) only when the env var is absent/unparseable/non-positive (`relay_members.rs:428-432`). Enforced in both write paths: create (`crates/buzz-db/src/lib.rs:1440`) and transfer (`relay_members.rs:528`).

**Client (hardcoded):** `desktop/src/features/communities/hostedCommunityApi.ts:4` `export const HOSTED_COMMUNITY_LIMIT = 5;`

Gating sites, all reading that constant:

| File:line | Behavior driven by the constant |
|---|---|
| `hostedCommunityApi.ts:72` | `limit_reached` error copy: "You've reached the limit of **5** hosted communities." |
| `HostedCommunityCreateFlow.tsx:192` | `atCommunityLimit` → disables the name input (`:411`) and the submit button (`:447`), and short-circuits `create()` (`:225`) |
| `HostedCommunityCreateFlow.tsx:375` | Feedback copy "the limit of **5**" |
| `HostedCommunityOnboarding.tsx:243` | `atCommunityLimit` → same disable/short-circuit pattern |
| `HostedCommunityOnboarding.tsx:356` | Feedback copy |
| `HostedCommunitiesSettingsCard.tsx:363` | Blocks `createCommunity()` |
| `HostedCommunitiesSettingsCard.tsx:415` | `atCommunityLimit` |
| `HostedCommunitiesSettingsCard.tsx:558` | Renders "**N of 5** used" |
| `HostedCommunitiesSettingsCard.tsx:622` | "You've reached the limit of **5** hosted communities." |

## Both failure directions

**Env set below 5** (e.g. `BUZZ_MAX_COMMUNITIES_PER_OWNER=3`): the client happily lets the user name and submit a 4th community. The relay rejects it, and the desktop renders the mapped `limit_reached` copy — which says *"the limit of 5"*. The user is told a number that is not the limit they just hit. The settings card also reads "3 of 5 used" while they are in fact maxed out.

**Env set above 5** (e.g. `7`): the client caps at 5 — input disabled, button disabled, "5 of 5 used" — while the relay would have accepted two more. The operator raised the limit and the UI silently ignores it, which is exactly the multi-tenant case #2600 asked for and #2599 shipped.

## Why this is not a one-line fix

**The number is nowhere on the wire.** The client cannot learn the effective limit today:

- `GET /operator/communities` (`crates/buzz-relay/src/api/operator.rs:331-339`) returns `owner_pubkey` + `communities[]` (`community_id`, `host`, `created_at`, `archived_at`) — no limit field.
- `ProvisionCommunityResponse` (`crates/buzz-relay/src/handlers/community_provisioning.rs:57-69`) has `community_id`, `host`, `status`, `owner_pubkey` — no limit field.
- The rejection string carries no number: `"limit_reached: owner already owns the maximum number of communities"` (`community_provisioning.rs:296`) and the transfer twin at `operator.rs:426`.

So an honest fix is an **API-shape change** plus a UI read: expose the effective limit on a response the client already calls (the list endpoint is the natural home — it is what populates `communities.length`), type it in `HostedCommunitiesResponse` (`hostedCommunityApi.ts:39-43`), and have the three gating sites read it with the constant demoted to a fallback for pre-upgrade servers. That is production code touching a public response shape, so it wants its own review — deliberately **not** folded into #4148, which is test-only and green.

## Defect family

This is the third instance of one shape: *a test or client hardcodes a value the code derives*.

1. `crates/buzz-db/src/lib.rs` owner-limit test seeded `0..3` while the code enforced the function — broke when #3829 raised the default. Fixed in #4148.
2. `crates/buzz-db/src/relay_members.rs` transfer-limit test, same. Fixed in #4148.
3. `crates/buzz-relay/src/api/operator.rs:1086` seeded from the **const** while the code enforces the **function** — survived #3829 because the const bump moved test and code together, but inverts as soon as the env is set. Found post-open, fixed in #4148 (`7db75a607`); Eva RED/GREEN-proved it at `BUZZ_MAX_COMMUNITIES_PER_OWNER=3`.
4. **This issue** — the same split, in production code rather than tests.

The generalizable rule: when a value becomes runtime-configurable, every consumer that duplicates it becomes a latent bug, and the const left behind as a default makes the duplication invisible to review. #2599 made the limit configurable; #3829 then edited the const in two files by hand — which is the tell.

## Repro (requires wiring the env)

1. Start the relay with `BUZZ_MAX_COMMUNITIES_PER_OWNER=3`.
2. As one owner, create 3 hosted communities from the desktop settings card.
3. The card shows "3 of 5 used" and leaves the create form enabled.
4. Submit a 4th with a fresh, available name → relay 409 `limit_reached`, and the desktop renders "You've reached the limit of 5 hosted communities."

Note step 1 is the reason this is latent: `BUZZ_MAX_COMMUNITIES_PER_OWNER` is set nowhere in this repo. I grepped the whole working tree (excluding `target/`) and it appears only in `relay_members.rs` — the reader itself plus its own unit tests. No chart, template, workflow, or compose file sets it. Same "latent until someone wires the env" posture as `BUZZ_REPLICA_READ_MAX_AGE_MS` in #4124.

## Caveat I could not close

The desktop does **not** call the relay's operator API directly. It calls Builderlab — `desktop/src-tauri/src/builderlab.rs:15` (`https://app.builderlab.xyz/api/goose`), via `POST /v1/buzz/communities/list` (`builderlab.rs:550`) and `POST /v1/buzz/communities` (`builderlab.rs:582`). That service is **not in this repo** and I did not read it. I verified the relay emits the `limit_reached:` prefix and the desktop maps a `limit_reached` code, but I cannot say whether the middle hop already forwards a limit value or normalizes the error. Anyone implementing the fix should check the Builderlab side first — if it already has the number, the change may be smaller than described; if it does not, it needs a change too.

## Non-findings, checked so they don't get re-litigated

- **Archived communities are counted consistently on both sides.** The server counts `relay_members WHERE role = 'owner'` regardless of `archived_at` (`lib.rs:1433-1438`, `relay_members.rs:521-527`), and `archive_community_owned_by` only sets `archived_at` without touching the owner row (`lib.rs:1486-1507`). The client uses `communities.length`, i.e. also includes archived. So the settings-card copy "Transfer one to free up a slot" is correct and archiving-vs-counting is not part of this bug.
- **Onboarding's `activeCommunities` filter is display-only.** `HostedCommunityOnboarding.tsx:236-238` filters archived for rendering, but `:243` gates on unfiltered `communities.length` — which matches the server. Not a defect.

## Provenance

Found while verifying #4148 (follow-up to #4124). All file:line references read at #4148 head `7db75a607` via `git show`, in `REPOS/buzz-pr4124-sami`. Related: #2600 (asked for configurability), #2599 (shipped `BUZZ_MAX_COMMUNITIES_PER_OWNER`), #3829 (raised both hardcoded values by hand), #4148 (fixed instances 1-3).

Contributor guide

Open the contributing guide

Research direction

Start with the relay list response in crates/buzz-relay/src/api/operator.rs and the desktop types and gating sites in hostedCommunityApi.ts, HostedCommunityCreateFlow.tsx, HostedCommunityOnboarding.tsx, and HostedCommunitiesSettingsCard.tsx. Check the Builderlab endpoints in desktop/src-tauri/src/builderlab.rs first to determine whether the limit already crosses that boundary. Done means the effective configured limit reaches the desktop and all displayed, disabled, and submission-limit behavior agrees with the relay.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust, typescript
Domain
api, backend-api-design, frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.