feat(web): web platform TS structural refactor#145
Open
ddfreiling wants to merge 30 commits into
Open
Conversation
Conventional source directory name; disambiguates from the separate assets/_helper_scripts/ webview-helper bundle. Update path references in package.json (4 script paths), webpack.config.js comment, .github/instructions/typescript.instructions.md, CLAUDE.md, CONTRIBUTING.md, docs/architecture.md, docs/parity/*, and bin/typecheck. No code changes — build, typecheck, and 151 Jest tests all pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… utils/ and model/ Move: - logger.ts → utils/ReadiumPluginLogger.ts - peripherals.ts → utils/Peripherals.ts - extensions/ReadiumPublication.ts → utils/ReadiumExtensions.ts - enums.ts → model/ReadiumReaderStatus.ts Re-export shims at old paths keep all existing imports unchanged. Typecheck clean; 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extract from helpers.ts into canonical locations: - utils/manifest.ts: fetchManifest - utils/colors.ts: dartColorToCss - utils/iframeInjection.ts: injectFlutterReadiumHelperScripts + asset cache - decorations/decorationOverrides.ts: UNDERLINE_GROUP_SUFFIX, sendDecorate, navIframeWindows, registerPendingDecorationGroup, injectDecorationOverrides, highlightSelection Add mediaTypes + findLinkByHref to utils/ReadiumExtensions.ts. helpers.ts is now a re-export barrel (keeping preferences helpers in-place until Phase A3). All existing import paths continue to work. Typecheck clean; 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move: - Epub/epubPreferences.ts → preferences/FlutterEpubPreferences.ts (+ convertVerticalScroll/textAlignFromJson/normalizeTypes inlined from helpers) - WebPub/webPubPrefences.ts → preferences/FlutterWebPubPreferences.ts (typo fixed) - TTS/ttsPreferences.ts → preferences/FlutterTTSPreferences.ts Update FlutterWebPubPreferences imports to come from FlutterEpubPreferences. helpers.ts barrel re-exports convertVerticalScroll/textAlignFromJson/normalizeTypes from their new home in FlutterEpubPreferences. Shims at all old paths keep existing imports unchanged. Typecheck clean; 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…module Extract enrichWithTotalProgression, enrichWithTocHref, flattenToc into navigators/locatorEnrich.ts (shared with WebPub in step 5). Create navigators/FlutterEpubNavigator.ts: class wrapping EpubNavigator with static create() factory (logic moved verbatim from free function). Epub/epubNavigator.ts becomes a shim re-exporting FlutterEpubNavigator, enrichWithTotalProgression, and a backwards-compatible initializeEpubNavigatorAndPeripherals wrapper. Typecheck clean; 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create navigators/FlutterWebPubNavigator.ts: class wrapping WebPubNavigator with static create() factory. Imports locatorEnrich from the shared module (removes the cross-Epub/epubNavigator import). WebPub/webpubNavigator.ts becomes a shim re-exporting the class and a backwards-compatible initializeWebPubNavigatorAndPeripherals wrapper. Typecheck clean; 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create navigators/FlutterAudioNavigator.ts: class wrapping AudioNavigator with static create() factory. All logic moved verbatim from audioNavigator.ts; module-level _emissionsEnabled + setAudioEmissionsEnabled preserved. Exports buildStatePayload, seekAudioAndResume, SeekableAudioNavigator, AudioLocatorMapper, and __testing__ (makeAudioTotalProgressionFn, withTocHref). Audio/audioNavigator.ts becomes a shim re-exporting everything from the canonical location plus a backwards-compatible initializeAudioNavigator wrapper. Typecheck clean; 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create navigators/FlutterTTSNavigator.ts: same class as WebTTSEngine but renamed FlutterTTSNavigator, with imports updated to canonical paths (utils/ReadiumExtensions, utils/ReadiumPluginLogger, preferences/FlutterTTSPreferences). TTS/ttsNavigator.ts becomes a shim re-exporting FlutterTTSNavigator under both the new name and the backwards-compatible WebTTSEngine alias. Typecheck clean; 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create navigators/FlutterMediaOverlayNavigator.ts with logic from Audio/mediaOverlayNavigator.ts. Imports updated to canonical paths. Calls FlutterAudioNavigator.create() directly instead of the initializeAudioNavigator free function. Audio/mediaOverlayNavigator.ts becomes a shim re-exporting all public symbols and __testing__ from the canonical location. Typecheck clean; 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
git mv flutter.d.ts → bridge/window.d.ts; update import to use model/ReadiumReaderStatus canonical path. Create bridge/ReadiumBridge.ts: the single module allowed to call window.* emit callbacks. Typed methods: emitReaderStatus, emitTextLocator, emitTimebasedState, emitTextSelected, emitError. Navigators will be wired to use the bridge in Phase D. Typecheck clean; 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create publication/PublicationManager.ts: encapsulates the static _publications cache and manifest-fetch glue from the god class. Methods: fetchAndCache (used by getPublication), getOrFetch (used by openPublication cache-or-fetch pattern), evict/evictAll. Phase D will wire _ReadiumReader to delegate to this collaborator. Typecheck clean; 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create decorations/DecorationController.ts: extracts applyDecorations, setDecorationStyle, _subgroupFor, _decorationsByGroup, and style state from _ReadiumReader into a focused collaborator class. Phase D will wire the god class to delegate to this instance. Typecheck clean; 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create preferences/FlutterAudioPreferences.ts: audioPreferencesFromJson (extracted from FlutterAudioNavigator) and applyAudioPreferences (extracted from god class setAudioPreferences). Normalizes Dart preference keys to IAudioPreferences before submitting. Update FlutterAudioNavigator.ts to import audioPreferencesFromJson from the new module; remove the now-redundant private preferencesFromString function. Typecheck clean; 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wire _ReadiumReader to use all Phase C collaborators: - ReadiumBridge: all window.* calls replaced with bridge.emit* methods - PublicationManager: static _publications + fetch logic delegated - DecorationController: applyDecorations/setDecorationStyle delegated - applyAudioPreferences from FlutterAudioPreferences Update imports to canonical paths (navigators/, preferences/, utils/, model/, bridge/, publication/, decorations/). FlutterTTSNavigator replaces WebTTSEngine. FlutterAudioNavigator.create()/FlutterEpubNavigator.create()/ FlutterWebPubNavigator.create() used directly instead of free-function shims. Public method names/signatures unchanged (Dart↔JS contract intact). Typecheck clean; 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create web/src/index.ts as the new webpack entry point (re-exports from ReadiumReader.ts). Update webpack.config.js entry from ReadiumReader.ts to index.ts. Output path lib/helpers/readiumReader.js is unchanged. Typecheck clean; 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove 13 re-export shims at legacy paths:
logger.ts, peripherals.ts, enums.ts, helpers.ts,
extensions/ReadiumPublication.ts,
Epub/{epubNavigator,epubPreferences}.ts,
WebPub/{webpubNavigator,webPubPrefences}.ts,
TTS/{ttsNavigator,ttsPreferences}.ts,
Audio/{audioNavigator,mediaOverlayNavigator}.ts
Update test imports to canonical paths:
__tests__/audioNavigator.test.ts → navigators/FlutterAudioNavigator
__tests__/epubNavigator.test.ts → navigators/locatorEnrich
__tests__/helpers.test.ts → utils/colors
__tests__/mediaOverlayNavigator.test.ts → navigators/FlutterMediaOverlayNavigator
__tests__/guidedNavigation.test.ts, mediaOverlayNavigator.test.ts → utils/ReadiumExtensions
Update Audio/guidedNavigation.ts + Audio/syncNarration.ts to import from
canonical utils/ paths (logger → ReadiumPluginLogger, extensions → ReadiumExtensions).
Fix implicit-any on Link callback parameter in guidedNavigation.ts.
Typecheck clean; 151 Jest tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
audioNavigator.test.ts → FlutterAudioNavigator.test.ts epubNavigator.test.ts → locatorEnrich.test.ts helpers.test.ts → colors.test.ts (helpers.ts deleted; only tests dartColorToCss) mediaOverlayNavigator.test.ts → FlutterMediaOverlayNavigator.test.ts closePublication.test.ts → ReadiumReader.test.ts (imports ReadiumReader.__testing__) guidedNavigation.test.ts and syncNarration.test.ts kept — their source modules (Audio/guidedNavigation.ts, Audio/syncNarration.ts) are unchanged. Also remove stale untracked shim files left on disk after Phase D15 git rm (enums.ts, logger.ts, peripherals.ts, Epub/, TTS/, WebPub/, extensions/). 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Audio/syncNarration.ts → mediaoverlay/syncNarration.ts Audio/guidedNavigation.ts → mediaoverlay/guidedNavigation.ts These are media-overlay helpers, not generic audio utilities; grouping them with FlutterMediaOverlayNavigator (navigators/) better reflects ownership. Update all import paths in: ReadiumReader.ts navigators/FlutterMediaOverlayNavigator.ts __tests__/syncNarration.test.ts __tests__/FlutterMediaOverlayNavigator.test.ts __tests__/guidedNavigation.test.ts Typecheck clean; 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- ReadiumReader.ts: merge two split imports from @readium/shared into one - FlutterWebPubNavigator.ts: add missing load error log + remove no-op try/catch - FlutterMediaOverlayNavigator.ts: replace Manifest.deserialize()! + null check with the correct null-guard pattern (non-null assertion is redundant when the runtime check follows immediately) - DecorationController.ts: remove stale 'Phase D' forward-reference from JSDoc - PublicationManager.ts: remove 'god class' / 'Extracts from' impl-note language No logic changes. 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
EpubNavigator and WebPubNavigator had three identical blocks: - iframe up/down scroll (17 lines × 2) - handleLocator external-URL check (12 lines × 2) - textSelected payload builder (9 lines × 2) Extract to navigators/navigatorUtils.ts: scrollVisibleIframes(direction) — iframe content scroll handleExternalLocator(href) — confirm-open / warn buildTextSelectionPayload(locator, selection) — selection JSON Both navigator create() functions now import and call these helpers, removing ~38 lines of duplicated code from each. 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
_emitState takes 8 parameters; each listener repeated the same 5 captured context arguments (locatorMapper, alsoText, computeTotalProgression, onTextLocatorChanged, getTocHref) verbatim. Introduce a local emit(state, locator, alsoText) closure inside create() that closes over the 5 context args. Listeners now only supply the 3 values they actually vary per event, making each callback self-explanatory. No logic change. 151 Jest tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- FlutterEpubNavigator/FlutterWebPubNavigator: remove the 7-method delegation façade (goRight/Left/Forward/Backward, goLink, go, destroy, currentLocator). ReadiumReader never stores the returned instance; it receives the raw upstream navigator via the setNav callback, so all wrapper methods were dead code. - FlutterWebPubNavigator.create() return type changed Promise<Wrapper>→ Promise<void> to match actual usage (return value was always discarded). - Fixed copy-paste log message: 'EpubNavigator loaded' → 'WebPubNavigator loaded' in FlutterWebPubNavigator. - decorationOverrides.ts: remove highlightSelection() and its exclusive imports (BasicTextSelection, Width, Layout, Locator, LocatorText, ReadiumPublication). The function was exported but never called anywhere in the codebase. - syncNarration.ts: replace per-item debug log loop with a single summary line; the old loop emitted one log entry per cue which is hundreds of lines on a typical audiobook, drowning useful output. - ReadiumExtensions.ts: merge duplicate @readium/shared imports; rewrite mediaTypes() as a single chained expression with const instead of let. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
FlutterAudioNavigator.create() return value is always discarded by both ReadiumReader and FlutterMediaOverlayNavigator — the actual navigator is delivered via the setNav callback. Remove the vestigial class constructor and underlying field; change Promise<FlutterAudioNavigator> → Promise<void> to match actual usage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Merge duplicate FlutterAudioNavigator import (class + functions were on separate import lines; now collapsed into one). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pre-PR formatting pass via bin/format. All changes are line-wrapping only — no logic changes. These files had accumulated formatting drift since the last dart format run. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
subosito/flutter-action v2.23.0 treats flutter-version-file as a semver constraint and resolves it to the latest matching stable (3.41.9 → 3.44.1), ignoring the intended pin. Fix: read the file in a dedicated step and pass the exact string via flutter-version:. Remove channel: stable (inferred from the exact version). Document the pitfall in ci.instructions.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
analysis_options.base.yaml sets formatter.page_width: 120 via flutter_lints. bin/format was not running pub get first, so the lints package could not be resolved and dart format fell back to 80-char width. CI runs pub get before dart format and correctly uses 120-char width, causing 69+ files to appear changed in CI despite looking correct locally. Fix bin/format to run flutter pub get --directory before each dart format call so local and CI formatting always agree. Apply the correct 120-char formatting to flutter_readium_platform_interface (69 files), flutter_readium (27 files), and flutter_readium/example (20 files). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Brings the Spotlight decoration feature (#142), iOS event buffering (#152), and the page_width-120 reformat from main into the web-TS refactor branch. Conflict resolutions: - Spotlight ported into the refactored web modules: body-dimming + CSS Custom Highlight restore lives in decorations/decorationOverrides.ts; group activation lives in decorations/DecorationController.ts. The thin ReadiumReader facade delegates to DecorationController (no direct spotlight imports). - FlutterTTSNavigator / FlutterEpubNavigator keep the shared navigators/locatorEnrich.ts (flattenToc / enrichWithTocHref) instead of main's per-module duplicates. - Navigators drop the focus-stealing window.focus() in positionChanged; FlutterWebPubNavigator gains the same 200ms trailing-edge text-locator debounce as EPUB. - Dart conflicts (reader_decoration isActive, method-channel formatting, example spotlight segment, integration-test list-stable waits) take main. - Web bundle regenerated from web/src and copied into example/web. Stale web/_scripts cleanup: - bin/install: install web deps from the plugin root where package.json lives. - Removed the orphaned readiumReader.js.LICENSE.txt (a Terser artifact the dev build no longer emits; all its module paths were stale). - Updated stale path/name references in a few source comments. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The old name described only one of the module's exports (injectDecorationOverrides). The module is really a collection of decoration plumbing that operates on iframe windows — sendDecorate, navIframeWindows, the pending-group pairing, and the spotlight DOM helpers — so decorationFrameUtils.ts reflects its actual scope and matches the navigators/navigatorUtils.ts naming precedent. File rename + import-path updates only; no behavior change. The injectDecorationOverrides function keeps its name (it accurately describes that single function). Bundle regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ℹ️ This PR is a test of CoPilot's Autopilot to drive a refactor plan devised by Claude Code Opus 4.8 from start to finished implementation and opened PR.
What this PR does
Follows #136 with a full TypeScript structural refactor that mirrors the OOP architecture of the native sides.
Part 2 — TypeScript structural refactor
The web implementation was a 991-line god class (
_ReadiumReader) in a flat directory. This PR restructures it to mirror the native iOS/Android OOP architecture, following the migration plan indocs/web-ts-refactor-plan.md.Directory layout (before → after)
Key changes:
helpers.tsgod-file dissolved into focused sub-modulesFlutter*Navigatorwrappers extracted from free init functions (matchFlutter*-prefix convention from native)ReadiumBridge.tsis the only module allowed to touchwindow.*callbacks — injected by dependency, not reached globallyPublicationManagerowns publication lifecycle stateDecorationControllerowns decoration style/group statewebPubPrefences.ts→FlutterWebPubPreferences.tsWebTTSEnginerenamed →FlutterTTSNavigatorhighlightSelection()export,underlyingfieldsInvariant preserved
The Dart↔JS contract is byte-identical:
globalThis.ReadiumReadermethod names/signatures andwindow.*callback names/JSON shapes are unchanged. No changes to Dartjs_interop.Verification
npm test)bin/typecheckclean (tsc --noEmit)bin/formatclean (Dart)bin/analyzeexits 0bin/update_web_example)