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

Migrated from github.com/Trevato/SO-ARM100

Grow box: a still every 10 min, a scrubbable timelapse, a grow log and a canopy chart #601

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

The grow card now has a still every 10 minutes, a timelapse you can scrub, a grow log and a canopy chart. The new tests and the neighbouring stations, cameras, readme and fake-serve suites pass (41 tests). The browser parts have not been run in a real browser. `tsc` still reports one error in `src/ui/grow-hint.js` that was there before this change.

shipped merged

opened this

The grow card now has a still every 10 minutes, a timelapse you can scrub, a grow log and a canopy chart. The new tests and the neighbouring stations, cameras, readme and fake-serve suites pass (41 tests). The browser parts have not been run in a real browser. tsc still reports one error in src/ui/grow-hint.js that was there before this change.

  • Stills: the hosted app takes one from the grow station's first camera every 10 minutes. GROW_STILL_MINUTES sets the interval (1–1440). They go under FORGE_APP_DATA/grow-stills and are kept 30 days, or until the store passes GROW_STILLS_CAP_MB (default 500), oldest first. The store size and cap show on the card. A still taken while the light is off is kept but flagged dark, and the timelapse leaves it out.
  • Timelapse: the card plays today, 7 days or all, with a scrubber and a play button. It is built in the browser from the stills, with no video encode. The routes are GET /grow/stills (paged), GET /grow/stills/<ms>.jpg and GET /grow/log, which anyone can read.
  • Log: the card shows the planted date, day N, the owner's notes and light hours per day from the light's transitions. The owner changes the planted date and notes with POST /grow/log.
  • Canopy: the chart is labelled "canopy (green area)" and is the green-pixel share inside the region drawn in Tune, averaged per day.
  • Tests:

- Retention removes the oldest past the cap. - Green fraction of a fixture image. - /grow/stills paging. - A relay test that takes stills from a fake camera, pages them and checks the card.

What to look at:

  • Where the stills live: this app is the relay on the forge, not the Pi, so the stills are stored under FORGE_APP_DATA here. The issue says "on the Pi", and I haven't moved them there.
  • Canopy is measured by the owner's browser: there is no JPEG-to-pixels decoder on the server, so the owner's browser works out the green fraction for up to 40 unread stills per visit and sends it back with POST /grow/canopy. Until the owner has opened the card, the chart stays empty.
  • Light-hours history: the Pi keeps the last 48 light transitions, so light hours per day cover about a month at most.
  • Wiring: startRelay takes a grow option, set up in bench/fake-serve.ts. The code is in src/grow-stills.ts, src/grow-log.ts, src/ui/grow-canopy.js and src/ui/grow-timelapse.js. The README has a new section.

The browser module src/ui/grow-timelapse.js has @ts-nocheck because the project has no DOM lib. Its pure helpers are covered by tests.

🤖 Generated with Claude Code

Closes #583

commented

The grow card now has a still every 10 minutes, a timelapse you can scrub, a grow log and a canopy chart. The new tests and the neighbouring stations, cameras, readme and fake-serve suites pass (41 tests). The browser parts have not been run in a real browser. tsc still reports one error in src/ui/grow-hint.js that was there before this change.

  • Stills: the hosted app takes one from the grow station's first camera every 10 minutes. GROW_STILL_MINUTES sets the interval (1–1440). They go under FORGE_APP_DATA/grow-stills and are kept 30 days, or until the store passes GROW_STILLS_CAP_MB (default 500), oldest first. The store size and cap show on the card. A still taken while the light is off is kept but flagged dark, and the timelapse leaves it out.
  • Timelapse: the card plays today, 7 days or all, with a scrubber and a play button. It is built in the browser from the stills, with no video encode. The routes are GET /grow/stills (paged), GET /grow/stills/<ms>.jpg and GET /grow/log, which anyone can read.
  • Log: the card shows the planted date, day N, the owner's notes and light hours per day from the light's transitions. The owner changes the planted date and notes with POST /grow/log.
  • Canopy: the chart is labelled "canopy (green area)" and is the green-pixel share inside the region drawn in Tune, averaged per day.
  • Tests:

- Retention removes the oldest past the cap. - Green fraction of a fixture image. - /grow/stills paging. - A relay test that takes stills from a fake camera, pages them and checks the card.

What to look at:

  • Where the stills live: this app is the relay on the forge, not the Pi, so the stills are stored under FORGE_APP_DATA here. The issue says "on the Pi", and I haven't moved them there.
  • Canopy is measured by the owner's browser: there is no JPEG-to-pixels decoder on the server, so the owner's browser works out the green fraction for up to 40 unread stills per visit and sends it back with POST /grow/canopy. Until the owner has opened the card, the chart stays empty.
  • Light-hours history: the Pi keeps the last 48 light transitions, so light hours per day cover about a month at most.
  • Wiring: startRelay takes a grow option, set up in bench/fake-serve.ts. The code is in src/grow-stills.ts, src/grow-log.ts, src/ui/grow-canopy.js and src/ui/grow-timelapse.js. The README has a new section.

The browser module src/ui/grow-timelapse.js has @ts-nocheck because the project has no DOM lib. Its pure helpers are covered by tests.

🤖 Generated with Claude Code

Changes requested — before the latest push

Good shape: small modules, zero dependencies, bounded inputs, atomic writes, and the preview answers /grow/log. Three misses against the list.

  1. FIRST: the writes skip the same-origin check. growRoutes runs before the !reading && !sameOrigin(req) gate (relay.ts ~956), so POST /grow/log and /grow/canopy are not checked. Call sameOrigin(req) in the POST branch, or move the dispatch below that gate. Add a test that a cross-site POST (sec-fetch-site: cross-site) gets 403.
  2. Both doors: nothing in src/tty.ts or src/terminal.ts. From the terminal you can neither read the stills and log (count, day N, light hours, canopy) nor set the planted date or add a note. Add /grow log to read it and /grow planted <date> and /grow note <text> (owner) to write, through the same routes, and describe them in the README. A note written there must reach the open page, as on the web.
  3. Tests: the owner gate is untested. grow-card.test.ts runs with no lobby, so every viewer is the owner. Add a test with a lobby: a guest gets 403 on both POSTs and a guest GET /grow/log shows no write form data. Also missing: the dark flag when the light is off (takeStill), and the 8 MB / non-ok snapshot refusal.

Experience notes (fix with the above, not blocking alone):

  • With JavaScript off the grow card is an empty div. Server-render at least the latest still as an <img> and a link to the log, with a <noscript> line.
  • The card is filled in after load, so the layout shifts. Reserve the box (aspect-ratio 4/3 on the lapse, min-height on .growlog) and add max-width:100%;height:auto for .grow img so the 640-wide image cannot overflow a 360px phone.
  • The date input has no 44px target. growCard swallows every failure (.catch(() => {}), unchecked send() results), so a failed save says nothing. Say "Could not save: …" in an aria-live region.
  • Canopy is empty until the owner opens the card; the README should say so. The stills sit on the forge, not the Pi as the issue says; the pull discloses this, so I accept it.
  • Clean: duplicate JSDoc lines in grow-timelapse.js (rangeStart, load); a stray ts-nocheck justification is fine.
commented

I've addressed all of juniper's points except item 1. The POST writes were already checked for same-origin, so I only added the test. Pushed; the grow, stations and terminal suites pass, and tsc shows only the existing error in grow-hint.js.

  • Same-origin check: growRoutes is called after the !reading && !sameOrigin(req) gate at the top of the request handler (src/relay.ts), so both POSTs were already covered. No code change. I added the test you asked for: a sec-fetch-site: cross-site POST to /grow/log and to /grow/canopy gets 403, even from the owner.
  • Terminal: /grow log shows day N, the stills store, light hours, canopy and the latest notes. The owner has /grow planted <YYYY-MM-DD>|none and /grow note <text>. They go through the same routes (growLogReader in src/terminal.ts, wired in src/tty.ts, described in the README and the help text). A note written there shows on the open card the next time it loads.
  • Tests:

- With a lobby, a guest gets 403 on both POSTs, and a guest can still read /grow/log and the card without the owner's data. - A still taken while the light is off is flagged dark and left out of the default page. - A 503 snapshot or one over 8 MB is not kept. - The terminal read, write and refusal lines.

  • Experience notes:

- The card now shows the latest still and a link to /grow/log without JavaScript, plus a <noscript> line. - The card reserves its space, .grow img can't overflow a narrow phone, and the date input is 44px tall. - A failed save says "Could not save: …" in an aria-live line, and a card that fails to load says so. - The duplicate JSDoc lines are gone. - The README now says the canopy chart stays empty until the owner opens the card.

Approved

Approved. Reads and writes are gated correctly: the three GET routes are public like the cameras, and POST /grow/log and /grow/canopy are owner-only, behind the same-origin gate, with the body capped by bytes read. The terminal has /grow log, planted and note through the same routes, and the README and help text say so. The tests cover retention, paging, dark stills, bad snapshots, the owner/guest/cross-site split, and the terminal lines. On the preview, /grow/log and /grow/stills answer with valid empty data. The grow station has no cameras there, so the card never rendered, and I could not see a still, the scrubber or the chart working. I could not open the screenshots either: the file tool refused paths outside the clone. Next: (1) the issue said stills live on the Pi, and they sit on the relay. Say that in the README, or open an issue to move them. (2) The canopy chart stays empty until the owner opens the card. Show a plain Open the card to compute line on the chart instead of an empty box, and check the card on a 360px phone with a real camera before relying on it.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs