The panel was part of the library screen, and the library screen is unmounted while you are reading. Ctrl+, went nowhere, and the only way to change a setting was to leave the book first. It belongs to the application shell, which is always there, so it now opens over whichever screen is showing and closes back onto it. The screens underneath stop reading the keyboard while it is up: an arrow key meant for the panel was turning the page behind it. Ctrl+F is now claimed by whichever screen is on, so it searches the book while you are reading one instead of doing nothing; the menu item says "Search" rather than "Search Library". The reader also stops listening for it when it closes — the shelf's own listener was being added again on every return from a book and never removed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
4.2 KiB
Liseur Desktop — Design
Liseur Desktop is the desktop counterpart of the Android EPUB reader Liseur. It is a reading application that happens to use web technology, not a website packaged into a desktop window.
The overriding quality goal is snappiness: the app must feel as immediate
as a well-optimized desktop editor such as VS Code. Perceived latency beats
feature completeness. See PERFORMANCE.md for the concrete budgets and
AGENTS.md for the non-negotiable engineering rules.
Product reference
The Android app (~/git/perso/liseur) defines the product model: local-first
EPUB library, calibre-web / Komga / liseur-sync integration, exact Readium
locators, bookmarks/highlights/notes, reading statistics, typography
configuration, sync conflict handling.
Branding — "paper & ink" palette:
| Token | Value |
|---|---|
| Paper | #FFFFFF |
| Ink | #15110C |
| Leather | #7A4A2B / #5C3018 / #FFDCC3 |
| LeatherSoft | #8B5E3C |
| Teal | #3A5F5C / #BDECE6 |
| Logo field | #FCF4ED |
| Dark bg | #17130E |
The reader has no theme of its own: a page is paper #FFFFFF with ink
#1A1A1A, and the reader chrome paints the same white so page and shell read
as one surface. Colour schemes belong to the app chrome, not to the text.
Planned fonts: Publisher default, Literata, Vollkorn, Atkinson Hyperlegible, Inter.
Library UX conventions carried over from Android:
- Filters: All / Downloaded / Unread / Archived (Archived is special)
- Sorts: Recent / Title / Author / Recently added; activating the current sort flips its direction
- Search preserves its query when closed and reopened
- Continue Reading card: cover + label + title + author + progress bar + percent
- Book card: cover + badges (download/server/finished) + title + author
The settings panel belongs to the application shell rather than to the library, and paints over whichever screen is showing. Anything scoped to one screen — the shelf's search, the reader's panels — stays with that screen and claims the menu accelerator only while it is mounted.
Vim mode
An optional keyboard grammar for people who already have one in their fingers, off until Settings turns it on. It is strictly additive: arrows, space, PageUp/PageDown, Escape, F11 and the menu accelerators behave the same whether it is on or off, because a vim key is only claimed when it is bound and nothing else answers to it.
src/renderer/vim/keymap.tsis pure and testable: it encodes a key event as a token, runs the count + multi-key state machine (2j,10G,]]), and holds the two binding tables. Everything else — the screens, the?sheet, the tests — reads those tables, so the keys cannot drift from their own documentation.src/renderer/vim/vim.tsis the reactive glue: the setting signal and a per-screen session that tracks the pending sequence, forgets it after 1.2 s, and never intercepts a keystroke aimed at a text field.- Escape belongs to the app, not to vim: it drops a half-typed sequence if
there is one, otherwise it closes what is open and finally leaves the
book.
qsteps out the same way.
Architecture summary
Three strictly separated layers (details in ARCHITECTURE.md):
- Main process — deliberately small: lifecycle, windows, menu, IPC wiring, utility-process creation. No parsing, DB, scanning, or networking.
- Renderer (SolidJS) — presentation and immediate interaction only.
contextIsolation: true,nodeIntegration: false. Never touches Node, filesystem, SQLite, or the network. - Worker (Electron
utilityProcess) — owns all expensive work: SQLite library queries, EPUB parsing, scanning, and server sync.
The renderer talks to the worker asynchronously through a narrow, typed
preload API (window.liseur.*). Updates are incremental (per-book events),
never full-dataset resends.
Not yet built
Markdown export of annotations, configurable shortcuts, packaging polish (signing, auto-update).