Skip to content

Commit 4b65f44

Browse files
Refine request settings, pricing, caching, and localization
1 parent fbfc82e commit 4b65f44

File tree

111 files changed

+12268
-3757
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

111 files changed

+12268
-3757
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Request Modifiers and VIP Token Rules
2+
3+
## Current shipped surfaces
4+
5+
- Streamers can already configure request-path modifiers in dashboard settings.
6+
- Viewers can already choose a requested path in the website search UI on `/$slug`.
7+
- Viewers can already choose a requested path in the Twitch panel extension.
8+
- Search path filters and request-path modifiers are separate:
9+
- search path filters narrow the catalog results a viewer sees
10+
- request-path modifiers change the actual request that gets submitted
11+
12+
## Current shared rule model
13+
14+
- Duration thresholds can force a song to become VIP-only.
15+
- A requested path can add a path-specific VIP token cost.
16+
- The same planner is used by chat, the website, and the Twitch panel:
17+
- `src/lib/requested-paths.ts`
18+
- `src/lib/server/viewer-request.ts`
19+
20+
### `requestPathModifierUsesVipPriority = true`
21+
22+
- Any paid path selection becomes VIP-only.
23+
- The total VIP cost is the higher of:
24+
- the song-duration VIP requirement
25+
- the requested-path VIP requirement
26+
- This treats the path selection as using the normal VIP-priority lane instead of stacking another token on top.
27+
28+
### `requestPathModifierUsesVipPriority = false`
29+
30+
- A paid path can stay a regular request when no other rule forces VIP.
31+
- The VIP total becomes additive:
32+
- base VIP request cost
33+
- plus any path cost
34+
- If a duration rule already forces VIP, the request is still VIP-only, but the path cost can stack on top.
35+
36+
## Current behavior matrix
37+
38+
Assumptions used below:
39+
40+
- base VIP request cost = `1`
41+
- long-song threshold example = `2`
42+
- requested path example cost = `1`
43+
44+
| Scenario | `requestPathModifierUsesVipPriority` | Regular action | VIP action | Total VIP cost |
45+
| --- | --- | --- | --- | --- |
46+
| No duration rule, no path modifier | `true` or `false` | `Add` | `Add VIP` | `1` |
47+
| No duration rule, bass path costs `1` | `true` | hidden | `VIP (1)` | `1` |
48+
| No duration rule, bass path costs `1` | `false` | `Add (1)` | `Add VIP (2)` | `2` |
49+
| Long song requires `2`, no path modifier | `true` or `false` | hidden | `VIP (2)` | `2` |
50+
| Long song requires `2`, bass path costs `1` | `true` | hidden | `VIP (2)` | `2` |
51+
| Long song requires `2`, bass path costs `1` | `false` | hidden | `VIP (3)` | `3` |
52+
53+
## UI direction now in code
54+
55+
- When a regular request is not valid, the viewer-facing request UI should not show a usable regular action.
56+
- The panel now shows only the VIP action in that case.
57+
- The website viewer action UI follows the same rule instead of presenting a dead-end regular option.
58+
59+
## Open product questions
60+
61+
- Should long-song cost and requested-path cost always stack, even when the channel says the path uses VIP priority?
62+
- Should there be a separate extra token for queue priority itself, beyond the song rule and path rule?
63+
- Should path selection stay a dropdown in the panel, or switch to compact chips when only a few paths are available?
64+
- Do we ever want a regular request to pay path cost but still not consume the same premium lane as a VIP request?
65+
66+
## Suggested product framing
67+
68+
- Treat duration thresholds as the rule that decides whether a request must become premium.
69+
- Treat requested-path cost as either:
70+
- a premium-lane selector
71+
- or an additive modifier
72+
- Keep that distinction explicit in settings copy and viewer-facing helper text so streamers can predict the exact cost model.

docs/twitch-panel-extension-feature-request.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ RockList.Live includes a Twitch panel extension that keeps playlist viewing and
66

77
- Viewers can read the playlist without identity sharing.
88
- Linked viewers can search the catalog, add regular requests, add VIP requests, edit the current request, and remove their own request.
9+
- When the channel enables request-path modifiers, linked viewers can choose a path before submitting a request.
910
- Blocked viewers can still view the playlist and search the catalog. Request actions stay unavailable.
1011
- When the channel blacklist is enabled, blacklisted artists, charters, songs, and versions stay hidden in panel search.
1112
- Channel search filters such as official-only, allowed tunings, and required parts stay active in panel search.
1213
- The panel does not expose a toggle for showing blacklisted results.
14+
- Detailed path-modifier and VIP-token combinations live in `docs/request-modifier-vip-token-rules.md`.
1315

1416
## Owner and moderator surface
1517

docs/web-viewer-requests-feature-request.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ RockList.Live lets viewers request songs directly from a channel playlist page a
77
- Playlist viewing stays public.
88
- Signed-in viewers see their Twitch identity, VIP token balance, and active request state for the current channel.
99
- Signed-in viewers can add a regular request or a VIP request from search results when channel rules allow it.
10+
- When the channel enables request-path modifiers, signed-in viewers can choose a path before submitting a request.
1011
- Signed-in viewers can replace or remove their own active requests.
1112
- Blocked viewers can still sign in, browse the playlist, search the catalog, and copy the request commands. Request buttons stay unavailable.
1213

@@ -17,6 +18,7 @@ RockList.Live lets viewers request songs directly from a channel playlist page a
1718
- The website can reveal blacklisted results with the public `Show blacklisted songs` toggle.
1819
- Channel request filters such as official-only, allowed tunings, and required parts stay active during search and submit checks.
1920
- Viewer requests still run server-side validation for request limits, VIP token balance, setlist rules, queue limits, and blocked-requester checks.
21+
- Detailed path-modifier and VIP-token combinations live in `docs/request-modifier-vip-token-rules.md`.
2022

2123
## Main surfaces
2224

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
ALTER TABLE channel_settings
2+
ADD COLUMN request_path_modifier_vip_token_costs_json text NOT NULL DEFAULT '{}';
3+
4+
UPDATE channel_settings
5+
SET request_path_modifier_vip_token_costs_json = json_object(
6+
'guitar',
7+
request_path_modifier_vip_token_cost,
8+
'lead',
9+
request_path_modifier_vip_token_cost,
10+
'rhythm',
11+
request_path_modifier_vip_token_cost,
12+
'bass',
13+
request_path_modifier_vip_token_cost
14+
);
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
ALTER TABLE channel_settings
2+
ADD COLUMN request_path_modifier_guitar_vip_token_cost integer NOT NULL DEFAULT 0;
3+
4+
ALTER TABLE channel_settings
5+
ADD COLUMN request_path_modifier_lead_vip_token_cost integer NOT NULL DEFAULT 0;
6+
7+
ALTER TABLE channel_settings
8+
ADD COLUMN request_path_modifier_rhythm_vip_token_cost integer NOT NULL DEFAULT 0;
9+
10+
ALTER TABLE channel_settings
11+
ADD COLUMN request_path_modifier_bass_vip_token_cost integer NOT NULL DEFAULT 0;
12+
13+
UPDATE channel_settings
14+
SET
15+
request_path_modifier_guitar_vip_token_cost = CASE
16+
WHEN json_valid(request_path_modifier_vip_token_costs_json)
17+
THEN coalesce(
18+
CAST(
19+
json_extract(
20+
request_path_modifier_vip_token_costs_json,
21+
'$.guitar'
22+
) AS integer
23+
),
24+
request_path_modifier_vip_token_cost,
25+
0
26+
)
27+
ELSE coalesce(request_path_modifier_vip_token_cost, 0)
28+
END,
29+
request_path_modifier_lead_vip_token_cost = CASE
30+
WHEN json_valid(request_path_modifier_vip_token_costs_json)
31+
THEN coalesce(
32+
CAST(
33+
json_extract(
34+
request_path_modifier_vip_token_costs_json,
35+
'$.lead'
36+
) AS integer
37+
),
38+
request_path_modifier_vip_token_cost,
39+
0
40+
)
41+
ELSE coalesce(request_path_modifier_vip_token_cost, 0)
42+
END,
43+
request_path_modifier_rhythm_vip_token_cost = CASE
44+
WHEN json_valid(request_path_modifier_vip_token_costs_json)
45+
THEN coalesce(
46+
CAST(
47+
json_extract(
48+
request_path_modifier_vip_token_costs_json,
49+
'$.rhythm'
50+
) AS integer
51+
),
52+
request_path_modifier_vip_token_cost,
53+
0
54+
)
55+
ELSE coalesce(request_path_modifier_vip_token_cost, 0)
56+
END,
57+
request_path_modifier_bass_vip_token_cost = CASE
58+
WHEN json_valid(request_path_modifier_vip_token_costs_json)
59+
THEN coalesce(
60+
CAST(
61+
json_extract(
62+
request_path_modifier_vip_token_costs_json,
63+
'$.bass'
64+
) AS integer
65+
),
66+
request_path_modifier_vip_token_cost,
67+
0
68+
)
69+
ELSE coalesce(request_path_modifier_vip_token_cost, 0)
70+
END;

drizzle/0037_search_cache_swr.sql

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
ALTER TABLE search_cache
2+
ADD COLUMN version_token text NOT NULL DEFAULT '';
3+
4+
ALTER TABLE search_cache
5+
ADD COLUMN fresh_until integer NOT NULL DEFAULT 0;
6+
7+
ALTER TABLE search_cache
8+
ADD COLUMN stale_until integer NOT NULL DEFAULT 0;
9+
10+
ALTER TABLE search_cache
11+
ADD COLUMN revalidating_at integer;
12+
13+
UPDATE search_cache
14+
SET
15+
fresh_until = expires_at,
16+
stale_until = expires_at;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CREATE TABLE catalog_search_state (
2+
scope text PRIMARY KEY NOT NULL,
3+
version integer NOT NULL DEFAULT 0,
4+
updated_at integer NOT NULL DEFAULT (unixepoch() * 1000)
5+
);
6+
7+
INSERT INTO catalog_search_state (scope, version, updated_at)
8+
VALUES ('catalog', 0, unixepoch() * 1000)
9+
ON CONFLICT(scope) DO NOTHING;

package-lock.json

Lines changed: 12 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
"@tanstack/react-query": "latest",
6969
"@tanstack/react-query-devtools": "latest",
7070
"@tanstack/react-router": "latest",
71-
"@tanstack/react-router-devtools": "latest",
71+
"@tanstack/react-router-devtools": "^1.166.11",
7272
"@tanstack/react-start": "latest",
7373
"@tanstack/react-table": "latest",
7474
"@tanstack/react-virtual": "latest",

scripts/write-latest-migration.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ if (!latestMigration) {
1515
throw new Error("No SQL migrations found in ./drizzle");
1616
}
1717

18-
const nextContent = `export const LATEST_MIGRATION_NAME = ${JSON.stringify(latestMigration)};\n`;
18+
const nextContent = `// biome-ignore format: generated file must stay single-line for sync checks\nexport const LATEST_MIGRATION_NAME = ${JSON.stringify(latestMigration)};\n`;
1919

2020
if (checkMode) {
2121
let currentContent = null;

0 commit comments

Comments
 (0)