Skip to content

Decisions

Short log of confirmed decisions and deviations from SPEC.md. Newest at the bottom. Format: D-n · date · topic: decision. Why / notes.

Open questions (SPEC §9)

All proposed defaults from WORKPLAN.md Part C were accepted by the owner on 2026-10-05. Any of them can be revisited when the chunk that implements it starts.

  • D-1 · 2026-10-05 · Task types (Q-1): One-off → carry_on; anchored routine → expires; flexible routine → window; tasks with a due date may opt into deadline; workflow steps inherit from their template (default carry_on). Users can create custom types in v1. Types are config rows, so custom types cost little.
  • D-2 · 2026-10-05 · Day end (Q-2): Configurable per user, default 04:00.
  • D-3 · 2026-10-05 · Flexible routine window (Q-3): Fixed calendar periods (week with configurable week start, calendar month), not rolling windows.
  • D-4 · 2026-10-05 · Assign to group member (Q-4): Not in v1; nullable assignee_user_id kept in the schema.
  • D-5 · 2026-10-05 · Reminders on HTTP (Q-5): In-app (SSE) always; optional ntfy backend in Phase 6; Web Push when served over HTTPS.
  • D-6 · 2026-10-05 · Skipped/deleted prerequisite (Q-6): Auto-unblock with a visible note on the dependent task; wont_do treated like skipped.
  • D-7 · 2026-10-05 · Routine workflow while previous unfinished (Q-7): Don't spawn; log the occurrence as skipped ("previous run still open"). Overridable per routine.
  • D-8 · 2026-10-05 · Planning ritual default (Q-8): Evening. One reminder at planning time, then a dismissible banner in Today until planned. No repeated nagging.
  • D-9 · 2026-10-05 · Overbooking (Q-9): Warn (non-blocking).
  • D-10 · 2026-10-05 · Mealie (Q-10): Watch one configured list; create the shopping task automatically; one task per meal.
  • D-11 · 2026-10-05 · Meal blocked by shopping (Q-11): Out of scope; manual dependency possible.
  • D-12 · 2026-10-05 · Shareable workflow templates (Q-12): JSON export/import (3b.5 if time allows, else Phase 6).
  • D-13 · 2026-10-05 · Printing (Q-13): A4/browser print first; printer model to be named before chunk 7.4.
  • D-14 · 2026-10-05 · Reconcile link (Q-14): Signed link valid 48 h, scoped to completing the printed items only.
  • D-15 · 2026-10-05 · Child profiles (Q-15): Postponed until the need is real.
  • D-16 · 2026-10-05 · Journal encryption (Q-16): No app-level encryption in v1; host/disk trusted (documented in README). Revisit later.
  • D-17 · 2026-10-05 · Historical import (Q-17): CSV import for metric entries (date, value).
  • D-18 · 2026-10-05 · Difficulty & estimates (Q-18): 3 levels stored as 1–3; estimates in minutes via preset chips 5/15/30/60/120 plus custom entry.
  • D-19 · 2026-10-05 · Auto-suggest (Q-19): Suggest only, on request in the planning wizard.
  • D-20 · 2026-10-05 · Pomodoro (Q-20): 25/5/15, long break after 4, configurable per user; closed-app notifications only via Web Push/ntfy.
  • D-21 · 2026-10-05 · Goal progress (Q-21): Derived by default, manual override per goal.
  • D-22 · 2026-10-05 · Eisenhower (Q-22): 0–3 scores, quadrant threshold ≥ 2; dragging changes the value only when crossing the threshold.
  • D-23 · 2026-10-05 · Units & locale (Q-23): Per-user locale and unit system (metric/kg default); canonical storage in kg, converted for display.

Architecture

  • D-24 · 2026-10-05 · Architecture baseline: WORKPLAN Part A was accepted ("go"), with the deviations below made while building the MVP.
  • D-25 · 2026-10-05 · Svelte + Vite SPA instead of SvelteKit: The app is a pure SPA embedded in the binary, so SvelteKit's routing, SSR and adapters add nothing. A ~60-line history router keeps the bundle smaller (MVP: about 41 KB gzipped in total). Revisit if routing gets complex.
  • D-26 · 2026-10-05 · Makefile instead of justfile: just isn't installed on the dev machine, and make is available everywhere.
  • D-27 · 2026-10-05 · Well-known crate majors: axum 0.8, sqlx 0.8, argon2 0.5, rand 0.8, tower-http 0.6, ulid 1. Newer majors exist (sqlx 0.9, argon2 0.6, rand 0.10, tower-http 0.7, ulid 3); upgrade them in one dedicated chunk rather than mixing it with feature work.
  • D-28 · 2026-10-05 · Dockerfile and compose at repo root: This lets docker compose up -d work right after cloning. The Dockerfile avoids BuildKit-only features (cache mounts), so it builds with the classic builder too; dependencies are cached with a stub-crate layer instead.
  • D-29 · 2026-10-05 · Container runs as root, in a scratch image: A bind-mounted ./data that Docker creates is owned by root, so a non-root default would fail on first start. The image contains only the binary and CA certs. Users can set user: in compose if they create data/ themselves.
  • D-30 · 2026-10-05 · First-run setup page: If INITIAL_ADMIN_* isn't set, the first visitor creates the admin in the UI. No generated password is printed to logs (spec §8: never log credentials).
  • D-31 · 2026-10-05 · One active day entry per task: Planning a task on another day moves its entry. Rollover moves carried entries to the new day. The history (planned, moved, snoozed, carried, missed) lives in task_events.
  • D-32 · 2026-10-05 · Rollover trigger: Runs per user from a 60 s background tick and lazily on /sync and /days/{date}, guarded by users.last_rollover_date. If the server was down, it catches up in one step (carry count += days missed).
  • D-33 · 2026-10-05 · Sync model: The client does one full /sync, then applies SSE change events. On every (re)connect (hello event) it calls /sync?since=<rev>. A full sync sends open tasks plus the last 30 days of history; older data is fetched on demand later.
  • D-34 · 2026-10-05 · OpenAPI (done 2026-10-06): generated by utoipa 5 (not 6, per D-27) from #[utoipa::path] annotations on every handler. Served at /api/v1/openapi.json (public, no auth); /api/docs is a small page using the Scalar viewer from a CDN, so the binary ships no viewer assets. A test fails if a route is missing from the document. Cost: +0.5 MB binary.
  • D-35 · 2026-10-05 · New users start in UTC: The web app adopts the browser's timezone once on first login (with a toast); it can be changed in Settings.
  • D-36 · 2026-10-06 · Own drag-and-drop layer (web/src/lib/dnd.svelte.ts, ~300 lines, no library): built on pointer events, so mouse, touch and pen share one code path. Mouse drags start after 5 px. Touch drags start on a 350 ms long-press, so normal scrolling never starts a drag, or immediately from the grip handle. Keyboard: grip + arrow keys. A floating copy of the row follows the pointer; drop targets are any element (droppable) or ordered lists (dropList); the window and the timeline auto-scroll near their edges. Chosen over svelte-dnd-action, which handles list-to-list moves but not free-form targets like timeline slots or sidebar links. Cost: +3 KB gzip.
  • D-37 · 2026-10-06 · Timeline on the day view: a 24 h grid (15-minute snapping, overlapping blocks side by side). Dropping a task sets start_time; the bottom-edge handle sets duration_min (also adjustable with arrow keys). Dropping a timed task on the plan list clears its time. Shown beside the plan on wide screens, above it on phones.
  • D-38 · 2026-10-06 · Nested projects: Projects have an optional parent_id with unlimited depth (owner's request: e.g. Paper › Research / Analysis / Writing). The server rejects cycles and parents owned by someone else (domain::tree). Subprojects inherit the parent's colour. Archive and delete cascade to the subtree. A project page shows its own tasks plus one section per direct subproject. Counts include descendants. Tasks still belong to exactly one project.

  • D-39 · 2026-10-06 · arm64 and HTTPS: Multi-arch images (amd64 + arm64) are built in CI with QEMU + buildx; pull requests only build them (this is what verifies arm64), while main and v* tags publish to ghcr.io/laurits7/streamline. Building on a Pi stays possible; LTO/CODEGEN_UNITS build args reduce compile-time RAM. The HTTPS setup is documented and tested as Streamline behind Caddy (docs/examples/caddy, flush_interval -1 for SSE). Verified: Secure cookie, origin check, live events through the proxy, HTTP→HTTPS redirect, container health check.

  • D-40 · 2026-10-06 · Sync queue: store.sync() runs syncs one at a time and in order. Repeated delta requests merge, but a full reload never merges into a pending delta. Responses that arrive after sign-out are dropped (found by the new store tests).
  • D-41 · 2026-10-06 · Places belong to tasks (owner): a project's place is only a default; tasks such as "buy a garden hose" for the country home can be done elsewhere. Spec §6.16, plan 2b.6a (details in Q-25, not yet confirmed).
  • D-42 · 2026-10-06 · Occasions (owner): the full nameday calendar is included; the user selects names of interest; each occasion has its own lead times and dependencies (e.g. buy present 2 days before → say happy birthday); task type per occasion kind with a good default. Spec §6.17, plan 3b.6a (details in Q-24, not yet confirmed).
  • D-43 · 2026-10-06 · Planning ritual details (Phase 2a):
  • Plan state: a day_plans record per day. draft remembers the wizard step, so planning resumes on any device; planned is confirmed. No record means unplanned. Going back into the wizard keeps a planned day planned.
  • Free time: a new per-user "my day" window (default 08:00–22:00) minus scheduled tasks (duration, else estimate, else 30 min). For today, only the remaining part of the window counts. "Planned" is the estimates of open, unscheduled tasks; tasks without an estimate are reported, not guessed. The rules live in domain::planning, mirrored in web/src/lib/planning.ts and tested against the same cases.
  • Reminders: one per kind and target day (reminder_log), skipped when the day is already planned. A planning time before the day end (e.g. 00:30) counts for the same logical evening. Reminders are delivered in-app through notify, the single place where Web Push and ntfy attach later.
  • Prompts on Today: evening (tomorrow unplanned after the evening time) beats morning (today unplanned after the morning time), which beats a gentle "today isn't planned yet". Dismissing is per day and per browser.
  • Wizard bundle: loaded on demand (5 KB gzip), like Help.
  • D-44 · 2026-10-06 · Views and focus details (Phase 2b):
  • "In progress": tasks.started_at on an open task, not a new status value (that would mean rebuilding the tasks table to change its CHECK). Reopening a task clears it, and starting focus on a task sets it.
  • View prefs: stored per scope (view:all, view:inbox, view:project:<id>) in users.prefs, a JSON object merged key by key via PATCH /me/prefs, so they follow the user across devices. Board and matrix pages use the wide layout, and the board auto-scrolls sideways while dragging.
  • Board columns: status (To do / In progress / Done in the last 2 weeks), project (inbox + top-level projects for All tasks, or the project itself + its direct subprojects, each column covering its subtree), difficulty, or task type. A drop changes that field; within a column it reorders.
  • Matrix: views.ts (quadrantOf/matrixPatch) implements D-22 and is tested over every score combination.
  • Focus timer: one per user, server-side (domain::focus). Work rolls into a break automatically; after a break the next work interval waits. Intervals abandoned within a minute aren't logged. Completed or partial work minutes are added to tasks.actual_min. Clients correct for clock differences with server_now, chime and buzz at zero (sound can be switched off), then ask the server to advance. A background job advances timers nobody is watching and sends an in-app notification; a device's own timer already signals, so those notifications only matter for other devices.
  • Bundles: Focus and Plan load on demand. Vite's shared chunk counts as initial in measure.sh, which now reads the initial set from index.html.
  • D-45 · 2026-10-06 · Places: manual or GPS (owner, answers Q-25): Places act as a filter. The user picks the current place from a list. If "Use GPS" is enabled in Settings and the browser grants location permission, the current place is detected automatically from each place's coordinates and radius; otherwise the list is used. Note: browsers expose geolocation only in secure contexts (HTTPS or localhost), so on plain-HTTP LAN installs it is always manual.
  • D-46 · 2026-10-06 · Recurrence via the rrule crate (Phase 3 spike): rrule 0.14 handled every case tested (weekdays, intervals, last day / last Friday of month, the 31st skipped in short months, Feb 29, exceptions) and shares chrono-tz with us, so nothing was hand-rolled. Occurrences are expanded as dates (with time of day applied later), so DST can't move a routine to another day. UNTIL/COUNT are rejected in rules; routines have their own start/end dates, which "change from a date on" needs.
  • D-47 · 2026-10-06 · Routine model (Phase 3):
  • Occurrences are ordinary tasks. A routine (series) creates one task per occurrence. The occurrence key (a date, or window start + #n) is unique per routine even among deleted rows, so a skipped or deleted occurrence never comes back.
  • When occurrences are created: up to tomorrow (job, sync), so the evening plan sees them; for later days, on demand when they're viewed or planned. Lists hide occurrences whose date hasn't come yet.
  • Kinds: repeat is due on its dates (default Carry on). anchored is also put on the day's timeline at its time (default Expires). flexible means N slots per fixed week or month, due at the window's end (default the new built-in "Within its week/month" type).
  • Day end: expiring occurrences are missed even if unplanned. Open window tasks whose window ended are missed, or roll into the current window when the type's window_overflow = roll.
  • Edits: "this one" means editing the task. "All future" (PATCH /series/{id} with from) updates content in place on open occurrences from that date. A schedule change splits the routine: the old one ends the day before (or is deleted if it has no history), and the new one starts after the last settled occurrence or window, so nothing is done twice. Ending a routine removes open occurrences from today on and keeps the history.
  • Mid-window changes (resolved by D-49): changing how often a "N per week" routine runs mid-window now tops up the current window instead of waiting a week.
  • Streaks: consecutive done occurrences (or fully done windows). Skips don't break a streak; open, carry-on occurrences are undecided.
  • D-48 · 2026-10-06 · Owner follow-ups (after Phase 3):
  • Unplanned banner: an unplanned day's banner is warm orange (warning colours), not neutral or accent.
  • Board: Done cards can be dragged back to To do / In progress to undo a mistaken "done". On the board only; elsewhere, closed tasks stay undraggable.
  • Tasks in several projects: tasks.also_project_ids (JSON array) next to the main project_id. The task is listed and counted in all of them, with one shared order. Making an "also" project the main one removes the duplicate link. Deleting a project only unlinks tasks that are merely also in it; tasks whose main project it is are deleted as before. Chosen over a join table: it keeps tasks a single synced record (D-33); a join table is still possible later if group sharing needs per-link data.
  • Activity log: GET /days/{date}/log, built on the fly (nothing new is stored). It includes, within the logical day:

    • task events: completed (unless later reopened), skipped, won't do
    • misses that belong to the day
    • tasks started, and tasks added by hand (not routine occurrences)
    • focus intervals
    • days marked planned

    Shown on the day view (open by default for past days) and in the planner's Review step, with "Copy as text". Groundwork for the Phase 5b daily summary. - D-49 · 2026-10-06 · Routine versions (owner chose option A): a schedule change links the new routine to the one it replaced (series.split_from). For "N per week/month" routines the new version starts at once, and in its first window creates only the slots still missing: wanted minus those already done under earlier versions. Example: twice → three times with one done gives two new slots, shown as "1 of 3". Window progress, streaks and the client's "x/y this week" count across all versions of a routine, so streaks also survive schedule changes. - D-50 · 2026-10-06 · Places implementation (2b.6a): - Data: a places table (name, optional lat/lon, radius 20 m–50 km). tasks.place_id, projects.default_place_id (given to new tasks in the project; the task's own place wins) and series.place_id (copied to each occurrence). Deleting a place makes its tasks, projects and routines "anywhere". - Current place: stored per device in localStorage, as is the GPS switch, since location and permission are per device and browser. It is never sent to the server. - GPS: uses watchPosition; a place matches if the position is within its radius plus the fix's accuracy (capped at 500 m), closest first. On plain HTTP, or if permission is denied, the GPS switch turns itself off with a message. - Filtering: the ready stack (pull-in, planner) and All tasks / board / matrix show tasks without a place or at the current place, with a "N at other places hidden · show all" note. Project pages and day plans stay unfiltered. - D-51 · 2026-10-06 · Prerequisites and workflows (Phase 3b): - Prerequisites: stored as tasks.depends_on (JSON ids) plus a cached blocked flag, kept current whenever a prerequisite changes status or is deleted (task edits, project/routine deletes, rollover). This follows the D-33 single-record sync choice, as D-48 did. Cycles are refused on the server (domain::deps) and hidden in the picker on the client. - What counts as open: done means resolved. Skipped, won't do, missed and deleted unblock (D-6). Reopening a prerequisite blocks again. - Blocked tasks can't be planned (API refuses), are hidden from the ready stack and planner, are dimmed elsewhere, and never miss at day end. - Wait time: after the last prerequisite resolves, ready_at = now + wait_min. A job clears it when the time passes and sends an in-app "… is ready". Waiting tasks can still be planned for later. - Workflow templates: steps and variants are JSON on the template. Chains are linear in v1 (a variant = the steps minus its skips; a skipped step's wait is dropped). Branching steps are left for later. A run = one chain per chosen variant, titled "Step (Variant)", grouped by workflow_instance_id, with step dots in the UI. Editing a template doesn't touch running chains. - Routines with a workflow: each occurrence starts a run on its own day (not the day before, unlike other routines), so "previous run still open" is judged at the right time (D-7: then the occurrence is recorded as skipped with the reason). Not yet available for "N times a week/month" routines. A per-routine override of D-7 is not built yet. Statistics count an occurrence as done when all its steps are. - D-52 · 2026-10-06 · Moving routine occurrences; weekly score (owner request): an occurrence can be moved to another day (its due date moves, and a fixed-time one keeps its time on the new day). "Expires" occurrences are missed only after their due day, so a moved workout isn't missed on its original day. Scheduled routines show "done of planned this week": the total comes from the schedule, so skipped, deleted or missed occurrences count as not done (e.g. 3 of 4). - D-53 · 2026-10-06 · Groups (Phase 4, D-4: no per-member assignment yet): - Groups: anyone can create a group and becomes its owner. Owners and admins rename it, add people (from a directory of all users) and remove them. Members can leave. A group needs an owner while it has other members, and can only be deleted once it owns nothing. Admins can manage every group but, like everyone, see shared content only as members. - Visibility: one rule, "owner or member of the owning group" (visibility::OWNED_VISIBLE_SQL, user bound as ?1), used everywhere, including the event stream: group audiences are g:<id>, resolved per subscriber. A membership event makes a client reload everything and reconnect. Day plans, plan status, focus and the activity log are per user. - Ownership follows projects: subprojects, tasks and routines take their project's owner. Only top-level projects can be shared; the whole subtree follows. Tasks or routines without a project can be shared on their own. People who lose access get removal events. Known limit: a client that was offline during an unshare keeps stale items until its next full reload. - Group tasks: any member completes one (completed_by shown as "✓ Name"). Recurring group chores have one shared occurrence, not one per member. Each member can plan a group task on their own day (day_entries unique per task and user). - D-54 · 2026-10-06 · Shared fixed-time routines (owner request, amends D-53): a group routine with a fixed time puts its one shared occurrence on every member's timeline at that time (one day_entries row per member, created when the occurrence is). When anyone completes it, it leaves the other members' days at once (the Day view hides group tasks completed by someone else); the completer sees it as done. People who join the group later get entries from the next newly created occurrence on. - D-55 · 2026-10-06 · Calendar stack (5.1 spike), tested against Radicale 3.8 with fixture calendars: - CalDAV transport: hand-rolled (as expected; ~250 lines) on reqwest (rustls with the ring provider, no C toolchain; CA bundle already in the image) and quick-xml. Discovery current-user-principal → calendar-home-set → PROPFIND Depth 1 (displayname, calendar-color, getctag, sync-token, component set). Sync: if the ctag is unchanged, do nothing; otherwise list etags (PROPFIND Depth 1), calendar-multiget only new/changed hrefs, drop removed ones. That works on every CalDAV server; sync-collection is an optional later optimization. No server-side time-range filter: we keep all VEVENT resources and expand locally, so recurrences behave the same everywhere. - Parsing: the ical crate (content lines, params, unfolding; only depends on thiserror). Expansion: the rrule crate we already use, plus a small layer in domain::calendar: RECURRENCE-ID overrides, STATUS:CANCELLED, EXDATE, all-day (VALUE=DATE, exclusive DTEND), floating times (in the user's zone), DTEND vs DURATION, TZID resolution (IANA names; vendor-prefixed paths like /mozilla.org/…/Europe/Tallinn; otherwise the user's zone). - Rejected calcard (Stalwart): it does the whole expansion, but recurring events with UTC times (DTSTART:…T170000Z, common in Google exports) were expanded as local wall-clock times in the default zone, which also breaks matching of their RECURRENCE-ID exceptions. It also pulls mail-parser/mail-builder. icalendar (parser feature) works too but is a heavier builder-oriented API that we don't need. - Streamline is a generic CalDAV client (like Evolution or Thunderbird): any server works (Nextcloud, Fastmail, iCloud with an app password, Radicale, …). Radicale was only the local test server for the spike; nothing extra runs in the compose file (owner, 2026-10-06). - D-56 · 2026-10-06 · Calendar read (Phase 5.2–5.4): - Accounts: one CalDAV account per user (the schema allows more). user:password@ in the URL is moved into the fields. The password is write-only in the API, encrypted with ChaCha20-Poly1305 (key from SECRET_KEY, else a generated data/secret.key, mode 0600). Basic auth only; Google needs OAuth and isn't supported. The server will request any URL a signed-in user enters; acceptable for a household server where accounts are admin-created. - Sync: every 15 min per account (and on demand, and right after saving). Syncs run one at a time. ctag unchanged → no fetch; otherwise etag diff + multiget. Instances are expanded for today −30 / +180 days into events, writing only real changes (stable ids per UID + occurrence key), re-expanded daily and when the user's time zone changes. On failure the account shows error + message, and the last events stay. - Privacy: events are personal (audience = the user), never shared with groups. - Display: the timeline shows events for the local calendar day 00:00–24:00 (clipped), read-only, in the calendar's colour (user-overridable); all-day events as chips above it; TRANSP:TRANSPARENT events striped. Busy timed events count against free time (server DayView.free_min and the client planner); all-day events don't. Agenda = one week (week start per user) of events and timed tasks. - D-57 · 2026-10-06 · Occasions and the nameday calendar (owner, answers Q-24; Phase 3b.6a): - One nameday calendar per server, visible to everyone. Default: the official Estonian list as published by Statistics Estonia (stat.ee/nimed/NIMEPAEVAD; source P. Mäeniit, Eesti nimed, 2011): 1,566 names on 366 days. The list is copyrighted, so it is not bundled: on first start the server downloads it (retrying every 6 h while it fails; NAMEDAYS_URL=off disables this, another URL can be set) and stores it. Admins can re-download it or upload another list (MM-DD Name, Name or DD.MM … per line) in Namedays & birthdays. Estonian Wikipedia was checked first but only 24 date pages carry namedays. - Names of interest are personal: each user keeps a list of people (display name, a name from the calendar found by an accent-insensitive search, an optional birthday with or without a year). Namedays of 29 Feb fall on the 28th in common years. - Templates per user and kind, editable: birthday = "Buy a present for {name}" 2 days before (deadline) → "Wish {name} a happy birthday" on the day (expires, waits for the present); nameday = "Wish {name} a happy nameday" on the day (expires). Tasks go to the inbox with due dates 7 days before an occasion's first step, once each (key in tasks.ext_id, even after deletion). A missed present unblocks the greeting (D-6). Unplanned occasion greetings expire like routine occurrences. Removing a person removes their open tasks that aren't due yet. - The day view and the agenda show the day's namedays, with your people highlighted. - D-58 · 2026-10-06 · Conflicts, time blocks and the planner (Phase 5a): - Conflicts (domain::conflicts, mirrored in web/src/lib/conflicts.ts): half-open intervals in local minutes, so touching items don't clash; task↔task, task↔event, block↔event and block↔block clash; tasks inside blocks don't; events among themselves are the calendar's business; zero-length events never clash. Deviation from §6.11: only clashes that involve a scheduled task raise the alert banner and notifications. A block overlapping a meeting is shown quietly (red block edge), since templated blocks would otherwise warn daily, and the planner already works around events. Quick fixes: move to the next free time, shorten to fit, unschedule. A notification goes out when a calendar sync leaves a task overlapping an event today or tomorrow (once per pair, in memory). - All-day events don't block time by default; per calendar, "all-day = busy" makes them block the whole day (free time, conflicts, planner). - Day templates and blocks: a template is a layout of themed blocks (hard/medium/easy/any) assigned to weekdays; a weekday belongs to one template. The next 8 days are set up from templates automatically and keep following template changes, until the user changes that day's blocks (move, resize, edit, delete, add, or apply/clear a template from the timeline's Blocks… menu), after which the day is left alone. Blocks don't cross midnight. - Planner (Q-19/D-19: on request only): domain::planner::suggest with the documented score (overdue 100, due today 90, expires today 80, due tomorrow 60, due in 2–3 days 30, within a week 10, + urgency×10 + importance×8; ties: harder first, then id), greedy earliest fit into the best-matching block (difficulty ↔ block energy), never on events or already scheduled tasks, after "now" for today; no blocks → the day window. Unknown estimates count as 30 min. It suggests times for the day's planned tasks that have none; nothing is saved until the user picks "Use selected" in the planner's Arrange step. Reasons are codes the UI words ("hard → Deep work · first free time"). - D-59 · 2026-10-06 · Summary, reflection and tracking (Phase 5b; D-16, D-17, D-23 applied): - Personal only: day records, metric definitions and entries are filtered by user in every query, and their live events go to the user alone; a test checks that a group co-member sees nothing via sync, endpoints or the event stream. No app-level encryption (D-16): the host is trusted. - Reflection: one record per user and day (free text plus "went well / didn't / tomorrow"), created on first save; autosaved while typing. It appears in the day summary and in the planner's first step (reflect on the day being reviewed → plan the next one). - Metrics: built-in mood (scale 1–5, emoji) and weight (stored in kg, shown in lb for imperial users, D-23), created on first sync; custom number / scale / yes-no metrics with a unit. Several entries a day combine per metric: latest (default for numbers, weight), average (scales, mood), sum, or highest (yes/no). Built-ins can be hidden, not deleted. A reminder time sends one notice a day if nothing is logged by then. - Summary (computed on request): planned tasks by outcome (done, missed, skipped, carried away, still open), completion rate = done / (done + missed + open), everything completed that day, workflow steps, routines with current streaks, events, focus minutes, estimated vs. recorded minutes, metrics, reflection; a month calendar shows done/planned, mood and journal markers. - Trends: client-side from synced entries (a full sync carries 400 days): 7 or 30 daily points, or 52 weekly averages; mood next to routine completion for the last 7/30 days. Plain SVG. - Export/import: all of a user's data as one JSON file (/export); CSV per metric; CSV import of date,value[,note] (, ; or tab, decimal comma allowed, DD.MM.YYYY dates) (D-17). - D-60 · 2026-10-06 · Long-term goals (Phase 5c, D-21): - A goal has a title, description, optional target date and a status (active, paused, achieved, dropped). It holds its milestones (title, optional due date, done) and links to projects and tasks. - Progress (D-21, mixed): derived = (done milestones + done linked tasks) / (all milestones + linked tasks), where linked tasks are the linked tasks plus every task in linked projects and their subprojects, ignoring skipped and won't-do tasks. A manual value (0–100%) overrides it and can be switched back. The server computes it on request (/goals/progress), since done tasks older than 30 days aren't on the device. Links must be visible to the user who adds them. - Sharing: personal by default; a goal can be shared with a group like a top-level project (members see and edit it; unsharing removes it from their devices). - Reviews: weekly (default; the user's week start), monthly, or off. When due and there's an active goal, the Today view shows a banner. The review page lists active goals with progress and open milestones, takes a note per goal (stored with a progress snapshot, shown as history on the goal), and includes today's reflection. Finishing records the review date. - Weekly planning (§6.2d "later") is left for later. - D-61 · 2026-10-06 · No native app for now (owner): the installable web app (PWA, Phase 6) is the phone app; no App Store / native app (WORKPLAN 7.8) unless asked for later. Phase 6 must deliver Web Push that reaches a closed app (iOS 16.4+, added to the Home Screen, permission asked from a tap in Settings, HTTPS, outgoing access to the push service). - D-62 · 2026-10-06 · Installable web app and notifications (Phase 6.1–6.2, D-61): - PWA: web manifest and icons (incl. maskable and Apple touch icon); a service worker (/sw.js), registered only in secure contexts, so plain HTTP is unchanged. Pages are fetched fresh with the cached shell as offline fallback; hashed assets are cached on first use; the main reads (/sync, /me, day views, namedays…) are fetched fresh and the last answer is kept. Offline answers are marked (X-Streamline-Offline), and the app shows a read-only banner; edits fail with "offline" as before. Signing out clears the cached data. - Web Push: subscriptions per device; VAPID key pair generated into data/vapid.key; payloads encrypted with web-push-native (no default features); the ES256 VAPID token is signed with p256 directly (avoids jwt-simple's RSA/Ed25519 dependencies). Dead subscriptions (404/410, or 20 failures in a row) are dropped. Verified end to end: a test decrypts what a mock push service receives, and a real round trip through Google's push service displayed the notification. - One notifier, all channels: every notification goes in-app (live stream), to Web Push devices, and to ntfy if the user set a topic URL (works without HTTPS). Users switch kinds off per account: planning reminders, a waiting task is ready, focus interval over, calendar overlaps, tracking reminders. iOS requires a visible notification for every push, so the app shows both the in-app toast and the system notification when it's open. - iPhone: notifications only after "Add to Home Screen" (iOS 16.4+); Settings explains this when it's missing. - D-63 · 2026-10-06 · Backups (Phase 6.3): VACUUM INTO data/backups/streamline-YYYYMMDD-HHMMSS.db every BACKUP_HOURS (24), keeping BACKUP_KEEP (7), files mode 0600. Admins list, create and download them. Restore is documented in the README (also keep secret.key and vapid.key). - D-64 · 2026-10-06 · Mood is a log (owner): mood changes through the day, so each tap is a new entry ("how I feel now") with its time, with undo; the day's value is the average, and the Today card and summary show the entries through the day. - D-65 · 2026-10-06 · Desktop layout (owner): content starts right after the menu with a responsive gutter (24–48 px) instead of being centred in the remaining space (which left a wide gap on big screens). It is wider: lists up to 980 px, the day view (plan + timeline) up to 1320 px; extra width on very wide screens stays on the right. The menu is grouped (main pages / Reflect / Projects), and Settings shows two columns of sections from 1280 px. - Revised (owner: "fill the screen"): content is fluid up to 1760 px. Today uses three columns from 1500 px (plan | tracking and activity log | timeline) and two from 1100 px. All tasks and Routines show their groups side by side, Goals and Namedays use card grids, the Summary sections sit in a grid next to the month calendar, and Trends charts sit side by side. Phones are unchanged. - D-66 · 2026-10-06 · Performance and accessibility pass (Phase 6.4): - Lighthouse (mobile profile, signed in): accessibility 100 on Today, All tasks, Summary, Settings, Agenda, Goals, Trends, Namedays and Inbox (fixed: the "faint" grey text #a1a1aa → #71717a, dark #6b6b75 → #8f8f9a; the warning colour #d97706 → #b45309 for white-on-orange buttons; past agenda days no longer use opacity). Best practices 100. Performance 98–99 except Today at 90 (Lighthouse's simulated parse cost of the main script; a real CPU profile under 4× slowdown shows ~50 ms of work). - Bundle: Settings, Projects, project/inbox lists, All tasks and Routines load on demand: initial JavaScript 179 → 116 KB (initial payload 78.6 KB gzipped). - Layout shift: the Summary renders its stats and task card with placeholders, and the reflection comes before the sections that only appear with data (shift 0.26 → 0). - Server, seeded with a busy year (3,000 tasks, 2,000 planned entries, 1,500 events, 1,200 mood entries), release build on a desktop: day view p95 9.9 ms, day summary 10.0, delta sync 8.7, planner 3.2, month glance 2.7; full sync 3.5 MB raw / 200 KB gzipped (once per session). New indexes for the summary (tasks(completed_by, completed_at), task history by kind and carried-from date); syncs no longer open write transactions when built-in records already exist, and occasions/day templates skip work when nothing changed. - crates/server/build.rs makes Cargo rebuild when a migration is added (sqlx::migrate! embeds them); without it a local build could miss a new migration. - D-67 · 2026-10-07 · Pre-release privacy and security review (owner request). Three parallel code reviews (authentication; access control; injection/SSRF/resource limits/client) plus a history and image scan. All findings were verified before fixing; regression tests cover the fixes. - Private information: no secrets, keys, tokens or personal data in the code, the full git history or the image. Commit metadata carries the author email (fine for a private repo; GitHub shows it on public ones). cargo audit: one advisory (rsa, via sqlx's MySQL driver) that is in the lockfile but not compiled into Streamline; npm audit: none. - High, fixed: hostile calendar data could crash the server in a loop: an extreme DURATION overflowed date arithmetic, and a non-ASCII calendar colour hit a byte slice; with panic = "abort" either one killed the process on every sync. Fixed: bounded durations (≤ 10 years) and lengths, validated colours (#rrggbb), plus a malformed-input test (every fixture cut at every position and with extreme values) to show the parser can't be made to panic. Release builds now unwind and the server catches panics per request (one request fails with 500); background jobs run each round in their own task, so a bug can't stop them. - Medium, fixed: (1) Outbound requests (CalDAV, Web Push, ntfy, nameday list) refuse link-local/metadata (169.254.0.0/16, fe80::/10), unspecified and multicast addresses, also after DNS resolution; redirects stay on the same host (none for push/ntfy); CalDAV hrefs must stay on the configured server; responses are size-capped (32 MB), XML nesting is capped (64), at most 5,000 objects and 50,000 instances per calendar. LAN addresses stay allowed on purpose (a home Radicale). (2) Password hashing is limited to 4 at a time; sign-in attempts are counted before the password check (parallel bursts are throttled); behind a trusted proxy the per-address limit uses the client address from X-Forwarded-For, and that limit (50) is above the per-user one (10), so one person's typos can't lock everyone out. (3) Changing or resetting a password revokes all API tokens. (4) The live event stream ends when the user's group membership changes (after delivering that notice) and re-checks every minute that the session or token, the user and their groups are unchanged. - Low, fixed: security headers on every response (CSP allowing only the app's own scripts, frame-ancestors 'none'/X-Frame-Options: DENY, nosniff, Referrer-Policy: same-origin); the API docs viewer is pinned to one version with an integrity hash; first-run setup can't race; project colours must be #rrggbb (they reach other members' CSS); creating with a known id can no longer overwrite another user's day entry or plan; workflow runs and routines re-check the project at use (someone who left a group can't keep writing into its project); end-of-day rollover, overlaps and the planner ignore tasks a user can no longer see; shared goals only count work the viewer can see; the stored CalDAV password is only re-sent to the server it was saved for; the offline cache is cleared on sign-in and on expiry as well as sign-out; display names are validated; CSV imports get a 6 MB body limit. - Accepted, documented: first visitor creates the admin on a fresh install (set INITIAL_ADMIN_* when exposed); users may point CalDAV/ntfy at LAN addresses; any group member (not only the owner) can take a shared item private or delete it; a task's prerequisite status is visible as "blocked" to whoever depends on it. - D-68 · 2026-10-07 · Calendar events linked to projects and todos (owner request): - Event → project: an event is assigned to a project by its calendar and iCalendar UID (event_projects), so every instance of a recurring event goes with it and the link survives syncs. Personal, like the events; the project must be visible to the user. Removing a calendar (or the account) removes its assignments; hiding a calendar keeps them. Effects: the project page lists the next five events, the event shows the project on the timeline and in the agenda, and new todos for it go into the project. Assigning a single instance differently isn't supported. - Todo → event: a task can be for one event instance (tasks.event_id, the instance's stable row id). Only the event's owner can link to it. Todos are added from the event sheet (tap an event on the timeline, the all-day list or the agenda); they're due on the event's day, with the default task type. The event shows done/total, the task sheet links back to the event and can detach it, and task rows show the event's name. - Privacy: events stay personal. A todo for an event can live in a shared project; group members see the todo but its event_id resolves to nothing on their devices, so the event's title and time aren't shared. - Not done (could follow): a todo template that repeats for every instance of a recurring event (like a routine anchored to the meeting). - D-69 · 2026-10-07 · Sync between Streamline servers (planned for v1.1) (owner request): collaborators shouldn't need accounts on the sharer's server. Design (WORKPLAN Phase 8): a shared project has a home server (the sharer's), which stays authoritative; a collaborator's server joins via an invite link, keeps a replica of the project (subprojects, tasks, routines) by following a project-scoped change feed, and forwards its users' edits to the home server, which checks them like local members' edits. Personal data (day plans, journal, tracking, calendar) never leaves its server. Only the home server must be reachable; the replica only connects outward. While the home server is unreachable, replicas are read-only (owner choice; no offline merge). First version leaves out goals, workflow templates, places and custom task types. v1.0 ships without it (owner choice); nothing in v1.0 has to change for it (ULID ids, additive tables). - D-70 · 2026-10-07 · Waiting for results (planned for v1.2) (owner request): a task can be waiting: the work is done or handed off, nothing needs doing until a result arrives, and there's a time to check back (e.g. after a model has trained). Design (WORKPLAN Phase 7a): stored like in progress, as fields on an open task (waiting_since, optional check_back_at and waiting_note) rather than a new status, so the tasks table isn't rebuilt and the task stays open until the result is checked. A waiting task needs no action: it leaves Up next and the ready stack, takes no planned time, never expires or counts as missed (it carries on), and is ignored by overlaps and the planner. The board gets a Waiting column. At the check-back time a reminder is sent and the task returns to in progress at the top of Up next; from there it's completed, or waits again. Without a check-back time it waits until changed by hand (listed in Today's Waiting section). As built: set through PATCH /tasks/{id} (waiting, check_back_at, waiting_note) rather than a separate endpoint; when the check-back time passes, waiting_since clears and check_back_at stays as the "check back now" marker until the task is completed, set waiting again or sent back to work. The reminder goes to whoever set the task waiting and belongs to the "ready" notification setting. - D-71 · 2026-10-07 · Checklists and default tasks (planned for v1.2) (owner request): quickly create a task such as "Do laundry" with its subtasks. Subtasks are a checklist inside one task (owner choice), not separate tasks: the task is planned, timed and completed as one, and shows progress (2/4). Stored as JSON on the task (single-record sync, as D-33/D-51). Ticking the last item offers to complete the task; it isn't automatic. Routines can carry a checklist too; each occurrence starts unticked. Default tasks are templates (title, notes, checklist, estimate, difficulty, importance, urgency, type, project, place), personal or shared with a group like workflow templates; quick-add suggests them as you type. Creating from one copies the fields; later edits to the default task don't change existing tasks. Workflow templates stay the tool for steps that are separate tasks with waits between them. Built in v1.2 together with D-70 (owner choice). As built: a default task has a title and no separate name; its project, when set, wins over where it's added (otherwise the task goes where it's added); the export includes default tasks, but there's no import of them (the app has no general data import). - D-72 · 2026-10-07 · Project ideas and descriptions (owner request, built in v1.2): not every project is under way; some are only ideas. Every project gets a description (plain text up to 10,000 characters; http(s) links are shown as links, nothing is rendered as HTML). A project has a status, active or idea (archiving stays separate). Owner's choices: ideas stay under Projects with a grey "Idea" tag (not a separate section, not greyed out), and an Activate button starts one. - Quiet: an idea's tasks don't appear in Today (due list, ready stack, pull-in), the planning wizard or All tasks, only on their project's page. They can't be planned (the API refuses, with a message to activate the project), are never missed, expired or carried over by the day-end rollover, and its routines create no occurrences. They count as open on the project page. - Subtree: the status applies to the whole subtree, like archiving: creating a subproject in an idea, or moving a project into one, makes it an idea. The tag is shown on the top-most idea only. A subproject can be activated on its own. - Changing status: moving a project to ideas takes its open tasks off the plan from the acting user's today on; earlier days keep their entries as history (and the rollover leaves them alone). Moving a single task into an idea does the same for that task. Activating lets routines resume from today; the days spent as an idea aren't caught up on.

  • D-73 · 2026-10-07 · Logo (owner choice): a grey stone in blue water, with eight streamlines parting around it (owner picked it from three line counts). The lines are computed as potential flow past a cylinder, so they bend most next to the stone. Generated by scripts/logo.py. One SVG (web/public/icons/icon.svg, also the favicon) is used in the sidebar and on the sign-in page; the PNGs (192, 512, a full-bleed 512 maskable and 180 Apple icon) are rendered from it. The accent colour moved from indigo to the logo's blue (#1e66cc light, #6aa9f5 dark; both meet WCAG AA with the button text and on the soft accent). The service worker's shell cache was bumped, since icons are served cache-first.

  • D-74 · 2026-10-07 · Project marks (owner request): every project has a colour and a shape (circle, square, triangle, diamond, hexagon, star), so it can be pinpointed in lists. New projects get one automatically (streamline_domain::marks, mirrored in web/src/lib/marks.ts): a top-level project gets the first colour/shape pair none of the user's visible top-level projects has, from 9 colours × 6 shapes handed out so that consecutive projects differ in both (red circle, blue square, green triangle…); once all 54 are used, the least-used pair. A subproject keeps its parent's colour (as before, D-38) and gets a shape its parent and siblings don't have. Grey is left out of the palette: ideas show an empty grey circle (owner choice) until activated, when their own mark shows. Both can be changed in the project menu. Existing projects get marks in the migration in the same order (top level by position, keeping a chosen colour; subprojects walking down the tree), with a new revision so devices pick them up. Ideas can also be created directly inside an active project (D-72).
  • D-75 · 2026-10-07 · Health check-in (planned for v1.2) (owner request): a one-answer-per-day health log next to mood. Choices: Feeling great, OK, Not feeling well, Sick (then what: cold, flu, fever, stomach, headache, other…) and Injured (then where: back, neck, hand/finger, arm/shoulder, knee/leg, foot/ankle, other…), with an optional short note; "other" takes free text. Once per day (owner choice): one record per user and date, changeable that day and later for past days; days without an answer stay empty (nothing is carried over). Only recorded (owner choice): it doesn't change streaks, completion rates, the planner or rollover. It shows on the Today card, in the evening reflection step, the day summary, the month calendar (a small marker on sick/injured days) and as a strip in Trends; it's personal like the other metrics (never shown to group members, D-59) and included in export/import.
  • D-76 · 2026-10-07 · Bundle budget measures the initial route: the CI check added up every JavaScript file, lazy views included, against the 150 KB budget that WORKPLAN sets for the initial route; with v1.2 that sum reached 155 KB while the initial route (index.html, its entry script, the chunks it preloads and the CSS) was 91 KB. The check now measures what the budget is for: the initial route stays under 150 KB gzip, and all assets together under the spec's 300 KB hard limit (SPEC §4; WORKPLAN A.3).
  • D-77 · 2026-10-08 · Guided tour (owner request, v1.2): new users get a short walk through the app so they know what can be done where. On the first sign-in a welcome card offers Show me around or Not now; either way (and when the tour is finished or ended early) the user pref tour records it, so it's offered once per user, on any device. It can be replayed from Help and Settings. Twelve steps open the page they describe and highlight the element (quick add, timeline, plan, "Plan this day", the Track card, then the Inbox, Projects, Routines, Agenda, Summary and Settings nav links; on phones the tab bar, with a "where to find it" line for pages that aren't in it). Clicks on the page are blocked while it runs; Esc ends it, arrow keys step. It doesn't create sample data. Existing users see the offer once after upgrading. Steps and card placement are in web/src/lib/tour.ts (tested); the component loads lazily.

  • D-78 · 2026-10-14 · Linux desktop app (planned for v1.3) (owner request; replaces D-61 for the Linux desktop, phones stay on the PWA): a Tauri 2 app that reuses web/ and connects to the owner's existing Streamline server. It isn't just a web view: it runs a local sync engine (Rust, in the app process) with its own SQLite replica of what the user can see. The UI reads from the replica, so the app works fully offline; edits go to the replica and an outbox, and are sent to the server as soon as it's reachable (immediately when online). It follows /sync and /events like the web client (D-33). The replica is a cache: it is never the server's database (SPEC §4b), and day-end rollover, routines and reminders keep running only on the server (occurrences are already materialized ahead, so upcoming routines show offline). It stays in the tray when the window is closed, so it keeps syncing; start at login is an option. Signs in with a per-device API token, kept in the system keyring. The desktop-only features (D-82) need it.

  • D-79 · 2026-10-14 · Offline edits: merge by field, newest wins (owner choice): each queued edit carries the values it was based on and when it was made. When the server's value of a field still equals the base, the edit applies. When both sides changed the same field, the later edit wins. Different fields are both kept. Checklists merge per item (by item id): ticks, text and additions from both sides are kept, and the later edit wins per item. A deletion beats an edit. After a sync where something was overwritten, the app shows a "synced with changes" notice listing what changed. The merge is pure logic in crates/domain (tested); the server applies it, so the web app could use the same path later. Unlike server-to-server sync (D-69, read-only while the home server is down), the desktop app accepts edits offline.
  • D-80 · 2026-10-14 · Checklists complete their task (owner choice; changes D-71): ticking the last checklist item completes the task (before, it only offered to). Unticking an item of a completed task reopens it (owner choice). This applies everywhere (task sheet, focus view, timeline popover, Agenda) and to routine occurrences. Done on the server, so every client behaves the same.
  • D-81 · 2026-10-14 · Checklists on the timeline and in the Agenda (owner request): on the day timeline, hovering any item with a checklist (routine occurrences and tasks) opens a small popover where items can be ticked; on touch screens a long-press opens it (a tap still opens the task). In the Agenda, a task with a checklist shows its items as an indented, tickable sublist under it.
  • D-82 · 2026-10-14 · Focus options: mute speakers and timer on every screen (owner request): the focus start screen gets two options, both off by default: Mute speakers (mutes the default output during focus intervals) and Show the timer on all screens, with a choice of small overlay (an always-on-top, click-through countdown in a corner of each monitor), full screen (covers every monitor) or full screen on breaks only (overlay while working). The choice is remembered as the next default. They're stored with the focus session on the server, so every computer running the desktop app for that user applies them, whichever device started the timer. Browsers can't do either; there the options are shown with "needs the desktop app". Muting goes through PipeWire/PulseAudio (pactl) and restores the previous mute state when the interval ends or the timer stops (proposed: speakers come back on during breaks).
  • D-83 · 2026-10-14 · Packaging for Arch (AUR) (owner choice: both): streamline-desktop-bin installs the app built by the release workflow, and streamline-desktop builds it from source (cargo + npm). Both PKGBUILDs live in packaging/aur/ and are checked with namcap. Pushing them to the AUR needs the owner's AUR account, so the owner publishes them.
  • D-84 · 2026-10-14 · Checklist steps are logged on the day they're done (owner choice): a chore's steps can be done on different days (wash on Monday, fold on Tuesday). Each tick records when and by whom (done_at, done_by on the item, set by the server; a tick made offline keeps its own time, never a future one). The day log shows "Did “Wash” of “Do laundry”" on the day of the tick; unticking takes it out again. Steps don't count as completed tasks in stats or streaks. A half-done routine occurrence whose type expires is still missed at day end (owner choice); its logged steps still show what was done.
  • D-85 · 2026-10-14 · How the server applies D-79 (implementation details, 9.3): updates to tasks, day entries, routines, projects, metric entries, reflections and health check-ins accept base (the edited fields' values the edit started from) and edited_at; the merge runs on the JSON fields before the usual checks (domain::merge). (1) The server keeps no per-field times, so the record's updated_at stands in for every field's: in "both changed", the client wins only if its edit is newer than the record's last change, which can favour the server when another field changed later. (2) A field the client sends back unchanged (equal to its base) never overwrites the server. (3) "Deletion beats edit" surfaces as 404 for edits of deleted records (and of a cleared health check-in when base is sent), so the outbox drops them. (4) A health check-in's kind follows whichever status was kept, so the pair stays valid. (5) The response is the record plus conflicts (field, checklist item, which side was kept, both values), left out when there were none. Without base, updates apply as before (last write wins). Replayed creates of group-owned records return the record instead of 409.
  • D-86 · 2026-10-14 · Default tasks have kinds; quick-add opens its list on focus (owner request): a default task can have up to 20 kinds (Laundry → linen, colours, whites; garbage → bio, paper, plastic), each with its own steps or, without them, the default task's. Picking a default task with kinds shows a chooser with no kind ticked (owner choice); adding makes one task per chosen kind (owner choice), titled "Laundry: linen", each planned and completed on its own; with none ticked it adds the default task itself. "Add a task…" opens the default-task list as soon as the field gets focus and narrows it as you type (titles, then kind names: "lin…" → Laundry › Linen, which opens the chooser with linen ticked); arrows and Enter pick, and Enter with nothing highlighted adds the typed text. Kinds are stored on the default task (task_templates.kinds, JSON, unticked).
  • D-87 · 2026-10-14 · Desktop app shell: how it reaches the server (implementation details, 9.4): the page in the desktop app never talks to the network itself. Its CSP allows only Tauri's IPC, and every request goes through the app's api command, which adds the device token. Live events come from the app following /events (events_open gives the page a fresh stream, so the server's hello reaches it). The token never reaches JavaScript and lives in the Secret Service keyring (GNOME Keyring/KWallet; without one the app says so instead of storing it elsewhere). First run asks for the server address and signs in once with the password to create a device token named "Desktop app (hostname)", then ends that session. Signing out revokes the token on the server. Outbound rules match the server's own client (D-67): http or https (http is allowed because home servers on the LAN often have no certificate), no link-local, multicast or unspecified addresses (literal or resolved), no redirects, 64 MB response limit; the page may only request paths under /api/v1. Downloads (export, CSV, backups) are saved to the Downloads folder. The app is its own Cargo workspace (crates/desktop, excluded from the root), so the server's tests, CI and image don't need WebKit. Identifier io.github.laurits7.streamline. 9.5 replaces the forwarding behind the same commands with the local replica.
  • D-88 · 2026-10-14 · Desktop app: "this computer only" mode (planned for v1.3) (owner request): besides connecting to a server, the desktop app can run on its own, for someone with just a laptop. The Streamline server (a Rust library) then runs inside the app on 127.0.0.1, with its data in the user's data folder, and the app connects to it the same way as to a remote one. Routines, rollover and reminders run while the app runs (tray and start at login help). The first-run screen offers both. Chunk 9.5b. As built: there's no full data import, so moving to a server means starting a Streamline server on a copy of the app's data folder, which is a normal server data folder (database, keys, backups). The app signs itself in with a fresh token on every start (no password or keyring needed, so you can't be locked out). Its user is me, created with a random password, and Settings → Desktop app sets a real one so the account can sign in on that server. "Use a server instead" leaves the data folder in place; choosing "this computer only" again resumes it. Merging into a server that already has the user's data isn't supported. The server starts at most once per app run.
  • D-89 · 2026-10-14 · Desktop app offline: replica and outbox (implementation details, 9.5): the app's engine (crates/desktop/src/engine.rs) keeps a SQLite replica of the /sync answers and the live change events. A full sync replaces it; deltas and events update it. When the server can't be reached, /sync (full, or a delta that only repeats the always-complete lists) and /today (computed from the user's timezone and day end) are answered from it. Writes the page made optimistically (it sends the records it changed, as they are now) wait in an outbox when the server can't be reached, or while earlier ones are still waiting, so the order holds. The page's own record is the answer. Only the record types the server merges (D-85) can wait: creating, editing and deleting tasks, day entries, projects, routines and metric entries, plus reflections and health check-ins. Other writes need the server, as in the browser. The outbox is sent in order when the live stream reconnects, after a successful sync, and every 30 s, with base taken from the replica's last server copy and edited_at set to when the change was made. A refused change (404 or 410 for something deleted elsewhere, 400, 409) is dropped and reported, except a delete of something already gone. 401, 408, 429 and 5xx stop the round for a later retry. Waiting changes are laid over /sync answers until sent, so a sync doesn't undo them on screen. The page shows "offline, N waiting", then "synced" or a "synced with changes" note naming what clashed or was dropped, and catches up. Signing in to another server or account, or signing out (which asks first if changes are waiting), clears the replica and outbox.
  • D-90 · 2026-10-14 · Focus options as built (9.6, D-82): the options are part of the timer (Timer.options, set by start and by a new options action, reset by stop), and Timer::effects decides what happens in each phase. Mute applies only while a work interval runs; breaks, pauses and the wait before the next interval have sound (the proposal in D-82, confirmed as the rule). The desktop app mutes the default output with pactl only if it wasn't muted, and unmutes only what it muted and is still muted, so a manual change is respected. Quitting restores it. Full screen covers only a running interval: paused or waiting it steps back to the corner timer, so the computer can't be locked out. Esc or Hide puts it away until the next interval. The desktop app follows the timer from its replica every second (the live events keep it current; the server completes intervals itself), with one window per monitor, rebuilt when monitors change. The corner timer is click-through and always on top. Its window can't be smaller than about 200 px, so the area under the pill is transparent: that needs a compositing window manager (on X11 without one it shows as a dark block). Window placement and always-on-top are reliable on X11; Wayland compositors may ignore them. The last choice is a user pref (focusOptions), so it follows the user across devices. In a browser the options can still be set, with a note that the desktop app applies them.
  • D-91 · 2026-10-14 · Desktop packaging as built (9.7, D-83): one install layout (packaging/linux/stage.sh: usr/bin/streamline-desktop, a .desktop entry, hicolor icons, the licence) feeds four things. (1) The release tarball, which streamline-desktop-bin unpacks. (2) The .deb, built with dpkg-deb from the same tree (deb.sh; licence as /usr/share/doc/…/copyright) rather than Tauri's bundler, so no Tauri CLI is needed and the name and dependencies are under our control. (3) streamline-desktop, which builds from the tag's source tarball and runs the app's tests in check(). (4) The release workflow's desktop job, which builds on Ubuntu 24.04 from the tag and attaches the tarball and .deb to the release; CI checks, lints and tests the app on every push. Both PKGBUILDs are namcap-clean and were built, installed and checked (ldd) in a clean archlinux container. Two harmless notes remain: namcap can't see the tray library, which is loaded at run time, and makepkg sees one $srcdir string, a folder Tauri embeds for development, even with --remap-path-prefix; the .deb installed with its dependencies resolved in ubuntu:24.04. The desktop crate's version must match the tag (the release job checks): scripts/bump-version.sh sets every version and refreshes both lock files. packaging/aur/update.sh fills in pkgver, checksums and .SRCINFO after a release; pushing to the AUR is the owner's step (their account), written down in the README. x86_64 only for now.
  • D-92 · 2026-10-14 · Security review of the v1.3 surface (9.8): a review of the desktop app, the in-app server, the offline merge, the step log and the packaging found nine issues; each was checked against the code before fixing. (1) "This computer only" listened before its user existed, so first-run setup was briefly open to other accounts on the computer: the user and token now come first. (2) After a 401 (key revoked) the replica and outbox stayed and could be replayed under the next account: the replica now belongs to server and user, and a 401 signs the computer out, which wipes it. (3) The checklist merge compared unbounded lists before validation, so one request could stall all writes: lists over 50 items are refused first. (4) Local files took default permissions: the app now sets umask 077, and the replica uses secure_delete. (5) Encoded dots (%2e%2e) could escape /api/v1: the parsed URL must stay under the server's /api/v1/. (6) The token answer and live-event lines had no size limit: both are capped now. (7) The step log showed tasks no longer visible: steps are filtered like everything else. (8) App commands were open to every window, and nothing stopped the app window navigating away: commands are now allowed per window (the focus windows only get focus_hide), the window only shows the app's own pages, and outside links open in the browser (open_external, http and https only). (9) The PKGBUILDs carry SKIP until a release: update.sh refuses to finish while any remains. The release workflow passes the tag through env, not interpolated into scripts. Checked and fine: the CSP and capabilities, the token never reaching JavaScript, outbound address rules, parameterised replica SQL, download file names, pactl and xdg-open arguments, merge running only after the visibility check, replayed creates, and stamp.
  • D-93 · 2026-10-14 · Scope of v1.4 (owner choice): productivity first: a command palette with keyboard shortcuts, inbox triage, a weekly review, better estimates from actual time, and a morning digest (Phase 10). Then Phase 7's print the day (A4, 7.3), calendar write-back (7.7), GitHub (7.6) and Mealie (7.1–7.2), then server-to-server sync (Phase 8, first planned as v1.1). Also README badges. Thermal printing (7.4) is dropped; child profiles (7.5) stay postponed. Not chosen for now: smart quick-add with dates and projects, a desktop quick-capture hotkey with OS notifications, and sharing from the phone.
  • D-94 · 2026-10-15 · Week view (owner request: "both, switchable"): a week page with two modes. Planner: seven day columns holding every planned task, timed or not, with each day's load against its free time; tasks drag between days, and in from a list of unplanned tasks. Calendar: the seven days on an hour grid with calendar events, timed tasks and time blocks, where a task drags to another day or time. The mode is remembered. The Agenda stays as the compact list of the week's events.
  • D-95 · 2026-10-15 · Better estimates as built (10.5): the learning lives in the web client (web/src/lib/estimates.ts, tested), not in crates/domain as the plan said, because it only reads what the client already has and only the client uses it. It learns from finished tasks with an estimate of 5 min or more and focus time logged (the latest 200): the median of actual ÷ estimate, per project (3+ samples), else per task type (3+), else overall (5+), capped to ¼×–4×. A suggestion shows only when the factor is more than 15 % off. A task whose suggestion was applied isn't offered one again (a user pref keeps the last 300), so applying can't compound. "Plan with realistic times" is a user pref, off by default; every load calculation now goes through store.dayLoadFor.
  • D-96 · 2026-10-15 · Mealie (owner runs it: v3.25.0): Streamline connects with a Mealie API token the owner creates in Mealie and pastes into Settings (stored encrypted, like the calendar password); no password is shared. Development and tests run against a local Mealie v3.25.0 container with made-up data, never the owner's instance. The Q-10 defaults stand: one watched shopping list, tasks created automatically, one task per meal.
  • D-97 · 2026-10-15 · Tasks from GitHub, GitLab and others (owner choice): accounts connect with access tokens (GitHub fine-grained token, GitLab personal access token, Gitea/Forgejo token), not an OAuth login button, so self-hosted servers need nothing registered. Several accounts and kinds of forge at once. Brought in as tasks: assigned issues, assigned PRs/MRs, review requests and mentions (all four, owner choice; each can be switched off per account, since mentions can be noisy). Repository rules choose the project each task goes to (else the inbox). A task completes when its item is closed, merged or no longer assigned upstream. Read-only towards the forge: completing the task doesn't close the issue.
  • D-98 · 2026-10-15 · Calendar write-back as built (7.7): off until the user picks one of their own calendars (a separate one is suggested). Tasks with a time, open or done, from yesterday to two weeks ahead, are written as busy (TRANSP:OPAQUE) events with the task's title, or "Busy" if the user prefers. The job runs every minute next to the calendar sync and only sends what changed (a hash per entry in written_events), at most 50 writes per run. Unscheduling, deleting or skipping a task, switching calendars and turning write-back off remove the events; older history is left as it was. Removing the calendar account first takes back what was written. Events Streamline wrote (UID streamline-entry-…) are skipped when the calendar is read, so they don't show up twice or clash with their own task. Times are written in UTC.
  • D-99 · 2026-10-15 · GitHub, GitLab and others as built (7.6): accounts live in forge_accounts (token encrypted like the calendar password; server address, login read from the token at connect time; the same login on the same server can't be added twice; up to 20 per user). They're settings only, fetched with GET /forges, not part of the sync feed. Every 10 minutes (or Check now) the job asks: GitHub's issue search (assignee:, user-review-requested: (your own requests, not your teams'), mentions:; GitHub Enterprise at /api/v3), GitLab's issues/MRs (assigned_to_me, reviewer_username) and, for mentions, pending to-dos (mentioned, directly_addressed) on open items, and Gitea/Forgejo's /repos/issues/search (assigned, review_requested, mentioned). One task per item (owner/repo#12, GitLab MRs !3), ext_source = forge, ext_id = account:item, link in ext_url, the reason in the notes, review-only items titled “Review: …”. forge_items remembers every item that got a task, so a deleted or completed task is never brought back (a reopened issue stays done), and the reasons it was last seen for: an item that drops out is completed only if one of those reasons is still switched on (turning mentions off doesn't complete mention tasks), and only when every answer was complete (no cut-off page, at most 3 pages per query). Repository rules (* wildcard, case-insensitive, first match wins) apply to tasks created afterwards; a rule's project that's gone means the inbox. At most 100 new tasks per poll. Removing an account deletes its open tasks (the usual task delete); finished ones stay as history (owner, 2026-10-15). The task sheet shows a Comes from link for any task with an ext_url.
  • D-100 · 2026-10-15 · Sync between servers as built (Phase 8, D-69): only a top-level project shared with a group can be shared with other servers, by that group's owners (or an admin): People on other servers makes a single-use invite link (https://home/join/<token>, 7 days, only its hash stored). The invitee pastes it on their own server, which redeems it (POST /federation/join, sending its person's id, name and its own address) and gets a per-link secret (stored encrypted). The home server adds them to the project's group as a login-less person with the same id as on their server (users.home), shown as name@their-server; such rows are left out of login, jobs, user lists and the directory, can't be added to other groups, and the address shown is what their server claims (the invite is the credential; the same id from a different address is refused). The replica keeps a copy by pulling GET /federation/links/{id}/feed?since= (projects of the subtree, their routines and tasks, deletions included, pages of 1000 rows; people; items that left, tracked per link in fed_sent), woken by a live stream (…/events, says poke; reconnects; plus a pull every 5 minutes). It mirrors the group (groups.home; same id; managed only at home, can't own anything made on the replica; only its own people who joined are in it). Writes to copied tasks, projects and routines (or creating one inside a copied project) are forwarded by API middleware to …/forward, which runs the same API call as that person, limited to the project (no moving out, no deleting or moving the shared project itself, no reowning; places, calendar events and workflow templates dropped); on success the replica pulls and answers with its own revisions. Home unreachable: copies stay, edits get 503 and the project shows a read-only banner (D-69). Removal at home, the project leaving the group, or the person leaving the group ends the link (410), and the replica drops its copy; leaving from Settings does the same from the other side. Day plans of copied tasks, focus time, journal and tracking stay local; rollover and routines leave copies alone (only the home server rolls over and spawns occurrences); printed-sheet ticks are forwarded; forge rules and workflows don't use copied projects. A feed can only write rows that belong to that project's copy (never this server's own items, groups or people). Known limits: focus time logged on a copied task isn't sent home; tasks in a copied project can't be planned automatically by routines on the replica (they arrive as tasks).
  • D-101 · 2026-10-15 · Mealie parked (owner choice): 7.1–7.2 (Mealie shopping list and meal plan) are taken out of v1.4 and parked; D-96 still describes how they'd be built.
  • D-102 · 2026-10-15 · Ongoing tasks (owner request and choice: "ongoing task" over a routine or a plain open task): for things kept at over a period ("keep reading xyz"). A property of the task, not a task type (types' day-end behaviour has a fixed CHECK constraint; a flag also combines with any type): tasks.ongoing, start_date (default: the day it's made, else its creation), the end is due_date, and worked_days (sorted dates, at most 2000). PATCH /tasks/{id} takes ongoing, start_date and worked: {date, worked}. Today shows open ongoing tasks of the day's period under Ongoing (and, today, ones past their end), where the round button marks "worked on it" for that day; the row shows days in total, this week and in a row (crates/domain/src/ongoing.rs, mirrored in web/src/lib/ongoing.ts, both tested). They're never carried over or missed (rollover skips them), aren't listed as overdue (past the end they ask to be finished), the digest mentions one only on its last day, ticking the last step doesn't finish one, and they're finished by hand (Finish in the task). Added from Today's quick-add, they aren't put on the day's plan.
  • D-103 · 2026-10-15 · Quick-add with steps, done and waiting (owner request and choices): when adding a task, Steps opens a box for one step per line (list markers are dropped; they become the checklist), and next to Add there are Add as done (completed right away, steps ticked), Add as waiting (waiting with no time, then the usual "what for / check back" picker) and Add as ongoing (D-102). Ctrl+Enter in the steps box adds.
  • D-104 · 2026-10-15 · v1.4 security review (phase gate 10.7): server-to-server sharing was tightened. A replica's copy can only be written by feeds from the same home server and project (a second server naming the same project, group or link ids is refused at join and can't touch another copy). A forwarded "create" that names an existing item outside the shared project is refused (it would have returned that item), and forwarded edits drop day-plan fields. Each joined project pulls under its own lock, a page must move the revision forward, and a pull gives up after 10 minutes, so a slow or hostile home server can't stall the others. The public join endpoint checks the invite before taking the writer. Someone who already has current links can only be bound again by a server that proves one of those links (its secret), so another server can't claim their id and address. Two-server tests cover the takeover and the create replay. Print/reconcile, calendar write-back, forge tokens, ongoing tasks, digest and palette: nothing found.
  • D-105 · 2026-10-15 · Habit goals (owner request and choices: a kind of goal; yes / partly / no per day; the week's days plus a verdict): goals.kind is outcome (as before) or habit, with start_date (default: the day it's made) and target_date as the end (none: for good). Day answers are habit_checks (one per goal, person and day; personal even for a shared habit; synced, last 400 days on a full sync; PUT/DELETE /goals/{id}/checks/{date}, also queued offline by the desktop app). They're asked in the Track card on Today and in the reflection (Summary & journal, evening planning) for active habits whose period includes the day; answering the same again clears it. A habit's progress is the share of good days over the last four weeks, partly counting half (crates/domain/src/habits.rs, tested), with the run of "yes" days; milestones, linked work and the manual override don't apply to habits. The weekly review shows each habit that had started by the reviewed week with that week's days, suggests a verdict (three quarters good or better: managed; under 40 %: not really; else partly; web/src/lib/habits.ts, tested) and keeps the chosen verdict with the note (goal_reviews.verdict).
  • D-106 · 2026-10-16 · Documentation site (owner request): the long README became a Material for MkDocs site in docs/ (pinned in docs/requirements.txt), published to GitHub Pages from main by .github/workflows/docs.yml and built with --strict on pull requests. Pages: home, features, getting started (install, configuration, HTTPS/phone/notifications, backups and updates, desktop app), guides (sharing between servers, integrations, security), API, development and releasing; the spec, work plan, decisions and changelog are included as they are. The README keeps a short overview, quick start and links. The in-app Help stays the user manual.
  • D-107 · 2026-10-17 · Scope of v1.5 (owner requests): a Tracking tab with metrics chosen in Settings (emoji, water with quick amounts); easier weekdays for routines, routines that are missed vs. chores that carry on until done (one copy rolling on), no completing a routine before its day; changing when a task was done; goals as a tree with tasks, projects and routines attached; and rules for oneself kept over a period. Phase 11, branch v1.5.
  • D-108 · 2026-10-17 · Tracking tab and metrics (owner request): Tracking gets its own page and nav entry (sidebar and phone tab bar). Metric definitions get emoji and presets (quick amounts, at most 6, shown as +amount buttons that each log one entry; the day's value is the metric's aggregate, so water sums). Built-in water (key water, 💧, ml, sum, presets 100/200/1000) joins mood and weight for every user on their next sync; like them it can be hidden, not deleted. Settings sets emoji, quick amounts, order and visibility, adds custom metrics and offers suggestions. Water stays in ml for imperial users (no fl oz conversion for now). Today's Track card stays and uses the same widgets. As built: the page is /tracking (and /tracking/YYYY-MM-DD), shortcut g m, labelled Track in the phone tab bar so seven tabs fit at 360 px; its 7-day strip is a plain bar strip. The migration gives every metric a new rev so incrementally syncing clients (and the desktop replica) get the new fields. Built-ins can't change their unit (mood's unit was editable but meaningless). A user's own earlier "Water" metric is left as it is. Suggestions: sleep, steps, coffee, energy, exercise, took vitamins (hidden once a metric of that name exists).
  • D-109 · 2026-10-17 · Routine days and chores that roll on (owner choices: an easier weekday picker; "one task rolls on"): weekdays already worked (RRULE BYDAY); the routine sheet now shows the day chips straight away. "Routine vs. chore" stays the routine's task type, worded Routine: missed if not done (expires) and Chore: carries on until done (carry on). A carry-on routine keeps a single open occurrence: at day start (rollover), when a newer occurrence's day has come while older ones are still open, the older ones become skipped with a rolled_on event, and their plan entry (if the newest has none) and carry count move to the newest. Doing it counts for the latest day; streaks aren't broken by the rolled-on ones. Flexible (N per week/month) routines keep their window rules. As built: occurrences are grouped by the routine's first version (split_from), so versions roll on together; blocked, waiting and multi-step-run occurrences and ideas and replicas are left out. Rolled-on plan entries are deleted and the newest gets one for each person who had none (on its day; the carry pass then moves it to today). Carry count: the longest any rolled-on one had been carried, counted up to the newest one's day (domain::rollover::carry_after_roll).
  • D-110 · 2026-10-17 · No completing a routine before its day (owner request): a routine occurrence can't be marked done while both its occurrence_date and its due_date are after the user's logical today (so an occurrence moved earlier can be done on its new day, and flexible slots of next week wait for the week). The server refuses it (400) and a last-step tick doesn't complete it; the web shows the check as "from ". Skipping, won't do and ticking steps stay allowed. As built: the rule is domain::rollover::too_early, mirrored in web/src/lib/completion.ts; the store guard toasts "Can be done from …" and still saves the other fields of the change.
  • D-111 · 2026-10-17 · Changing when a task was done (owner request): PATCH /tasks/{id} takes completed_at for a done task (or one being completed); it can't be in the future. The latest completed event moves with it, so the day log, summary and review count it on the new day. The task sheet shows and edits Done on; on the Week planner a done task can be dragged to an earlier day (also a past one), which sets its done day (keeping the time of day, or noon) and moves its plan entry; completing a task from a past day's page dates it on that day. Non-drag path: the task sheet. As built: a time up to 5 minutes ahead is taken as now; more is refused. The day log now hides a completion that was reopened afterwards by the order the events were recorded (rowid), not by their times, since a completion can now be moved earlier than a reopen. Plan's review Done for yesterday's leftovers also dates them on that day.
  • D-112 · 2026-10-17 · Goal tree, attachments and rules (owner request; rules: "more like a goal or a rule for myself", not a habit question): goals get parent_id (any number of top-level goals) and series_ids (routines). A child has its parent's owner (moving a goal under a shared one shares it); no cycles (domain::tree); deleting a goal moves its children up. Progress of a goal is the average of its parts: each child's progress (achieved = 100 %, dropped and rules left out) and its own linked work (milestones, tasks, projects as before, plus each routine as done ÷ (done + missed) over the last four weeks); the manual override still wins. A third kind, rule (kind = 'rule': title, notes, start_date, optional end target_date, status), has no daily question and no progress; rules have their own Rules tab and sit in the goal tree under any goal. Goals can also be attached from a task, a project, a routine or a rule. Only newly linked ids are checked, so a deleted linked task no longer blocks edits. Goals, reviews and habit answers are added to the JSON export. As built: rules can't have sub-goals (and a goal with sub-goals can't become a rule); the web offers only open outcome goals as parents (the server also allows habits); a habit's progress stays its habit score even with children; rules without a parent show only on the Rules page; "Kept it, end now" ends a rule (status achieved, end today). A sub-goal can't set its own owner_group_id; moving re-owns the whole subtree. Goal links follow a routine when a schedule edit splits it into a new version (D-49); ended routines drop out of progress; rules don't make the review due. Roll-up: domain::goals::roll_up, routine rate routines::recent_outcomes (28 days, across versions, skipped and pending neutral).
  • D-113 · 2026-10-18 · Week templates with routines and tasks in blocks (owner request and choices: "there is a deep work or admin block, but some routines are visible there and can be scheduled"; a visual week editor, several week templates, one-off tasks too, a preview on the Week view): blocks keep their theme; a template block can also hold items: routines and one-off tasks. Routines keep their own days: on a day a template sets up, a routine in one of its blocks is planned into that block if it has an occurrence that day (an N-per-week/month routine: one open slot of its current window, if any is left); items are laid one after another from the block's start (by estimate, 30 min if none; what doesn't fit goes on the day's plan without a time). One-off items become a new task in that block each day the template is used (keyed per item and date, so a deleted or moved one isn't made again). As before (D-58), days are set up 8 days ahead and follow template changes until changed by hand; a routine occurrence already planned by the user is left where it is. Week templates map Mon–Sun to day templates (or none): one default, at most one alternating (every other week, counted from its start week) and any number for date ranges (a range wins, then alternation, then the default). The existing weekday assignments become the default week template. The week editor is a page with 7 columns on an hour grid; every drag has a menu alternative. The Week view previews days not yet set up (template blocks and their items, marked as a preview). As built: placement is domain::templates::lay_out (around timed tasks and busy events); block_days.placed records what each day got, so setting a day up again never plans an item twice; items already planned stay where they are when their block moves, and items removed from a template stay on days already set up. Template edits now re-apply to days set up automatically (blocks recreated, new items added), as D-58 describes; days changed by hand stay as they are. The alternating week applies in its start week and every second week after. Overlapping ranges: the first by position wins. day_templates.weekdays stays in line with the default week both ways, so older clients keep working. A default week can be deleted or changed to another mode, and then nothing is set up automatically until there's a default again. Routine items point at the routine's current version; one whose routine was deleted is dropped the next time the template's blocks are saved. The preview is computed in the web client (web/src/lib/weekTemplates.ts, tested). It shows custom rules and N-per-week routines with a "?", because it doesn't know which flexible slots are already used.
  • D-114 · 2026-10-18 · v1.5 security review (phase gates 11.5 and 12.5): the diff from v1.4 was reviewed for cross-user and group visibility, ownership in the goal tree, SQL binding, the export, sync and live-event audiences, template items and roll-on. Nothing exploitable was found. Checked: a goal can only be put under a goal the user can see, and takes its owner; check_links compares against the stored goal, so an unseen id can't pass as "already linked"; progress counts only what the viewer sees; week templates, day templates in them and template items are owner-checked (routine items again when placed); new sync fields and events go to the owner only; the export uses the visibility rule; no new query mixes ?1 with a plain ?; no user content is rendered as HTML. Noted, not exploitable: the template-task lookup isn't limited to the owner (item ids are personal and never shared). Encryption at rest was discussed and left for later (owner's choice); the server's database files are readable by other accounts on the host (0644), unlike the desktop app's (D-92).

Resource log

Phase Image size Idle RSS Initial payload (gz) Day aggregate p95 Date
MVP (Phase 1 + core of 2) 7.0 MB 4.6 MiB (after browser smoke test) 41 KB (36.2 JS + 4.6 CSS + 0.4 HTML) not measured 2026-10-06
End of Phase 2 7.5 MB 3.6–4.1 MiB (fresh container, 20 s) 48.0 KB initial (+4.0 KB lazy Help) not measured 2026-10-06
End of Phase 2a 7.6 MB 4.0 MiB (fresh container, 20 s) 51.2 KB initial (+4.5 Help, +5.4 Plan lazy) not measured 2026-10-06
End of Phase 2b 7.8 MB 4.2 MiB (fresh container, 20 s) 59.6 KB initial (+5.3 Help, +5.5 Plan, +2.6 Focus lazy) not measured 2026-10-06
End of Phase 3 8.7 MB (+rrule) 4.6 MiB (fresh container, 20 s) 66.2 KB initial not measured 2026-10-06
End of Phase 3b 9.2 MB 4.5 MiB (fresh container, 20 s) 76.3 KB initial not measured 2026-10-06
End of Phase 4 9.3 MB 4.4 MiB (fresh container, 20 s) 78.6 KB initial not measured 2026-10-06
End of Phase 5 11.3 MB (+HTTPS client: reqwest/rustls/ring) 5.0 MiB (fresh container, 20 s) 82.2 KB initial (+1.9 Agenda lazy) not measured 2026-10-06
End of Phases 3b.6a, 5a, 5b, 5c 12.5 MB 6.8 MiB (fresh container, 20 s, incl. the first nameday download) 94.8 KB initial (new pages lazy) not measured 2026-10-06
End of Phase 6.1–6.4 12.8 MB 7.0 MiB (fresh container, 20 s, incl. first nameday download) 78.6 KB initial (more views lazy) day view p95 9.9 ms on a desktop with a busy year of data (Pi not measured) 2026-10-06
After the security review 14.3 MB (+1.5 MB: unwinding panics) 7.0 MiB (fresh container, 20 s) 78.6 KB initial unchanged 2026-10-07
End of Phase 9 (v1.3) 15.0 MB 7.8 MiB (fresh container, 20 s) 97.9 KB initial not re-measured 2026-10-14
End of v1.4 (Phases 10, 7.3/7.6/7.7, 8) 16.2 MB 9.0 MiB (fresh container, 20 s) 110.4 KB initial not re-measured 2026-10-15
End of v1.5 (Phases 11, 12) 16.7 MB 9.4 MiB (fresh container, 20 s) 117.7 KB initial not re-measured 2026-10-18

Desktop app (Linux, release build, signed in to a server, idle on Today for 45 s, 2026-10-14): 294 MiB PSS in total: the app process 91, WebKit's web process 170, its network process 33. RSS adds up to 548 MiB but counts shared libraries once per process. Most of it is the web engine itself.