- focus mode: folders slide in from the left (button, title click, or the
left edge on wide screens); picking a note keeps focus mode
- workspace bar: borderless live/shared/save pills in one style, divider
before the note actions; footer without top line
- journal panel and calendar open with an animation, the view fades in
- paste and markdown import nest a list under the paragraph introducing it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- VAPID keys from VAPID_* or generated once (push_keys, migration 9);
devices in push_subscriptions, forgotten on 404/410 from the push service
- push column for every event (also due tasks) and the daily summary,
bundles as one notification; /push/key, /devices, /subscribe,
/unsubscribe, /test
- settings: switch push on per device, test it, see the devices; the
service worker shows notifications and focuses MindSift on click
- tests decrypt a sent push like a browser (RFC 8291) and check VAPID
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the journal opens as a framed panel and closes on a second click,
returning to the previous note; journal state and the last regular note
survive tab switches, the fallback note is never a journal day
- pasted and imported markdown nests content under its heading (H2 below
H1), Enter on a heading creates a child, '- ' / '* ' / '+ ' start a bullet
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- block type 'numbered' (/number): 1., a., i. by nesting, continues on
Enter, an empty item ends the list; markdown export/import as 1.
- pasting: parser moved to lib/pasteOutline with tests; blank lines split
paragraphs, numbered lists keep their type, indentation levels follow
the widths in use (2, 3 or 4 spaces, tabs)
- text that stays in the focused block after pasting (a paragraph with
line breaks) was dropped; the block now takes it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- journal_days (migration 8) maps a person's day to an ordinary note;
GET /journal lists the days, POST /journal/{date} opens or creates one
- the journal sits on top of the notes sidebar: days below each other
(today first, older ones load while scrolling), a calendar with month
and year picker follows the day in view and jumps to any day
- the header shares the day in view; journal notes stay out of the folder
tree; can be switched off per device under Darstellung
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Per-user routing (own mail / own Discord webhook) for task alarms
(sent at once), shares, completed shared tasks and watched tasks;
settings as JSON on the user (GET /auth/me, PATCH /auth/settings).
- Everything but alarms is queued and sent as one message per user
after NOTIFY_DIGEST_MINUTES (default 5); mails now multipart with an
HTML version, Discord as one embed. Three failed attempts drop it.
- Own changes never notify the actor, except on watched tasks when
asked for; "paused" stops everything including alarms.
- Task watchers: POST/DELETE /tasks/{id}/watch, isWatching on tasks.
- Daily summary at a chosen time: overdue, today, tomorrow, plus the
count of the next 7 days; nothing due, nothing sent.
- Migration 7; jobs do not run in the demo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The server only creates the new note and returns the link; the editor
swaps the branch for it as an ordinary own edit through the sync engine.
No more clash with unsent typing (conflict copies), the link shows at
once also with polling, and Strg+Z restores the branch. Undoing (toast or
Strg+Z) puts the new note into the trash via POST /extract/undo. The
sidebar gets the new note right away and opens its folder.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Extract: button on the bullet (desktop) or /extract (/notiz). The bullet
becomes the title (hashtags stay at the link), its children the content
of a new note in the same folder with the source's shares (a foreign
source owner gets write access); the branch becomes [[title]] through
the operation log, so open editors follow.
- Merge: drop a note onto another in the sidebar or use "Mit anderer
Notiz zusammenführen…" in the note header. The own source is appended
as an h2 section with its bullets below and goes to the trash; the toast
offers a complete undo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- GET /link-preview?url= fetches the page on the server (https only,
SSRF-guarded like webhooks), reads Open Graph/Twitter tags, title,
description and icon from the head, loads picture and icon (no SVG) and
keeps them in link_previews (migration 6) for 7 days; readers' browsers
never contact the page. Off in the demo; 300 fetches per address in
15 minutes.
- Compact card above the bullet (icon, site, title, one line, small
picture); the duplicate link badge is left out.
- ✕ turns the address into [title](url), so hiding syncs with the note;
"Link-Vorschauen anzeigen" under appearance switches all off per device.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A bare SMTP_FROM address gets the display name in the From header; a name
already in SMTP_FROM wins, the envelope keeps the bare address. Also a
field in the admin mail settings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Backup ZIP (download and automatic per-user server backups) carries the
files of the user's own notes as media/<id><ext>; restoring creates
them with new IDs and rewrites the blocks.
- Markdown archive puts them under attachments/ and embeds them relative
to each note (); re-importing turns them
back into image/audio blocks.
- Obsidian import takes images and audio a note embeds on its own
(![[file.png]], ); unused or invalid files appear as
skipped in the preview instead of vanishing.
- Format detection and file paths moved into storage for this.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Table media (migration 5), files in $DATA_DIR/media/<docId>/.
- POST /documents/{id}/media (write access), POST .../media/fetch copies an
external https image (SSRF-guarded, not in the demo), GET /media/{id}
with range requests for readers of a note that shows the file.
- Format detected from the content (no SVG); own sandbox CSP on answers.
- Blocks point to files through their content (media:<id>), so the note
sync needs no change; compaction removes files no block mentions any
more after a day. Demo reset and demo start clear the folder.
- Limits MEDIA_MAX_IMAGE_MB, MEDIA_MAX_AUDIO_MB, MEDIA_QUOTA_MB,
MEDIA_DEMO_MAX_MB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- NewID uses 96 random bits; invite codes get their own 100 bits in Base32.
- Registration and forgot-password are limited per address via loginGuard.
- The invite is claimed atomically before the account is created and
released again when the account cannot be created.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Every login is a session (device, IP, last use); the token's jti is its ID.
Sessions end on logout, per device from the profile, or after 30 idle days.
- Tokens older than a day are renewed via the X-Session-Token header.
- Changing password or login e-mail needs the current password; a new
password logs out all other devices and mails the old address.
- Demo reset keeps the demo visitors' sessions.
- revoked_tokens is replaced by sessions (migration 4); everyone is logged
out once after the upgrade.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Admins switch automatic backups on, pick the time and the retention
(default 03:00, 3 days). Each run writes a gzip snapshot of the whole
database (VACUUM INTO, consistent while others write) and one MindSift
backup ZIP per active user below $DATA_DIR/backups, then deletes files
past the retention. A restart after the time catches up on that day.
Admins list, download and trigger runs; users list, download and restore
their own backups.
Import and restore can be limited to sections (notes, tasks, lists,
inbox, themes): restore only empties the chosen sections. Export and
import now carry custom themes. File names are checked against a fixed
pattern, so no other file can be fetched through the API.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
accessibleTaskListsSQL in tasklists.go defines which task lists a user
can see; GetTaskLists, GetTaskListByID and the task queries use it
instead of three copies of the same subquery.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Store.execInTx runs related statements in one transaction and returns
the first error. Used for deleting and moving folders (with the trash),
shared folders, folder shares and mounts, deleting task lists and
accounts, pinning, and the password reset (new password and the end of
all other links together). Writes whose success the API reports now
pass their error to the client (logout, new password, setup, sharing
mode, new group, restore, unlinking a note block), follow-up tasks and
alarm markers log failures. Remaining ignored writes are listed as
intentional in flaws.md.
Found on the way: DeleteTaskList unlinked all tasks before checking
ownership, so a stranger with the list ID could empty it; viewers could
unlink tasks from note blocks. Both fixed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A load test with 16 concurrent writers lost 12 of 480 writes with
SQLITE_BUSY: transactions that read before writing cannot be helped by
busy_timeout once another connection writes. _txlock=immediate makes
every transaction take the write lock at BEGIN, so it queues instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
schema_migrations records every applied step; the former migrate() is
step 3, the baseline that builds the v0.4.1 schema and repairs older
databases. Columns are only added when missing, so every other error now
stops the start with a clear message instead of being swallowed, and the
cleanup statements no longer run on each start. migrations/001_init.sql
was never read and is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keys get scopes when created (webhook:triage, webhook:task,
webhook:list; default triage, old keys become triage). The webhook
ingest learns target task (optional task list) and list_item (needs a
list the owner may write to); /inbox checks the scope of its target as
well; a missing scope answers 403. Keys can no longer open live streams,
which carried all of the owner's events.
The visible key UI lived in the account settings while EcosystemView
was never rendered; it is replaced by ApiKeysPanel in the settings with
scope chips, scope badges per key and a simulator for all three
targets. Examples send the key as X-API-Key header, not in the URL.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Creating, checking, editing, deleting, reordering and clearing list
items did not check the list at all: any logged-in user with a list or
item ID could change it, viewers of shared lists as well. All item
endpoints and converting a thought into a list item now require owner
or editor rights and that the item belongs to the list (404 without
access, 403 for viewers).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DEMO_RESET_MINUTES (default 20, 0 = off) resets the database to the
sample data. Demo accounts get fixed IDs so sessions survive; requests
hold a read lock so the reset never runs under a half-finished request
(streams excluded). DEMO_RESET goes to every connection, /auth/status
announces the next reset. The app shows "✨ Demo · Reset in MM:SS" in
place of the version, then clears cached answers, remounts the views,
reloads in the background and shows a short notice; if no event
arrives (long polling) it asks the server after the countdown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New stat listItemsOpenCount: unchecked items in all lists the user sees
(own, shared directly or through a group), shown as accent counter on
Listen in the header and the mobile bottom bar. Workspace loses its
counter. Stats refresh on list events and after deleting an item.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New column alarm_failures counts ticker runs in which no channel
delivered; at three the alarm is marked as done and a warning is logged
instead of retrying every minute forever. Success, a new due date or
reopening the task starts over with three attempts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Hausrenovierung 2027" in Privat goes down seven levels (floor → room →
topic → appointment → variants → details) with tasks and hashtags mixed
in, so zooming into a branch and back via the breadcrumb shows something
realistic.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pure moves, no behaviour change: the storage layer now has users,
apikeys, thoughts, tasklists, documents, folders, tags, lists, teams and
system files next to sqlite.go (store, schema, migrations); the API has
handlers_*.go per area plus realtime.go and alarms.go next to server.go
(server, router, middleware). Declarations were moved verbatim with an
AST-based tool; imports pruned with goimports.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Passwort vergessen?" appears on the login screen when SMTP and BASE_URL
are configured. The request always gets the same answer and sends the mail
in the background (at most every 2 minutes per account). The link holds a
random token valid once for one hour; only its SHA-256 is stored. Setting
the new password invalidates all open links and ends every session. The
app removes the token from the address bar and offers a way back for
expired links.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GET /documents/{id}/backlinks lists the notes the user can open whose
blocks link [[Title]] (also |alias and #section, case-insensitive),
excluding the note itself and the trash. The collapsible "Verlinkt auf
diese Notiz" section shows each note with folder and the linking bullets;
a click jumps straight to the bullet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deleting a note (or its folder) moves it to the trash instead of erasing
it. Trashed notes leave the tree, search, export and stats and are hidden
from everyone they were shared with; the owner sees them read-only with a
restore banner. New endpoints list, restore, purge and empty the trash;
restoring recreates a deleted folder. Sidebar entry and mobile menu item
with counter, list view with two-tap permanent delete.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Due dates can carry a time (YYYY-MM-DDTHH:MM). Alarms fire at that time,
or 09:00 for all-day tasks, minus the user's lead time, in TZ (default
Europe/Berlin, tzdata embedded). A new due date or reopening a task
re-arms the alarm. Recurrence keeps the time; Markdown export/import
carry it. The UI shows relative labels ("Heute, 14:30"), marks overdue
tasks to the minute and groups by local day instead of the UTC date.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Discord and custom webhook URLs are checked when saved and before every
call: https only, Discord URLs must point to discord.com. A guarded HTTP
client resolves hosts itself and connects only to allowed IPs; localhost,
link-local and cloud metadata are always blocked, LAN ranges (incl.
Tailscale CGNAT) and plain http only with the new admin switch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- recurrence on tasks, set in the detail dialog; 🔁 on rows and Kanban cards
- Completing a repeating task (detail, list, Kanban drag to done, note
checkbox) creates the next one: title, tags, priority, notes, list,
assignees and rule copied, subtasks reset, first open column, due date
one interval later (month end clamped); pushed live as TASK_CREATED
- Each task spawns at most once (re-open + complete makes no duplicate)
- Export/import keep the rule (Obsidian Tasks "🔁 every week", JSON field)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Danger zone in settings: delete all own data or the account, both with
password confirmation; the last server admin cannot delete the account
- DELETE /user/data and /user/account; PurgeUserData removes everything a
user owns in one transaction (others' tasks in shared lists stay theirs),
also used by admin user deletion
- MindSift import: 'Hinzufügen' or 'Wiederherstellen' (mode=restore, wipes
own data only after the file is fully validated, confirm checkbox)
- API key name field and button 44 px on phones (flex-1 collapsed them)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All installations are past schema version 2, so the one-time reset is gone;
data is never deleted on start (demo mode still resets). user_version is kept
for future migrations. Legal test uses an in-memory DB (cleanup race).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GET /export?format=zip streams the JSON backup compressed (plain JSON stays
the API default). The MindSift import recognises the backup ZIP next to the
plain JSON and the Markdown archive.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Two separate paths: POST /import/mindsift (JSON backup or the Markdown
archive) and POST /import/obsidian (vault ZIP into a folder named after it)
- Everything is planned and validated first; ?preview=1 returns what would
be created and what is skipped (with reason), nothing is written
- MindSift data round-trips: folders, note structure, task lists with
subtasks/due dates/priorities/notes, lists with quantity/category/done,
triage thoughts from the JSON backup
- Markdown to outliner: nested lists, heading sections, callouts, code,
paragraphs; front matter tags become hashtags; .obsidian/attachments skipped
- Limits against zip bombs and path tricks (100 MB upload, 10 MB per note,
500 MB unpacked, 20k files, no .. escapes)
- Settings: two import cards with drag & drop, preview, confirm, result;
touch-sized buttons; mobile download/upload verified
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gemini: full emoji picker (desktop + mobile bottom sheet), global search
with SQLite FTS5 and Cmd+K palette (access-checked), demo mode resets the
database on every start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- GET /export/markdown streams the workspace as ZIP (no temp files):
notes as .md with YAML front matter in the user's folder structure
(shared notes and mounted folders included, empty folders kept)
- Clean Markdown: top-level headings/dividers/callouts/code as real blocks
(their children move up a level), lists with tabs, todos as - [ ]
- Aufgaben/<list>.md in Obsidian Tasks format (due date, priority,
subtasks, notes), Listen/<list>.md for shopping and other lists
- Safe, unique file names; download button in settings (mobile-safe)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- CountBadge component (accent / muted, inline / corner, 99+, showZero);
used in the nav bar, task list and list tabs, admin organizations and
numeric badges of the mobile menu; old badge-count removed
- Counters sit on the top right corner of tabs; "Alle Aufgaben" gets one
(desktop tab and mobile menu)
- Open/done task counters include tasks in shared lists, matching what
"Alle Aufgaben" shows (nav counter was too low)
- Theme template derives --accent-dim/--accent-border from the accent
- Docs: ui-patterns counters section
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Explicit SMTP flow (dial -> EHLO -> STARTTLS -> AUTH -> MAIL/RCPT/DATA)
with errors naming the refusing step (also logged as smtp_step)
- EHLO with the sender's domain instead of "localhost"
- Envelope gets the bare address; "Name <addr>" stays in the From header
(was rejected as invalid MAIL FROM)
- Date and Message-ID headers, RFC 2047 subject, quoted-printable body
- SMTP_SKIP_VERIFY / admin switch for self-signed certificates;
SMTP checkboxes are switches now
- Tests against a fake SMTP server (STARTTLS, 451, display names)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Theme manager in settings: upload a .css file or paste CSS, template
download/copy/insert, edit and delete; custom themes show up in the
gallery and the quick switchers
- Themes are injected live as <style id="mindsift-custom-theme">; the
active one is cached so it applies on boot without flashing
- Backend: user_themes table and GET/POST /themes, DELETE /themes/{id}
(per user, max 200 KB)
- Docs: theming, REST, database
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Server admins manage organizations (one per user); new admin section,
organization on user creation and invites, setup creates the first one
- Sharing modes private / organizations / open, enforced centrally
(CanSee, CanShareWith) for directory, all shares and groups; unknown and
invisible targets give the same error
- Losing visibility (org change, mode change, leaving a group) prunes shares,
group memberships and task assignments; dry run warns before tightening
- Only owners share; editors can no longer reshare lists or remove others'
shares; legacy task_lists.team_id and old /organizations endpoints removed
(FLAW-06, FLAW-10, FLAW-24)
- Schema version: outdated databases are wiped and recreated on start
- Fix setup sample data (createSampleData was ignored); setup and admin
use a shared ToggleSwitch instead of checkboxes
- Docs: sharing-and-permissions rewritten, ui-patterns, REST, DB, flaws
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Tasks in shared task lists can be assigned to one or more people with access
(task_assignees); assignments are pruned whenever access is lost
- UserAutocomplete: type-to-search everywhere, nothing before typing, max 3 results,
first match preselected
- Task list and list tabs: avatar stack of members and round monochrome counter
- Shared list items remember who added them (list_items.created_by)
- Doku/ui-patterns.md documents the shared UI patterns
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mobile
- Plus button in triage did nothing: the stored action returned the
capture function instead of calling it
- Workspace: tabs share the full width; save status sits as an icon next
to the note title instead of wrapping into its own row; "Neue Notiz" /
"Neuer Ordner" only in the top menu (and +), not repeated in the list
- Outliner fields measured while hidden stayed one line high (cut-off
callout); they are measured again when their width changes
- Task dialog: scrolls in a div instead of a fieldset, height in dvh,
notes editor grows with its text, so long notes are fully reachable
Desktop
- Delete button of the selected task/shopping list sits inside the tab as
its own rounded button (matches rounded themes like Soft)
Both
- Tasks sometimes did not open: every reload after an action replaced the
list with a loading placeholder, swallowing clicks; reloads are now
silent after the first load of a list, stale responses are dropped
- Hashtags need whitespace or "(" before "#", so URL anchors are no
longer tags (server, browser); one-time cleanup of stored anchor tags
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Turn the collaboration plan into module docs (Doku/modules/collaboration.md)
describing the implemented state; remove Doku/plans
- Header version comes from the build: git tag via APP_VERSION (CI passes
it for tag builds), otherwise "<package version>-dev"; package 0.2.0
- Sample data (setup option and demo mode): six inbox thoughts, two task
lists with Kanban states, subtasks and due dates, shopping and packing
lists, three notes in folders incl. a long nested outline; demo mode
shares the note and two lists with user/user
- e2e/screenshots.sh renews the README screenshots from demo data
- README: live collaboration, editor sharing, image tags, version badge
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Background job at start and every 6 h: log entries older than 30 days
(private notes: 7) are removed, documents.log_floor marks the boundary;
clients behind it get resync and keep their queued changes
- Deleted blocks are purged 30 days after deleted_at via a "purge" log
effect, so open editors drop them without reloading; a tombstone stays
while a deleted child still points to it
- Text for a purged block becomes a new block at the end; inserts under a
purged parent go to the top level (server and browser model)
- doc_op_results kept for 90 days; expired lease/session maps are swept
- Tests: storage, API, engine resync, simulation with compactions
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 5 of the collaboration plan:
- Remove PATCH /documents/{id}/blocks, PatchDocumentBlocks, the
DOC_BLOCKS_PATCHED event and its types, FlushDocumentState and
LoadDocumentState. PUT /documents/{id} rejects blocks (400) and only
changes title, icon, folder and pin; editors may now change title/icon.
- The to-do overview ticks via a single operation on /docs/{id}/sync
(new skipLog flag). Task links set the block tick with a targeted
operation and notify open editors; creating a linked task needs edit
rights.
- Keep the three share tables: DocRole already decides centrally, and
rewriting the visibility queries would only add risk (documented in plan).
- Tests adapted to the new paths; share e2e test now also ticks a to-do in
the overview.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>