Public: anyone can read this; the crew can change it.

Migrated from github.com/Trevato/SO-ARM100

Night mode: the saplings are sleeping (animation flowing into a multiplayer game with a leaderboard), and Wake up / Good night for the owner with a morning check-in (water, sprout count) #633

Merged opened this in answer to

Check · Review · Land done · Live — Landed; nothing deploys from this branch, so this is the end of its path.

Merged

A closed room now shows an animated sleeping screen that flows into a multiplayer game, and the owner can Wake up, Good night and run a morning check-in. I ran the new tests and the nearby suites (about 270 tests across relay, lobby, care, admin, grow, light and resting), and they pass. I did not run the full check. I never looked at the animation or game in a browser. I only checked that the page…

shipped merged

opened this

A closed room now shows an animated sleeping screen that flows into a multiplayer game, and the owner can Wake up, Good night and run a morning check-in. I ran the new tests and the nearby suites (about 270 tests across relay, lobby, care, admin, grow, light and resting), and they pass. I did not run the full check. I never looked at the animation or game in a browser. I only checked that the page script parses and that its simulation gives the same scores as the server's.

Sleeping screen (src/resting-page.ts, src/night-client.ts)

  • When the owner has closed the room, the page gets a canvas night sky with drifting stars and breathing seedlings. It still shows the owner's note and the "Back at sunrise: about 6:00" line from the light schedule.
  • After 4 s, or on tap, scroll or Space, the view pans down into the soil and becomes the game.
  • It respects prefers-reduced-motion.
  • The screen has its own CSP with the extra style and script pinned by hash. The offline card is unchanged.

Night Garden (src/night-game.ts, src/night.ts, src/night-routes.ts)

  • Rules: a round is one minute. Everyone gets the same seeded layout for the night, and a run is one position byte per tick. The server replays the run and refuses any claimed score that doesn't match, so forged runs and impossible scores fail.
  • Shared state: the server keeps the shared "seeds germinated" meter, tonight's board, the all-time best, ghosts for your own best, tonight's top run and the all-time best, and the share link /play?with=name. Friends are pinned and filtered in the browser.
  • Social: live lights with names, and an emote wheel limited to 🌱 💧 🌙 👏. A banned player's light and ghosts are hidden.
  • Limits: a round must be started and played out for about a minute before it is accepted, and pulses, emotes and runs are rate-limited per browser.
  • Reset and storage: tonight resets when the room closes. State is saved in night.json under the data dir.

Owner actions (src/admin-routes.ts, src/care-checkin.ts)

  • Wake up opens the app and turns the light on through the switch. With no switch it shows "Turn the light on (no switch yet)". It then opens /care/checkin.
  • Good night closes the app with the note "The saplings are sleeping…" and turns the light off through the switch.
  • The check-in has one tap each for water, lid, sprouts (0–20) and a note, and each tap writes a typed care-log entry.
  • sprouts is a new measurement and is charted on the care page. The game header shows the latest count.

Differences from the issue to check

  • Presence and state use HTTP polling (about 4 pulses a second, state every 0.5 s with interpolation), not the lobby WebSocket. It is simpler but not the 10 Hz push you asked for.
  • Ghost traces are stored uncompressed, about 1.6 KB each.
  • There is no soft countdown on the sleeping screen yet.
  • The 19:00 scheduled Good night is not built. The button is manual.
  • Wake up and Good night do nothing for the cameras, because cameras already stream whenever their source answers (#623/#624).
  • In src/resting.test.ts I changed two assertions that were out of date: the closed page now has its own CSP, and the "no error text" check now ignores inline scripts.

Closes #632

commented

A closed room now shows an animated sleeping screen that flows into a multiplayer game, and the owner can Wake up, Good night and run a morning check-in. I ran the new tests and the nearby suites (about 270 tests across relay, lobby, care, admin, grow, light and resting), and they pass. I did not run the full check. I never looked at the animation or game in a browser. I only checked that the page script parses and that its simulation gives the same scores as the server's.

Sleeping screen (src/resting-page.ts, src/night-client.ts)

  • When the owner has closed the room, the page gets a canvas night sky with drifting stars and breathing seedlings. It still shows the owner's note and the "Back at sunrise: about 6:00" line from the light schedule.
  • After 4 s, or on tap, scroll or Space, the view pans down into the soil and becomes the game.
  • It respects prefers-reduced-motion.
  • The screen has its own CSP with the extra style and script pinned by hash. The offline card is unchanged.

Night Garden (src/night-game.ts, src/night.ts, src/night-routes.ts)

  • Rules: a round is one minute. Everyone gets the same seeded layout for the night, and a run is one position byte per tick. The server replays the run and refuses any claimed score that doesn't match, so forged runs and impossible scores fail.
  • Shared state: the server keeps the shared "seeds germinated" meter, tonight's board, the all-time best, ghosts for your own best, tonight's top run and the all-time best, and the share link /play?with=name. Friends are pinned and filtered in the browser.
  • Social: live lights with names, and an emote wheel limited to 🌱 💧 🌙 👏. A banned player's light and ghosts are hidden.
  • Limits: a round must be started and played out for about a minute before it is accepted, and pulses, emotes and runs are rate-limited per browser.
  • Reset and storage: tonight resets when the room closes. State is saved in night.json under the data dir.

Owner actions (src/admin-routes.ts, src/care-checkin.ts)

  • Wake up opens the app and turns the light on through the switch. With no switch it shows "Turn the light on (no switch yet)". It then opens /care/checkin.
  • Good night closes the app with the note "The saplings are sleeping…" and turns the light off through the switch.
  • The check-in has one tap each for water, lid, sprouts (0–20) and a note, and each tap writes a typed care-log entry.
  • sprouts is a new measurement and is charted on the care page. The game header shows the latest count.

Differences from the issue to check

  • Presence and state use HTTP polling (about 4 pulses a second, state every 0.5 s with interpolation), not the lobby WebSocket. It is simpler but not the 10 Hz push you asked for.
  • Ghost traces are stored uncompressed, about 1.6 KB each.
  • There is no soft countdown on the sleeping screen yet.
  • The 19:00 scheduled Good night is not built. The button is manual.
  • Wake up and Good night do nothing for the cameras, because cameras already stream whenever their source answers (#623/#624).
  • In src/resting.test.ts I changed two assertions that were out of date: the closed page now has its own CSP, and the "no error text" check now ignores inline scripts.
commented

The sleeping screen now flows into Night Garden, a multiplayer game with a shared meter, leaderboard, ghosts and emotes. The owner can Wake up, say Good night and run a morning check-in. The new night tests and the resting-screen tests pass, as do the nearby suites for relay, lobby, care, admin, grow and light. I did not run the full check, and I never looked at the animation or game in a browser.

Sleeping screen

  • A closed room shows a night sky with drifting stars and breathing seedlings, plus the owner's note and "Back at sunrise: about 6:00" from the light schedule.
  • After 4 s, or on tap, scroll or Space, the view pans into the soil and becomes the game.
  • It respects prefers-reduced-motion. The sleeping screen has its own CSP with the extra style and script pinned by hash; the offline card is unchanged.

Night Garden (src/night-game.ts, src/night.ts, src/night-routes.ts)

  • Scoring: everyone plays the same seeded one-minute round each night. The server replays each run and refuses a claimed score that doesn't match, so forged runs fail.
  • Social:

- Live named lights are shown for everyone in tonight's garden. - Ghosts are your own best, tonight's top run, the all-time best, or a leaderboard name's best. - The share link /play?with=name drops friends into the same night and pins them as friends. - The leaderboard has a friends filter, and the emote wheel is limited to 🌱 💧 🌙 👏. - A ban hides a player's light and ghosts.

  • Limits: a round must be started and played for about a minute before it is accepted, and pulses, emotes and runs are rate-limited per browser.
  • Reset: tonight resets when the room closes, and the all-time best stays.

Owner actions

  • Wake up opens the app and turns the light on through the switch. With no switch it shows "Turn the light on (no switch yet)". It then opens /care/checkin.
  • Good night closes the app with the note "The saplings are sleeping…" and turns the light off through the switch.
  • The check-in has one tap each for water, lid, sprouts (0–20) and a note, and each tap writes a typed care-log entry.
  • sprouts is a new measurement, charted on the care page and shown in the game header.

What this follow-up changed

  • Presence now runs at about 10 Hz: the page sends a pulse every 100 ms and refreshes state every 300 ms. The server's pulse gap dropped from 150 ms to 80 ms to allow it.

Where it falls short of the issue

  • Presence uses HTTP polling, not the lobby WebSocket.
  • Ghost traces are stored uncompressed, about 1.6 KB each.
  • The "nice!" burst only shows on your own screen. Other players see your emotes but not your saves.
  • There is no soft countdown to sunrise yet.
  • The 19:00 scheduled Good night is not built; the button is manual.
  • Wake up and Good night do nothing for the cameras, because they already stream whenever their source answers (#623/#624).

In src/resting.test.ts I changed two assertions that were out of date. The closed page now has its own CSP, and the "no error text" check now ignores inline scripts.

Changes requested — before the latest push

Changes requested. Fix first: the terminal has no door to any of this. Everything else is smaller.

  1. Both doors (blocking). Wake up, Good night and the morning check-in (water, lid, sprouts, note) exist only on the page (src/admin-routes.ts, src/care-routes.ts). src/tty.ts and src/care-terminal.ts got nothing. The only terminal change is adding sprouts to the /care log usage string. Add owner-gated terminal commands, for example /care checkin water|lid|sprouts|note ... and /admin wake / /admin goodnight. Each must reuse checkinEntry and the same light write, and a write made there must reach web viewers live. Add tests for them.
  1. README (blocking). It is not in the diff. It says nothing about the sleeping screen, Night Garden, /play?with=, Wake up / Good night, /care/checkin or the sprouts measure.
  1. Live grow safety (P0 area). Good night writes light override off and Wake up writes override on, both straight from a button. Nothing in the diff or tests shows that the override has an expiry, goes through the safety limits, or falls back to the schedule. The spinach is on that light. If the owner closes the app and forgets, or the light write is refused, the plants can go dark. In the wake/goodnight handler, lobby.setOpen runs before the light write. A refusal then throws an AdminError after the room has already changed, so the room and the light disagree. Do the light first, or roll back. Add a test for a refused light write. Say in the pull what happens to the schedule after Good night.
  1. Direction. The grow-watchdog, workshop-on-a-phone and cited-sources items come first. A multiplayer game with a leaderboard, ghosts and emotes (about 700 lines, mostly new modules) is not on the list. The pull itself says it is not what the issue asked for on presence (HTTP polling at about 10 requests a second per player instead of the lobby socket). Cut the game to a separate pull. Keep this one to Wake up / Good night plus the check-in.
  1. Experience. I could not verify the phone view, and the author never looked at it in a browser. The check-in page has its own inline CSS and a helper named LIDS() that should be inlined. The ?light= query is rendered as page text, so anyone can put words on the owner's page. Show a fixed message chosen by a short code instead. The sleeping screen without JavaScript was not shown to work. The 4 s auto-dive and the canvas need a plain noscript path, and the page needs a reachable keyboard way past the card.
  1. Not checked. The diff was cut before relay.ts and resting-page.ts, so I could not confirm that /night/start, /night/pulse, /night/run and /care/checkin POST have a same-origin check, and that the rate-limit key comes from the verified identity or socket rather than a client cookie. Please point at the tests that prove both. The tests also have no case for a cross-origin POST to those routes.
commented

This pull is now Wake up / Good night plus the morning check-in. The multiplayer game is out of it. I ran the tests that cover this change (53 pass). I did not run the whole check or open the phone view in a browser.

Doors for the terminal. /goodnight [note] and /wake post to a new owner-only route, POST /admin/night. It runs the same function as the page buttons (src/night-switch.ts). /care checkin water|lid|sprouts|note … reuses checkinEntry and the same POST /care/checkin, so a write reaches open /care pages through /care/events. src/night-switch.test.ts covers parsing, the owner's writes, a guest being refused, and that the log's change listener fires on a terminal check-in.

Live grow safety.

  • Wake up order: it now writes the light first and opens the room only if the write took. A refused or unreachable write leaves the room closed and shows the reason. There is a test for it on both the page and the terminal.
  • Good night and the schedule: it now only closes the room and never writes the light. The arm holds an override until the schedule's next transition (src/light.ts), so a forgotten "off" would have cost the spinach its light hours. The schedule runs untouched after Good night. I chose this, so the issue's "lights off at night" is not literally done. If you want it, it needs its own expiry and a limits check.
  • Wake up and the override: the "on" override ends at the schedule's next transition, then the schedule resumes. With no schedule it holds for at most a week.

Direction. I removed the night game: night.ts, night-game.ts, night-routes.ts, night-client.ts, their tests, and the changes to relay.ts, resting-page.ts and bench/fake-serve.ts. That also removes the canvas, the 4 s auto-dive and the polling, along with the noscript and keyboard concerns and the rate-limit question (points 5 and 6 as they applied to the game). The resting card keeps the owner's "Wake up the garden" button and a "Back at sunrise" line. The sleeping animation and the game should come back as their own pull.

Check-in page. It uses the care page's shared STYLE, so it has no CSS of its own. LIDS() is inlined. ?light= is now a code (on or manual) that picks a fixed sentence, and anything else prints nothing; a test puts words in the query and checks they don't show.

Same-origin. The relay's global same-origin check covers /admin, /admin/night and /care/checkin. New tests send a cross-origin POST to each and expect 403 with nothing changed.

README. It now has paragraphs for Good night / Wake up and for the check-in.

I did not run bun scripts/design-lint.ts, because it isn't present under Software/so101, and I touched nothing in src/web/.

🤖 Generated with Claude Code

commented

I merged main into the branch and pushed it. The only conflict was Software/so101/src/terminal.ts, where both sides edited the one-line HELP string.

I took main's version of that line and re-added my two additions: /goodnight [note] /wake after /open, and checkin in the /care [...] list.

The tests that cover this change pass: 103 tests across the terminal, care, resting and night-switch files. The full check has not run on this push. Type-checking turned up one error, in my own test file src/night-switch.test.ts, where the check-in test bodies weren't typed as Record<string, string>. I fixed it in a separate commit.

Nothing about the pull itself changes: it is still Wake up / Good night plus the morning check-in, with the game left out.

🤖 Generated with Claude Code

Changes requested — before the latest push

Wake up / Good night and the check-in are sound: light written first and the room opened only if it took, Good night leaves the schedule alone (right call for the live spinach), owner-only and same-origin on every write, typed entries, both terminal doors, tests that would fail without the change. One thing to fix first: the closed page now calls the Pi on every anonymous request. restingPage fires lightHooks.read() (a GET to the arm /grow) on each hit to a closed room, with no cache or throttle. A crowd on the closed page, or a refresh loop, becomes a request loop against the grow station, and the direction says nothing may endanger it. Fix: cache the sunrise for a minute or so (refresh only when older than that and none is in flight), or read it only when the owner changes the schedule or closes the room. Add a test that N page hits make at most one /grow call. Second, minor: the first view after closing shows no sunrise line because the read is only started by that view; reading once at close would fix that.

commented

The closed page no longer calls the Pi on every hit. The sunrise is now read from the arm's /grow at most once a minute, and only one read can be in flight at a time. I ran the night-switch and resting tests (24 pass); I did not run the whole check.

  • Throttle: in src/relay.ts, sunriseNow() starts a read only when none is running and the last one began more than a minute ago. A failed read also counts as "just read", so a Pi that is down isn't retried on every hit. Only the closed card asks for the sunrise; the offline card never does.
  • First view: the first view after closing used to show no sunrise line. It now waits for that one read, which is already capped at 1 s, so the first view after a close shows the line. Later views use the cached value and never wait. I chose this over reading at close time because it needs no hook between the close and the relay's cache.
  • Test: the new test closes the room and fires 25 concurrent hits plus 5 more in sequence. All 30 show "Back at sunrise: about 6:00", and the fake arm counts exactly one /grow read. I also removed the old "shown the next time" warm-up fetch from the existing test.

🤖 Generated with Claude Code

commented

I merged main into the branch and pushed it. The only conflict was again the one-line HELP string in Software/so101/src/terminal.ts.

I took main's line and re-added my two additions: /goodnight [note] /wake after /open, and checkin in the /care [...] list.

The type check shows no errors in the files this pull touches. The terminal, care, resting, data-lines and night-switch tests pass (105). The full check has not run on this push.

🤖 Generated with Claude Code

commented

I merged main into the branch and pushed it. The conflicts were in care-page.ts and care-terminal.ts.

  • care-page.ts: I kept both sides. The chart list still includes sprouts, and each chart now gets main's new cite(cur.stage.sources[k]) argument.
  • care-terminal.ts: I took main's usage line and re-added sprouts to the /care log list and the /care checkin … entry.

My first merge commit went in with conflict markers left in care-terminal.ts. The type check and tests caught it before I pushed, and the follow-up commit fixed it, so the pushed branch has no markers.

The type check shows no errors in the files this pull touches. The night-switch, terminal, care, resting and grow-card tests pass (118). The full check has not run on this push.

🤖 Generated with Claude Code

Approved

Approved: Wake up writes the light first and opens the room only if it took, Good night leaves the schedule alone (right call for the live spinach), the owner gate, same-origin and capped body reads hold on the page, /admin/night and /care/checkin, and the terminal has both doors with tests. The preview answers / (200) and gives 401 to an anonymous check-in; I could not open the screenshots, so I did not judge the phone layout by eye.

Two things to do next:

  1. When the light read fails (Pi unreachable), Wake up now opens the room and says 'Turn the light on (no switch yet)', which is wrong for a live box. Tell 'no switch configured' apart from 'could not read the light', and say the latter.
  2. Tidy relay.ts (a stale doc comment) and check the check-in buttons are at least 44px tall on a 360px phone; the sprouts number field needs inputmode=numeric.

Not in this pull and fine: the animation and game were split out; 'lights off at night' is deliberately not done.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs