Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
b955bdb
docs: spec filesystem cloud provider
Gnathonic Apr 23, 2026
91bf41a
docs: plan filesystem cloud provider implementation
Gnathonic Apr 23, 2026
3a9f4a3
feat(sync): add 'filesystem' to ProviderType union
Gnathonic Apr 23, 2026
6655941
feat(sync): recognize filesystem provider in active-provider detection
Gnathonic Apr 23, 2026
01080e7
feat(sync): add filesystem slot to provider-manager status records
Gnathonic Apr 23, 2026
2056999
feat(filesystem-provider): add feature-detect helper
Gnathonic Apr 23, 2026
042fe39
feat(filesystem-provider): add path helpers
Gnathonic Apr 23, 2026
87b1a52
feat(filesystem-provider): add dedicated IDB handle store
Gnathonic Apr 23, 2026
952ec45
feat(filesystem-provider): add cache wrapper
Gnathonic Apr 23, 2026
b0b06e3
feat(filesystem-provider): scaffold provider with auth/restore/logout
Gnathonic Apr 23, 2026
6621e41
fix(filesystem-provider): remove unused ts-expect-error in handle-sto…
Gnathonic Apr 23, 2026
509b171
feat(filesystem-provider): implement list/upload/download/delete/rename
Gnathonic Apr 23, 2026
27a86a5
feat(sync): lazy-load filesystem provider on startup
Gnathonic Apr 23, 2026
1de4775
feat(sync): include filesystem in exhaustive provider-type branches
Gnathonic Apr 23, 2026
a66ef9b
feat(filesystem-provider): surface Local Folder option in CloudView
Gnathonic Apr 23, 2026
33f5015
fix(download-queue): add main-thread branch for non-worker providers
Gnathonic Apr 23, 2026
0ef3709
fix(filesystem-provider): detect NotFoundError via DOMException.name
Gnathonic Apr 23, 2026
5fdbf2e
fix(backup-queue): add main-thread branch for non-worker upload provi…
Gnathonic Apr 23, 2026
0fdf628
fix(backup-queue): keep filesystem upload inside WorkerPool throttling
Gnathonic Apr 23, 2026
a453803
fix(download-queue): route filesystem downloads through WorkerPool
Gnathonic Apr 23, 2026
921c22d
docs: spec OneDrive cloud provider
Gnathonic Apr 24, 2026
fd9f56a
docs: plan OneDrive cloud provider implementation
Gnathonic Apr 24, 2026
c21807a
chore: add @azure/msal-browser for OneDrive provider
Gnathonic Apr 24, 2026
65c634b
feat(sync): add 'onedrive' to ProviderType union
Gnathonic Apr 24, 2026
a94530f
feat(sync): recognize onedrive in active-provider detection
Gnathonic Apr 24, 2026
15c78cb
feat(sync): add onedrive slot to provider-manager status records
Gnathonic Apr 24, 2026
e9d1785
feat(onedrive): add provider constants
Gnathonic Apr 24, 2026
8bbff1a
feat(onedrive): add upload-session chunking helpers
Gnathonic Apr 24, 2026
26b3d87
feat(onedrive): add Microsoft Graph REST client
Gnathonic Apr 24, 2026
52eddd7
feat(onedrive): add MSAL-based token manager
Gnathonic Apr 24, 2026
7572c8a
feat(onedrive): add worker-core adapter for Graph uploads and downloads
Gnathonic Apr 24, 2026
b3268db
feat(sync): register onedrive-core in worker-core registry
Gnathonic Apr 24, 2026
156800d
feat(onedrive): add cache wrapper
Gnathonic Apr 24, 2026
61e88eb
feat(onedrive): implement SyncProvider — auth, list, upload, download…
Gnathonic Apr 24, 2026
8d07c74
fix(onedrive): respect Uint8Array byteOffset/byteLength in upload pay…
Gnathonic Apr 24, 2026
a5b3ccb
feat(sync): lazy-load onedrive provider on startup
Gnathonic Apr 24, 2026
e8fe2de
feat(onedrive): surface OneDrive option in CloudView
Gnathonic Apr 24, 2026
1a72997
chore: prettier format for onedrive spec
Gnathonic Apr 24, 2026
f974a7f
fix(onedrive): handle popup-flow redirect and recover from interactio…
Gnathonic Apr 27, 2026
d14bfcb
fix(onedrive): use static callback page for popup redirect
Gnathonic Apr 27, 2026
b768c8f
fix(onedrive): share PKCE verifier between opener and popup via local…
Gnathonic Apr 27, 2026
fafb19f
fix(onedrive): switch from popup to redirect-based MSAL flow
Gnathonic Apr 27, 2026
cb23d7b
fix(onedrive): anchor uploads under /mokuro-reader/
Gnathonic Apr 28, 2026
49afe2d
fix(onedrive): paginate listChildren via @odata.nextLink
Gnathonic Jun 2, 2026
0a8c0dd
fix(onedrive): surface deep-folder 404s instead of returning a partia…
Gnathonic Jun 2, 2026
827dc8f
fix(filesystem): prevent data loss when renaming a folder into itself
Gnathonic Jun 2, 2026
bb27928
fix(catalog): show display names for filesystem/onedrive providers
Gnathonic Jun 2, 2026
5fc6c23
Merge branch 'develop' into feat/filesystem-provider
Gnathonic Jun 9, 2026
324aac6
Merge remote-tracking branch 'origin/develop' into feat/filesystem-pr…
Gnathonic Jun 14, 2026
07803b9
Merge remote-tracking branch 'origin/develop' into feat/filesystem-pr…
Gnathonic Jul 5, 2026
20bc461
docs: deployment-readiness plan for onedrive/filesystem providers
Gnathonic Jul 5, 2026
a6d41d6
fix(sync): shared isSyncableFile — libraries.json syncs, jpg sidecars…
Gnathonic Jul 5, 2026
90a0e00
fix(onedrive): logout clears state before redirect; handle interactio…
Gnathonic Jul 5, 2026
36338fc
fix(onedrive): surface init errors, clear provider key before logout …
Gnathonic Jul 5, 2026
5d97a7c
feat(onedrive): typed ProviderError classification; 401 flags reconnect
Gnathonic Jul 5, 2026
9e410e1
fix(onedrive): coalesce folder creation; tolerate 409 from concurrent…
Gnathonic Jul 5, 2026
ec0b168
feat(onedrive): chunk upload retry/resume via nextExpectedRanges, tim…
Gnathonic Jul 5, 2026
7f2be16
fix(filesystem): typed errors, dead-handle recovery, honest quota
Gnathonic Jul 5, 2026
ab361fe
fix(filesystem): idempotent renameFile retry (source gone, destinatio…
Gnathonic Jul 5, 2026
378cd67
feat(sync): removeDirectoryIfEmpty for onedrive and filesystem providers
Gnathonic Jul 5, 2026
0cc815e
feat(ui): gate actions on reconnect states; shared provider labels; c…
Gnathonic Jul 5, 2026
5dc4573
docs: document VITE_ONEDRIVE_CLIENT_ID and Azure app registration
Gnathonic Jul 5, 2026
475b1eb
feat(ui): OneDrive above WebDAV in provider list; mention Mokuro-Bunko
Gnathonic Jul 5, 2026
d4bbb8f
feat(ui): OneDrive copy says persistent login, matching MEGA/WebDAV
Gnathonic Jul 5, 2026
f9238de
feat(gdrive): gesture-retry re-auth + verified browser-aware popup help
Gnathonic Jul 5, 2026
8171d35
feat(gdrive): reconnect at moments that matter; post-reauth sync guar…
Gnathonic Jul 5, 2026
65bc5e9
revert(sync): keep libraries.json out of the sync filter — feature un…
Gnathonic Jul 5, 2026
c568f07
fix(reader): restore solid circle background on quick actions FAB
Gnathonic Jul 5, 2026
ca669d4
refactor: remove the unshipped libraries feature
Gnathonic Jul 6, 2026
40d1a06
Merge pull request #250 from Gnathonic/feat/filesystem-provider
Gnathonic Jul 6, 2026
acabc6a
fix(reader): enforce the no-overlap invariant on per-line auto layout
Gnathonic Jul 6, 2026
7fec5d1
docs: design spec for paged page gap + ctrl+shift+scroll (#234)
Gnathonic Jul 6, 2026
cb19ae0
docs: implementation plan for page gap (#234)
Gnathonic Jul 6, 2026
73659f6
feat(settings): add pagedGap setting
Gnathonic Jul 6, 2026
e2656db
feat(reader): spreadContentSize includes the pair gap in fit math
Gnathonic Jul 6, 2026
48ec6fd
feat(reader): gap-adjust wheel intent helpers
Gnathonic Jul 6, 2026
4407dae
feat(reader): render pagedGap between spread pages (#234)
Gnathonic Jul 6, 2026
96cf248
feat(reader): ctrl+shift+scroll adjusts pagedGap in paged mode
Gnathonic Jul 6, 2026
5f3fe69
feat(reader): ctrl+shift+scroll adjusts dividers in continuous modes
Gnathonic Jul 6, 2026
eb0af46
feat(settings): page gap slider + chord hints
Gnathonic Jul 6, 2026
4470f58
docs: record gap-adjust wheel chord in input contracts
Gnathonic Jul 6, 2026
19f098a
chore: project verify skill (e2e drive recipe)
Gnathonic Jul 6, 2026
49e6e07
Merge pull request #251 from Gnathonic/feat/page-gap-234
Gnathonic Jul 6, 2026
047cb4b
fix(onedrive): don't report completed uploads as failed; retry sessio…
Gnathonic Jul 6, 2026
5b10dcd
fix(filesystem): guard rename against clobbering and destructive cleanup
Gnathonic Jul 6, 2026
80d8c4d
fix(export): local volume export no longer drops embedded OCR data
Gnathonic Jul 6, 2026
0ce08f2
fix(gdrive): prevent duplicate concurrent token requests
Gnathonic Jul 6, 2026
ef8f846
chore(release): 1.8.0 — OneDrive/Local Folder providers, page gap, gd…
Gnathonic Jul 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .claude/skills/verify/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
name: verify
description: Verify reader features end-to-end by importing a synthetic volume through the real upload modal and driving the reader with Playwright. Use when a change needs runtime observation in the actual app (not unit tests).
---

# Verifying mokuro-reader changes end-to-end

## Launch

```bash
npm run dev -- --port 5199 --strictPort # dedicated port; NEVER bare 5173 (other worktrees own it)
```

Browser: Playwright from `node_modules`, executablePath
`~/.cache/ms-playwright/chromium-1208/chrome-linux64/chrome` (note `chrome-linux64`, not `chrome-linux`).
Headless is fine. Don't drive the user's Chrome (stale service worker, port collisions, hidden-window rAF freeze).

## Synthetic volume fixture

The import needs a `.mokuro` + matching `.cbz` (same basename):

- Pages: ImageMagick solid-color portraits with page numbers:
`magick -size 1400x2000 xc:"#d8f0ff" -bordercolor black -border 8 -resize 1400x2000\! -gravity center -pointsize 400 -annotate 0 "1" 001.png`
- `vol1.cbz`: `zip -j vol1.cbz *.png`
- `vol1.mokuro` (all fields required): `{version, title, title_uuid, volume, volume_uuid, chars, pages: [{version, img_width, img_height, blocks: [], img_path}]}`

## Import through the real UI

1. Navbar icon cluster: `div.flex.gap-5 > button` — nth(2) is the upload icon → opens the Import modal (`<dialog>`).
2. **Gotcha:** `getByRole()` fails to match buttons inside the flowbite modal `<dialog>` — use CSS locators (`page.locator('dialog button', { hasText: ... })`) or `page.evaluate` with `textContent.trim()` matching.
3. `waitForEvent('filechooser')` + click the `choose files` button, `setFiles([vol1.mokuro, vol1.cbz])`, then click the exact-text `Import` button.
4. Wait for `text=GapTest` in the catalog, click series → volume → reader (`#/reader/<title>/<uuid>`).

## Driving the reader

- RTL default: `ArrowLeft` = forward. Auto view mode pairs pages (cover shows alone first).
- Page elements: `#manga-panel [data-page-index]` — measure `getBoundingClientRect()` for gap/scale/pan assertions (natural page width 1400 → scale = rect.width/1400).
- Wheel with modifiers: `page.keyboard.down('Control'/'Shift')` + `page.mouse.wheel(0, ±100)` — Playwright applies held modifiers to the wheel event. CDP does NOT swap deltaY→deltaX under shift the way real input does; handlers reading `deltaY || deltaX` cover both.
- Reader toast: search text nodes for the notification string.
- Reader settings drawer: `button.reader-hud.right-3` (there are 3 `.reader-hud` buttons; `.first()` is the page-counter). Toggle labels (e.g. "Continuous scroll") are clickable once the drawer opens; close by clicking far from the drawer.
- Persisted settings check: `JSON.parse(localStorage.getItem('profiles'))[currentProfile]`.

## Known noise

- `[pageerror] Unexpected token '<'` on first dev-server load — pre-existing dev artifact, unrelated to features.
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Google Drive sync (optional — omit to disable)
VITE_GDRIVE_CLIENT_ID=
VITE_GDRIVE_API_KEY=

# OneDrive sync (optional — omit to hide the OneDrive option)
# Azure AD app registration with the deploy origin as an SPA redirect URI.
VITE_ONEDRIVE_CLIENT_ID=
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

## [1.8.0] - 2026-07-05

### Added

- New Local Folder provider: Chromium-only bulk export, or direct access to sync-only files (#250)
- OneDrive added as a cloud sync provider (#250)
- Paged mode pages can now have a gap between them (#234)
- Ctrl/Cmd+Shift+Scroll live-adjusts the page gap (#234)

### Fixed

- Quick Actions button's solid background restored (#249)
- Fixed a bug where reconnecting Google Drive could revert synced progress
- File type matching on Drive and MEGA is now case-insensitive
- Fixed overlapping text lines in auto mode

### Changed

- Expired Google Drive sessions now show a Reconnect button
- Google Drive reconnect works reliably everywhere now, no popup setup needed
- WebDAV now mentions Mokuro-Bunko server compatibility

## [1.7.5] - 2026-07-05

### Added
Expand Down
11 changes: 9 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,14 +250,21 @@ Night mode applies a CSS `filter` to `<dialog>` elements (see `app.html`). The `

## Environment Variables

Create a `.env.local` file for Google Drive integration:
Create a `.env.local` file for cloud provider integration:

```
VITE_GDRIVE_CLIENT_ID=your_client_id
VITE_GDRIVE_API_KEY=your_api_key
VITE_ONEDRIVE_CLIENT_ID=your_azure_app_client_id
```

These are only required for Google Drive sync. MEGA and WebDAV don't require env vars.
- `VITE_GDRIVE_*`: required only for Google Drive sync.
- `VITE_ONEDRIVE_CLIENT_ID`: required only for OneDrive sync. Register an
Azure AD app (any Microsoft account tenant, "common" authority) and add the
deploy origin as a **Single-page application** redirect URI. Scopes used:
`Files.ReadWrite`, `offline_access`, `User.Read`. When unset, the OneDrive
option is hidden from the cloud screen.
- MEGA, WebDAV, and Local Folder require no env vars.

## Testing

Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,13 +183,18 @@ npm test

### Environment Variables

For Google Drive integration, create a `.env` file:
For Google Drive and/or OneDrive integration, create a `.env` file:

```env
VITE_GDRIVE_CLIENT_ID=your_client_id
VITE_GDRIVE_API_KEY=your_api_key
VITE_ONEDRIVE_CLIENT_ID=your_azure_app_client_id
```

For OneDrive, register an Azure AD app ("common" authority) and add your deploy
origin as a **Single-page application** redirect URI. When unset, the OneDrive
option is hidden. MEGA, WebDAV, and Local Folder need no configuration.

## 💬 Community

Wanna chat with the devs? Share your hopes, dreams, and issues (with Mokuro Reader specifically)? Come join the [Mokuro Reader Discord](https://discord.gg/AU5pjjSQBw)!
Expand Down
9 changes: 8 additions & 1 deletion docs/INPUT-CONTRACTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,14 @@ Zoom settles carry a `SettleReason` (`zoom-controller.ts`): only
| Pinch survivor | keeps panning | ignored until fresh press |
| Tap commit | immediate | deferred (300 ms) |
| Swipe-to-flip | yes (mobile setting, edge-gated) | no (panning is the scroll) |
| Wheel | zoom or camera glide | zoom or (native/strip) scroll |
| Wheel | zoom, gap, or camera glide | zoom, gap, or (native/strip) scroll |

Ctrl/meta+shift+wheel is the page-gap adjustment chord on every surface
(paged writes `pagedGap`; scroll readers write `scrollGap` and sync
`pageDividers`), checked before the zoom intent. Binding rule: wheel combos
with a native browser meaning keep that meaning tuned for the reader
(ctrl+wheel zooms, shift+wheel stays a horizontal pan); the gap chord is
deliberately one no browser binds.

## Testing

Expand Down
Loading
Loading