# Guild H5 architecture

## Decision

The existing `gonghui/` tree is the Lalu legacy product and remains independently deployable. New guild products use a zero-build, static multi-package architecture under `guild/`:

```text
guild/
├── packages/
│   ├── core/                 # Product context, date ranges, errors, state machines
│   ├── ui/                   # Product-neutral center layouts and components
│   ├── host-center/          # Host feature package (added with the first host screen)
│   ├── agency-center/        # Agency feature package
│   ├── bd-center/            # BD feature package
│   └── wallet/               # Shared coin-seller and USDT withdrawal flow
├── products/
│   └── fami/                 # Fami manifest, assets and visual overrides
└── fami/
    ├── host-center/          # Thin deployable page entry
    ├── agency-center/
    ├── bd-center/
    └── wallet/
```

Each page entry composes packages with ordered classic `<script>` and `<link>` tags. There is deliberately no bundling step: the current repository is deployed as static files, so a build-system failure must not take down existing H5 pages.

## Package boundaries

| Package                    | Owns                                                                                  | Must not own                                 |
| -------------------------- | ------------------------------------------------------------------------------------- | -------------------------------------------- |
| `common/`                  | Existing cross-H5 API transport, environment selection, i18n and base theme           | Guild role semantics or Fami page state      |
| `guild/packages/core`      | Product context, shared time range model, request DTO mapping, async state primitives | DOM layout and product colors                |
| `guild/packages/ui`        | Responsive cards, filters, tables, empty/loading/error presentation                   | API paths or role permissions                |
| `guild/packages/<feature>` | One role/flow's state, API adapter and render controller                              | Fami-only text, assets or hardcoded app code |
| `guild/products/fami`      | `app_code=fami`, feature flags, Fami assets and permitted visual overrides            | Shared business calculations                 |
| `guild/fami/<feature>`     | HTML composition and page-only wiring                                                 | Reusable business logic                      |

## Runtime composition

Every Fami page must load scripts in this order:

```html
<script src="../../packages/core/product-context.js"></script>
<script src="../../products/fami/product.js"></script>
<script src="../../../common/api.js"></script>
<script src="../../../common/params.js"></script>
<script src="../../../common/i18n.js"></script>
```

`product.js` runs before `common/api.js`, making Fami the page-level default tenant even when an older client omits `app_code`. An explicit URL `app_code` still wins for diagnostics. Pages under the old `gonghui/` tree load none of these files, so their default remains Lalu.

## Shared domain rules from the initial specification

- Host, agency and BD statistics consume one date-range model: today, yesterday, last 7 days, last 30 days, this month, last month and custom range.
- Metrics are response-driven. The frontend renders values and definitions but does not recompute diamonds, gift senders, online duration, valid live duration/days, private-message users or new followers.
- Agency and BD list rows use stable IDs as keys. Removing a host or agency updates the view only after the server confirms the command.
- Wallet is a separate feature package because host, agency and BD reuse it. Coin-seller withdrawal and USDT withdrawal are separate state machines with separate validation, idempotency command IDs and histories.
- Permissions and feature switches come from backend capabilities. Functional switches default to enabled; only external addresses, secrets and third-party credentials may require environment configuration.

## Backend/admin contract direction

New endpoints should be app-scoped through `X-App-Code: fami` and return server-calculated metrics. Prefer additive `/api/v1/guild/...` contracts or additive fields on existing center endpoints; do not change Lalu response meaning in place.

The admin coin-seller withdrawal whitelist belongs to backend/admin storage. The H5 only consumes eligible coin sellers and must never treat a locally cached list as authorization.

### Implemented host statistics contract

`GET /api/v1/host-center/stats?start_date=YYYY-MM-DD&end_date=YYYY-MM-DD` returns an inclusive UTC natural-day range and these server-owned metrics:

- `diamond_earnings`, `diamond_exchanged`, `gift_senders` from wallet-service POINT ledger entries;
- `online_duration_ms` from App session heartbeat spans, merged per day so overlapping sessions are not double-counted;
- `valid_mic_duration_ms`, `valid_mic_days` from user-service microphone daily facts;
- `private_message_senders` from authenticated Tencent IM `C2C.CallbackAfterSendMsg` events;
- `new_followers` from follow/unfollow delta events.

The Tencent C2C callback URL must include the matching product context, for example `app_code=fami`. The callback stores only event ID, sender, receiver and event time; message content is never written to the statistics table.

### Implemented Agency contract and revenue policy

`GET /api/v1/agency-center/stats?start_date=YYYY-MM-DD&end_date=YYYY-MM-DD` returns the Agency summary and current host rows for the same inclusive UTC date range. Host earnings come from wallet POINT entries; engagement metrics come from user-service facts.

Agency share is configured in the shared Admin revenue-policy template JSON as `agency.point_ratio_percent`. The common legacy template defaults to `0`; the seeded `fami_guild_revenue_policy` template defaults to `20`. Publishing an App policy compiles the value into `wallet_policy_instances.agency_point_ratio_ppm`. Each successful gift atomically credits the owner POINT account and writes `agency_point_share_entries`, including the host, owner, ratio and policy instance snapshots. Historical earnings therefore do not move when a host later changes Agency.

### Implemented BD and wallet contracts

`GET /api/v1/bd-center/stats` aggregates the current BD's direct Agencies, active host counts and host gift POINT income without including Agency-share POINT. The page reuses the same date-range model and invitation acceptance flow as the existing organization domain.

The Fami wallet is a shared `guild/packages/wallet` feature mounted at `guild/fami/wallet/`. `GET /api/v1/point-wallet/overview` returns the POINT balance, published policy exchange/fee snapshot and the country-filtered coin-seller whitelist. POINT-to-seller transfers never accept an exchange ratio from H5: wallet-service locks `point_withdrawal_coin_seller_configs` and atomically debits `POINT` while crediting `COIN_SELLER_COIN`. Admin edits the same app-scoped table from `/host/point-withdrawal-config`; switching the selected App supports Fami, Huwaa, Lalu and future products without adding boolean columns.

## Adding another guild product

1. Add `guild/products/<product>/product.js` and optional assets/theme overrides.
2. Add thin entries under `guild/<product>/<feature>/`.
3. Reuse a feature package when its API/state semantics match; add a product adapter when DTOs differ.
4. Run `npm run test:guild-architecture` to prove the new default app code does not change legacy Lalu requests.
