# Business rules — skrmiish

Evidence date: 2026-10-03. Source: `master` at `5077fba`.

Rules below are taken from the front end. The API is not in this repository. A rule the server might enforce, and that this client does not show, is **Unknown**.

Labels:

- **Implemented** — the client does this.
- **Inferred** — a reasonable reading of the client, not stated as a rule.
- **UI copy** — text on the screen. Enforcement was not found in the client.

## Identity and access

| ID | Rule | Evidence | Label |
|---|---|---|---|
| BR-001 | A successful auth payload with `is_streamer === true` is a Partner. Every other payload is a Supporter | `authSlice.js` `setUser` | Implemented |
| BR-002 | `/accounts`, `/profile`, `/manageLeaderboard`, and `/streamer` require `loggedIn` and `role === "Partner"`. Other sessions redirect to `/` | `ProtectedRoute.js` | Implemented |
| BR-003 | The public home page `/` has no login check | `App.js` | Implemented |
| BR-004 | Twitch `broadcaster_type` of `affiliate` or `partner` chooses the streamer signup modal after HTTP 401. Any other broadcaster type with a stored Twitch token chooses the supporter modal | `App.js` interceptor | Implemented |
| BR-005 | `broadcaster_type` does not set `role`. Only `is_streamer` does | `App.js`, `authSlice.js` | Implemented |
| BR-006 | Email login is login type 0 and sends `mobile_platform: "web"` | `NormalLogin.js` (`logintype`) | Implemented |
| BR-007 | Twitch login is login type 1 | `LogInWithTwitch.js` (`loginType`) | Implemented |
| BR-008 | Normal registration creates a non-streamer (`is_streamer: false`) with `signup_type` `"0"` | `NormalRegister.js` | Implemented |
| BR-009 | Supporter registration sends `isStreamer: false` and `signupType` `"twitch"` | `RegisterAsSupporter.js` | Implemented |
| BR-010 | Partner registration sends `is_streamer: true` and `signup_type` `"twitch"` | `RegisterAsStreamer.js` | Implemented |
| BR-011 | The session, including the user object, is stored in `localStorage` under `root` and restored on load | `store.js` | Implemented |
| BR-012 | There is no client-side password rule, email format check, or age check on the forms that were read | Sign-up components. One comment says a validation script could be added | Implemented as absence. **Inferred** that the API may still reject bad input |

## Leaderboards

| ID | Rule | Evidence | Label |
|---|---|---|---|
| BR-013 | A leaderboard title input allows at most 30 characters | `CreateTab.js` `maxLength="30"` | Implemented |
| BR-014 | The only games in the create form are Fortnite and PubG. The default is Fortnite | `CreateTab.js` | Implemented |
| BR-015 | Format is fixed to the string `KOTK` and the input is disabled. Format is not sent to the API | `CreateTab.js` | Implemented |
| BR-016 | Prize and description are collected and then omitted. A comment says to send them when the API is ready | `CreateTab.js` | Implemented |
| BR-017 | A leaderboard has six carousel slots. `item_type` is 1 when "link to support" is on, otherwise 0 | `CreateTab.js` payload | Implemented |
| BR-018 | Carousel images are uploaded as base64 to `/api/v1/Image` before the leaderboard POST. A failed image fetch keeps the original card | `CreateLeaderBoard.js` | Implemented |
| BR-019 | Schedule start is a timestamp parsed as `GMT+0200`. `durationHours` is `end.getTime() - start.getTime() / 3600000` | `CreateTab.js` | Implemented. **Inferred** that the author meant a duration in hours. The expression does not compute that |
| BR-020 | The right rail shows the three streamers with the highest `clicker_game.clicks` | `RightTopSection.js` | Implemented |
| BR-021 | Public leaderboard and streamer lists are requested without an auth header in the sagas | `Leaderboards.js`, `Streamers.js` | Implemented |

## Money

| ID | Rule | Evidence | Label |
|---|---|---|---|
| BR-022 | Overview shows `account.supporter_balance` and `account.revenue_share.amount` | `Overview.js` | Implemented. The meaning of each field is **Inferred** from the labels around them (supporter balance, revenue share in dollars) |
| BR-023 | A payout request sends the streamer uuid and an amount | `RequestPayout.js` | Implemented |
| BR-024 | "You only qualify for Payouts if your Revenue Share is a minumum of $100." | `PayoutDetails.js` | UI copy. The client does not block amounts under 100 |
| BR-025 | The payout method label on the details form is the string `Paypal` | `PayoutDetails.js` | Implemented as a fixed label |
| BR-026 | Buying subscription credits is described as taking funds from revenue share | `BuyCredits/CongratsTab/CongratsTab.js` | UI copy. No purchase request exists in `src/sagas` |
| BR-027 | Currency shown next to the credit amount input is the string `USD` | `BuyCredits/CongratsTab` | Implemented as a fixed field |

## Profile

| ID | Rule | Evidence | Label |
|---|---|---|---|
| BR-028 | Email cannot be edited in settings (input disabled) | `SettingsTab.js` | Implemented |
| BR-029 | Password cannot be edited in settings (input disabled) | `SettingsTab.js` | Implemented |
| BR-030 | Cell number uses a disabled prefix `+27` and a disabled number field | `SettingsTab.js` | Implemented |
| BR-031 | Locked details are changed by emailing `changemyinfo@skrmiish.com` | `SettingsTab.js` | UI copy. No mail form is in the client |
| BR-032 | A profile save sends date of birth as `{year}-{MM}-{day}T00:00:00+02:00` | `UpdateUser.js` | Implemented |
| BR-033 | Social links sent to the API are twitch, instagram, facebook, website, and twitter. `merch` is always an empty string. The `discord` argument is not posted | `UpdateLinks.js` | Implemented |
| BR-034 | A bio update sends `text` and `timestamp: Date.now()` | `UpdateBio.js` | Implemented |
| BR-035 | Delete Account is a button with no request attached in the settings file that was read | `SettingsTab.js` | Implemented as a non-functional control |

## Other client behaviour that acts like a rule

| ID | Rule | Evidence | Label |
|---|---|---|---|
| BR-036 | HTTP 401 does not log the user out. It may open a signup modal when a Twitch token is already in state | `App.js` | Implemented |
| BR-037 | On viewports narrower than 768px, leaderboard management opens on TRACK, not CREATE | `ManageLeaderboards/.../Main.js` | Implemented |
| BR-038 | The downloads profile tab is not shown under 768px | `Profile/.../Main.js` | Implemented |
| BR-039 | Newsletter unsubscribe and "check subscription" both request the leaderboard collection | `Unsubscribe.js`, `CheckSubscribtion.js` | Implemented. **Inferred** defect: the saga names do not match the URL |
| BR-040 | Partner registration failure does not dispatch the failure toast | `RegisterAsStreamer.js` | Implemented |
| BR-041 | Footer support mail is `support@skrmiish.com` in one column and `support@skirmiish.com` in the address block | `SPA/Footer/LeftSection/LeftSection.js` | Implemented. Which spelling is correct is **Unknown / Requires Confirmation** |

## Not established

- Eligibility to become a Partner beyond the client sending `is_streamer: true` or a Twitch broadcaster type. The API's decision is **Unknown**.
- How supporter balance, revenue share, or clicks are calculated.
- Whether $100 is enforced by the API.
- Tax, payout schedule, chargebacks, or refunds.
- Age restriction, even though date of birth is collected.
- Terms of use and privacy policy text. Footer items are labels. Modal components named `TermsOfService` and `PrivacyPolicy` exist under the sign-up modal; their body text was not treated as a confirmed legal rule.
- Rate limits, captcha, and row-level security. None were found in this front end.
