Skip to content

Converted Decodable Reader Setup Dialog to React - #8132

Draft
josiahwall wants to merge 2 commits into
masterfrom
modernizeReaderSetup
Draft

Converted Decodable Reader Setup Dialog to React#8132
josiahwall wants to merge 2 commits into
masterfrom
modernizeReaderSetup

Conversation

@josiahwall

@josiahwall josiahwall commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

This pr is for converting the decodable reader's setup dialog to React; the leveled reader's dialog still needs to be converted.

Devin review


This change is Reviewable

@josiahwall
josiahwall force-pushed the modernizeReaderSetup branch from 70feaa3 to 76e069f Compare July 30, 2026 23:44
@andrew-polk
andrew-polk marked this pull request as draft July 31, 2026 05:16

@andrew-polk andrew-polk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Opus 5 (during /preflight on Andrew's machine) - Devin consultation log

Devin reviewed this PR at 3e0c2978. Its passes raised 9 bugs and 22 flags; most were either already fixed on the branch or have been fixed during this preflight (see the two commits above). What follows are the findings that are still open and need a human decision, one thread each.

No other review bot ran: this repo's .coderabbit.yml sets auto_review.enabled: false, and there is no Greptile review on this PR.

Fixed during preflight: typed sample words saved with line breaks (Devin's only severe finding - the words silently stopped counting as decodable); the hard-coded English hint under the sight-words box; the missing accessible names on the three text boxes; the dropped doc comment on the word-list cleaner; fail-fast when the toolbox bundle is missing; the props type aliases. Plus two things Devin caught in my own first preflight commit: sample words losing the legacy de-duplication, and save() comparing cleaned values against uncleaned ones.

Reported from a stale commit, already fixed before preflight: stage removal not deleting its word-list file, the stacked ternaries, the legacy 3-argument save path, and the dead LESS / reactHost block. I verified each against the current code rather than trusting the line numbers.

Comment thread src/BloomBrowserUI/bookEdit/toolbox/readers/readerTools.ts Outdated
Comment thread src/BloomBrowserUI/bookEdit/toolbox/readers/readerSetup/decodableStagesUtils.ts Outdated

@andrew-polk andrew-polk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Opus 5 (during /preflight, round 2) — Devin consultation log

Devin re-reviewed at 91864be and raised no new bugs against this commit. Every bug it still lists carries the SHA of a superseded commit; I checked each against the current code.

The developer's decisions from the first round are now implemented and each of those threads has a reply recording the outcome. One thread is deliberately left open for discussion (the persisted-settings shape), and Devin's later passes surfaced three further things worth a decision — one thread each below.

Round 2 also fixed one bug in my own round-1 work, which Devin caught: getSynphonyAlwaysMatchSymbols filtered on typeof symbol === "string" and so silently dropped an array-valued AlwaysMatch — the very symptom the function was added to remove. Fixed in 91864be with four tests; the array case fails against the previous implementation.

Gate at 91864be: typecheck pass, lint 0 errors, 612 TypeScript tests pass (up from 574 at the start of preflight — 38 added across this run), CI green. The C# suite was run earlier this preflight (2981 passed; the one failure needs Reading App Builder's build tools, which are not installed on this machine); nothing since has touched C# or the XLF files.

Comment thread src/BloomBrowserUI/bookEdit/toolbox/readers/readerTools.ts
Comment thread src/BloomBrowserUI/bookEdit/toolbox/readers/readerSetup/DecodableStagesSetup.tsx Outdated
@andrew-polk

Copy link
Copy Markdown
Contributor

Claude Opus 5 (during /preflight, round 3) — consultation log

Devin re-reviewed at 8a7f5c6 and raised no new bugs. Every bug it still lists carries the SHA of a superseded commit. CI is green and the front-end suite is 615 passing.

Round-3 decisions, all now implemented:

  • Restored the legacy drop-empty-stages rule on save, with renumbering. Verified in a running Bloom: added an empty 14th stage, pressed OK, and the file still has 13 stages named 1..13.
  • Accepted the kept-vocabulary limitations (stale grapheme segmentation, and inflated word-frequency counts on repeated saves in allowed-words mode) and documented both where the split happens.
  • Investigated the two Synphony entry points and found nothing to fix — they call the same routine with the same arguments; noted so it is not re-raised.

Two things worth flagging for someone other than this PR.

1. Master's own leveled-reader tests were failing, and this branch had to unbreak them to go green. They fail the same way on origin/master, independently of this PR:

  • LeveledReaderStats was declared without export, though master's own LeveledReaderToolControls.spec.tsx imports it by name — all 7 tests in the file died on render. Added the export.
  • The spec still expected an empty Max cell where there is no limit to display. BL-16585 (e6b66aad, 3 Aug) changed that cell to render an em dash so the column stays aligned, and did not update the spec, which predates it (e38ccc66, 30 Jul). Updated the two expectations and the now-inaccurate test name.

Someone from BL-16585 should sanity-check that second one — it assumes the em dash is the intended display, which is what the component does today. If blank was intended, the component is what needs changing, not the spec.

2. Master's shared ReaderSetupButton collided with this branch (BL-16585 extracted the Set Up Stages/Levels button while this branch was rewiring it). Resolved by taking master's component and styling wholesale and giving it an optional onClick: the Decodable Reader passes the action that opens the converted React dialog, and the Leveled Reader keeps falling back to the legacy href. Verified both live — the decodable button opens the React dialog with all 13 stages, and Set Up Levels still opens the old jQuery "Set up Leveled Reader Tool" dialog.

One thread is still open, on the sample-text extension filter, pending discussion with the developer.

@andrew-polk

Copy link
Copy Markdown
Contributor

Claude Opus 5 (during /preflight, after rebase) — consultation log

Rebased onto master and force-pushed (--force-with-lease); the branch is now 9 linear commits with no merge commits. Devin re-reviewed and found no new bugs at 6e3e779; CI is green and the front-end suite is 618 passing.

Master's test fix landed, and mine dropped out. 246991f8b0 did exactly what my earlier commit did — added the missing export on LeveledReaderStats and introduced kNoMaxShown = "—", even renaming the same test. My commit became redundant and was skipped during the replay. That also settles the open question from last round about whether the em dash was intended: the BL-16585 author independently reached the same conclusion, so nothing is left to decide there.

The shared ReaderSetupButton reconciliation had to be redone, because it had lived only inside a merge commit's conflict resolution and rebase drops merges. It now sits in the conversion commit itself. Re-verified live afterwards: the decodable button opens the React dialog with all 13 stages, and Set Up Levels still opens the legacy jQuery "Set up Leveled Reader Tool" dialog.

Two further bugs, found by Devin on the rebased branch and fixed:

  1. The edit view could be left locked with no way out (025fc05). Opening the dialog locked the edit view before the dialog rendered, while only the dialog itself unlocks. A failed render therefore left the user unable to touch the book until Bloom restarted — and it was reachable, since clicking Set Up Stages before the reader settings finish loading makes the component throw. The lock now comes from a mount effect, so a failed render locks nothing.

  2. Closing could later unlock the edit view spuriously (6e3e779). close() tried to disarm its own handle, but the handle was re-armed by the very re-render that closing causes, so a later close request re-ran the unlock. Closing is now idempotent and the handle is published from an effect rather than during render — which also removes the assign-module-state-while-rendering pattern Devin flagged in its first review.

Both verified in a running Bloom by counting the actual setModalState requests: one on open, a second on Cancel, and still only two after a further close request, where previously a third would have fired.

One item remains open — the thread on the sample-text extension filter. Worth noting what that finding is and is not: the extension list and its case-sensitivity are identical to the pre-PR dialog, so neither is a regression. What genuinely changed is that the old dialog listed unreadable files with an explanation ("Cannot read this format" / "File needs .TXT extension") plus a help link, and showed a message for an empty folder; the new one filters them out silently. Those strings still exist and are translated, but are now referenced only from the legacy pug. Awaiting the developer's call.

@andrew-polk
andrew-polk force-pushed the modernizeReaderSetup branch from 6e3e779 to 133fb11 Compare August 3, 2026 23:37
@andrew-polk

Copy link
Copy Markdown
Contributor

Claude Opus 5 (during /preflight) — last Devin item, assessed and not actioned

Devin's final pass at 942c43ad raised one thing: that some new helpers use function declarations where the project asks for arrow functions.

Assessed as not worth a change, for two reasons:

  • AGENTS.md scopes that rule to React — "Always use arrow functions and function components in React" — and the two files involved are plain TypeScript modules, not components.
  • Each file is already internally consistent, which seems the more useful kind of consistency here. decodableStagesUtils.ts is arrow-style throughout except cleanSpaceDelimitedList, which is a function because it was moved verbatim from readerSetup.io.ts — Devin itself asked earlier in this review that the move leave the code (and its doc comment) untouched. readerToolsModel.ts is a class module whose existing top-level exports are function declarations, and the two helpers I added there match them.

Recording it here rather than silently dropping it. Happy to convert them if the team reads the rule more broadly than I have.

Everything else from Devin is fixed; this was the only item left, and CI plus the 624-test front-end suite are green.

@andrew-polk
andrew-polk marked this pull request as ready for review August 4, 2026 00:33
@andrew-polk
andrew-polk marked this pull request as draft August 4, 2026 16:05
Follow-on work to Josiah's conversion of the dialog to React: the review pass
over it, the fixes that came out of that, and the fixes that came out of Devin's
passes over those.

Data corruption and correctness:

- Typed sample words were saved verbatim, newlines and all, and the stored
  moreWords string is later re-split on plain spaces, so words typed one per
  line became a single run-together word and silently stopped counting as
  decodable. Saving now normalizes moreWords the way the legacy dialog did, in a
  new unit-testable prepareSettingsForSave() helper, and de-duplicates the list
  (via cleanSampleWordList) so repeats no longer inflate word frequency.
- save() compared normalized values against the raw values it had loaded, so any
  stored setting with a comma or doubled space looked changed even when the user
  edited nothing, forcing a needless reload of every sample text file. The
  baseline is normalized the same way before comparing.
- prepareSettingsForSave now drops stages the user put nothing into and
  renumbers the survivors, which is what the legacy dialog did.
- Deleted letters kept counting in allowed-words mode: the full language-data
  rebuild was the only thing that ever removed a grapheme, and it had been
  skipped for allowed-words collections. Split out ResetLanguageDataGraphemes so
  the alphabet is always rebuilt while the loaded sample words are preserved.
- Removed the dialog's private copy of Synphony's word splitting; it now calls
  LanguageData.getGpcForm directly, so a typed word is segmented by exactly the
  same code as a word from a sample text.
- Special language symbols were read from the workspace frame's always-empty
  copy of the Synphony data, dropping typed words that contained one from the
  matching-words preview. They now come from the toolbox frame via the new
  getSynphonyAlwaysMatchSymbols, which handles AlwaysMatch holding either a
  single symbol or an array (Synphony concats it, so both shapes are legal).
- getFileExtension split the whole path on ".", so a file with no extension
  under a folder whose name contains a dot was mislabelled "Cannot read this
  format" instead of "File needs .TXT extension". It now looks only at the last
  path segment.

Dialog lifecycle:

- showDecodableReaderSetupDialog locked the edit view before rendering the
  dialog, and only the dialog posts the unlock -- so a render failure left the
  edit view locked with no way back except restarting Bloom. That was reachable
  by pressing Set Up Stages before the reader settings had loaded. The lock now
  happens in a mount effect, alongside the unlock.
- close() is idempotent via a ref, and the dialog's close handle is published
  from an effect rather than during render, so a second close request can no
  longer re-post setModalState = false and unlock the edit view underneath
  whatever is relying on it.

User-visible restorations and additions:

- Unreadable files in the Sample Texts folder were being silently filtered out.
  They are listed again with the already-translated explanations ("Cannot read
  this format", "File needs .TXT extension") and the export/convert help link,
  and extension matching is case-insensitive everywhere, via the single
  kReadableSampleTextFileExtensions list in readerToolsModel.
- New ReaderSetup.NoSampleTextsYet message for an empty folder; the old
  dialog's #please-add-texts label had no l10n key and never displayed anything.
- The Help button opens the page for the tab you are on again, rather than
  always Decodable Stages.
- "Separate words with spaces." is localized instead of hard-coded English, and
  the three shared text boxes take a localized ariaLabel so they have an
  accessible name.

Tests, comments and cleanups:

- New specs for decodableStagesUtils (prepareSettingsForSave,
  cleanSpaceDelimitedList, cloneReaderSettings, hasOnlyKnownGraphemes) and for
  getSynphonyAlwaysMatchSymbols and getFileExtension.
- Fail fast if the toolbox bundle is missing instead of silently skipping
  long-press support; inlined props type aliases per AGENTS.md; drive the tabs
  with MUI's onChange.
- Comments recording the accepted limitations (stale grapheme segmentation and
  the word-frequency inflation on repeated saves in allowed-words mode, the
  stage-letter pruning on remount, the two matching-words helpers that really do
  ask Synphony the same question) and the three spots that would trip up whoever
  converts the Leveled Reader setup dialog next.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@andrew-polk
andrew-polk force-pushed the modernizeReaderSetup branch from 942c43a to 2f319d8 Compare August 4, 2026 16:09
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.

3 participants