The image switched to the node user (uid 1000) at build time, so a host
folder mounted at /data kept its host ownership and SQLite failed with
SQLITE_CANTOPEN in a restart loop. Named and anonymous volumes worked only
because Docker copies the image's /data ownership into them.
The container now starts as root, hands DATA_DIR and the database folder
to node (only entries not already owned by it), and execs the app via
setpriv as node. Containers started with an explicit `user:` skip this.
Adds a CI step that runs the image with a host-owned bind mount.
Fixes#93
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MHECwBhBpM248EFSBi4L5
Closes#90.
Code blocks and inline code inherited the typography plugin's defaults,
which shrank them to ~86% of an already-monospace body and painted them
--bg-surface — the same colour as the default note card — so a snippet
was only recognisable by being smaller. Long lines also grew a horizontal
scrollbar inside card previews.
- New --code-bg surface (one step below the page in light mode, page-level
ink in dark) with a subtle border, and code keeps the surrounding text
size. Decorative backtick pseudo-elements dropped now that the chip
carries the signal.
- Card previews clip wide code instead of scrolling; editor and share page
keep horizontal scroll.
- Syntax highlighting via one shared lowlight instance feeding both the
TipTap editor (extension-code-block-lowlight) and the markdown-it
renderer, so a fence looks identical in the editor, on cards and on the
share page. Only highlight.js "common" grammars are bundled.
- No auto-detection: highlight.js misreads note-sized snippets too often
(JavaScript as CSS, SQL as VB.NET). Unlabelled fences stay plain; the
language comes from the fence info string or a toolbar picker shown
while the cursor is in a code block.
- Six --hl-* token colours per theme; a unit test parses app.css and
asserts AA contrast against --code-bg and mutual distinguishability.
- Sanitizer now allows only language-* / hljs-* classes on code and span.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014u5WimmZvshm9RJSd1c6wx
Admins can now reset the password of an OAuth-only user, which creates
their credentials and enables password login; the new 'Disable password
login' row action removes those credentials again. Resetting a password
revokes all existing sessions.
- add users.password_login_enabled column (migration 0005)
- admin reset: set password, enable login, revoke sessions
- disable action: clear password, disable login (only on OAuth-linked rows)
- account endpoint: refuse password updates on OAuth-linked users
- e2e: wipe the test DB before the webServer starts (Playwright starts
the server before globalSetup, so wiping there orphaned the DB file)
- row-actions strip test: five square actions, wrap on phone widths
Issue #77 was caused by these docs. FEATURES.md promised "Reset
passwords for any user" when no such control existed, so a user
reasonably filed a bug against a promise the software never made. The
docs were describing the API surface, not the UI. Now that the control
ships, they describe what it actually does — and the same audit turned up
three neighbouring claims that were already wrong.
Corrected:
- "Reset passwords for any user" — it is any user other than yourself,
whose account signs in with a password, and Crumbs generates the
password and shows it once rather than the admin choosing one.
- "Revoke all sessions ... for any user" — Revoke has always been
self-excluded in the UI. Wrong before this feature existed.
- The AUTH.md sentence covering both capabilities in one breath, so
fixing only the reset half would have left the revoke half wrong.
- Two references to a "Settings > Users > Invite User" affordance that
does not exist; the section is headed Create User. Same defect as #77
— documentation promising UI that was never built — one of them buried
in the Authentik setup guide.
Newly documented, all previously silent:
- The sole-admin dead end, and its bound. There is no forgot-password
flow, the reset control excludes your own row, and Profile requires
your current password. Another admin can reset you. Failing that, an
OAuth sign-in recovers the account when a provider is configured and
the email matches — at the cost of handing the account to that
provider permanently. Only with no second admin and no provider is
there genuinely no way back in through the app. An earlier draft
stated that absolutely, which would have pushed an operator toward
restoring from backup when one OAuth sign-in would have done.
- The OAuth pre-link trap: a password set during invite works only until
the first OAuth sign-in, which hands the account to the provider.
Ticking OAuth-only avoids the surprise. Tracked for a real fix in #78.
- That the reset notification is best-effort. Both layers swallow send
errors, so a successful reset is not evidence the user was told.
- That a displayed password is only good until that account's password
changes again, by any route, with nothing marking it stale.
- That an unconfirmed reset shows the password anyway, and that one
should be kept and retried.
- That the control is also unavailable while a password is on screen —
the one unavailable state that shows no reason, so an admin meeting a
dead button has somewhere to look.
No openapi.yaml or API.md change: neither documents /api/admin/* at all,
so the new 400 needs no spec edit and pnpm docs:api regenerates nothing
relevant. That gap predates this work.
Verified: pnpm check 0 errors / 14 pre-existing warnings. Every factual
claim traced to code by the author and again by an independent reviewer,
which is how the false recovery claim was caught.
Reviewed-by: adversarial-review (13 of 14 claims verified true on the
first pass; failed for one false absolute, since bounded)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RbqJcFofZSbhbGwUwZP3wU
11 TDD tasks covering schema, extraction/sync/backlink service functions,
the NoteLink tiptap node (parse/serialize + NodeView + title index),
toolbar note-search UI, click-to-navigate wiring, the backlinks UI
section, preview/public rendering, and e2e coverage.
Self-review caught and fixed three real bugs before this was usable:
a test referencing a USER_ID constant that doesn't exist in the actual
notes-service.test.ts (it's OWNER_ID), a posAtDOM-based position lookup
in the NodeView's remove button where tiptap's own getPos() is the
correct API, and a title-index gap where trashed notes remain in a
note's own IndexedDB cache (only collaborators have them filtered out
at sync time) — so the index needed its own explicit trashed filter or
a trashed note's links would never grey out for its owner.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Re-reviewed the committed spec and found instant-navigate-on-click left
no way to ever remove or repoint an existing note-link, since external
links solve this via a click-triggered popover that navigation now
replaces. Resolved with a hover-revealed x on the chip (touch-visible
by default), keeping the chosen click-to-navigate behavior intact.
Also verified three feasibility risks while reviewing: tiptap-markdown's
custom node serialization API is real and documented; version restore
already goes through updateNote() so link-sync comes for free; and the
line-based content-merge algorithm is markdown-agnostic, so concurrent
edits can't corrupt a note-link reference any more than any other text.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Brainstormed with the user for issue #74. Notes can reference each
other via the existing link toolbar, rendered as a live tiptap node
(not a mark) so the label always tracks the target's current title,
plus a backlinks section on the referenced note.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Separates --hover-wash from --border so the softened dark-mode border grey
no longer collapses hover/active tint washes to near-invisibility (measured
5% wash on --bg-surface: 4.7 -> 17.0 RGB distance). Light mode is unaffected
since --hover-wash falls back to var(--border) in :root.
Also fixes stale/incorrect claims surfaced by the final review: the
checkbox rule and missing CSS variables in CLAUDE.md, the "warm parchment"
description of dark mode in FEATURES.md, the chip contrast claim in the
palette design doc (chips are 3:1 UI, not 4:1 text), and adds a measured
lightness-floor test guarding against chips regressing to card lightness.
The distinguishability threshold (10) sat only 2 units below the palette's
measured closest pair (coral/peach at 12.0), making it brittle to a minor
future nudge. Lower to 8: still fails the old palette's closest pair
(fog/storm at 7.55) while giving the current palette 4 units of headroom.
Also corrects the spec's lightness-change figure from -0.1 (computed from
rounded HSL) to the precise -0.18.
The lightness-band guard was looser than the palette it was meant to catch
(old band 0.0175 vs new 0.0246) and the muted-AA test never failed against
the old palette (5.08:1), so both docs' stated expectations and one spec
claim ("chroma roughly doubles") were measured falsehoods. Replace the band
test with an RGB-distance pairwise check, which does discriminate (old
closest pair fog/storm = 7.5, new closest pair coral/peach = 12), fix the
misleading test comment, and correct the spec/plan docs to match reality.
The reactive {#html} binding inside contenteditable replaced innerHTML on
every keystroke, destroying the cursor and causing text duplication.
Instead, use a use:editableContent action that sets innerHTML on mount
and only syncs when the element is NOT focused. Linkification happens
on blur, matching Google Keep's behavior. Update e2e tests to blur
before asserting link visibility.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address reviewer feedback: null-check meta element, use var(--border-subtle)
for card-shadow, specify .svelte.ts for reactivity, add data-testid attrs,
clarify SSR guards, resolve (auth) layout via root layout, defer landing page.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Design spec for issue #26 — dark parchment theme with system/light/dark
toggle in Settings > Preferences.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Strip server paths from public attachment responses (new PublicAttachment type)
- Use $page.url.origin instead of window.location.origin (SSR-safe)
- Fix toggle switch left-4.5 → left-[1.125rem] (valid Tailwind)
- Add aria-label to SharingIndicator button for screen readers
- Add opportunistic pruning to IP rate limiter to prevent unbounded growth
- Reduce shared attachment cache to 1 hour (respect share revocation)
- Update OpenAPI schema with PublicAttachment type
- Add unit test asserting server paths are not leaked
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- FEATURES.md: add public link sharing feature details
- ARCHITECTURE.md: add shared_notes table to schema section
- openapi.yaml: add share create/revoke, public note, and public
attachment endpoints with SharedNoteData schema
- Regenerate API.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add SMTP configuration example to README install section
- Add note version history, sharing, and email notifications to README feature list
- Expand FEATURES.md with all 9 email notification types and version history section
- Add note sharing, version history, and email alerts feature cards to landing page
- Add all OAuth/SSO env vars to docker-compose.yml with empty defaults
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Send an email to collaborators when they are added to a shared note.
Uses nodemailer with optional SMTP configuration (auto-enabled when
SMTP_HOST is set). Users can opt out via the notifyOnShare preference.
https://claude.ai/code/session_01P4HSMr7QvKw9TMNikUJD9e
- Add docs/TESTING.md with 10 BDD testing best practices
- Add createNote() helper to fixtures to DRY up state setup
- Add createChecklistNote() helper in checklist tests
- Rename scenario titles to describe outcomes, not actions
- Replace imperative Given steps with createNote() helper
- Fix fragile conditional in tag filtering test (silent pass)
- Remove implementation details from Gherkin comments
- Improve Given/When/Then rhythm consistency across all tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace single "Insert table" button with a Table dropdown that shows
contextual row/column operations when the cursor is inside a table:
add row, add column, delete row, delete column, delete table.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Install @tiptap/extension-table packages, register Table/TableRow/
TableHeader/TableCell extensions, add Table button to the More dropdown
(inserts a 3×3 grid), and style tables to match the parchment theme.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add note sharing section to FEATURES.md
- Document collaboration access control model in ARCHITECTURE.md
- Add collaborators and user search endpoints to openapi.yaml
- Regenerate API.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace brand-specific "Google Keep alternative" copy with generic
privacy-focused messaging across meta tags, hero subtitle, README,
and feature docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create AUTH.md as the canonical reference for authentication: password
auth, OAuth/SSO setup, provider-specific guides (Authentik, Keycloak,
Okta/Auth0), env vars, callback URLs, security notes, and
troubleshooting. Fixes incorrect env var names and callback URL patterns
that had drifted in DEPLOYMENT.md. Other docs now cross-link to AUTH.md
instead of duplicating content.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Update FEATURES.md, ARCHITECTURE.md, DEPLOYMENT.md with MCP sections
- Add MCP endpoints to OpenAPI spec and regenerate API.md
- Add rebase-before-PR best practice to CLAUDE.md git conventions
- Update README with MCP/API feature mention
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- GET /api/sync now attaches tags to notes (matching GET /api/notes
and GET /api/search behavior)
- Add trashedAt field to Note schema in OpenAPI spec
- Add optional id field to NoteInput schema
- Add 400 error response for POST /api/sync
- Add response schema for GET attachments list
- Regenerate API.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- FEATURES.md: add rich text editor section (Tiptap, formatting toolbar,
raw markdown mode) and expand text formatting list
- DEPLOYMENT.md: fix docker-compose example to use image from ghcr.io
instead of local build
- CLAUDE.md: add documentation maintenance instructions, update CI/CD
section to reflect consolidated bump.yml workflow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Slim README to essentials, link to docs/ for details
- Add docs/openapi.yaml as single source of truth for the API
- Add scripts/generate-api-docs.ts to produce docs/API.md from the spec
- Add pnpm docs:api / make docs-api target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>