Skip to content

Simple Format import: ask for a role when the bracket doesn't name one, instead of guessing (#91) - #139

Merged
ShuhaoZQGG merged 5 commits into
devfrom
factory/91-table-topics-role
Nov 13, 2026
Merged

ShuhaoZQGG merged 5 commits into
devfrom
factory/91-table-topics-role

Conversation

@claude

@claude claude Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Closes #91

Plan: .factory/plans/91-table-topics-role.md (rewritten 2026-11-13 for the new direction).

Why

Simple Format import guessed a role whenever the bracket did not name one of your roles exactly. Verified on dev a4439d6:

  • (Table Topics Speech) → Table Topics, not a rules key: Standard Speech timing, START refused (the original report).
  • (Table Topics) → a custom Table Topics Master (10/20/30 s), (Speaker) → Keynote Speaker: the closest-match step from Simple Format import ignores custom roles in "Name (Custom Role)" lines #77 gives false positives.
  • (Ice Breaker) with Ice Breaker hidden → still Ice Breaker, with Standard Speech rules. Defaults can be removed, so a fallback to them is not safe.

The organizer finds out when the card changes colour at the wrong time. The first plan (fix the keyword ladder) kept the guessing; this PR drops it.

What changes

  • Import matches exactly or not at all. A bracket with the same words as one of your roles (the dropdown list; case, spacing and punctuation ignored) gets that role and its rules. Any other line imports with its text as written, no role and no rules. A line with no bracket gets a role only when the whole line names one.
  • The Agenda tab asks for the role. Rows that need a role are red, show the text as written ("(Table Topics) is not one of your roles" / "No role given"), and have a "Pick a role…" list of your saved roles. A banner counts them. Clicking such a row shows "Pick a role for {name} first" instead of opening the Live tab. Its Edit modal starts on "Pick a role…" with Update disabled.
  • Toast and analytics. "Imported N speaker(s)" when every line matched (unchanged); "Imported N speaker(s). M need a role" as a warning otherwise. agenda_imported gains needs_role_count; a new agenda_role_picked event (is_custom).
  • The check is derived from your current roles (agendaItemNeedsRole), so rows saved earlier as Table Topics, and rows whose role you later removed, are highlighted too. Completed rows and breaks never are.
  • Add Item starts on Standard Speech, or the first listed role when Standard Speech is hidden. Before, it showed the first role but saved Standard Speech, which would now be highlighted at once.
  • Code: findRoleByName, agendaItemNeedsRole and a new parseSimpleFormatText contract ({ name, role, roleText }) in packages/shared/simpleFormatParser.js; importBulkSpeakers (now returns { count, needsRole }) and AgendaTab in both apps. matchCustomRole and detectRoleFromText are removed (no callers left). EasySpeak's mapping is unchanged.

Security

None. Client-side parsing and UI only.

Production impact

After release, master users who paste loosely named roles get highlighted rows to pick instead of a guessed role, in both the Zoom app and the web app. Agenda rows already saved with a role that is not in the user's list (e.g. Table Topics) show highlighted. No flags, no migration, no API or sync format change. Rows synced to an older build keep their text as role and no rules; that build times them with Standard Speech, as it does today for Table Topics. Rollback is a revert.

Testing

  • npm test: 2266 pass (116 files). New: apps/zoom-app/src/components/AgendaTab.test.jsx; rewritten simpleFormatParser.test.js; importBulkSpeakers cases in both TimerContext.test.jsx; needs-role cases in web AgendaTab.test.jsx.
  • npm run test:e2e: 43 pass. zoom.timer-flow and web.agenda-flow import tests now expect 3 highlighted rows, the click guard and the picks; new Zoom test "(Table Topics Speech) times 1/1.5/2 min, (Table Topics) needs a pick (Simple Format import: ask for a role when the bracket doesn't name one, instead of guessing #91)" presses START.
  • npm run test:specs: valid (8 specs).
  • Oracle: none. The plan changed direction during review and was implemented in an interactive session, not by the build station; tests were written with the code.
  • Also checked by hand in a 420 px browser pane (Zoom dev server): highlighted rows, banner count, and the inline pick clearing a row.

Evidence (real Zoom client)

Run 2026-11-13 14:06–14:27 EST on branch @ b029a29 (code 568a64d), deployed to dev as version a249d5d7 (committed vars), Zoom 7.2.5 on macOS 26.7.2. Log: test-runs/2026-11-13-91-zoom-specs/run.md (local; the folder is git-excluded). Specs updated in 95fbaeb (status: current, verified_at: b029a29).

agenda-import: IM-01…IM-06 all ✅

  • IM-01 ✅ Three custom roles saved (Opening 15/30/45, Closing Remarks, Hand over). The dropdown lists them after the device's own roles.
  • IM-02 ✅ The six lines were pasted with ⌘V into the webview. The toast read "Imported 6 speaker(s). 4 need a role", and the banner read "4 rows need a role".
    • Matched rows: Cam-Ly (Opening) and Sam (Hand over, case ignored).
    • Highlighted: (Closing), (Closing Remarks - 2 min), (Moment of Reflection), and Bob's (Ice Breaker), which this device hides.
    • PostHog agenda_imported: items_count 6, custom_role_count 2, needs_role_count 4.
  • IM-03 ✅ Picking a role on each row counted the banner down to none. Clicking Cam-Ly opened the Live tab on Opening. The real video went green at 0:19, yellow at 0:36 and red at 0:50 (thresholds 0:15/0:30/0:45). There were four agenda_role_picked events.
  • IM-04 ✅ Finish uploaded the speech (POST 200), Clear All emptied the agenda, and the three roles were removed.
  • IM-05 ✅ "Tim Timer (Tech Tip)" kept Tech Tip, and its Live tab showed Tech Tip's 3/4/5 min. Dominique's (Table Topics) was highlighted. The toast read "Imported 2 speaker(s). 1 needs a role".
  • IM-06 ✅ The toast read "Imported 3 speaker(s). 2 need a role".
    • Alex (Table Topics) was highlighted, and Bob showed "No role given".
    • Clicking Alex kept the Agenda tab open with the toast "Pick a role for Alex first".
    • After the two picks, nothing was highlighted.
    • David Kim (1-2min Role, 1:00/1:30/2:00) started with no warning. The video went green at 1:05 and yellow at 1:35, with nothing before 1:00.
    • PostHog: needs_role_count 2 and two agenda_role_picked events.

timer-core: TC-01…TC-08, TC-10, TC-11 all ✅ (TC-09 and TC-12 ⏭, human)

  • TC-01 to TC-05: the app loads from the zoom. host. The count-up shows on the video. Green, yellow and red came on at 0:36, 0:50 and 1:06 (thresholds 0:30/0:45/1:00). Finish reset the video. The backgrounds folder stayed at 12 files.
  • TC-06 to TC-08: after ⋯ → Close current app the app reloads.
    • A running timer came back with "Timer resumed from where it was", already red.
    • A paused timer came back with "Paused timer restored" at 01:23, and Continue carried on.
    • After Reset or Finish, the app reopened at 00:00 with no toast.
  • TC-10 and TC-11: after a cold start, the picker showed the first listed role. timer_started carried audience_size: 1 (host-only meeting).

Substitutions: this device runs its own copy of Claude Test Club's list, which has 17 custom roles and hides every built-in. Listed roles stood in where the spec names a hidden built-in, so is_custom was true on the picks. The spec's needs line and a run note under IM-06 now describe this.

Not verified

  • timer-core TC-09 (stale saved session) and TC-12 (non-host second account) are human steps.
  • The webview inspector is off, so the run did not read localStorage (toastmaster_agenda, toastmaster_role_order) or the debug log's setVirtualBackground counts. The rows on screen and PostHog stood in for them.
  • The built-in names (Table Topics Speech, Standard Speech, Short Roles) did not run in the real client on this device, because it hides all built-ins. Unit and Playwright tests cover them.
  • The Live-tab guard is in #140 and is not part of this PR.

Look here first

🤖 Generated with Claude Code

…Table Topics' with no timing rules

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude claude Bot added the plan:review Factory: plan is up; answer its open questions, then add plan:approved label Nov 13, 2026
@claude

claude Bot commented Nov 13, 2026

Copy link
Copy Markdown
Author

plan: The cause is the role ladder in detectRoleFromText (packages/shared/timingRules.js:82, :113-114). It returns 'Table Topics', which is not a rules key, for any text containing "table topics". I reproduced it on dev with the shared parser for (Table Topics Speech), (Table Topics), (Table Topics Master) and (Table Topics Evaluator). The fix stays in that one function and changes nothing in the apps:

  • "table topics evaluator" → Table Topics Evaluation
  • "table topics master" → Short Roles
  • any other "table topics" → Table Topics Speech

With a valid key, import and START in both apps work unchanged. The fix updates the tests that assert the old result. Two of them are #90 oracle tests; their #90 point still holds, only the role string changes. It also updates agenda-import IM-05, adds IM-06 (Table Topics Speech import + START on real video), and adds a new invariant test: the detected role is always a real role.

Open questions (default in bold):

  • Q1: Should Table Topics Master import as Short Roles and Table Topics Evaluator as Table Topics Evaluation? Yes, include both.
  • Q2: Should agenda rows already saved as Table Topics be migrated or aliased? No, out of scope.
  • Q3: Should timingRules.js be added to agenda-import's covers so the Zoom station runs it? Yes.
  • Q4: Should the Zoom placeholder Alex (Table Topics) stay? Keep it.

@ShuhaoZQGG ShuhaoZQGG changed the title Simple Format import: "(Table Topics Speech)" imports as 'Table Topics' with no timing rules (#91) Simple Format import: ask for a role when the bracket doesn't name one, instead of guessing (#91) Nov 13, 2026
…atch

The first plan fixed the keyword ladder so "(Table Topics)" landed on
Table Topics Speech. The direction changed: a fallback to a built-in can
land on a role the user removed, and the closest-match step from #77
gives false positives ("(Table Topics)" became a custom Table Topics
Master). Import now takes only an exact role name and highlights the
rest on the Agenda tab for a manual pick.
…e rest

A line gets a role only when its bracket names one of the roles in the
dropdown (same words; case, spacing and punctuation ignored). Any other
line keeps its text as written, with no rules. The Agenda tab highlights
such rows, with a "Pick a role" list of the saved roles and a banner
that counts them; clicking one asks for a role instead of opening the
Live tab. The check is derived from the current roles, so rows saved
earlier as 'Table Topics' and rows whose role was later removed are
highlighted too. Completed rows and breaks never are.

matchCustomRole and detectRoleFromText had no callers left and are
removed. Add Item now starts on the first listed role when Standard
Speech is hidden, so it never saves a role the picker didn't show.

agenda-import gains IM-06 and new expectations for IM-02 and IM-05.
timer-core is reviewed: importBulkSpeakers is under its Not covered.
The real-client run was blocked: the Mac's screen was locked, so no
step ran. The branch is deployed to dev (version 9b18a973) for the
rerun. verified_at stays at the last real run.
@ShuhaoZQGG ShuhaoZQGG added needs:zoom-test Factory: waiting for the real-Zoom station and removed plan:review Factory: plan is up; answer its open questions, then add plan:approved labels Nov 13, 2026
Both specs ran green in the real Zoom client against dev a249d5d7:
agenda-import IM-01..IM-06 and timer-core TC-01..TC-08, TC-10, TC-11
(TC-09 and TC-12 are human steps). The device hides every built-in
role, so the agenda-import needs line now says to use a listed role
in its place, and to keep the device's own custom roles unless one
clashes with a line in the spec.
@ShuhaoZQGG ShuhaoZQGG added ready-for-review Factory: report written; final review and merge and removed needs:zoom-test Factory: waiting for the real-Zoom station labels Nov 13, 2026
ShuhaoZQGG added a commit that referenced this pull request Nov 13, 2026
INDEX conflict on the timer-core row: kept #140's Covers text and #139's
verified column (b029a29, 2026-11-13). timer-core stays `stale` here:
its full run on b029a29 predates #140's LiveTab.jsx change, on which only
TC-13 ran (5de8185). agenda-import takes #139's `current`; #140 changes
none of its covered files. timer-core's #91 note now names the steps that
reran on b029a29.
@ShuhaoZQGG
ShuhaoZQGG marked this pull request as ready for review November 13, 2026 19:38
@ShuhaoZQGG
ShuhaoZQGG merged commit 80c1e96 into dev Nov 13, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-review Factory: report written; final review and merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant