Cherry-picked and re-verified the applicable subset of
julianshen/readest@fa1b74a0 (its "address PR #15 bot reviews" commit). The
fork-only AI-annotation-tool change was dropped — readest has no 'ai' toolbar
tool. Each logic fix is covered by a failing-first test.
- TTS position sequence is now an app-wide monotonic counter, so a fresh
TTSController (constructed per `tts-speak`) isn't dropped by consumers holding
`lastSequenceSeen` from a prior session.
- share.ts only swallows AbortError (user cancel); other failures — e.g.
NotAllowedError when a quick action fires without a user gesture — fall back to
the clipboard so the text still reaches the user.
- document.isTxt tolerates MIME params (text/plain;charset=utf-8), uppercase
extensions (BOOK.TXT), and a nameless Blob, so a TXT can't slip onto the
non-text path and yield a null book.
- updater getNightlyPlatformKey matches x86_64/aarch64 explicitly; a 32-bit or
otherwise unknown arch yields no nightly instead of mis-routing to aarch64.
- UpdaterWindow downloadWithProgress resolves on tauriDownload completion even
when Content-Length is absent (no more hang on portable/AppImage/Android).
- nightly_update.rs uses async tokio::fs::read in the async command.
- nightly.yml: serialize runs via a concurrency group (no cancel) and
persist-credentials:false on checkouts.
- edge TTS route only emits the word-boundary header when it fits under ~8KB;
oversized values get dropped by proxies, and the client falls back to [].
- RSVPOverlay drops the contradictory aria-disabled on the functional rate
button (it opens the pace picker).
- nightly verify harness handles artifact stream errors instead of crashing.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(reader): open TXT files shared via "Open with" by converting to EPUB
The Android "Open with Readest" (VIEW intent) transient path hands the
reader the original .txt file (its filePath points at the content:// URI),
unlike the managed library which stores the already-converted EPUB. The
DocumentLoader had no branch for a raw .txt, so open() returned
{ book: null } and initViewState crashed with
"TypeError: Cannot read properties of null (reading 'metadata')",
leaving the user stuck on the library splash.
Add an isTxt() check that converts the raw .txt to EPUB in-memory (the
same TxtToEpubConverter the import path runs) and parses that. The
converter emits a .epub-named file, so the importer's own
DocumentLoader.open() on the converted file is unaffected.
Verified on-device (emulator, warm + cold start): the TXT now opens and
renders in the reader instead of crashing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(settings): allow adjusting highlight opacity in e-ink mode
Drop the isEink prop that disabled the highlight Opacity slider under
e-ink. Opacity is still meaningful on e-ink, so let users change it.
Removes the prop from HighlightColorsEditor, its ColorPanel call site,
and the test render helper.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(send): mock fetch to fix flaky article conversion test
The article/page conversion paths fetch a favicon + author image for the
synthetic cover via globalThis.fetch. In jsdom that hit the real network:
a live fetch to the sample URL can hang up to faviconFetcher's 6s timeout,
exceeding the 5s test timeout and intermittently failing the suite. Stub
fetch so the cover falls back to its initial-letter tile (the pattern other
tests in this suite already use). Article test: ~5003ms hang -> ~80ms.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(agent): update annotation-share-toolbar memory
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Add a 'Reference Pages' reading progress style that shows physical book
page numbers in the footer progress info:
- When the book carries a page list (EPUB3 nav page-list or EPUB2 NCX
pageList — foliate-js already parses both and resolves the current
pageItem on relocate; it was just never consumed), display the current
page label and use the highest numeric label as the total, so a
trailing roman-numeral index page can't corrupt the total (#672).
- When the book has none, a per-book 'Reference Page Count' input
appears; the reading fraction is mapped linearly onto the entered
count (#4542). The count is saved per book only and never propagates
to global view settings.
- Falls back to percentage display when neither source is available.
Verified with the sample books from #672: Caleb's Crossing (EPUB3
page-list, 419 pages) and Count Zero (EPUB2 NCX pageList/page-map,
346 pages — chapter 2 lands exactly on page 22 per its page-map), plus
a stripped no-pagelist copy for the manual-count path (175/350 at 50%).
Closes#672Closes#4542
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* perf(epub): add native EPUB parser in Rust
Introduce a Rust-side EPUB pre-parser exposing three Tauri commands:
* parse_epub_metadata - title/author/cover + partialMD5 in one
shot, for the import hot path
* parse_epub_full - OPF + nav.xhtml + toc.ncx bytes plus a
manifest size table, for the reader open
hot path
* extract_epub_cover_full - full-resolution cover bytes, for the
lock-screen wallpaper writer
All three avoid ferrying multi-MB blobs across the JS<->Rust IPC
boundary. Cover bytes returned by parse_epub_metadata are downscaled
to a webview-friendly JPEG when the long edge exceeds the library
thumbnail size.
No JS callers yet -- wired up in the following commits.
* perf(import): use native EPUB parser and downscale covers on Tauri targets
On Tauri (desktop/iOS/Android), importBook now forwards EPUB
metadata + cover extraction to the Rust parse_epub_metadata
command and reuses the partialMD5 it returns, skipping the
foliate-js full archive parse and the second pass over the file
for hashing.
As a side effect, the cover written to cover.png is downscaled
to a webview-friendly JPEG (long edge <= 512px), shrinking the
on-disk thumbnail from multi-MB to ~30-60KB per book. To keep
the lock-screen wallpaper feature unchanged, useAutoSaveBookCover
now pulls the original full-resolution cover via the Rust
extract_epub_cover_full command instead of copying the (now
downscaled) cover.png; falls back to the thumbnail when the
native path is unavailable.
Web targets and non-EPUB formats keep the existing path.
* perf(reader): prefetch EPUB OPF/nav from Rust on book open
When opening an EPUB on Tauri targets, DocumentLoader now calls the
Rust parse_epub_full command up-front to pull the OPF, EPUB3 nav,
NCX and the central-directory size map in a single IPC. The
foliate-js zip loader is wrapped so that loadText() of these
entries (and a synthetic META-INF/container.xml) is served from
that in-memory cache without inflating through zip.js, while
all other assets keep flowing through the original loader.
A small in-flight dedupe is added to the spine-text loader so the
nav pipeline (loadText + createDocument back-to-back on the same
href) doesn't pay for two zip.js inflate calls per chapter on
first open.
Reader store / app service plumbing: readerStore.openBook now
resolves an absolute on-disk path via the new
appService.resolveNativeBookFilePath / bookService.resolveNativeBookFilePath
helper and threads it into DocumentLoader as nativeFilePath so
the prefetch can fire. Web targets, non-EPUB formats and books
without a managed/external on-disk path skip the prefetch and
take the original code path.
* perf(nav): parallelize section scans and memoize fragment lookups
computeBookNav now processes sections via Promise.all instead of
a sequential for-loop, and within each section issues loadText()
and createDocument() concurrently. Combined with the in-flight
loadText dedupe added to the zip loader, each chapter pays for a
single zip inflate per nav build, and the inflates of different
chapters overlap.
enrichTocFromNavElements is restructured into two concurrent
phases: a cheap '<nav' substring filter on the inflated text, and
a parsed-document walk for the survivors. Most chapters fall out
in phase 1 without ever being parsed.
In fragments.ts, calculateFragmentSize now consults a
per-section position cache (makeFragmentPositionCache) so the
N-fragment loop is O(N) over the chapter HTML instead of O(N²).
A small isCfiAddressable guard is added to skip elements that
foliate-js's CFI generator can't address (documentElement, body
itself, detached nodes, nodes outside <body>) — these previously
threw and spammed console.warn for every fragment, now they
silently fall back to the section CFI.
* perf(import): use native MOBI/AZW/AZW3 parser on Tauri targets
On Tauri (desktop/iOS/Android), importBook now forwards
MOBI/AZW/AZW3/PRC metadata + cover extraction to the Rust
parse_mobi_metadata command and reuses the partialMD5 it returns,
skipping the foliate-js full-buffer parse and the second pass over
the file for hashing. Mirrors the existing EPUB native fast-path
added in e3fc4767 — bookService tries EPUB first, then MOBI; both
bridges fall back to the foliate-js DocumentLoader when the native
path is unavailable (web target, parse error, format mismatch).
The new mobi_parser is built on the mobi crate (KF7+KF8 reader,
zero JS-side touch). It reads title, author, publisher, ISBN, ASIN,
publish date, language, subjects and description from the MobiHeader
+ EXTH records, resolves the EXTH 201 cover offset against the PDB
image-record table (with ThumbOffset / first-image fallbacks), and
strips KindleGen's HTML wrapping in EXTH 103 so the description goes
into the library DB as plain text. The parsed cover is funneled
through the same maybe_resize_cover path as EPUB, so MOBI library
thumbnails are also clamped to a 512px-long-edge JPEG.
Cover-resize / partialMD5 / RawCoverImage are extracted into a new
parser_common module shared between epub_parser and mobi_parser, so
a single tweak (e.g. raising the thumbnail target) applies to every
native importer and the partialMD5 implementation can't drift between
the two paths (a divergent algorithm would silently re-import every
existing book under a new hash on the first run).
Web targets and non-Kindle formats keep the existing path.
* test(tauri): verify native Rust EPUB parser parity with foliate-js
Add a Tauri WebView parity suite (epub-parser-parity.tauri.test.ts) that
cross-checks the native Rust parser against foliate-js on the same fixtures:
parse_epub_metadata / parse_epub_full (title, author, language, identifier,
publisher, published, subjects, partialMD5, OPF + per-entry size table), and
that opening with the native prefetch produces the same BookDoc and
computeBookNav (TOC) output as the pure-JS path.
Fix a parity divergence the suite caught: the Rust OPF parser mapped
dcterms:modified onto `published`, but foliate-js keeps them separate and
leaves `published` empty -- so EPUB3 books carrying only the mandatory
dcterms:modified got a bogus publication date on the native import path. Map
only dc:date now; add regression tests.
Test infra:
- vitest.tauri.config.mts: add optimizeDeps (mirroring vitest.browser.config)
so foliate-js-importing tauri tests load -- otherwise esbuild's dep scan
can't resolve '@pdfjs/pdf.min.mjs', pre-bundling is skipped, and the CJS
deps fail to import ("Importing a module script failed").
- capabilities-extra/webdriver.json: fix __test__ -> __tests__ fs scope typo
so import tests can open fixtures under src/__tests__/.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(import): foliate-js owns EPUB/MOBI metadata via standalone extractors
Rust contributes only the mechanical work that's expensive on a
WebView — partialMD5, the downscaled cover, and (for EPUB) the raw
OPF bytes Rust already had to read for cover resolution. Metadata
extraction is delegated to foliate-js's two new standalone entry
points (`parseEpubMetadataFromXML`, `readMobiMetadata`) so the
import-path BookDoc and the reader-path BookDoc share a single
parser implementation.
EPUB
- `parse_epub_metadata` returns
`{ partialMd5, cover, coverMime, opfPath, opfBytes }`. OPF bytes
are a free byproduct of the cover-resolution scan.
- `tryNativeParseEpub` runs `parseEpubMetadataFromXML` on the OPF
bytes and assembles a lightweight BookDoc stub (metadata +
getCover). The importer doesn't drive `DocumentLoader.open()`, so
no zip central-directory scan, no nav/ncx inflate, no spine walk.
- `coverMime` is preserved so `bookService.importBook`'s
`cover.type === 'image/svg+xml'` branch still routes SVG covers
through svg2png.
MOBI / AZW / AZW3 / PRC
- `parse_mobi_metadata` returns `{ partialMd5, cover, coverMime }`.
`tryNativeParseMobi` runs foliate's `readMobiMetadata` on the
same File, which uses `MOBI.open(file, { metadataOnly: true })`
to parse PalmDB + MobiHeader + EXTH and short-circuit before the
MOBI6 / KF8 init() that walks every text record.
- `Book.metadata.identifier` is foliate's `mobi.uid.toString()`
(PalmDB UID), the canonical MOBI identifier the reader path uses.
bookService.importBook
- EPUB and MOBI native branches consume the bridge's BookDoc stub
directly. The stub's `getCover()` returns the Rust-downscaled
blob, falling back to foliate's own `getCover` thunk when Rust
didn't extract a cover.
Other
- Drop the unused `base64` Rust dependency: cover bytes go over IPC
as `Vec<u8>` (Tauri 2 transports them natively, like opfBytes /
navBytes / ncxBytes).
- Drop the `nativePrefetch` option on `DocumentLoaderOptions`; no
caller passes it. `nativeFilePath` keeps driving `parse_epub_full`
on the open hot path.
Tests
- vitest.tauri parity test asserts byte-equal partialMD5, cover
presence parity, OPF bytes that decode to a real `<package>`
document, and that `parseEpubMetadataFromXML` on those bytes
produces the same user-visible metadata fields (title / author /
language / identifier / published) as `DocumentLoader.open()`.
* test(tauri): add War and Peace MOBI fixture for native parser parity
The .tauri parser-parity suite previously had no .mobi/.azw3 asset, so the native MOBI parser (metadata + EXTH cover resolution) was uncovered. Adds a real KF8 MOBI ("War and Peace") to enable MOBI parity coverage against foliate-js.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(foliate-js): bump submodule to readest/foliate-js main (91191ca)
Replaces the ad-hoc 02f435a with the merged main commit 91191ca, which lands the standalone OPF/MOBI metadata extractors (parseEpubMetadataFromXML, readMobiMetadata) the import fast-path depends on (foliate#19), plus the RTL multi-view rect-mapper fix (foliate#20). The extractor code is byte-identical to 02f435a, so the bridges are unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Huang Xin <chrox.huang@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Some EPUBs (e.g. downloaded from Baidu Netdisk) have their first few bytes
corrupted while the rest of the archive is still well-formed. The zip spec
locates archives via the End-of-Central-Directory record at the file tail,
so a corrupted local file header signature does not actually invalidate the
file. When the leading magic check fails, fall back to scanning the last
~64 KiB for the EOCD signature (PK\x05\x06); if present, treat the file
as a zip and let zip.js read it normally.
Some EPUB writers (e.g., "ebookredo") emit a non-standard local file
header signature on the first entry — bytes like PK\x03\x02 instead of
PK\x03\x04. The archive is still readable via the End of Central
Directory record, and @zip.js/zip.js handles it without complaint, but
DocumentLoader.isZip() rejected the file at the magic-bytes gate before
zip.js ever ran. The user saw "Unsupported or corrupted book file" on
a perfectly readable EPUB.
Drop the strict 4th-byte equality check. PK\x03 alone identifies a
local file header — no other ZIP record signature starts that way — so
loosening the check is safe and aligns with what tolerant ZIP parsers
already do.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Try an exact ZIP entry match first, then fall back to a case-insensitive lookup for EPUB resources when archive entry casing differs from manifest paths.
Keep ambiguous case-only duplicates exact-match only, and add a regression test that opens a real EPUB through DocumentLoader.
* refactor(toc): cache TOC + section fragments per book
Moves the TOC regrouping and section-fragment computation out of
foliate-js/epub.js #updateSubItems into the readest client as
computeBookNav / hydrateBookNav in utils/toc.ts. The result is
persisted to Books/{hash}/nav.json — capturing the book's full
navigable structure (TOC hierarchy + sections with hierarchical
fragments). Compute once, persist locally, hydrate on subsequent
opens. Designed to serve current human-facing navigation (TOC
sidebar, progress math) and future agentic navigation (LLM-driven
seeking by structural location).
Versioned by BOOK_NAV_VERSION for forward invalidation. Existing
books regenerate transparently on next open.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update worktree scripts
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(hardcover): add one-way Hardcover sync integration
- Add HardcoverClient with ISBN/title-based book lookup, progress push, and note sync
- Add HardcoverSyncMapStore for persistent local mapping of note IDs to Hardcover journal IDs
- Add GraphQL queries/mutations for Hardcover API (insert/update/recreate journal entries)
- Add DB migration for hardcover_note_mappings table (schema: hardcover-sync)
- Add HardcoverSettings dialog component (connect/disconnect/enable toggle)
- Add useHardcoverSync hook wired in Annotator for push-notes and push-progress events
- Add Hardcover Sync section in BookMenu with per-book toggle (persisted in BookConfig)
- Add HardcoverSettings type and DEFAULT_HARDCOVER_SETTINGS to system settings
- Add hardcoverSyncEnabled per-book flag to BookConfig
- Mount HardcoverSettingsWindow in Reader alongside KOSync and Readwise windows
Sync is one-way (Readest → Hardcover), manual-action only, per-book opt-in.
Supports idempotent note sync: insert new, skip unchanged, update changed, recreate on stale remote ID.
* fix(hardcover): proxy API calls server-side to fix CORS, normalize Bearer token
- Add /api/hardcover/graphql Next.js route that proxies POST requests server-side,
bypassing CORS restrictions (Hardcover API has no Access-Control-Allow-Origin header)
- On Tauri (desktop), calls Hardcover directly; on web, routes through the proxy
- Normalize token in HardcoverClient: accept raw JWT or 'Bearer <jwt>' format
- Update helper text to point to hardcover.app → Settings → API
* fix(hardcover): surface note-sync no-ops and harden book resolution
- Show toast feedback when book data is still loading, Hardcover is not configured,
or the current book has no annotations/excerpts to sync
- Show an explicit info toast when note sync finds no new changes
- Parse Hardcover search results in the current hits/document response shape
- Resolve note sync through ensureBookInLibrary for parity with progress sync
- Add console logging for note/progress sync failures
* debug(hardcover): add runtime instrumentation for note sync
* feat(metadata): keep identifier stable and store ISBN separately
- Add dedicated metadata.isbn field
- Expose ISBN as its own editable field in book metadata
- Preserve identifier semantics for existing source IDs and hashes
- Route metadata auto-retrieval ISBN handling through the new field
- Prefer metadata.isbn for Hardcover matching
* fix(hardcover): avoid wasm sqlite for note mappings on web
- Store Hardcover note sync mappings in localStorage on web
- Keep sqlite-backed mappings for desktop/native environments
- Remove the web-only database dependency from manual note sync
* fix(hardcover): dedupe notes by payload hash across unstable note IDs
- Add payload-hash lookup in HardcoverSyncMapStore
- Reuse existing journal mapping when payload already synced
- Prevent duplicate insertions when note IDs change or duplicate locally
* fix(hardcover): avoid duplicate quote export when annotation note exists
- Detect excerpt+annotation pairs for the same highlight
- Skip standalone excerpt export when annotation has note text
- Keep annotation export as the single source of truth
* docs(hardcover): add consolidated change summary for review
* fix(hardcover): suppress excerpt export by CFI when annotation note exists
* fix(hardcover): suppress empty-note annotation duplicates when note exists
* fix(hardcover): deduplicate notes by text and cfi base node
* refactor(hardcover): optimize sync performance, add rate limiting and clean up debug tools
* chore: remove dev-only change log from main
* test(hardcover): add unit tests for sync mapping and client logic
* chore: custom deployment and UI fixes
* fix(hardcover): use timestamptz for accurate annotation time
* fix(hardcover): use date scalar and RFC 3339 formatting for journal entries
* Revert "chore: custom deployment and UI fixes"
This reverts commit 0329aba7129d1e1ebf2c663804b8fba9a9f87b91.
* Fix hardcover progress dates and surface imported ISBNs
* Fix Hardcover currently reading sync
* fix(hardcover): avoid promoting note sync status
* style(hardcover): apply prettier formatting
* test(hardcover): fix strict TypeScript assertions
* test(hardcover): harden sync regression coverage
* test(hardcover): fix lint and formatting regressions
* fix(hardcover): narrow note dedupe range matching
* refactor(hardcover): extract note dedupe helpers
* refactor(isbn): extract metadata normalization helpers
* feat(hardcover): improve synced quote formatting
* feat(ai): add dependencies
* chore: bump zod version to default version
* feat(ai): define types and model constants
* feat(ai): ollama provider for local LLM
* feat(ai): implement openrouter provider for cloud models
* feat(settings): register ai settings panel in global dialog
* refactor(ai): expose provider factory and service layer entry point
* test(ai): add unit tests for the providers
* test(ai): add unit tests for the providers
* feat(ai): settings panel for ai configurations
* refactor(ai): rewrite aipanel with autosave and greyed out disabled state
* fix: remove unused onClose prop from aipanel
* test(ai): update mock data
* refactor(ai): remove models
* refactor: use centralised defaults in system defaults
* chore(ai): remove comments
* fix(ai): merge default ai settings on load to prevent undefined values
* refactor(ai): rewrite settings panel with autosave and model input
* feat(ai): add ai tab with simplified highlighting
* feat(sidebar): render AIAssistant for ai tab
* feat(ai): add chat UI
* feat(ai); add chat service with RAG context
* feat(ai): temp debug logger
* feat(ai): add RAG service
* feat(ai): add text chunking utility
* feat(ai): add structured method
* feat(ai): add chatstructured method
* feat(ai): add rag types nd structured output schema
* feat(ai): add aistore, indexdb, bm25
* fix: update lock file
* feat(ai): update types for AI SDK v5
* feat(ai): add placeholder gateway model constants
* refactor(ai): update OllamaProvider for AI SDK
* feat(ai): add native gateway provider
* refactor(ai): update provider exports
* refactor(ai): use streamText from AI SDK
* refactor(ai): use embed from AI sdk
* refactor(ai): update provider factory exports
* feat(ai): add AI Elements and shadcn components
* config: add shadcn component config
* deps: add AI SDK and AI Elements dependencies
* config: add ai packages to transpilePackages
* refactor(ai): remove OpenRouterProvider and old tests
* feat(ai):add assistant-ui components
* feat(ai): add TauriChatAdapter for assistant-ui runtime
* refactor(ai): remove ai-elements components
* dep(ai): install assistant-ui and update next config
* chore(ai): export adapters from service index
* feat(ui): enhance ui components for assistant integration
* feat(settings): migrate ai settings to gateway
* feat(sidebar): integrate assistant-ui
* feat: add ai settings toggle to sidebar content
* feat: conditionally show ai tab in sidebar navigation
* feat: update ai model constants for cheaper options
* feat: add gateway provider with proxied embedding
* feat: add timeouts to ollama provider health checks
* feat: add retry logic to rag service embeddings
* feat: add error recovery to ai store
* feat: add ai feature tests
* feat: add ai api endpoints
* feat: add proxied gateway embedding provider
* feat: add ai runtime utilities
* feat: add ai retry utilities
* feat: add tauri env example template
* feat: add web env example template
* chore: add env
* feat(ai): update models and pricing, remove GLM-4.7-FlashX
* feat(ai): improve system prompt with official headings and no numeric citations
* feat(ai): optimize system prompt for tauri chat
* feat(ui): refine ai chat UI and relocate sources
* feat(ui): update ai settings panel with model pricing and custom model support
* feat(ai): add custom model support to ai settings
* test(ai): update constants tests for removed model
* feat(api): implement ai chat proxy route
* feat(api): implement ai embedding proxy route
* feat(ai): implement ai gateway health check and proxy logic
* feat(ai): simplify proxied embedding provider
* feat(ui): improve markdown text rendering
* feat(ui): add input group component
* test(ai): update ai provider tests
* feat(ai): add pageNumber to text chunk schema
* feat(ai): implement page-based chunking with 1500 char formula
* feat(ai): bump db to v2 and add store reset migration
* feat(ai): transition rag pipeline to page level spoiler filtering
* feat(ai): overhaul readest persona and antijailbreak prompt
* feat(ai): update tauri adapter for page tracking and persona
* chore(ai): export aiStore and logger from core index
* feat(reader): integrate page tracking and manual index reset
* feat(ui): add re-index action and reset logic to chat
* chore: sync pnpm lockfile with ai dependencies
* feat(utils): add browser-safe file utilities for web builds
* refactor(utils): use dynamic tauri fs import to prevent web crashes
* refactor(services): defer osType call to init() for web compatibility
* refactor(services): import RemoteFile from file.web
* refactor(services): import ClosableFile from file.web
* fix(libs): cast Entry to any for getData access
* fix(annotator): cast Overlayer to any for bubble access
* refactor(ai): replace SparklesIcon with BookOpenIcon for index prompt
* test(ai): add pageNumber to TextChunk mocks
* test(ai): fix chunkSection signature in tests
* chore: update files
* fix(ai): prevent useLocalRuntime crash when adapter is null
* refactor: optimize annotator overlay drawing
* feat: stabilize AI assistant runtime and adapter
* refactor: improve document zip loader type safety
* feat: update tauri chat adapter for dynamic options
* fix: restore architecture comments and refine platform properties
* build: update lockfile with assistant-ui patch
* fix(library): patch @assistant-ui/react for runtime initialization
* build: update dependencies in readest-app
* build: update root dependencies and patch configuration
* fix(ai): patch @assistant-ui/react for thread deletion and runtime init
* fix(ai): update assistant-ui patch with dist guards and deletion fallback
* build: sync lockfile with assistant-ui patch updates
* chore(env): update .gitignore by removing .env files from it
* chore(env): update .gitignore by adding .env.local
* chore(env): update .gitignore by adding .env*.local
* fix: restore static osType import
* chore: sync submodules with upstream/main
* refactor: remove redundant file.web module and revert import
* chore: update pnpm-lock.yaml
* refactor: revert guards
* refactor; remove deprecated codes and extract prompts.ts
* refactor(ai): remove unused ragservice exports
* refactor: remove unused ollama and embedding models
* refactor: remove unused type
* test: remove test for the now deleted constants
* refactor: remove unused export
* style: fix ui component formatting
* style: fix core and style file formatting
* test: fix broken ai provider import
* fix: typescript error
* fix: add eslint disable command
* fix(deps): remove unused ai sdk provider util after v6 ai sdk migration
* fix(patch): add lookbehind regex patch
* feat(dep): upgrade vercel ai sdk to v6 and ai-sdk-ollama to v3
* chore: update lockfile for vercel ai sdk v6
* refactor(ai): remove EmbeddingModel generic for ai sdk v6
* refactor(ai): remove EmbeddingModel generic for ai sdk v6
* test(ai): update mock to use embeddingModel
* fix(patch): add lookbehind regex patch for email autolinks in markdown
* refactor(ai): use ai sdk v6 syntax
* fix: prettier formatting
* chore: revert cargo.lock
* fix(ai): update proxied embedding model to v3 spec
* feat(ai): add aiconversation types for chat persistence
* feat(ai): add conversation/message indexeddb and crud operations
* feat(ai): create aiChatStore zustand store for chat state management
* feat(notebook): add notebookactivetab state for Notes/AI
* refactor(ai): refine conversation and message types for persistence
* feat(types): add notebookActiveTab to ReadSettings type
* chore: update deps
* feat: add notebookactive tab default value
* feat: add hook for ai chat
* feat: update left side panel with history/chat icon
* feat: integrate ChatHistoryView into sidebar content
* feat: create UI for managing AI chat history
* feat: implement persistent history with assistant-ui adapter
* feat: create tab navigation component for notes and AI
* feat: add tab navigation and AI assistant view
* feat: update header to display active tab title
* fix: formatting
* feat: remove title and update new chat button
* fix: formatting
* fix: revert tooltip and styling
* feat: implement cross-platform ask dialog bridge
* feat(ai): preserve history during ui clear & use native dialogs
* fix: align notebook navigation height with sidebar tabs
* fix(ai): add missing dependency to handleDeleteConversation hook
* docs: update PROJECT.md with session highlights
* chore: delete projectmd
* chore: update package.json and lock file
* chore: update package.json
* chore: remove patch
* chore: upgrade react types to 19 and show ai features only in development mode for now
---------
Co-authored-by: Huang Xin <chrox.huang@gmail.com>
* feat(sync): implement KOReader progress synchronization
This commit introduces a comprehensive feature to synchronize reading progress with a KOReader sync server. It includes a compatibility layer to handle discrepancies between Readest's CFI-based progress and KOReader's XPointer/page-based progress, primarily using the `percentage` field as a common ground.
Key additions include:
- A new settings panel under "KOReader Sync" for server configuration, authentication, and sync strategy management (e.g., prompt on conflict, always use latest).
- A conflict resolution dialog that appears when remote progress differs significantly from local progress, allowing the user to choose which version to keep.
- A client-side `useKOSync` hook to manage the entire synchronization lifecycle, including API calls, state management, and conflict resolution logic.
- A new API endpoint `/api/kosync` that acts as a secure proxy to the user-configured KOReader sync server, handling authentication and forwarding requests.
- Logic to differentiate between paginated (PDF/CBZ) and reflowable (EPUB) formats, using page numbers for paginated files where possible and falling back to percentage for reliability.
- Spanish translations for all UI elements related to the KOReader sync feature.
- Addition of `uuid` package to generate a unique `device_id` for sync purposes.
Refactor:
- The `debounce` utility has been improved to include `flush` and `cancel` methods, allowing for more precise control over debounced function execution, which is now used in the sync hook.
* fix(kosync): add support for converting between XPointer and CFI in progress synchronization
* fix(kosync): update navigation method to use select instead of goTo for paginated formats
* fix(kosync): refactor synchronization settings and improve conflict resolution handling
* fix(kosync): add event dispatcher for flushing KOReader synchronization
* fix(sync): handle xpointer in a different section, fix styling
* i18n: update translations
---------
Co-authored-by: Huang Xin <chrox.huang@gmail.com>
* Add H and L keyboard shortcuts for back and forward navigation, consistent with Vimium
* Fallback bookmark text to page number if current page has no text, closes#51
* Add delete and edit buttons for highlights and booknotes in the sidebar, closes#57
* Each book now has its own sidebar default tab