Skip to content

feat(web): web platform TS structural refactor#145

Draft
ddfreiling wants to merge 106 commits into
mainfrom
agents/docs-web-ts-refactor-todo
Draft

feat(web): web platform TS structural refactor#145
ddfreiling wants to merge 106 commits into
mainfrom
agents/docs-web-ts-refactor-todo

Conversation

@ddfreiling
Copy link
Copy Markdown
Member

@ddfreiling ddfreiling commented Jun 3, 2026

ℹ️ 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 in docs/web-ts-refactor-plan.md.

Directory layout (before → after)

web/_scripts/          →  web/src/
  ReadiumReader.ts        ReadiumReader.ts  (thin dispatcher, ~200 lines)
  helpers.ts              index.ts          (webpack entry)
  Audio/                  bridge/           ReadiumBridge.ts  (all window.* calls)
  Epub/                   publication/      PublicationManager.ts
  WebPub/                 navigators/       FlutterEpubNavigator.ts
  TTS/                                      FlutterWebPubNavigator.ts
  extensions/                               FlutterAudioNavigator.ts
                                            FlutterTTSNavigator.ts
                                            FlutterMediaOverlayNavigator.ts
                                            navigatorUtils.ts
                          mediaoverlay/     syncNarration.ts, guidedNavigation.ts
                          decorations/      DecorationController.ts, decorationOverrides.ts
                          preferences/      FlutterEpubPreferences.ts, FlutterWebPubPreferences.ts,
                                            FlutterAudioPreferences.ts, FlutterTTSPreferences.ts
                          model/            ReadiumReaderStatus.ts
                          utils/            ReadiumExtensions.ts, ReadiumPluginLogger.ts,
                                            colors.ts, manifest.ts, iframeInjection.ts,
                                            Peripherals.ts

Key changes:

  • helpers.ts god-file dissolved into focused sub-modules
  • Three Flutter*Navigator wrappers extracted from free init functions (match Flutter*-prefix convention from native)
  • ReadiumBridge.ts is the only module allowed to touch window.* callbacks — injected by dependency, not reached globally
  • PublicationManager owns publication lifecycle state
  • DecorationController owns decoration style/group state
  • Typo fixed: webPubPrefences.tsFlutterWebPubPreferences.ts
  • WebTTSEngine renamed → FlutterTTSNavigator
  • Dead code removed: 7 unreachable delegation methods per navigator wrapper, highlightSelection() export, underlying fields
  • ~270 lines of dead code removed; O(n) noisy debug loop replaced with single summary line

Invariant preserved

The Dart↔JS contract is byte-identical: globalThis.ReadiumReader method names/signatures and window.* callback names/JSON shapes are unchanged. No changes to Dart js_interop.


Verification

  • ✅ All 151 Jest unit tests pass (npm test)
  • bin/typecheck clean (tsc --noEmit)
  • bin/format clean (Dart)
  • bin/analyze exits 0
  • ✅ JS bundle rebuilt (bin/update_web_example)
  • ⚠️ Web/Chrome smoke test still pending — run the example app on Chrome and exercise: EPUB open/navigate/decorate, TTS play/pause/next, Media Overlay sync, audio seek, comic FXL. Per project conventions this must be verified manually before merging.

ddfreiling and others added 30 commits May 23, 2026 16:58
Bumps the three ts-toolkit packages to latest:
  @readium/navigator             2.2.4 → 2.5.5
  @readium/navigator-html-injectables  2.2.1 → 2.4.2
  @readium/shared                2.1.1 → 2.2.0

Adapter changes required by the new API surface:
- EpubNavigator/WebPubNavigator listeners: add stub handlers for
  contentProtection, peripheral, and contextMenu (now required fields).
- EpubPreferences: add scrollPaddingLeft / scrollPaddingRight fields
  introduced in 2.5.x.
- helpers.ts: note that highlightSelection() is experimental and will
  be superseded once ts-toolkit PR #209 (Decorator API) merges.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Brings the web platform to parity with iOS and Android for timebased
playback features.  All three share the same Dart API and event streams
(onTimebasedPlayerStateChanged, onTextLocatorChanged) already wired up
for audiobooks on native.

AudioNavigator (audiobooks)
- New Audio/audioNavigator.ts wraps @readium/navigator AudioNavigator.
- Introduces AudioLocatorMapper — an optional hook applied by every
  state-emitting listener (play, pause, positionChanged, trackEnded,
  error, stalled) so all state transitions carry correctly-mapped
  locators, not just periodic poll events.
- openPublication now initialises AudioNavigator for audiobook profiles
  (was a TODO stub).

TTS (Audio/ttsNavigator.ts + Audio/ttsPreferences.ts)
- WebTTSEngine walks EPUB text via PublicationContentIterator +
  HTMLResourceContentIterator and speaks each TextElement via the
  browser SpeechSynthesis API.
- Sub-utterance onboundary granularity (word/sentence) with 100 ms
  throttle; degrades silently when unavailable (Firefox, some mobile).
- Voice list: { identifier, name, language, networkRequired } —
  gender/quality enriched by ReaderTTSVoiceUtils in the platform
  interface via readium/speech voice data.
- Per-language voice map and global voice override.
- TODO(#209): visual word/sentence highlight deferred until ts-toolkit
  Decorator API (PR #209) merges.

Media Overlay / Sync Narration
  (Audio/syncNarration.ts + Audio/mediaOverlayNavigator.ts)
- Parses Readium Sync Narration JSON alternates
  (application/vnd.readium.narration+json) into SyncNarrationItem[].
- Builds a synthetic audiobook reading order (one Link per unique audio
  file) and reuses AudioNavigator via AudioLocatorMapper to emit
  text-based locators on every state event — matching iOS/Android.
- audioSeekBy wired through AudioNavigator.jump(seconds).

Dart wiring
- js_publication_channel.dart: JS interop + static wrappers for the
  full timebased playback API (play, pause, resume, stop, next,
  previous, seekBy, setAudioPreferences, ttsEnable,
  ttsGetAvailableVoices, ttsSetVoice, ttsSetPreferences, audioEnable).
- flutter_readium_web.dart: replaces UnimplementedError stubs for all
  playback methods with real JsPublicationChannel calls.
- readium_webview.dart: registers updateTimebasedPlayerState JS export
  so AudioNavigator/TTS state events reach the Dart stream.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Webpack 5's default splitChunks optimisation, combined with the
pre-split @readium/* packages (locale and ReadiumCSS variant files),
was generating 30+ chunk files alongside the main bundle.  These had
to be committed and served as separate assets.

Fix: add splitChunks: false and output.asyncChunks: false to
webpack.config.js.  asyncChunks: false (Webpack 5.84+) inlines all
dynamic imports — including the ReadiumCSS injection files that
@readium/navigator loads lazily — into the single bundle.  The
trade-off is a slightly larger readiumReader.js (~1.2 MiB dev build)
with no practical penalty since the webview loads everything up front.

Deleted all stale chunk files from lib/helpers/.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Link.fromJsonArray: guard against non-Map elements (e.g. JSON-LD
  @context strings embedded in link arrays) instead of hard-casting,
  preventing a TypeError on manifests that follow the JSON-LD convention.
- MediaType: add readiumNarration constant for
  application/vnd.readium.narration+json (Readium Sync Narration format).
- Publication.containsMediaOverlays: extend to recognise both
  vnd.syncnarr+json and vnd.readium.narration+json alternates, so
  isAudioBook returns true for Sync Narration EPUBs and the example app's
  play-button dispatch routes them to audioEnable correctly.
- FlutterReadiumWebPlugin: override setLogLevel (was throwing
  UnimplementedError; the web layer needs only ReadiumLog.setLevel).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PlayerControlsBloc: add SeekRelative event dispatching audioSeekBy so
  ±10 s relative seeks can be triggered from the UI.
- PlayerControlsWidget: show replay-10 / forward-10 buttons when audio
  (audiobook or Media Overlay) is active; buttons carry ValueKeys for
  marionette automation.
- webManifestList.json: replace placeholder URLs with a Nota EPUB and
  audio-only webpub for smoke-testing TTS and Media Overlay on web.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a 'build_web' task that runs bin/update_web_example (builds the TS
bundle and copies readiumReader.js into example/web/) and an
'example (web)' launch configuration that targets Chrome, running the
build task as a pre-launch step.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
browser scrolling in publication emits updates with 60hz
timing between calls during init was all wrong before, now uses a ready promise for when first track is loaded. Should avoid observed race-conditions
ddfreiling and others added 28 commits June 3, 2026 18:16
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>
@ddfreiling ddfreiling changed the title feat(web): web platform feature parity + TS structural refactor feat(web): web platform TS structural refactor Jun 3, 2026
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>
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.

2 participants