- 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>
- docker-compose.yml only maps variables; every variable is explained in
.env.example (German, with defaults, units and choices).
- The whisper service is a commented block to switch on instead of a
compose profile.
- TZ, TRUSTED_PROXIES, LOGIN_PROTECTION, DEMO_RESET_MINUTES and
LEGAL_*_CONTENT are now passed through (they had no effect from .env).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- New useVoiceInput hook (lib/voice.ts): "server" records with MediaRecorder
and sends to /voice/transcribe, "browser" keeps the live recognition,
"off" or missing browser support hides the microphone.
- Quick dump bar, mobile capture sheet and hold-to-record use it; live
waves from the real input level (VoiceWave) and "Wird erkannt…".
- Recordings stop after STT_MAX_SECONDS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- STT_URL switches voice input to an OpenAI-compatible Whisper server
(e.g. speaches); without it the browser's recognition stays. Configured
by environment only, with limits for upload size, parallel jobs, hourly
budget per user and timeout. The demo always uses the browser.
- POST /voice/transcribe forwards recordings and returns the text; nothing
is stored. On startup MindSift waits for the server and asks it to
download the model if missing.
- Admin card shows the active mode and server state, with a test recording.
- docker-compose: optional "whisper" service (speaches 0.8.3 CPU, profile
"whisper", models in ./whisper).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Demo: senders for mail, Discord and webhooks refuse, no alarm ticker;
test alarm, SMTP test, server settings, backups, account and session
changes of the shared demo accounts answer 403; the session list only
shows the own device. The UI shows the notices and disables the controls.
- SMTP password is never sent back (smtpPassSet), empty keeps it.
- ?token= on webhook ingest accepts only API keys.
- Request bodies capped at 2 MB (sync 8 MB, imports 101 MB).
- Deleting an account removes its server backups.
- Backup downloads, exports and import uploads get 30 minutes.
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>
safeHref lets only http, https and mailto become links (Markdown viewer,
outliner, legal links); javascript: and friends show as text. A
middleware sets a Content-Security-Policy (scripts only from MindSift,
no framing, connections only to this server), X-Frame-Options,
nosniff, Referrer-Policy and a Permissions-Policy. The two inline
scripts of index.html moved to files so script-src stays 'self'.
The second inline script also deleted every cache on each page load,
which undermined the offline start; it is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tags become three-part (v0.6.0), the header shows the full version and
pre-release tags no longer move :stable. CHANGELOG.md (Keep a Changelog)
starts with an Unreleased section; older versions will be added from the
history. The quantity field opens the number keypad on phones.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The old list (FLAW-01 to 27) was done and is removed; references to it
in other documents are gone or replaced by the content. New findings
from a review after v0.5, among them a high one: javascript: links in
task notes run on click (Markdown viewer), which with the session in
localStorage allows taking over accounts through a shared task list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The + button lost its click handler for pointer events, so keyboard,
VoiceOver and switch control could no longer open the capture sheet; a
click without pointer (detail 0) now taps. "Im Quick Dump gespeichert"
appears after the save succeeded, and the cancel state uses an existing
border color. FLAW-16 (setup open until the first account, demo
accounts) is removed from the list: intended behaviour, not a flaw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Admin settings: switch, time, retention, list with download and a
"Jetzt sichern" button. Account settings: own server backups with
download and restore (replace or add, for chosen sections, replace needs
a second tap). The MindSift import gets the same section picker.
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>
WebSocket and EventSource put the 30-day session token into the URL,
where proxies log it. The app now gets a one-time ticket right before
connecting (POST /auth/stream-ticket, 60 s, bound to the token version);
?token= is no longer accepted for streams, non-browser clients may use
the Authorization header.
With BASE_URL set, CORS allows only that origin and WebSocket upgrades
only it or the requested host; credentials are off (no cookies exist).
Logout, ending all sessions, a new password and deactivation close all
live streams of the account; clients with a valid session reconnect
with a fresh ticket.
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>
Failed logins are counted per account+address (3), per account (20) and
per address (30) within 15 minutes; a full bucket answers 429 with
Retry-After for 15 minutes. Locking the account+address bucket first
means an attacker cannot lock the owner out from elsewhere. Unknown
names count like accounts. Success clears the account buckets, a
password reset (mail or admin) lifts the lock. LOGIN_PROTECTION=false
switches it off.
chi's RealIP believed X-Forwarded-For from anyone. The client address
now comes from the headers only when the connection is from a trusted
proxy: TRUSTED_PROXIES=private (default: loopback, Docker, LAN), all
(old behaviour), none, or CIDRs. A proxy that sends no client address
is logged once and per-address limits are skipped for it.
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>
The global rule giving .bg-panel.border the theme's card radius made the
round search button square in themes with 0px corners. Elements that ask
for rounded-full now stay round in every theme. Also marks FLAW-21 as
fixed (Unlicense is in the repository).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Corner counters on tabs (header, task lists, lists, workspace) sat on
the tab's top border and close to the text; now -top-0.5 -right-2 with
pr-3 on the label. The mobile bottom bar keeps its icon placement.
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>
auth.ValidatePassword checks every way a password is set: setup,
registration, profile, admin create and reset, reset by mail. Same rule
in the browser before sending (lib/password.ts), with our own German
message instead of the browser popup. Existing shorter passwords still
log in. Profile and admin reset no longer trim the password, which made
passwords with surrounding spaces unusable for login.
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>
The service worker now caches the app shell: page loads are network
first with the last index.html as fallback, hashed bundles come from the
cache, icons and manifest are refreshed in the background; API calls are
never intercepted. GET answers are kept per user in IndexedDB and served
when the server cannot be reached, so the app starts logged in with the
last known thoughts, tasks and lists. Captured thoughts and checked tasks
or list items go to an outbox that is sent in order when the connection
returns (also at start and every 30 s). A banner shows offline state and
waiting changes. Logging out clears cache and outbox.
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>
Until API keys get scopes they have exactly one right: POST /api/v1/inbox
(thought or task via target) and the webhook ingest; every other endpoint
answers 403 for a key. Middleware and webhook ingest share one token check
(revocation list, token_version, active account), closing FLAW-08.
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>
Without JWT_SECRET, or with one of the published placeholders, the server
generates 32 random bytes once and keeps them in the data directory
(jwt.secret, mode 0600), so tokens cannot be forged and logins survive
restarts. Instances that ran on the old default log in once more.
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>
Manifest: short name, blue background/theme colour for the Android splash.
index.html shows a plain blue screen with the logo until React mounts.
theme-color starts blue and then follows the active theme background.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- FormSheet: create dialogs (task list, shopping list, folder) as bottom
sheet right above the on-screen keyboard (visualViewport), large fields;
centred or anchored dialog on desktop
- Emoji picker: category icons keep their sprite size (no doubled icons,
round highlight), one 'Zuletzt verwendet' (library category, German
names), sheet and picker height follow the keyboard
- Mobile header no longer repeats the current section next to the logo
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>