Skip to content

Dev - #150

Merged
ShuhaoZQGG merged 85 commits into
masterfrom
dev
Nov 14, 2026
Merged

Dev#150
ShuhaoZQGG merged 85 commits into
masterfrom
dev

Conversation

@ShuhaoZQGG

Copy link
Copy Markdown
Contributor

Closes #

Why

What changes

Security

Production impact

Testing

  • npm test:
  • npm run test:e2e:
  • npm run test:specs:
  • Oracle:

Evidence (real Zoom client)

Not verified

Look here first

…icker

The mode menu's "My Background…" opened a modal that only wrapped
OwnBackgroundPicker, which the Card Images modal already shows. Remove the
item, the wrapper modal and its LiveTab wiring. The "Show my own background"
toggle stays in the menu.
…emoval

No step changes: no step opened the mode menu's "My Background…" item.
Note under "Not covered" that the own-background picker now lives only in
Card Images.
…enu-item

fix(overlay): drop the My Background menu item; Card Images has the picker
A soft chime when the card changes (one note at green, two at yellow,
three at red) in the Zoom app and the web timer, and music while Take a
Break counts down in the Zoom app. Pro only, behind the new `sounds`
release flag, and off until the user turns it on.

Sound plays on the timer's own computer; the Sounds modal says the
speaker hears it only through Zoom's Share sound. The chime and the
placeholder "Music box" track are synthesized with Web Audio; file
tracks can be added under public/music/ via BREAK_TRACKS.

The player is mounted above the tabs so sound carries on when the Live
tab is switched away or the web panel is minimized.

Tests: shared engine and component unit tests, web and Zoom e2e that
count real oscillators, and a new timer-sounds Zoom-client spec
(timer-core reviewed: no step changes, pointer under Not covered).
feat(sounds): card-change chime and break music for Pro (#105)
Hiding the Sounds button whenever a user could not use it meant free
users never learned the feature existed, so nothing told them what Pro
adds. The button and modal now always render; anyone who cannot use
them sees them locked (grey text, lock icons, a tooltip saying why).

Pro users are locked too while the `sounds` release flag is off, with
"coming soon" instead of an upgrade pitch. This ships a visible, locked
Sounds button to production on merge, before the flag is turned on.
feat(sounds): show Sounds locked instead of hiding it
fix(wrangler): change FLAGS_FORCE from "1" to "0"
A Pro plan on dev (a subscription, a grant, or a club on the device) saw
"Sounds are coming soon" while the pro flag was off, with no Pro chip to
say why. useEntitlement's isPro now needs the pro flag as well as the
plan, in both apps, so while pro is dark every Pro feature reads a Pro
plan as free: Sounds shows the Pro lock, Card images says images stay in
this browser. "Coming soon" is left for pro on and sounds off. `known`
waits for the flags too, so a Pro user never sees a lock flash.

The server still keys on `entitled`, and clubs on the club record, so a
paying user's sync, backups and club archive carry on. The flag is
retired before Pro has real customers, so nobody pays and sees free.

timer-sounds spec: covers useEntitlement, TS-08 now needs pro on and
sounds off (PostHog targeting), new TS-09 for a Pro plan under
FLAGS_FORCE "0". Not rerun in the real client.
fix(pro): make the pro flag the source of truth for Pro in the UI
An admin or editor presses "Share with my club" on one of their own custom
card sets in Card Images. The images go to R2 under club-card/<clubId>/ and
the set to club-cards:<clubId>, and the club's ver moves, so every club device
downloads it into IndexedDB and lists it in the picker.

A device that never chose cards of its own opens on the club's set; one that
did keeps its own, with the club's one tap away (toastmaster_card_source). A
lapsed or left club drops the set and the device's own choice shows again.
Admins can remove the set from /club/admin.

The images are private: they live outside club/<clubId>/, which
/api/club-assets serves to anyone. Uploads are checked against their SHA-256,
sniffed as JPEG/PNG/WebP, capped at 2 MB each and 20 MB per club. Publishing
prunes images no set names, sparing uploads under 15 minutes old.

Phase 1 of docs/plans/10-pro-is-the-club-plan.md (local plan, not tracked).
New Zoom-client spec: e2e/zoom-client/club-card-sets.md (draft, not yet run).
Claude used to finish the code and stop, then ask whether Zoom was open.
The repo's own instructions made that the right finish: CLAUDE.md let an
unrun spec end as `status: stale`, the zoom-app-testing skill triggered
only when the user asked to test and wanted a yes first, and preflight
told the user to start the meeting.

- CLAUDE.md: a definition of done that includes running the covering
  Zoom-client specs, standing approvals for the steps that needs (dev
  deploy, starting Zoom, flag flips with restore, branch/PR work), and a
  pointer to the factory.
- zoom-up.sh launches Zoom and starts the test meeting (PMI from the
  gitignored meeting.local, or Zoom's Start Meeting shortcut). Preflight
  and the skill now treat "Zoom not running" as Claude's step.
- Stop hook (scripts/factory/stop-gate.mjs): on a factory/ branch, the
  first stop is refused while covered files changed and no newer run.md
  names the spec. CI, headless stations and a second stop pass.
- scripts/lib/zoom-specs.mjs: the spec front-matter reader, shared by
  check-zoom-specs.mjs and the hooks. vitest now runs scripts/.
Each step of the pipeline is now a repo skill with one input (an issue
number), one artifact and one label change, runnable in a session, headless
(`claude -p`) or from a trigger: factory (the router), factory-issue,
factory-plan, factory-build (oracle → implement → verify in fresh
subagents), factory-zoom and factory-report.

- scripts/factory/state.mjs is the one reader of the label state machine:
  where an issue is, what runs next, and the WIP queue. Human gates
  (factory:ready, plan:approved) count only when a config.json actor added
  them; the repo is public.
- Oracle tests: factory-oracle records them in .factory/oracles/<n>.json.
  A PreToolUse hook refuses edits to them, and check-oracle.mjs fails a PR
  that changed them after the oracle commit.
- scripts/factory/gh.sh drops the shell's work-account GITHUB_TOKEN locally.
- .factory/README.md documents labels, conventions and artifacts.
There was no CI at all, so an agent's work was checked only by the agent.
ci.yml runs unit tests, the spec check, the oracle check, the build and
the Playwright suite on every PR into dev or master and every push to
them. The PR template is the completion report's shape (PR #128's
sections plus Evidence, Not verified and Look here first).
factory-plan.yml plans the oldest promoted issue when a factory actor adds
factory:ready or a factory PR closes (WIP allowing). factory-build.yml runs
oracle → implement → verify when a factory actor adds plan:approved. Both
re-check the gate with state.mjs, so a label from anyone else does nothing.

Inert until the repo variable FACTORY_ENABLED is true, the Claude GitHub
App is installed (its token, unlike GITHUB_TOKEN, makes pushes trigger CI)
and CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY is set. The Zoom station
stays on the Mac (.factory/schedules.md).
factory-ideate reads PostHog (usage, errors, surveys), recent issues and
comments, and drafts 3–5 evidence-backed issues labelled idea; promoting
stays human. A "Factory-ready issue" form asks for the same sections the
plan station needs. .factory/schedules.md holds the prompts for the two
runs set up by hand: the Mac's Zoom night shift and weekly ideation.
ubuntu-latest moved to 26.04, which Playwright 1.58 cannot install
browsers for, so the first CI run failed at "Install Playwright browser"
after unit tests, specs, the oracle check and the build had passed.
First real run of zoom-up.sh: Zoom launched and Start Meeting (⌃⌘V)
worked in 23 s, but what opened was the pre-join video preview, titled
"<Host>'s Zoom Meeting", and the substring check took it for the meeting.
zoom-up.sh and preflight now require the window named exactly "Zoom
Meeting" (what zoom-front.sh raises); zoom-up.sh presses Return on the
preview's default Start button. Gotchas: the preview, and Zoom popovers
needing click.sh --activate.
chore: lights-on factory (phases 1–5)
The 02:00 night shift rarely found the Mac awake. The local scheduled task factory-zoom-daily now runs at 17:00, starts only between 16:55 and 17:30, and does nothing unless a PR is labelled needs:zoom-test.
chore(factory): run the Zoom station daily at 17:00
… in step with master

master is the default branch, so issue-label events run master's copy of the factory workflows and claude-code-action skips any workflow that differs from it. The skip reported success, so #90's build looked green without running. Each workflow now checks that its station moved the issue on, and says why when it didn't.
The Custom-set card-switch tests opened the app, stored four images in IndexedDB, then reloaded. The web timer page is lazy-loaded, so on a slow CI runner it was still starting while the test stored the images; its start-up sweep saw images no set listed yet and deleted them, and after the reload the set fell back to the built-in card. Seed from robots.txt on the same origin instead, where no app code runs, then open the app once.
test(e2e): seed custom card sets before the app starts
ci(factory): fail when the Claude step is skipped; keep the workflows in step with master
ShuhaoZQGG and others added 19 commits November 13, 2026 14:52
Live tab: refuse an agenda row that needs a role instead of timing it under the default (#91 follow-up)
…e release

timer-core TC-01..08, 10, 11, 13 and club-card-sets CC-01..05, 09 passed in the
real Zoom client on dev 3ce801a6. CC-06 ran only as a guest (401), CC-07/CC-08
and TC-09/TC-12 not run (human or unapproved KV edits).
test(zoom-client): rerun timer-core and club-card-sets on dev before release
… before stopping

The #80 plan run (actions run 43532812887) ended cleanly after 14 turns with
nothing written: no branch, PR, comment or label. The likely cause is a
subagent launched in the background: a headless run can end the moment the
station ends its turn to wait for it.

- .factory/README.md: new convention, subagents run in the foreground.
- factory-plan: research subagent in the foreground; step 9 checks
  state.mjs before stopping.
- factory-build: sub-stations one at a time in the foreground; don't end
  the turn before needs:zoom-test unless factory:stuck.
- factory-verify: review subagent in the foreground.
…agents

Factory: run station subagents in the foreground; check state before stopping
Once the pro flag is on, a free device gets a 'New: Pro for your club'
window after a speech ends (PeriodicPrompts timing, shown once), handing off
to the Upgrade dialog, and the footer's Upgrade button carries a red dot
until any Upgrade dialog has been opened or 28 days after the window showed.
Pro devices see neither. New zoom-client spec pro-launch.
…ce released

A chime when the card changes, and music during breaks, listed only while the
sounds flag is on, so nobody is sold a feature they would find locked.
…can be bought

Sounds stay visible and locked. The lock says "Hold tight! Sounds are coming
soon." whenever the sounds or pro flag is off, and "Sounds are a Pro
feature" only once buying Pro would unlock them. Same rule in the web app.

A dismissible banner above the tabs in the Zoom app says a new look and Pro
are coming. It shows only while the pro flag is known and off, so it goes away
by itself at launch.
The pre-launch banner becomes the first entry in ANNOUNCEMENTS
(packages/shared/announcements.js). To announce something, add an entry
with a new id: lead, text, tone, surfaces, optional dates, an optional
flag condition and an optional link. The Zoom app shows the first entry
that applies; a close is remembered per announcement on that device.

The banner itself is presentational and shared (packages/ui), so the web
app can render it later. Events become announcement_shown,
announcement_dismissed and announcement_link_opened, each with
announcement_id.
A run now ends with zoom-down.sh: end the test meeting and quit the client,
then say in chat that the Mac is free. With Zoom left open the user could not
tell whether the run was over. Not done when the run paused because the user
took the Mac.
…er only in the last 60 days

Before the launch (pro off) the footer keeps a grey Pro button with a lock.
Pointing at it or clicking it lists what Pro brings: sounds, club card sets
on every club timer, voting for Best Table Topics and Best Evaluator, and
more. Once pro is on it is the real Pro / Upgrade button again.

The club renewal reminder now shows for a club that will not renew only in
its last 60 days, and only with a known end date. A failed payment is still
warned about at once.
feat(pro): pre-launch — announcement banner, locked Pro button, "coming soon" Sounds; renewal reminder only in the last 60 days
chore(zoom-testing): quit Zoom at the end of a run
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
toastmaster-timer-dev e28d733 Nov 14 2026, 09:51 PM

Every club feature now reads one gate, clubFeaturesOn (packages/shared/club.js):
the club is active AND the pro flag is known and on. Until now they checked
only club.entitled, so a club device kept its badge, club card set, club
timings, archive strip / History / share, report branding and renewal line
while pro was off.

While pro is off a club device behaves as a lapsed one: nothing is deleted,
it stays in its club, and everything is back once pro is on. Speeches are not
sent to the club's archive meanwhile. useClub and the renewal banner re-render
when the flags arrive; the club card sync re-checks on a flag change too.
Both apps.
…-pro

feat(pro): club features only while Pro is released
The release PR (dev → master, #150) failed it: across the release range,
#91's commits legitimately updated tests in #90's and #107's oracles, and the
check counts every later commit. The factory README already scopes it to a
factory/ PR, where an oracle is frozen for its own issue's build; each feature
PR into dev still runs it there.
Resolves the spec-index conflict with dev (#147, #151): every row from both
sides, pro-launch kept after timer-sounds and in the run order. Footer.jsx
merged cleanly: with pro on, the Upgrade / Pro button with the launch dot;
with pro off, dev's locked 'Pro (coming soon)' button. pro-launch is marked
stale (the Sounds line and this merge came after its last run) and points to
pro-coming-soon for the pro-off footer.
…s-only

ci: run the oracle check on factory/ PRs only
feat(pro): one-time Pro launch announcement in the Zoom app
@ShuhaoZQGG
ShuhaoZQGG merged commit 6fd3713 into master Nov 14, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant