* feat(sync): cross-device dictionary sync
Custom MDict / StarDict / DICT / SLOB dictionaries now sync across
signed-in devices via the replica layer.
- Store mutations publish replica rows with field-level LWW + tombstones.
- Re-importing the same content (renamed or after delete) preserves the
user's label and reincarnates the server row instead of duplicating.
- Manifest commits after binary upload so other devices never see a row
whose binaries aren't on cloud storage yet.
- Pull-side orchestrator creates a placeholder dict, queues the binaries
via TransferManager, and clears the unavailable flag on completion.
- Toast copy branches by transfer kind so dict uploads don't read
"Book uploaded".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sync): boot pull and binary download path
- Defer the boot pull until TransferManager is initialized so download
enqueues aren't dropped.
- Auto-persist the local dict store after applyRemoteDictionary; otherwise
the next loadCustomDictionaries wipes the in-memory rows.
- Boot pull passes since=null so a device whose cursor advanced past
unpersisted rows can still recover.
- Skip pulling when not authenticated instead of logging
"SyncError: Not authenticated" on every boot of a signed-out device.
- downloadReplicaFile resolves the destination against the kind's base
dir; binaries previously landed at the literal lfp and openFile then
failed with "File not found".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(sync): per-page useReplicaPull hook
Lifts the boot-time pull out of EnvContext into a hook each page mounts
for the kinds it needs: useReplicaPull({ kinds: ['dictionary'] }).
Library page and the shared Reader component opt in. The hook fires 10s
after page load (so feature mounts hydrate first), dedups per-kind
across navigation, and releases the slot on failure so a later mount
can retry. Future kinds plug into the hook's per-kind switch.
Also closes two refresh-loop bugs:
- Hydrate the dict store from settings BEFORE the apply loop, so the
auto-persist doesn't clobber persisted rows that the in-memory store
hadn't yet read. Library-page refresh was the visible victim.
- Skip the download queue when every manifest file is already on disk
under the resolved bundle dir. Refreshing is a no-op; partial-
download recovery still queues because some files would be missing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(reader): play sound:// links in MDict definitions via MDD lookup
MDX entries reference audio resources with `<a href="sound://name.ext">`.
Until now those anchors fell through to the browser, which tried to
navigate to an invalid scheme and did nothing useful.
Wire each `sound://` anchor inside the rendered MDX body to:
- preventDefault + stopPropagation (so the parent card's tap-to-expand
doesn't fire),
- look up the path in every companion `.mdd` until one returns bytes
(js-mdict's `MDD.locateBytes` auto-normalizes the leading separator),
- wrap the bytes in a Blob and play via `new Audio(URL.createObjectURL)`,
- cache the resolved URL on the anchor so subsequent clicks reuse it,
with the URL tracked for revocation in `dispose()`.
Note: many MW-style dictionaries use `.spx` (Speex) which Chromium and
Safari don't natively decode — the lookup will succeed but playback may
fail silently. Other formats (mp3, wav, ogg vorbis) play fine.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(dict): improve MDict rendering and dictionary management
Builds on the sound:// fix to round out MDict rendering and tighten
the dictionary settings panel.
MDict provider:
- Follow MDict-specific URL schemes inside the rendered HTML:
`sound://path` plays via Audio (with a deprecation toast for `.spx`
whose codec no major browser decodes), and `entry://word` /
`bword://word` forward to ctx.onNavigate so the popup re-looks-up
the target. Cycle-bounded (5 hops) `@@@LINK=<word>` content-level
redirects are followed transparently, so entries that are pure
redirect strings (e.g. "questions" → "question") render the
canonical entry instead of the literal redirect text.
- Render the body inside a shadow root so each dict's CSS stays
scoped — `<link rel="stylesheet">` references are resolved against
the companion .mdd, loose .css files imported alongside the bundle
are read at init, and `url(...)` refs inside both are rewritten to
blob URLs sourced from the MDD (covers sound icons, background
images, @font-face sources). The body is tagged `data-dict-kind="mdict"`
for downstream targeting.
- A baseline app-level stylesheet (`getDictStyles`) is injected into
every shadow root with theme-adaptive `mix-blend-mode` for `<a>`
background icons / `<a> img` (multiply on light, screen on dark);
isDarkMode is forwarded via the lookup context.
- `<img src="/path">` is now treated as MDD-relative (the tightened
IMG_SRC_PROTOCOL_RX skips schemes / protocol-relative only); a
fallback retry strips the leading slash for bundles that store the
resource without it.
- The auto-prepended light-DOM headword `<h1>` is hidden when the
dict body either leads with a same-text element (any tag — covers
`<h3 class="entry_name">`, etc.) or contains an `<h1>` with the same
trimmed text anywhere (covers wrapper-div-then-h1 layouts).
Dictionary management:
- Importing a dict whose name matches an existing one now replaces
it in place, preserving the slot in providerOrder and inheriting
the previous enabled flag. The .css extension is added to the file
picker, and loose .css files imported alongside .mdx/.mdd are
bundled with the dictionary regardless of stem-match.
- The settings panel gains an Edit mode (parity with Delete mode):
trailing pencil button on imported dicts and custom web searches
opens a rename modal. Edit and Delete are mutually exclusive.
Below 400px, the Edit/Delete labels collapse to icons only.
Card UX:
- The card's tap-to-expand handler now walks `composedPath()` so
clicks on anchors / buttons / images inside the shadow root no
longer fold the card.
i18n:
- Translations added for new strings across 33 locales.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends the dictionary system beyond StarDict/MDict with two more open
formats and a pluggable Web Search tier so users can fall back to online
sources when their offline bundles miss a word.
Formats:
- DICT (dictd, RFC 2229): .index + .dict.dz bundles. Shared DictZip
parsing with StarDict via new dictZip.ts helper.
- Slob (Aard 2): self-contained .slob containers, zlib-compressed
utf-8 entries; non-zlib/non-utf-8 bundles flagged unsupported at
import.
Web Search:
- Built-in templates for Google, Urban Dictionary, Merriam-Webster
(seeded into providerOrder, disabled by default).
- Custom URL templates via %WORD% placeholder, URL-encoded at
substitution; entries persist in settings.webSearches.
- V1 renders an "Open in {{name}}" external link (iframe embedding is
blocked by every major target site's X-Frame-Options).
UI:
- CustomDictionaries panel: flat outline-primary buttons for Import /
Add Web Search, end-aligned type badges for a uniform column,
hover states, compact tips block.
- Dictionary popup: bottom-right Manage icon (tooltip-only) deep-links
into Settings → Language → Dictionaries; rounded-corner clipping fix
on the tab strip.
File picker accepts .index and .slob; importer recognizes DICT and
Slob bundles and reads bundle metadata for friendly names.
Tests cover DICT/Slob readers and providers with real freedict-eng-nld
fixtures, web search substitution + provider rendering, and the new
store CRUD for web searches.
Closes#4038
Relocate the toggle from the library settings menu to the Behavior settings
panel under the Device section. Add a matching command palette entry so the
setting remains discoverable via search.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(reader): custom dictionaries (StarDict + MDict)
Adds a pluggable dictionary provider system. Built-in Wiktionary +
Wikipedia (extracted from the legacy single-popup model into a tabbed
shell) plus user-importable StarDict (.ifo/.idx/.dict.dz/.syn) and MDict
(.mdx/.mdd) bundles.
Settings → Language → Dictionaries: import / enable / drag-reorder /
delete (delete-mode toggle mirrors CustomFonts). Drag uses @dnd-kit with
pointer/touch/keyboard sensors. Reader popup: tabbed UI, per-tab lookup
history, scroll-aware back button, last-active tab persists. Tabs grow
to natural width up to a cap, truncate with ellipsis when crowded; phantom
bold layer prevents layout shift on focus.
StarDict reader is self-contained (replaces unused foliate-js/dict.js),
with lazy random-access binary search on .idx + .syn (~420 KB Int32Array
of byte offsets vs ~10 MB of parsed JS objects), lazy DictZip chunk
decompression via fflate streaming Inflate (cmudict/eng-nld both
chunked), and an optional .idx.offsets sidecar generated at import to
skip the init scan. Cmudict 105K-entry init drops from ~10 MB heap and
2 MB IO to ~1.7 MB heap and ~500 KB IO.
MDict uses the readest/js-mdict fork (added as a submodule, consumed via
tsconfig paths so deps stay out of readest's pnpm-lock) which adds a
browser-friendly BlobScanner reading via blob.slice(...).arrayBuffer()
— slices are lazy when the Blob is Readest's NativeFile / RemoteFile.
encrypt=2 (key-info-only) MDX is fully supported via ripemd128-based
mdxDecrypt; encrypt=1 (record-block, needs user passcode) surfaces as
unsupported.
Wikipedia annotation tool removed (Wikipedia is now a tab inside the
unified popup); legacy WiktionaryPopup / WikipediaPopup deleted. Stale
annotationQuickAction === 'wikipedia' coerced to 'dictionary' on settings
load. iOS-friendly external links: skip target="_blank" on Tauri to
avoid the WebView's "open externally" path triggering the shell scope
error; the popup's container click handler routes through openUrl.
i18n: 939 strings translated across 31 locales (30 base keys + CLDR
plural forms for ar/he/sl/pl/ru/uk/ro/it/pt/fr/es).
Test fixtures bundled: cmudict (StarDict, 105K entries), eng-nld
(StarDict, smaller), and a Longman Phrasal Verbs MDX (encrypt=2).
3396 unit tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(stardict): resolve fflate from js-mdict source for vitest + Next
js-mdict is consumed as TypeScript source via tsconfig paths from
packages/js-mdict/src/. Its sources `import 'fflate'` directly, but
fflate is only installed under apps/readest-app/node_modules — so
vite's import-analysis (and Next/Turbopack's resolver) can't find
fflate when it walks up from the redirected js-mdict source location.
CI's fresh checkout exposes this; locally a leftover
packages/js-mdict/node_modules/fflate from the old workspace setup
masked it.
Pin fflate resolution to apps/readest-app/node_modules/fflate in:
- vitest.config.mts (Vite alias)
- next.config.mjs (webpack alias + Turbopack resolveAlias — Turbopack
rejects absolute paths so use a project-relative form)
- tsconfig.json (paths entry so tsgo / Biome see it)
Verified by deleting packages/js-mdict/node_modules locally and
re-running pnpm test (3396 pass), pnpm lint (clean), and both
pnpm build-web and a tauri-platform Next build (clean).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ui): restore highlight options layout and clean up color name editing
Restore the pre-#3741 layout for both the AnnotationPopup highlight
options row and the HighlightColorsEditor settings panel.
HighlightOptions: re-add `justify-between` on the outer container and
remove `flex-1` from the color strip so the gap between the style box
and color strip responds to the number of colors.
HighlightColorsEditor: restore `grid-cols-3 sm:grid-cols-5` grid,
remove always-visible name inputs, and add a click-to-edit popover on
each color circle with hover tooltip for the label.
Add a browser screenshot test that renders the real AnnotationPopup
component with Tailwind CSS and compares against baseline PNGs for
5, 10, and 15 colors.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The translate.toil.cc relay that backs the yandex provider is currently
unavailable. Introduce a `disabled` flag on TranslationProvider and
filter disabled providers out of both `getTranslators()` and
`getTranslator()` so the settings panel, translator popup, and the
fallback logic in `useTranslator` all agree the provider doesn't exist —
no per-callsite changes needed. Flip the flag back (or delete the line)
on `yandexProvider` to re-enable once the upstream is healthy.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add highlight color label fields
* fix: add default highlight label sync fields
* fix: add highlight prefs sync helpers
* fix: add highlight color name inputs
* fix: persist and sync highlight color names
* fix: add highlight color label helpers
* fix: add long press highlight label preview
* fix: pull highlight color prefs during library sync
* test: cover highlight color label helpers
* fix: widen highlight color name inputs
* fix: show highlight names on hover and touch hold
* fix: prevent highlight name input overlap
* fix: improve highlight name input responsiveness
* fix: support drag scrolling for highlight colors
* fix: batch custom color and label updates
* fix: serialize highlight prefs saves
* fix: align color strip drag and restore color clicks
* fix: translate default highlight color labels
* refactor: remove highlight preference sync wiring
* fix: align highlight option i18n with existing pattern
* fix: remove redundant english highlight keys
* fix: support raw and normalized highlight label keys
* fix: use underscore translator in highlight options
* fix: translate custom highlight color labels in editor
* refactor: simplify highlight settings persistence
* fix: maintianer review
* refactor: simplify highlight prefs save and clean up editor
- Drop the skipUserColors/skipLabels options from handleHighlightPrefsChange
and always persist both arrays; the flags only masked a no-op caller.
- Type handleHighlightColorsChange with Record<HighlightColor, string>
instead of typeof so the signature reads clearly.
- Remove the always-true `|| true` guard around the custom colors section.
- Stop wrapping user-typed custom color labels with _(), matching the
built-in color inputs and keeping the input value equal to what the
user typed.
* refactor: couple highlight labels to their colors
Replace the parallel `highlightColorLabels: Record<string, string>` map
with label storage that lives next to each color. This removes the hex
key normalization layer and its whole class of orphan/case-drift bugs.
Data model:
userHighlightColors: string[] -> UserHighlightColor[]
({ hex, label? })
highlightColorLabels: Record<string, string> -> (removed)
defaultHighlightLabels:
Partial<Record<DefaultHighlightColor, string>>
A `migrateHighlightColorPrefs` helper runs during `loadSettings` and
handles both the shipped `string[]` layout and the draft-build
`highlightColorLabels` layout: hex-keyed labels attach to matching user
colors, name-keyed labels move into `defaultHighlightLabels`. Malformed
entries are dropped.
Editor:
- Label inputs commit on blur (Enter also commits), so typing a long
label no longer fires `setSettings`/`saveSettings` on every
keystroke. A small `LabelInput` component owns the draft state and
syncs if the prop changes externally.
- Three explicit callbacks (`onCustomHighlightColorsChange`,
`onUserHighlightColorsChange`, `onDefaultHighlightLabelsChange`)
replace the previous single callback with opaque skip flags.
Picker:
- Extracted a reusable `useDragScroll` hook (mouse only, 6px
threshold, 120ms click suppression) from the inline state machine
in `HighlightOptions`. The picker drops to ~280 lines.
- Long-press preview stays touch/pen only. It now reads labels via
`getHighlightColorLabel` (which returns `undefined` when no user
label is set), letting the component layer decide whether to fall
back to a translated default name.
i18n:
- Default color names ('red' | 'yellow' | 'green' | 'blue' | 'violet')
are registered once at module scope via `stubTranslation` and
translated at the picker layer through `useTranslation`. User-typed
labels are never run through `_()`, so what the user types is what
the editor shows.
Tests:
- `annotator-util.test.ts`: rewritten around the new helper contract
(user label -> undefined fallback) and case-insensitive hex matching.
- `settings-highlight-migration.test.ts`: new, covers legacy
`string[]`, already-migrated entries, malformed hex filtering, and
the two draft-label fold paths.
---------
Co-authored-by: Huang Xin <chrox.huang@gmail.com>
Hidden easter egg: re-clicking the active light mode sun icon
or dark mode moon icon activates an ambient atmosphere with a
komorebi leaf shadow video overlay and forest background audio. Audio
adapts to theme: birds for light mode, crickets for dark mode.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a new TTS tab in settings with media metadata update frequency control
(sentence/paragraph/chapter) to reduce Bluetooth notification spam, and move
TTS highlight settings from Color tab to the new TTS tab. Also add a highlight
opacity setting with live preview in the Color tab.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* added current time to desktop bar
* added time prototype to footer, needs code cleanup and settings toggle
* fixed settings toggle, added translations and code cleanup
* added battery support and moved Statusbar to own Component
* #3306 added 24 hour clock support
* refactored code styling and getting rid of any type in battery hook
* Add battery info for Tauri Apps
---------
Co-authored-by: Huang Xin <chrox.huang@gmail.com>
* feat: enable linking to settings items
* feat: integrate command palette with global state and styles
* feat: add command pallete ui and fuzzy search
* feat: add command palette with global state and styles
* feat: add command registry and search dependencies
* fix: open command palette with shortcuts in reader page
---------
Co-authored-by: Huang Xin <chrox.huang@gmail.com>
* feat(highlight): extend types and constants for custom hex colours
* feat(highlight): update colour to support hex strings
* refactor(annotator): update component to accept hex colours
* feat(highlight): add colour picker with max 4 custom colors
* feat(settings): add custom colour editor with limit
* refactor: custom highlight colors can only be modified in settings
---------
Co-authored-by: Huang Xin <chrox.huang@gmail.com>
* refactor(ai): remove chat icons from conversation list items
* feat(ai): add scroll-to-bottom button with animations and dynamic spacer
* fix(ai): align connection status icon with text in settings
* feat(ai): scroll to bottom on chat panel open and improve scroll behavior
* feat(ai): add loading overlay with blur transition for chat history
* feat(ai): pass loading state through component chain for history loading
* 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>