@homie-rocks/studio 0.28.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -17,9 +17,38 @@ To bring a studio up to date, tell Claude: "Upgrade my studio to the newest Homi
17
17
  `npx -y @homie-rocks/studio@latest upgrade`, which shows what's new since the version your studio pins (from this
18
18
  file) and what the upgrade would change, and changes nothing until you agree.
19
19
 
20
+ ## [0.29.0] - 2026-10-03
21
+
22
+ **Plugin 0.30.0** · [#44](https://github.com/homie-rocks/homie/pull/44)
23
+
24
+ The Lounge: a community room on your own studio, with play nights, moderators, live rooms and "show what you made" cards, and chat history you can turn on.
25
+
26
+ ### Added
27
+
28
+ - The Lounge. `"lounge": true` in studio.json gives your studio a community room at `/lounge/`, in your studio's own look: one column of talk on a phone with "What's on" a tap away, the talk beside a sidebar on a computer. It is room chat in a room of its own, on your own Cloudflare: reactions and quick lines for anyone, typing for players signed in with a passkey (Sign in and Make a passkey are on the page itself), and every typed line through the word list and your Workers AI review (Clef) before anyone sees it. Slow mode is 3 s to start with. `chat/LOUNGE.md` has the whole of it.
29
+ - Play nights. Set them in the office (a name, a time in your own zone, how long, a game) or with `homie-studio lounge night "<title>" --at <time>`. Everyone sees the time in their own zone, a countdown, "On now", the game's Play, and Add to calendar (a small `.ics` the browser makes).
30
+ - Live rooms in the Lounge, each with Watch and Join: your studio's own, and with a directory the busiest across it (`"featured": "studio"` keeps it to yours, `"none"` shows none).
31
+ - Show what you made. A signed-in person pastes a link to a game and it becomes a card with the game's own title, pitch, picture and a Play button. Only a Homie studio's game can be one (yours, or a site that answers the studio manifest), its words pass the word list and the review like a typed line, and each person posts three an hour. Typed links stay held.
32
+ - Moderators. Name a player account as a moderator from one of their lines in the office (or `homie-studio lounge mod <player id>`): from the Lounge page they take a line down, mute or kick its sender for up to an hour (their lines come down with them) and set slow mode, never on your lines or another moderator's. You have the same from the page, for up to a day, and everything else in the office's new Lounge section: its rules, play nights, moderators, last lines and reports.
33
+ - Chat history you can turn on (`history`, a number of days, 1 to 90). In the office, "Keep what is said" for the Lounge, or `homie-studio lounge history <days>`; for a game, `"chat": { "history": 7 }` or the office. What is kept, in your studio's own D1: typed lines, quick lines, cards and your own studio lines, the name shown, when, and for a signed-in sender their account id. Never a reaction, an address or a browser key. It is deleted after that many days; keeping fewer days (or none) deletes the rest at once. The room gives kept lines back when it opens ("Earlier messages" pages further), a report still finds an older line, and a mute or kick from a kept line holds its sender's account.
34
+ - Anyone takes their own message down: Delete on their own line in the Lounge (a signed-in person's kept lines too, a guest's while it is in the room's last minutes). Deleting an account at `/account/` deletes every line it kept, and the account's download has them.
35
+ - `homie-studio lounge` (rules, play nights, moderators, last lines, reports), `lounge night`, `lounge history`, `lounge rules`, `lounge mod` and `lounge remove`. From your AI's office key, keeping more days, opening chat up, a new moderator, a mute or a kick only ASK (your one tap); a play night, slow mode, taking a line down and keeping less happen at once.
36
+ - `/lounge/api/now`: the Lounge's public facts (its rules, play nights and live rooms) as JSON, and the studio's manifest names its Lounge. homie.rocks can show a Lounge live when its rules allow it (`hub`): a visitor's own browser opens the Lounge's socket, as for a room's card; they react there and type on your site. The sitemap and llms.txt list the Lounge's page; robots.txt keeps its socket and APIs out.
37
+ - "Get Homie updates" on the setup card (Claude, Claude Desktop and the Claude Code mod): a link to homie.rocks's own sign-up for a short email when Homie ships something. The person signs up there themselves (double opt-in); the card never asks for an address.
38
+
39
+ ### Changed
40
+
41
+ - Room chat's rules have one more field, `history`. It is 0 for every game and for the Lounge until an owner turns it on, and always 0 on a kids server, a beginner server and a kids Lounge, so nothing changes for a studio that does nothing: a room still keeps the last 50 lines of the last 15 minutes in memory and nothing else.
42
+ - A line on the wire may carry `card`, `mod`, `kept` and, on a page's own copy, `mine`; a page that knows none of them shows the line's words. A watching page may send `unsay` for its own line. The room's facts count watching pages (`counts.shells`). The relay stays revision 8.
43
+
44
+ ### Upgrade notes
45
+
46
+ - `homie-studio upgrade` adds migration `0009_studio_lounge.sql` (kept chat, play nights, moderators; all empty until you use them) and `npm run deploy` applies it. Nothing else changes until you add `"lounge": true` to studio.json and deploy. A game already called `lounge` keeps its page, and the build says the Lounge is off.
47
+ - Before you turn history on, read `chat/OWNERS.md`: it says what is kept and what that means for children's data. The Lounge's page tells people what is kept, in its "How it works".
48
+
20
49
  ## [0.28.0] - 2026-10-03
21
50
 
22
- **Plugin 0.29.0** · [#43](https://github.com/homie-rocks/homie/pull/43)
51
+ **Plugin 0.29.0** · [#43](https://github.com/homie-rocks/homie/pull/43) · [release-2026-10-03-studio-0.28.0](https://github.com/homie-rocks/homie/releases/tag/release-2026-10-03-studio-0.28.0)
23
52
 
24
53
  Tell Homie: when something confuses you or gets in your way, Claude can offer to send the people who make Homie a short note about it. You see the note word for word first, and nothing is sent without your yes.
25
54
 
package/README.md CHANGED
@@ -27,6 +27,7 @@ npx homie-studio stats # the studio's own number
27
27
  npx homie-studio office # who is playing now, in every live room (the back office)
28
28
  npx homie-studio servers # each game's servers: humans-only, hybrid AI seats, beginner guides
29
29
  npx homie-studio chat # room chat: each game's rules, the rooms' last minutes, reports
30
+ npx homie-studio lounge # the studio's Lounge: play nights, moderators, kept chat
30
31
  npx homie-studio upgrade # what a newer template adds to this studio (--apply to take it)
31
32
  ```
32
33
 
@@ -353,10 +354,38 @@ npx homie-studio chat words # the bu
353
354
  lines taken down), the same in the owner's own game, the reports players make, and Announce (a studio line).
354
355
  - **Speech bubbles:** `net.on('say')` and the port kit's `createBubbles` / `paintBubbles`; Gem Rush, Ember Vale and
355
356
  Gem Rush 3D draw them over the speaker.
356
- - **Nothing is stored** but a report (the one message, 30 days): the room keeps 15 minutes in memory.
357
+ - **Nothing is stored** but a report (the one message, 30 days): the room keeps 15 minutes in memory. An owner may
358
+ turn a room's `history` on (0.29.0, a number of days; off for every game, never on a kids server): then what was
359
+ said (never a reaction) is kept in the studio's own D1 for that long, and people take their own lines down.
357
360
  [chat/CHAT.md](chat/CHAT.md) is the short version and why Clef; [chat/OWNERS.md](chat/OWNERS.md) a plain note for
358
361
  studio owners on chat and children's data; NETPLAY.md section 19 the wire.
359
362
 
363
+ ## The Lounge: a room for the studio's community
364
+
365
+ `"lounge": true` in studio.json gives the studio a community room at `/lounge/` (0.29.0): room chat in a lasting room
366
+ of its own, on the studio's own Cloudflare like everything else. Reactions and quick lines for anyone, typing for
367
+ players signed in with a passkey (signing in is on the page), every typed line through the word list and the Clef
368
+ review first.
369
+
370
+ ```sh
371
+ npx homie-studio lounge # its rules, play nights, moderators, lines, reports
372
+ npx homie-studio lounge night "Night Rush night" --at 2026-10-09T19:00:00-07:00 --game night-rush
373
+ npx homie-studio lounge history 14 # keep what is said for 14 days (asked: the owner confirms)
374
+ npx homie-studio lounge mod <player id> # a moderator (asked)
375
+ ```
376
+
377
+ - **Play nights** the owner sets in the office: everyone sees the time in their own zone, a countdown and Add to
378
+ calendar. **Live rooms** with Watch and Join: the studio's, and with a directory the busiest across it.
379
+ - **Show what you made:** a signed-in person pastes a link to a Homie studio's game and it becomes a card with the
380
+ game's own title, pitch and picture (read from that studio's manifest; its words pass the word list and the
381
+ review). Typed links stay held.
382
+ - **Keeping it kind:** anyone reports a line or takes their own down; moderators the owner names Remove, Mute and
383
+ Kick (an hour at most) and set slow mode from the page; the owner has all of it and the office. A kids Lounge
384
+ keeps to emoji and quick lines and keeps nothing.
385
+ - **History is off** until the owner turns it on (1 to 90 days), and the page says what is kept.
386
+ - **homie.rocks** can show a Lounge live when its rules allow (`hub`): the visitor's browser opens the Lounge's own
387
+ socket, as for a room's card; they react there and type here. [chat/LOUNGE.md](chat/LOUNGE.md) has the whole of it.
388
+
360
389
  ## Selling things: the shop
361
390
 
362
391
  A studio sells items for its games (0.24.0; Stripe's own agent tools since 0.24.3) with **its own Stripe account**: the studio is the seller, money goes
@@ -106,6 +106,11 @@
106
106
  * minutes, players' reports, the review's day; chat rules <game> [--server <id>]
107
107
  * --mode off|emoji|lines|text --who anyone|signed-in|members … | --reset;
108
108
  * chat remove <game> <room> <line id>; chat budget <neurons>; chat words
109
+ * homie-studio lounge the studio's Lounge (studio.json "lounge", chat/LOUNGE.md): its rules, play
110
+ * nights, moderators, last lines and reports; lounge night "<title>" --at <time>
111
+ * [--minutes 120] [--game <id>]; lounge night remove <id>; lounge history <days>;
112
+ * lounge rules [--slow <s>] [--who …] | --reset; lounge mod <player> [--remove];
113
+ * lounge remove <line id> | --all
109
114
  * homie-studio servers [--game <id>] every server of every game (worker/servers.mjs): its policy, door, the AI's
110
115
  * level, live rooms and AI, members, and builds that predate servers
111
116
  * homie-studio servers new <game> "<Name>" --policy open|humans-only|hybrid|beginner [--ai <n>] [--guides <n>]
@@ -258,6 +263,7 @@ import { statsKey, statsLink, statsRevoke, statsShare, statsShow } from '../lib/
258
263
  import { playersOwner, playersShow } from '../lib/players.mjs';
259
264
  import { officeAnnounce, officeClose, officeInvite, officeKey, officeKick, officeLaunch, officeLines, officeLink, officeMute, officeRevoke, officeShow } from '../lib/office.mjs';
260
265
  import { chatBudget, chatLines, chatRemove, chatRulesSet, chatShow, chatWords } from '../lib/chat-cli.mjs';
266
+ import { loungeLines, loungeMod, loungeNight, loungeRemove, loungeRulesSet, loungeShow } from '../lib/lounge-cli.mjs';
261
267
  import { detectLocalAi, localAiVars } from '../lib/local-ai.mjs';
262
268
  import { agentsBrain, agentsBrainKey, agentsPass, agentsPasses, agentsRevoke, agentsTry, serversClose, serversLevel, serversLines, serversList, serversMember, serversNew, serversSet } from '../lib/servers.mjs';
263
269
  import { AgentSeat } from '../lib/agent-seat.mjs';
@@ -479,8 +485,13 @@ function print(result) {
479
485
  lines.push(result.note);
480
486
  break;
481
487
  case 'chat remove':
488
+ case 'lounge night':
489
+ case 'lounge remove':
482
490
  lines.push(result.message);
483
491
  break;
492
+ case 'lounge':
493
+ lines.push(...loungeLines(result));
494
+ break;
484
495
  case 'shop':
485
496
  case 'shop orders':
486
497
  case 'shop check':
@@ -498,6 +509,8 @@ function print(result) {
498
509
  break;
499
510
  case 'chat rules':
500
511
  case 'chat budget':
512
+ case 'lounge rules':
513
+ case 'lounge mod':
501
514
  case 'shop refund':
502
515
  lines.push(result.asked ? `Asked: ${result.what}` : result.message, ...(result.asked ? [`Owner's one-tap link (until the ask ends, 15 min): ${result.link}`, result.use] : []));
503
516
  break;
@@ -791,6 +804,17 @@ async function main() {
791
804
  if (sub === 'close') return officeClose(root, positional[2], positional[3], { url, minutes: flags.get('minutes'), reopen: flags.has('reopen') });
792
805
  if (sub === 'revoke') return officeRevoke(root, { url });
793
806
  }
807
+ if (cmd === 'lounge') {
808
+ // The Lounge (0.29.0, chat/LOUNGE.md): its rules and history, play nights, moderators, lines.
809
+ const url = flags.get('url');
810
+ if (!sub) return loungeShow(root, { url });
811
+ if (sub === 'night') return loungeNight(root, positional.slice(2), flags, { url });
812
+ if (sub === 'history') return loungeRulesSet(root, flags, { url, history: positional[2] });
813
+ if (sub === 'rules') return loungeRulesSet(root, flags, { url });
814
+ if (sub === 'mod') return loungeMod(root, positional[2], { url, remove: flags.has('remove') });
815
+ if (sub === 'remove') return loungeRemove(root, positional[2], { url, all: flags.has('all') });
816
+ return { ok: false, command: 'lounge', why: `unknown: lounge ${sub} (night, history, rules, mod, remove)` };
817
+ }
794
818
  if (cmd === 'chat') {
795
819
  // Room chat (0.23.0, NETPLAY.md section 19): rules, the room's last minutes, reports, the review's budget.
796
820
  const url = flags.get('url');
package/chat/CHAT.md CHANGED
@@ -48,11 +48,15 @@ studio's owner is responsible for, read [OWNERS.md](OWNERS.md).
48
48
  | `links`, `swears` | `block` or `allow` (a link is never clickable; slurs, sexual words, threats and contact details are always held) | `block` |
49
49
  | `ai` | review typed messages with the studio's own Workers AI | `true` |
50
50
  | `bubbles`, `watchers`, `hub` | over the speaker's character; watchers may send; homie.rocks may show it | `true` |
51
+ | `history` | days to keep what is said (typed lines, quick lines, cards; never a reaction) in the studio's own D1, 0 to 90 (0.29.0; never on a kids server) | `0`: nothing past the room's few minutes |
51
52
  | `emoji`, `lines` | up to 3 more reactions; up to 12 quick lines (each passes the chat floor at build) | the five; 8 friendly lines |
52
53
  | `block`, `allow` | the studio's own words to hold (`word*` inside words) or to let through | none |
53
54
 
54
55
  `"chat": false` turns it off. A kids server, and every beginner server, keeps chat to emoji and
55
- quick lines whatever the game says.
56
+ quick lines whatever the game says, and keeps nothing.
57
+
58
+ **A room for the studio's community** is the Lounge (0.29.0): room chat at `/lounge/` with play nights, moderators,
59
+ live rooms, "show what you made" cards and history the owner can turn on. Read [LOUNGE.md](LOUNGE.md).
56
60
 
57
61
  **Where it sits on the screen, in game.json `"screen"`**, so a game with a busy HUD keeps typed
58
62
  chat without anything covering its clock, its title or its controls. The Chat pill sits in the
package/chat/LOUNGE.md ADDED
@@ -0,0 +1,114 @@
1
+ # The Lounge
2
+
3
+ A studio's own community room, at `/lounge/` on the studio's site (`@homie-rocks/studio` 0.29.0): play nights,
4
+ demos, "show what you made", and talking with the people who play your games. It runs on your own Cloudflare
5
+ account like everything else in the studio. Nobody else hosts it, reads it or moderates it for you.
6
+
7
+ It is room chat ([CHAT.md](CHAT.md), [NETPLAY.md section 19](../netplay/NETPLAY.md)) in a room of its own: the same
8
+ reactions floating up every screen, quick lines, typing for signed-in players, the same word list and Clef review
9
+ before anyone sees a typed line, and the same owner's tools. On top of that it has what a lasting room needs:
10
+ history (only if you turn it on), moderators, play nights, live rooms to jump into, and cards for games people made.
11
+
12
+ ## Turn it on
13
+
14
+ In `studio.json`:
15
+
16
+ ```json
17
+ "lounge": true
18
+ ```
19
+
20
+ or, to say more:
21
+
22
+ ```json
23
+ "lounge": {
24
+ "name": "The Lounge",
25
+ "blurb": "Play nights, demos and what everybody is making. Come and say hi.",
26
+ "featured": "directory",
27
+ "kids": false,
28
+ "chat": { "slow": 5 }
29
+ }
30
+ ```
31
+
32
+ | Field | | Default |
33
+ |---|---|---|
34
+ | `name`, `tab` | the page's title, and the word in the site's top line | "The Lounge", "Lounge" |
35
+ | `blurb` | one line under the title | the line above |
36
+ | `featured` | live rooms in the sidebar: `directory` (yours, then the busiest across your directory), `studio` (yours only), `none` | `directory` |
37
+ | `kids` | emoji and quick lines only, nothing kept | `true` for a studio with `"audience": "kids"` |
38
+ | `chat` | the Lounge's chat rules before you change them in the office (the fields of game.json `chat`) | below |
39
+
40
+ Then `npm run build` and `npm run deploy` (it applies migration `0009_studio_lounge.sql`). A game already called
41
+ `lounge` keeps its page, and the build says the Lounge is off.
42
+
43
+ ## What it does, with no work
44
+
45
+ - **The talk.** Reactions and quick lines for anyone; typing for players signed in with a passkey (no password, no
46
+ email). Every typed line passes the word list and your Workers AI review first. Slow mode is 3 s by default.
47
+ - **Sign in in place.** "Sign in" and "Make a passkey" are on the Lounge page itself; the account is the same one your
48
+ games and `/account/` use.
49
+ - **Show what you made.** A signed-in person pastes a link to a game. If it is a Homie studio's game (yours, or any
50
+ site answering `/.well-known/homie-studio.json`), it becomes a card with the game's own title, pitch and picture, and
51
+ a Play button. Its words pass the word list and the review like a typed line. Three cards an hour each. Plain links
52
+ are never allowed in typed lines, so a card is the only way a link gets in.
53
+ - **Play nights.** You set them in the office (or with `homie-studio lounge night`). Everyone sees the time in their
54
+ own zone, a countdown, "On now", the game's link, and Add to calendar (a small `.ics` the browser makes; nothing
55
+ else is contacted).
56
+ - **Live now.** Your live rooms, and with a directory the busiest rooms across it, each with Watch and Join.
57
+ - **On a phone** it is one column of talk with "What's on" a tap away; on a computer, the talk and a sidebar.
58
+ - **On homie.rocks** (when your rules let it, `hub`), a visitor's own browser opens the Lounge's socket and shows it
59
+ live, like a room's card there. Those visitors react and send quick lines; typing stays on your site, where they can
60
+ sign in. homie.rocks keeps none of it.
61
+
62
+ ## Keeping it kind
63
+
64
+ | Who | Can |
65
+ |---|---|
66
+ | Anyone | react, send quick lines, report a line (the studio sees the line and the reason, never who reported it), take their own line down |
67
+ | Signed in | type, show what they made, take down their own lines, kept ones too |
68
+ | A moderator | take any line down, mute or kick its sender for up to an hour (their lines come down with them), set slow mode; never on your lines or another moderator's |
69
+ | You, the owner | all of that, holds of up to a day, every rule, history, play nights, moderators, from the Lounge itself or the office |
70
+
71
+ Make someone a moderator from one of their lines in the office ("Make moderator"), or with
72
+ `homie-studio lounge mod <player id>`. A moderator needs an account with a passkey.
73
+
74
+ **Kids.** A kids Lounge keeps chat to emoji and quick lines and keeps nothing, whatever else it says. Nobody in the
75
+ Lounge is ever asked their age, where they live or for photos; the page says so, and the review holds it.
76
+
77
+ ## History: what is kept, and only if you say so
78
+
79
+ By default the Lounge keeps what a room keeps: the last 50 lines of the last few minutes, in memory, and nothing
80
+ else. You can choose to keep what is said for 1 to 90 days (the office, or `homie-studio lounge history <days>`).
81
+ Then, in your D1 (`chat_history`):
82
+
83
+ - typed lines, quick lines, cards and your own studio lines; **never a reaction, an address or a browser key**;
84
+ - the name shown, when, and for a signed-in sender their account id (so they can take their lines down, and deleting
85
+ their account deletes them);
86
+ - deleted after that many days. Keeping fewer days (or none) deletes the rest at once.
87
+
88
+ A person sees what is kept on the page ("What's said is kept for 14 days"), takes their own lines down any time,
89
+ downloads theirs with everything else at `/account/`, and deletes them with their account. Reports stay as before:
90
+ one line, 30 days.
91
+
92
+ History is a room chat rule (`history`, in days), so a game can keep its chat too, but it is **off for every game**
93
+ and stays off unless you turn it on in the office for that game. Turning it on is the owner's one tap when your AI
94
+ asks for it.
95
+
96
+ ## From the studio folder
97
+
98
+ ```sh
99
+ npx --no-install homie-studio lounge # rules, play nights, moderators, last lines, reports
100
+ npx --no-install homie-studio lounge night "Night Rush night" --at 2026-10-09T19:00:00-07:00 --minutes 120 --game night-rush
101
+ npx --no-install homie-studio lounge night remove pn_0123456789ab
102
+ npx --no-install homie-studio lounge history 14 # asked: the owner confirms with one tap
103
+ npx --no-install homie-studio lounge rules --slow 10
104
+ npx --no-install homie-studio lounge mod pl_… [--remove] # asked
105
+ npx --no-install homie-studio lounge remove <line id>
106
+ ```
107
+
108
+ The Lounge's public facts are at `/lounge/api/now` (its rules, play nights and live rooms; homie.rocks reads it).
109
+
110
+ ## What it costs
111
+
112
+ A line or a reaction is one message on the Lounge's room (Durable Objects, billed 20:1); the fan-out is free. Typed
113
+ lines and cards use the review's daily budget, shared with your rooms (2,000 neurons a day by default, inside
114
+ Workers AI's free 10,000). History is a few D1 rows a line.
package/chat/OWNERS.md CHANGED
@@ -41,7 +41,9 @@ What that means in practice:
41
41
 
42
42
  | What | Where | How long |
43
43
  |---|---|---|
44
- | The chat itself (every message and reaction) | Only in the room's memory, on your Cloudflare account | The last 50 messages of the last 15 minutes, for someone who just opened the room. **Never written to storage.** An empty room forgets them a minute after the last person leaves. |
44
+ | The chat itself (every message and reaction) | Only in the room's memory, on your Cloudflare account | The last 50 messages of the last 15 minutes, for someone who just opened the room. **Never written to storage** unless you turn history on (below). An empty room forgets them a minute after the last person leaves. |
45
+ | **Kept chat**, only where you turn `history` on (off for every game and for the Lounge until you choose; never on a kids server) | Your D1 (`chat_history`) | Typed lines, quick lines, cards and your own lines (never a reaction, an address or a browser key): the words, the name shown, when, and for a signed-in sender their account id. For the number of days you chose (1 to 90), then deleted; choosing fewer days deletes the rest at once. A person takes their own lines down, downloads them at `/account/`, and deleting their account deletes them. |
46
+ | The Lounge's play nights and moderators | Your D1 (`lounge_nights`, `lounge_mods`) | A night until a day after it ends; a moderator (their account id) until you remove them. |
45
47
  | Who said what | The same memory, for your office's Mute and Kick | As long as the message is in that memory. Never an IP address. |
46
48
  | A **report** | Your D1 (`chat_reports`) | The one message reported (its words, its sender's name and account id if they had one, the room, when, the reason picked), for **30 days** or until you dismiss it. Never who reported it. |
47
49
  | Your chat rules and your words | Your D1 (`chat_rules`) | Until you change them. |
@@ -63,7 +65,9 @@ or that are directed at children. A typed message can contain personal informati
63
65
  child types anything, and there is nothing personal in your chat to keep or lose.
64
66
  - **Typing for signed-in players only** (the default) means a person behind each typed line has an
65
67
  account you can act on.
66
- - **Little is stored**, and only briefly (see above). Reports are the exception, kept 30 days.
68
+ - **Little is stored**, and only briefly (see above). Reports are the exception, kept 30 days, and kept chat when
69
+ you turn history on (never on a kids server or a kids Lounge). If you keep chat, say so where people talk (the
70
+ Lounge does, on its page) and keep it as short as is useful.
67
71
  - **You can delete**: a report with Dismiss, a message with Remove, a player's whole account in
68
72
  their own account page (or `players` in the office).
69
73
 
package/lib/build.mjs CHANGED
@@ -53,6 +53,7 @@ import { screenChatProblems } from '../worker/chat-page.mjs';
53
53
  import { vocabularyOf } from '../worker/brain.mjs';
54
54
  import { shopForBuild } from './shop.mjs';
55
55
  import { audienceOf } from '../worker/shop-rules.mjs';
56
+ import { loungeConfig, loungeProblems } from '../worker/lounge-store.mjs';
56
57
 
57
58
  const SOURCE_SKIP = new Set(['node_modules', 'dist', '.git', '.wrangler', '.port']);
58
59
  /** Never copied into a static game's served folder. */
@@ -456,6 +457,9 @@ export async function build(root, { only = null, log = () => {}, deploy = proces
456
457
  writeFileSync(join(dist, '_site', 'posts.json'), `${JSON.stringify({ v: 1, posts })}\n`);
457
458
  const s = studio.site && typeof studio.site === 'object' ? studio.site : {};
458
459
  const studioExtras = schemaExtras(s.schema, { where: 'studio.json site', types: 'Organization', log });
460
+ // The Lounge (0.29.0): studio.json "lounge", checked; off when a game already has /lounge/.
461
+ for (const p of loungeProblems(studio, rows, chatProblems)) log(`warning: studio.json: ${p}`);
462
+ const lounge = loungeConfig(studio, rows, { audience: audienceOf(studio) });
459
463
  const catalogue = {
460
464
  studio: {
461
465
  name: studio.name, slug: studio.slug, version: STUDIO_VERSION,
@@ -481,6 +485,7 @@ export async function build(root, { only = null, log = () => {}, deploy = proces
481
485
  // (this studio takes no referral statements as a referrer).
482
486
  ...(audienceOf(studio) !== 'general' ? { audience: audienceOf(studio) } : {}),
483
487
  ...(studio.referrals === false ? { referrals: false } : {}),
488
+ ...(lounge ? { lounge } : {}),
484
489
  build: buildInfo(root),
485
490
  },
486
491
  games: rows,
@@ -0,0 +1,70 @@
1
+ /**
2
+ * `homie-studio lounge` — the studio's Lounge from the studio folder (worker/lounge.mjs says what it is; chat/LOUNGE.md
3
+ * the whole of it). The office key it uses is `office`'s: minted with the studio's own Cloudflare login, ten minutes
4
+ * long, dropped as soon as the answer is in.
5
+ *
6
+ * homie-studio lounge its rules, play nights, moderators, last lines and reports
7
+ * homie-studio lounge night "<title>" --at <time> [--minutes 120] [--game <id>|<https link>] [--note "…"]
8
+ * a play night; --at is an ISO time with its zone, or UTC (…Z)
9
+ * homie-studio lounge night remove <id>
10
+ * homie-studio lounge history <days> keep what is said for that many days (0 keeps nothing)
11
+ * homie-studio lounge rules [--slow <s>] [--who anyone|signed-in] [--mode …] [--hub on|off] [--ai on|off] | --reset
12
+ * homie-studio lounge mod <player id> [--remove] a moderator: Remove, Mute, Kick (an hour at most), slow mode
13
+ * homie-studio lounge remove <line id>|--all
14
+ *
15
+ * Keeping more (a longer history), opening chat up, a new moderator, a mute or a kick are only ASKED for here: the answer
16
+ * is the owner's one-tap link, as `office kick` gives. A play night, slow mode, taking a message down and keeping less
17
+ * happen at once.
18
+ */
19
+ import { askedFor, withKey } from './office.mjs';
20
+ import { chatFields } from './chat-cli.mjs';
21
+
22
+ export async function loungeShow(root, { url } = {}) {
23
+ const r = await withKey(root, url, (call) => call('/_studio/api/lounge'));
24
+ if (!r.ok) return { ok: false, command: 'lounge', why: r.why ?? r.message ?? 'the office did not answer' };
25
+ return { command: 'lounge', ...r, ok: true };
26
+ }
27
+
28
+ export async function loungeNight(root, args, flags, { url } = {}) {
29
+ if (args[0] === 'remove') {
30
+ if (!/^pn_[a-f0-9]{12}$/.test(String(args[1] ?? ''))) return { ok: false, command: 'lounge night', why: 'usage: homie-studio lounge night remove <id> (the ids are in `homie-studio lounge`)' };
31
+ const r = await withKey(root, url, (call) => call('/_studio/api/lounge/night', { remove: args[1] }));
32
+ return r.ok ? { ok: true, command: 'lounge night', message: 'Play night removed.' } : { ok: false, command: 'lounge night', why: r.message ?? r.why };
33
+ }
34
+ const title = args.join(' ').trim();
35
+ if (!title || !flags.get('at')) return { ok: false, command: 'lounge night', why: 'usage: homie-studio lounge night "<title>" --at 2026-10-09T19:00:00-07:00 [--minutes 120] [--game <id>] [--note "…"]' };
36
+ const body = { title, at: String(flags.get('at')), ...(flags.has('minutes') ? { minutes: Number(flags.get('minutes')) } : {}), ...(flags.has('game') ? { game: String(flags.get('game')) } : {}), ...(flags.has('note') ? { note: String(flags.get('note')) } : {}) };
37
+ const r = await withKey(root, url, (call) => call('/_studio/api/lounge/night', body));
38
+ return r.ok ? { ok: true, command: 'lounge night', id: r.id, message: `Play night added (${r.id}): it shows in the Lounge in everyone's own time.` } : { ok: false, command: 'lounge night', why: r.message ?? r.why };
39
+ }
40
+
41
+ export async function loungeRulesSet(root, flags, { url, history = undefined } = {}) {
42
+ const body = flags.has('reset') ? { reset: true } : { ...chatFields(flags), ...(history !== undefined ? { history: Math.floor(Number(history)) } : flags.has('history') ? { history: Math.floor(Number(flags.get('history'))) } : {}) };
43
+ if (!body.reset && !Object.keys(body).length) return { ok: false, command: 'lounge rules', why: 'say what to change: --history <days>, --slow, --who, --mode, --hub, --ai, --block, --allow; or --reset' };
44
+ const r = await withKey(root, url, (call) => call('/_studio/api/lounge/rules', body));
45
+ return askedFor(root, url, r, 'lounge rules');
46
+ }
47
+
48
+ export async function loungeMod(root, player, { url, remove = false } = {}) {
49
+ if (!/^[A-Za-z0-9_-]{1,64}$/.test(String(player ?? ''))) return { ok: false, command: 'lounge mod', why: 'usage: homie-studio lounge mod <player id> [--remove] (`homie-studio lounge` shows the ids of signed-in senders)' };
50
+ const r = await withKey(root, url, (call) => call('/_studio/api/lounge/mod', { player, ...(remove ? { remove: true } : {}) }));
51
+ return askedFor(root, url, r, 'lounge mod');
52
+ }
53
+
54
+ export async function loungeRemove(root, id, { url, all = false } = {}) {
55
+ if (!id && !all) return { ok: false, command: 'lounge remove', why: 'usage: homie-studio lounge remove <line id> | --all' };
56
+ const r = await withKey(root, url, (call) => call('/_studio/api/lounge/remove', all ? { all: true } : { id }));
57
+ return r.ok ? { ok: true, command: 'lounge remove', message: `Took ${r.removed ?? 0} message${r.removed === 1 ? '' : 's'} down, on every screen.` } : { ok: false, command: 'lounge remove', why: r.message ?? r.why };
58
+ }
59
+
60
+ export function loungeLines(r) {
61
+ const L = r.lounge;
62
+ if (!L) return ['This studio has no Lounge: add "lounge": true to studio.json and deploy.'];
63
+ const c = L.rules ?? {};
64
+ const out = [`${L.name} (${L.page}): ${c.mode}${c.mode === 'text' ? `, typing ${c.who}` : ''}, emoji ${c.react}, slow ${c.slow} s, ${c.history ? `kept ${c.history} days` : 'nothing kept'}${c.hub ? ', shown on homie.rocks' : ''}${L.kids ? ', kids' : ''} · rules from ${L.from === 'office' ? 'the office' : 'the defaults'} · ${L.here} here now`];
65
+ for (const n of L.nights ?? []) out.push(` night ${n.id}: ${n.title}, ${n.at} for ${n.minutes} min${n.game?.name ? ` (${n.game.name})` : ''}`);
66
+ for (const m of L.mods ?? []) out.push(` moderator ${m.player}: ${m.name ?? '?'}`);
67
+ for (const l of (L.lines ?? []).filter((x) => x.kind !== 'react').slice(-12)) out.push(` ${l.id} ${l.by === 'studio' ? 'Studio' : l.name}${l.player ? ` [${l.player}]` : ''}: ${l.card ? `card: ${l.card.title} by ${l.card.studio}` : l.text}`);
68
+ for (const p of L.reports ?? []) out.push(` report ${p.id}: "${p.text}" by ${p.name} (${p.reasonText})`);
69
+ return out;
70
+ }
package/lib/mcp-tools.mjs CHANGED
@@ -49,6 +49,8 @@ export const UI = Object.freeze({
49
49
  ...ART_UI,
50
50
  });
51
51
 
52
+ /** Homie's own updates by email (0.29.0): the sign-up page on homie.rocks, double opt-in, for the first-run card. */
53
+ export const HOMIE_UPDATES = 'https://homie.rocks/updates/?from=plugin';
52
54
  const inside = (base, p) => p === base || p.startsWith(base.endsWith(sep) ? base : `${base}${sep}`);
53
55
  const realOr = (p) => { try { return realpathSync(p); } catch { return resolve(p); } };
54
56
 
@@ -214,6 +216,9 @@ async function setupCard(ctx, root, { fresh = false, made = null, install = null
214
216
  studios, checklist: steps,
215
217
  status: { rows: status.rows.map(({ parts, ...r }) => r), features: status.features, next: status.next, meanwhile: status.meanwhile, blocking: status.blocking },
216
218
  install: job ? jobView(job) : null, made,
219
+ // Homie's own updates by email (0.29.0): a link the person opens and signs up at themselves (double opt-in on
220
+ // homie.rocks). The card never asks for an address, and an agent never types one in for the person.
221
+ updates: HOMIE_UPDATES,
217
222
  };
218
223
  const text = [
219
224
  current ? `Studio: ${current.name} (${root})${current.installed ? '' : job ? ' (installing its toolkit)' : ''}` : `No studio yet${ctx.studiosDir ? ` in ${ctx.studiosDir}` : ''}.`,
@@ -223,6 +228,7 @@ async function setupCard(ctx, root, { fresh = false, made = null, install = null
223
228
  '',
224
229
  formatStatus(status),
225
230
  '',
231
+ `Homie updates by email (optional): ${HOMIE_UPDATES} (the person signs up there themselves; never type an address in for them).`,
226
232
  'The card on screen shows this; it follows the install by itself.',
227
233
  ].join('\n');
228
234
  return ok(text, data);
package/lib/scaffold.mjs CHANGED
@@ -30,6 +30,7 @@ import { OFFICE_MIGRATION, OFFICE_MIGRATION_FILE } from '../worker/office-schema
30
30
  import { SERVERS_MIGRATION, SERVERS_MIGRATION_FILE } from '../worker/servers.mjs';
31
31
  import { CHAT_MIGRATION, CHAT_MIGRATION_FILE } from '../worker/chat-store.mjs';
32
32
  import { SHOP_MIGRATION, SHOP_MIGRATION_FILE } from '../worker/shop-store.mjs';
33
+ import { LOUNGE_MIGRATION, LOUNGE_MIGRATION_FILE } from '../worker/lounge-store.mjs';
33
34
  import { themeFile } from './site.mjs';
34
35
 
35
36
  export const COMPAT_DATE = '2026-06-01';
@@ -652,6 +653,7 @@ export { default, Table, Lobby } from '@homie-rocks/studio/worker';
652
653
  [`site/migrations/${SERVERS_MIGRATION_FILE}`]: SERVERS_MIGRATION,
653
654
  [`site/migrations/${CHAT_MIGRATION_FILE}`]: CHAT_MIGRATION,
654
655
  [`site/migrations/${SHOP_MIGRATION_FILE}`]: SHOP_MIGRATION,
656
+ [`site/migrations/${LOUNGE_MIGRATION_FILE}`]: LOUNGE_MIGRATION,
655
657
  'wrangler.jsonc': wranglerConfig({ worker, name, d1: studio.cloudflare.d1, r2: studio.cloudflare.r2, layout: 'root' }),
656
658
  '.claude/skills/.gitkeep': '',
657
659
  };
@@ -766,13 +768,22 @@ function ensureChatMigration(root) {
766
768
  return `site/migrations/${CHAT_MIGRATION_FILE}`;
767
769
  }
768
770
 
771
+ /** A studio made before 0.29.0 has no Lounge tables: kept chat (only when an owner turns history on), play nights, moderators. */
772
+ function ensureLoungeMigration(root) {
773
+ const file = join(root, 'site', 'migrations', LOUNGE_MIGRATION_FILE);
774
+ if (existsSync(file)) return null;
775
+ mkdirSync(dirname(file), { recursive: true });
776
+ writeFileSync(file, LOUNGE_MIGRATION);
777
+ return `site/migrations/${LOUNGE_MIGRATION_FILE}`;
778
+ }
779
+
769
780
  export function ensureMigrations(root) {
770
- return [ensureStatsMigration(root), ensurePlayersMigration(root), ensureOfficeMigration(root), ensureServersMigration(root), ensureChatMigration(root), ensureShopMigration(root)].filter(Boolean);
781
+ return [ensureStatsMigration(root), ensurePlayersMigration(root), ensureOfficeMigration(root), ensureServersMigration(root), ensureChatMigration(root), ensureShopMigration(root), ensureLoungeMigration(root)].filter(Boolean);
771
782
  }
772
783
 
773
784
  /** What a migration file the template added is for, in a few words (deploy and dev say it). */
774
785
  export function migrationWord(file) {
775
- return /shop/.test(file) ? 'the shop: orders, what players own, refunds and referral books (it sells nothing until shop.json and the owner\'s Stripe key are in)' : /players/.test(file) ? 'player accounts and cloud saves' : /chat/.test(file) ? 'room chat: the owner\'s chat rules and players\' reports (never the chat itself)' : /servers/.test(file) ? 'servers and agent seats: room pools with their own rules, AI passes' : /office/.test(file) ? 'the back office: launch states, invites, the owner\'s controls' : 'the studio\'s own stats: counts, never tracks';
786
+ return /lounge/.test(file) ? 'the Lounge and kept chat: play nights, moderators, and what was said only where an owner turns history on' : /shop/.test(file) ? 'the shop: orders, what players own, refunds and referral books (it sells nothing until shop.json and the owner\'s Stripe key are in)' : /players/.test(file) ? 'player accounts and cloud saves' : /chat/.test(file) ? 'room chat: the owner\'s chat rules and players\' reports (never the chat itself)' : /servers/.test(file) ? 'servers and agent seats: room pools with their own rules, AI passes' : /office/.test(file) ? 'the back office: launch states, invites, the owner\'s controls' : 'the studio\'s own stats: counts, never tracks';
776
787
  }
777
788
 
778
789
  /**
@@ -1391,6 +1391,46 @@
1391
1391
  }
1392
1392
  }
1393
1393
  }
1394
+ },
1395
+ "0.29.0": {
1396
+ "files": {
1397
+ "CLAUDE.md": "ddf2b2a0376202f6",
1398
+ "README.md": "684b3cca8001673c",
1399
+ "HANDOFF.md": "ce7a8fd91c6bbee2",
1400
+ "games/README.md": "5ad32fe80c61f117",
1401
+ "music/README.md": "a3dc042dadd39df8",
1402
+ "videos/README.md": "16babc3437d5545b",
1403
+ "posts/README.md": "37841aae476bd540",
1404
+ "site/README.md": "2928ca4f079ab76b",
1405
+ "site/src/worker.mjs": "ca3f1630f2420ad3"
1406
+ },
1407
+ "sections": {
1408
+ "AGENTS.md": {
1409
+ "(intro)": "214aa46eb3a895d5",
1410
+ "## Layout": "8baf49e4291e7e88",
1411
+ "## Commands (all through the pinned CLI in node_modules)": "d80ec56e4b4fc4c6",
1412
+ "## The site": "366ecce1a424837a",
1413
+ "## Making games": "abf2f0d59341512d",
1414
+ "## The Game Codex and progress": "0738ef719cef3a4c",
1415
+ "## Art direction and models": "aaea65236db52126",
1416
+ "## Running live games (the back office)": "4aa5f513a7cd2c3b",
1417
+ "## Servers and AI seats": "98d08705087a33ec",
1418
+ "## Selling things (the shop)": "b04b0a2b25f524f2",
1419
+ "## Continuing a build from the Claude app": "0d68081e924ca419",
1420
+ "## Rules": "7e8458adc1b81ece",
1421
+ "## Telling Homie": "80b46fdd9e578db8",
1422
+ "## Beta": "9d02638207a26e73"
1423
+ }
1424
+ },
1425
+ "named": {
1426
+ "Homie Arcade": {
1427
+ "sections": {
1428
+ "AGENTS.md": {
1429
+ "## Commands (all through the pinned CLI in node_modules)": "b35df2c19cd4b534"
1430
+ }
1431
+ }
1432
+ }
1433
+ }
1394
1434
  }
1395
1435
  }
1396
1436
  }
package/mcp/ui/setup.js CHANGED
@@ -60,6 +60,8 @@
60
60
  acts.appendChild(C.btn('Check again', 'ghost small', function () {
61
61
  C.call('setup_status', { fresh: true }).then(function (r) { C.take(r); }).catch(function () {});
62
62
  }));
63
+ // Homie's own updates by email: the person signs up on homie.rocks (double opt-in); the card asks for nothing.
64
+ if (sc.updates) acts.appendChild(C.btn('Get Homie updates', 'ghost small', function () { C.open(sc.updates); }));
63
65
  foot.appendChild(acts);
64
66
  card.appendChild(foot);
65
67
  if (install && install.state === 'done') C.tell('install-' + install.job, (cur ? cur.name : 'The studio') + '\'s toolkit finished installing; the studio is ready to build.');
@@ -1230,6 +1230,7 @@ every watching shell. homie.rocks runs nothing: its room page opens the room's o
1230
1230
  | `swears` | `block`, `allow` (`block`) | Slurs, sexual words, threats and contact details are held whatever this says. |
1231
1231
  | `ai` | `true`, `false` (`true`) | The studio's review of typed text. |
1232
1232
  | `bubbles`, `watchers`, `hub` | `true` | A line may show over its sender's character; watchers may send (not only read); homie.rocks's page for the room may show it. |
1233
+ | `history` | 0–90 days (0) | 0.29.0: keep what is said (never a reaction) in the studio's D1 for that many days, and give it back as the window when a page opens; 0 keeps nothing past the window. A kids server, a capped room or chat `off` keeps 0. |
1233
1234
  | `emoji` | the five, then up to 3 of the game's own | `{ k, e }`: a kind and its glyph. |
1234
1235
  | `lines` | 8 default lines, or up to 12 of the game's own | `{ id, text }`: the game's words; each passes the floor at build. |
1235
1236
  | `capped` | `kids`, `server-lines`, `server-off` | Why the mode is lower than the game asks. |
@@ -1261,6 +1262,12 @@ stays in the room; clients get the rest in `welcome.policy.chat` and every `poli
1261
1262
  | `lines` | down, a watching shell as it opens | `lines` | The window: the last 50 lines of the last 15 minutes. Never to a page the room holds out. |
1262
1263
  | `unline` | down, everyone | `ids` | The owner took them down: hide them, and their bubbles. |
1263
1264
  | `held` / `slow` | down, the sender | `why`, `message`, `n?`, `until?` | Not sent. `why`: `off`, `emoji`, `lines`, `sign-in`, `sign-in-react`, `members`, `members-react`, `watchers`, `hub`, `muted`, `slow`, `repeat`, `words`, `harm`, `contact`, `link`, `swears`, `ai`, `busy`, `unknown`, `empty`, `ai_seat`. |
1265
+ | `unsay` | up, a watching shell | `id` | 0.29.0: take my own line down (a line of this browser, or of this signed-in account). Everyone gets `unline`. |
1266
+
1267
+ 0.29.0 adds to `line`: `card` (`{ url, title, studio, pitch, image }`, a "show what you made" card the Worker read
1268
+ from a Homie studio's manifest; `text` stays a plain fallback such as "Made: <title> by <studio>"), `mod` (a moderator
1269
+ the owner named), `kept` (it came from the room's history) and, on a page's own copy of the window only, `mine` (this
1270
+ browser's or account's own line). A page that knows none of these shows the line's `text`.
1264
1271
 
1265
1272
  A line's sender is the socket's own client, or for a watch socket the client of the same browser
1266
1273
  (its room key) or account in the room: its seat, name and colour. A page with no client in the
@@ -1361,7 +1368,10 @@ ticker.
1361
1368
 
1362
1369
  **Privacy.** Nothing a person says is stored: the room keeps the last 50 lines of the last 15
1363
1370
  minutes in its memory (never in Durable Object storage), and an empty room forgets them with
1364
- everything else a minute after its last person leaves. The counts (lines, reactions, held by why,
1371
+ everything else a minute after its last person leaves. Only where the owner turns `history` on (0.29.0; off by
1372
+ default for every game and the Lounge, never on a kids server) does the Table keep what was said in D1
1373
+ (`chat_history`: the words, the name shown, when, a signed-in sender's account id; never a reaction, an address or a
1374
+ browser key) for that many days; what is taken down there is deleted there, and a deleted account takes its lines. The counts (lines, reactions, held by why,
1365
1375
  reviews and neurons) are daily numbers in the studio's own D1, never a message or a sender. A
1366
1376
  report keeps one message for 30 days. The review sends the message's words (and nothing about who
1367
1377
  said it) to the studio's own Workers AI; Cloudflare says it does not keep or train on them.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homie-rocks/studio",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "description": "A studio for games, music and video on its own Cloudflare: scaffold, multiplayer starters on the netplay contract, a port toolkit that makes existing single-player web games multiplayer, the site Worker with public rooms (Table/Lobby Durable Objects and D1, all on the free Workers plan, no payment method), a site with the hub's sections (Home, Games, Music, Videos, Rooms, Posts with feeds) in the studio's own look, an epic landing page for every game, big songs and videos in the studio's own R2 once it has storage (uploaded, checked by SHA-256, served at the same addresses), deploy, and the homie.rocks directory.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
package/site/SITE.md CHANGED
@@ -13,6 +13,7 @@ and every game gets a landing page. Anything the studio puts in `site/` wins.
13
13
  | `/music/`, `/videos/` | Songs and videos (`media/MEDIA.md`) | the studio has one published |
14
14
  | `/rooms/` | Every public room playing now across the studio's games, each with Join (`/api/rooms` is the same as JSON, cacheable for 15 s) | the studio has a game |
15
15
  | `/posts/` | The studio's posts, newest first, with `/posts/feed.xml` (Atom) and `/posts/feed.json` (JSON Feed) | the studio has a post |
16
+ | `/lounge/` | The Lounge (0.29.0): the studio's community room, with play nights, live rooms and "show what you made" (`chat/LOUNGE.md`) | studio.json has `"lounge"` |
16
17
 
17
18
  A section with nothing in it has no tab, and its address answers 404. Every page ends with a
18
19
  "Made with Homie" link to https://homie.rocks/studio/. studio.json may add `"tagline"` (one line, on Home and in
@@ -35,6 +36,18 @@ the hub offers Watch beside Join. From 0.23.0 a room whose chat is on and whose
35
36
  socket from the visitor's browser and shows its lines and reactions live (NETPLAY.md section 19). A visitor there
36
37
  sends what a watcher may send (reactions and quick lines when the room's `react` is `anyone`), never types.
37
38
 
39
+ ## The Lounge: `/lounge/` (0.29.0)
40
+
41
+ With studio.json `"lounge": true` (or `{ "name", "tab", "blurb", "featured", "kids", "chat" }`), `/lounge/` is the
42
+ studio's community room (worker/lounge.mjs, lounge-page.mjs; chat/LOUNGE.md): room chat in a room of its own, in the
43
+ studio's look, one column on a phone ("Chat" and "What's on") and the talk beside a sidebar on a computer. Its script
44
+ is `/_homie/lounge.js`; its socket is `/lounge/__watch` (a page of this site carries its signed-in account; a page of
45
+ another site is a watcher that reacts, never types). `/lounge/api/now` is its public facts as JSON (rules, play
46
+ nights, live rooms; CORS open, cacheable for 30 s), and the manifest names it (`lounge: { name, page, now }`).
47
+ `/lounge/api/history`, `show`, `report`, `delete` and `mod` are this site's pages' only. The owner's tools are
48
+ `/_studio/api/lounge` and `/_studio/api/lounge/rules`, `night`, `mod`, `remove` and `hold`. A game called `lounge`
49
+ keeps its page, and the build says the Lounge is off.
50
+
38
51
  ## Room chat on the play, watch and TV pages
39
52
 
40
53
  Every play page, big screen and watch page carries room chat (worker/chat-page.mjs; chat/CHAT.md): the Chat pill