72 lines
5 KiB
Markdown
72 lines
5 KiB
Markdown
# The Mews
|
|
|
|
## Project layout
|
|
|
|
- `app/` — Flutter application (main codebase)
|
|
- `mews-spec/` — Mews Profile definition files
|
|
- `nix-android-sdk/` — Nix flake providing the Android SDK for devbox
|
|
|
|
Flutter version: `3.41.9-sdk-links` (keep in sync with devbox.json).
|
|
|
|
---
|
|
|
|
# Product spec
|
|
|
|
## What it is
|
|
|
|
A Flutter browser/client for Mews Pages (see mews-spec).
|
|
|
|
## Approach
|
|
|
|
Local-first: all data is stored locally in a Drift database. Network operations are secondary and never block local functionality.
|
|
|
|
Posts found on a subscribed page are stored per site with a read mark, so an unread post survives a restart. A post's address identifies it for its lifetime (spec 6.1), so finding it again never clears its mark. Subscribing to a site stores what it already published as read, so a new subscription arrives without a backlog.
|
|
|
|
# Architecture directives
|
|
|
|
Keep files small and focused.
|
|
|
|
## Layer separation
|
|
|
|
- `presentation/` — rendering, input, gestures, navigation.
|
|
- `application/actors/` — isolate-backed actors for CPU/IO work (image processing, export, file I/O).
|
|
- `application/controllers/` — plain coordinators that own signals and handle synchronous UI commands.
|
|
- `database/` — Drift tables, DAOs, queries.
|
|
- Signals expose UI-consumable state derived from the application layer.
|
|
- No business logic in widgets.
|
|
|
|
## State management — Signals
|
|
|
|
- Use the `signals` package for reactive UI state.
|
|
- Widgets observe signals and dispatch actions to the application layer.
|
|
- Derived UI state should be computed, not manually synchronized.
|
|
- Do not duplicate state across multiple signals.
|
|
|
|
## Business logic — Actor Model (`actors` package)
|
|
|
|
Use the `actors` package only where Dart Isolate isolation is genuinely useful: CPU-heavy work, blocking I/O, or tasks that must not block the UI thread. Do not use it for pure UI state or simple synchronous coordination.
|
|
|
|
**Rules for all actors and coordinators:**
|
|
- Model interactions as explicit sealed command types.
|
|
- No business logic in widgets — dispatch commands to the application layer.
|
|
- Keep classes small and focused; avoid large catch-all actors.
|
|
- Pass references explicitly when communication is needed.
|
|
|
|
**Isolate constraint:** The `actors` package runs handlers in separate Dart Isolates. Handlers cannot directly update signals on the main isolate. Isolate-backed actors must return results via message passing; the calling code applies results to signals.
|
|
|
|
## Persistence — Drift
|
|
|
|
- Use `drift` for all tables, DAOs, and queries.
|
|
- Keep database definitions in `database/` separate from UI code.
|
|
|
|
## v1 reader UI
|
|
|
|
The v1 client presents one reader screen:
|
|
|
|
- An address field pinned at the top; it hides when the reader scrolls down and returns when they scroll up or reach the top. Its leading icons open the hub and, on pointer platforms, go back and forward. Its trailing icons reload the page, subscribe to the site, and bookmark the page; subscribing is offered only on a page that carries dated links or declares an Atom feed.
|
|
- A swipe right goes back and a swipe left goes forward through the history stack. The whole empty margin beside the text column accepts the swipe, with a 40 px floor, so the gesture stays clear of selecting text. Touch devices navigate this way; pointer platforms get the address bar's buttons as well.
|
|
- The client's own pages live under the `mews:` scheme. `mews:hub` names them and says what each one holds; `mews:posts`, `mews:bookmarks`, `mews:subscriptions`, `mews:history` and `mews:settings` each carry a strip linking to the others. Bare `mews:` is the hub, and the hub is where a session with no history to return to starts. Each page is built as an ordinary Mews document and rendered by the same renderer as a fetched page, so it joins the history stack and the swipe transitions, and none of them is recorded in history or offered for subscription.
|
|
- `mews:posts` is every post from every subscribed site in one list. The reader can order it by date, by site, or unread first, and limit it to unread posts; those choices ride in the address as a query string, so going back undoes one and a swipe preview shows the page the reader will land on. Unread posts are shown bold, and opening a post marks it read.
|
|
- Subscribed sites are checked once in the background at launch, and whenever the reader asks. Sites are checked one after another rather than at once, and a site that cannot be reached keeps the posts already held and is still recorded as checked, so one site being down never stops the rest.
|
|
- A change is a link too: following `mews:do/<verb>` applies it and rerenders the page in place without adding a history entry. Only a link followed from one of the client's own pages is honored, so a fetched page cannot clear history or change a setting on the reader's behalf (spec 8.1). A form on one of these pages is routed the same way rather than sent to the network.
|
|
- Fonts are bundled, and v1 ships Atkinson Hyperlegible. Adding a font is one TTF in `app/assets/fonts/`, one `fonts` entry in `app/pubspec.yaml`, one option on the settings page, and one label in `app/locales/en-US/main.ftl`.
|