CHANGELOG.md
Rama main · réplica de lectura del repositorio original.
# Changelog
All notable changes to this project are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
follows independent [Semantic Versioning](https://semver.org) for the plugin
and the server.
## [Unreleased]
## [1.10.4], 2026-10-15
A full review of the code (plan 018, phases 1 and 2).
### Fixed: data and security
- A device now refuses changes from others for paths Havemind never syncs
(for example `evil.html` or `.git/hooks/…`); such a change is parked.
- A failed read of `.obsidian` no longer deletes every setting, theme and
snippet on every device.
- When many notes go missing at once at startup, Havemind holds the deletes
and asks before removing them on every device.
- A conflict copy you edited is never overwritten when the same change
arrives again; a deleted-vs-edited conflict says "Deleted on another
device" instead of an empty note, and Keep theirs moves the note to trash.
- `Note.md` from one device and `note.md` on another become a conflict copy
instead of a stuck change.
- Agents: a write without text no longer empties a note; a run is never
repeated (and paid for) because the server blinked; folders like
`havemind/` or `.Obsidian` are refused as `Havemind/` and `.obsidian`;
two agents with similar names no longer share a folder; Codex and Cursor
stay off on servers.
- Server: each vault keeps its own agent login, so one vault's owner cannot
change another vault's agents (an existing login moves to the vault on a
one-vault server); errors never show internal details; a device left
waiting more than an hour must be invited again.
- Installer: an unattended re-run no longer prints a new owner code; files
with secrets are private from the start; a failed download or a missing or
old Docker Compose stops with a clear message.
### Fixed: what you saw
- Invite someone and Add agent only for the owner.
- Typing a code or a key survives the pane refreshing; the pane no longer
jumps to the top.
- The six-digit code goes straight into the "wants to join" card.
- A fresh vault on a new device no longer reports a settings file as a
conflict; when files are held, the notice names them.
- Old requests to join disappear when they expire and after you disconnect.
- After Undo, an agent does not redo the same work; Inbox sorter adds only
tags a note does not have.
## [1.10.3], 2026-10-15
### Fixed
- 1.10.2 never reached the plugin catalogue: the Server URL field's example
address looked like a real tailnet name, and the distribution release
refuses any. The example is generic now, and a test applies the same rule
to the plugin sources before a release.
## [1.10.2], 2026-10-15
### Fixed
- A new invitation showed the code and, under it, an empty box that read as
a place to type. It is one box now.
- Joining with an invitation or the installer line no longer asks for a
server URL; the field appears only for a pairing token without one.
- The agents service no longer shows as unhealthy: it inherited the
server's healthcheck, which calls a port it never opens. Run the installer
again (or pull the new compose file) to pick it up.
## [1.10.1], 2026-10-14
### Fixed
Found by running all five ready-made agents live on a fresh server:
- After the agents service restarted (every server update), every agent was
skipped in silence: locks from the old container carried the same process
id as the new one. A lock now carries a token of its own process.
- Agents now know who changed each note, so a daily summary names people
instead of "author unknown".
- An agent that thinks with Claude Code now sees its own notes, so it can
update one it wrote before (the daily summary) instead of skipping it.
## [1.10.0], 2026-10-14
### Added
- AI agents as members of the vault (plans 014 to 016). People, Add agent:
pick a task (Inbox sorter, Daily summary, Link gardener, Meeting to tasks,
Clip digester, or Custom) and it joins with one click; no invitation, no
command, no six digits. It works in the background on its schedule, only
when people changed something, and only in the folders its task note
(Havemind/Agents/<Name>.md) names.
- Agents are yellow, each with a pixel icon, in the flower, People and
Activity; the approval card says "As AI agent".
- How agents think: your Claude subscription (claude setup-token; your
server installs Claude Code on first use, it is not in the public image),
an Anthropic API key, or any OpenAI-compatible model (OpenRouter, OpenAI,
Gemini, Ollama). The login is a 0600 file on your server, never in the
database or its backups. A running agent sends the changed notes to that
model.
- Undo only covers what this device's Activity remembers (200 changes).
- Undo on an agent's row: everything it changed goes back.
- Server: member kind (migration 008), agent enrollments (009), the agents
service in the installer and the image, and a
havemind-agent command with an MCP server for Claude Code.
### Changed
- Over the invite form the status is one compact line.
### Note
- Back up before updating the server: migrations 008 and 009 cannot be
undone, and an older server refuses a newer database.
## [1.9.2], 2026-10-14
### Fixed
- Installer: run as `curl ... | bash`, it stopped silently after "Your vault"
without printing the code, because `docker compose exec` read the rest of
the script from stdin. The script now runs from a function that bash reads
in full first. Tested piped on Ubuntu 24.04 arm64, fresh and re-run.
## [1.9.1], 2026-10-14
### Fixed
- Installer, found on a fresh Ubuntu 24.04 arm64 machine: the server could not
open its database because the new data volume was owned by root; the
installer now gives it to the server's user before starting it.
- Installer: a run that stopped before owner setup now sets the owner up when
run again, instead of asking for a new code from a server with no owner.
- Plugin: the connect form names the installer line and says the Server URL
can stay empty for it.
## [1.9.0], 2026-10-14
### Added
- One-command server install for Linux and macOS (`install.sh`, attached to
each release): Docker and Tailscale through their official installers after
asking, the published image, Tailscale Serve only, never Funnel. It ends with
one line to paste. Guide: `docs/install.md`.
- Server image `ghcr.io/mikolajsapek/havemind-server` (amd64, arm64) with
build provenance, published on every release.
- Server CLI prints the pairing code as one line, `hm_pt_...@<server>`, after
setup, create-vault and rotate-pairing.
- Plugin: the connect form takes that one line; the "I'll run the server"
screen shows the one command.
### Fixed
- Server: a restart no longer pushes the next scheduled backup a whole
interval out. When the newest backup is recent, the first run is armed for
the time it has left, so backups stay daily across deploys instead of
leaving a gap of up to 47 hours.
## [1.8.2], 2026-10-14
### Fixed
- Plugin: in the history window, picking two versions quickly could show the
first one's diff beside the second one's mark; only the latest pick draws.
- Plugin: the history window reserves the diff's space up front, so it no
longer grows and moves the list under the pointer on the first pick, and
says what to do before a version is picked.
## [1.8.1], 2026-10-14
### Fixed
- Plugin: a diff, in the history and in the conflict window, shows the
changed lines with three lines around each and folds the rest into
"... N unchanged lines"; a long note used to show in full.
- Plugin: the history window marks the picked version, puts Restore above the
diff, and says when a version is the same as the note now.
- Plugin: the conflict and history windows get the pane's design tokens. They
render outside the pane, so their hairlines and accent resolved to nothing.
- Plugin: modal titles drop the hexagon icon (plan 010, fewer icons), and the
history rows read like the Activity list.
- Plugin: a history or version that fails to load is logged to the console
with its error, besides the message on screen.
## [1.8.0], 2026-10-14
### Added
- Plugin: "Show history of this note", in the command palette and the file
menu (plan 012). Every synced version of the note with who made it and
when it reached the server, the difference between a picked version and the note now, and a
two-step Restore that writes it back as a normal edit. Plugin-only: the
time comes from the `serverTime` every server already puts on each receipt.
### Changed
- Plugin: the stored revision log moves to format 2, which carries each
revision's accepted time. A log stored by an earlier version is read again
in full once, on the first connection after the update.
## [1.7.5], 2026-10-14
### Changed
- Plugin: the Activity feed survives a restart. The last 200 entries are kept
in the device's client database (never data.json), saved at most once per
burst, emptied on Disconnect and deleted with Reset.
### Fixed
- Plugin: invitation expiry reads as a clock time ("Expires at 16:05") for
the owner, and the joining device now sees it, together with the name of
the person who invited it.
- Plugin: Rejoin, Remove, Retry, Discard and Retry now are 44px tall on a
touch screen.
## [1.7.4], 2026-10-14
### Fixed
- Plugin: "Open activity" opens the pane on the Activity tab, not on
whichever tab was last open.
- Plugin: "Connect to Havemind" is offered only while nothing is connected;
on a live vault it only swapped the pane for the connect form.
- Plugin: a pending retry ("Retrying...") offers Retry now, as offline does.
- Plugin: Getting started no longer tells a connected user to paste a token
"below", where there is no form.
## [1.7.3], 2026-10-14
### Fixed
- Plugin: the stylesheet no longer uses `!important`, which the Obsidian
plugin review flags. Reduced motion now names each animated rule instead.
## [1.7.2], 2026-10-13
### Fixed
- Plugin: on a phone the pane's More options (Sync now, Getting started,
Disconnect, Reset connection) could not be reached. 1.7.0 left the button
to Obsidian's view header, and the sidebar drawer the pane opens in on a
phone shows none. The pane now draws its own "..." next to the tabs on
every platform.
## [1.7.1], 2026-10-07
### Changed
- Plugin: the flower's core spins while this device sends and turns green
when everything is on the server; a conflict keeps it violet. The status
bar hexagon is green when synced too.
### Fixed
- Plugin: the first-run screen opened with the old "Not connected" row and
its two network lines above the empty flower. Reconnect and reset keep the
row, it carries the way out.
- Plugin: on the join screen the token and server fields ran 24px past the
pane's right edge, and the first line sat on the pane's top edge.
## [1.7.0], 2026-10-07
### Changed
- Plugin: a new, quieter pane (plan 010). Three text tabs, Status, Activity
and People, with no header strip and no Connect tab. Sync now, Getting
started, Disconnect and Reset connection moved into the More options menu,
which on a phone is the view's own menu.
- Plugin: the Status tab draws the vault as a flower: the server in the
middle, one hexagon per device, initials for the others. Its shape is the
state: filled in sync, dashed when this device cannot reach the server,
split in red on the other side of a conflict, "?" for a device waiting for
approval, "+N" past six devices. One title and one line say the rest.
- Plugin: Activity groups changes by day and leads with the note's name, then
who changed it and when.
- Plugin: the status bar shows the state as a small hexagon and counts what
waits: "3 to send" while syncing, "3 waiting" offline, "2 notes" in
conflict.
- Plugin: the first-run screen leads with the empty flower and two choices.
### Added
- Plugin: a device waiting for approval shows as a card in People, with a
button that opens the code entry.
- Plugin: Disconnect asks first and names the server it stops syncing with.
## [1.6.0], 2026-10-06
### Fixed
- Plugin: a queued local change, including a 25 MiB attachment, used to sit
inside data.json until the server accepted it. It now goes to the device's
local database first, as intended, so data.json stays small.
- Plugin: a local change is now saved in one write instead of four. A crash or
a full disk in the middle of a save can no longer leave a queued change
without its file identity or the saved state half-recorded. A recovery
journal covered most of these gaps since the duplicate file ids of
2026-09-19; the delete of a never-sent file was not covered.
- Plugin: a corrupt sync state recovered from its backup right after the
upgrade can no longer drop the list of files.
### Changed
- Plugin: the list of files the plugin tracks now lives inside the sync state
instead of a separate `pushProducer` entry in data.json. On first start the
old entry is moved over once and removed. Nothing to do on your side.
Devices on 1.5.9 and 1.6.0 sync with each other normally. No server change
and no deploy needed.
- Plugin: internal cleanup. The glue between the two stores
(local-base-lifecycle and the seed hooks) is gone, a test now replays a real,
anonymised 1.5.9 data.json through the migration, and a timing test in the
protocol package no longer flakes.
## [1.5.9], 2026-10-06
### Fixed
- Plugin: when the server had already accepted the same merge from another
device, the plugin kept a full copy of the note in its sync state forever,
and that file is rewritten on every save, phones included. It no longer
keeps that copy, and copies made by a recovery are dropped after 7 days.
On one device the file shrinks from about 577 KB to about 110 KB as the
old copies age out.
## [1.5.8], 2026-09-30
### Fixed
- Plugin: a change queued while the plugin was still starting up could drop
out of the queue, so that edit never reached the server.
- Plugin: an attachment deleted on another device is now deleted here too.
It used to stay, with an empty conflict copy next to it.
- Plugin: a server whose responses carry fields this version does not know no
longer stops a device from joining.
- Plugin: stored copies of queued changes no longer pile up in the device's
local database, and Reset connection deletes the database it leaves behind.
### Changed
- Plugin: opening the app no longer reads and hashes every file. A file whose
size and modification time match what the last start saw is skipped.
- Plugin: each change carries a note's text once instead of twice, so an edit
uploads and stores half as much, and notes of roughly 250 to 490 KiB that
were refused as too large now sync. Merges keep the old form, so devices on
older versions still recognise the same merge made on both sides.
- Plugin: sending and receiving a large attachment takes about a third of the
time and a fraction of the memory it did (a 25 MiB file: about 3 s instead of
9 to 12 s, measured on a computer).
- Server: `HAVEMIND_TRUSTED_PROXIES` (empty by default) names the proxy in
front of the server, such as `tailscale serve`, so each client gets its own
pre-login rate limit instead of all of them sharing one.
- Which paths sync is decided in one place, the protocol package, so the
plugin can no longer queue a path the server would refuse.
- Plugin (internal): notes and attachments share one apply flow, `main.ts` is
split into modules, the longest files carry half the comment lines, and
duplicated helpers, eight-parameter functions and an apply branch that
never ran are gone.
## [1.5.7], 2026-09-29
### Fixed
- Plugin: an incoming change this device cannot apply (like the PDF iOS
refused to read on 26 September) no longer stops the whole vault. After
three tries it is set aside, listed in the pane with its reason and Retry
and Discard, and everything else keeps syncing.
### Changed
- Plugin: Restore in the Activity feed now really restores. It fetches the
note's text from that revision in the server history and writes it as a
normal edit. It used to add a feed entry only.
- Plugin: the status bar names who last edited the open note, from the
author the server stamps on each revision, so it survives a restart.
- Plugin: People no longer shows an invented connected/disconnected state;
Rejoin is offered on every other member. The owner can reject a waiting
device.
### Removed
- Server: encrypted checkpoints (`havemind checkpoint`, plans/006) and the
`@havemind/crypto` package with its libsodium dependency. They were never
enabled in production; backups stay plaintext on the host and are encrypted
off it by restic, as the docs now say.
- sync-core: the revision DAG and recipe modules the old Restore used.
- Plugin: two one-time data migrations. The July canonicalization rebase
predates the public release; the September mapping compaction now happens
on the next ordinary save of the producer state.
- Plugin: the author overlay and its Show authors toggle. It coloured a whole
note by an in-memory entry lost on restart; the status-bar label replaces
it. sync-core's provenance module went with it.
## [1.5.6], 2026-09-29
### Fixed
- Plugin: conflict resolution never loses both versions or locks a copy, and
the automatic sweep never overwrites a note edited mid-merge or a
same-named note of a different file.
- Plugin: the panel says why a sync failed; Sync now and Retry run one cycle
instead of rebuilding the connection, and never show Synced over a refused
session.
- Plugin: one quarantined change no longer blocks every later edit of that
file, a full vault quota no longer quarantines the whole queue, and Retry
never silently drops a change whose queued copy was lost.
- Plugin: a failing upload no longer stops the device receiving changes, and
instant updates keep arriving while an apply waits on an unsaved editor.
- Plugin: Reset connection asks first; Disconnect and Reset are not undone by
a connection still being built.
- Plugin: a rejected guest is no longer stuck failing every start, a
case-only name collision no longer stops the whole vault, and a guest
device stops getting 403 and 401 at every start.
- Plugin: a half-typed approval code survives a repaint, and a huge
conflict diff no longer exhausts memory.
- Server: a device that keeps syncing stays signed in; the session deadline
slides on every refresh instead of ending 30 days after pairing.
- Server: attachments up to the advertised 25 MiB are accepted, and a slow
upload is no longer cut off after 60 to 90 seconds.
- Server: a replayed old refresh token still burns its session, junk
pre-auth traffic can no longer starve token refresh, `cleanup-stale` keeps
onboardings in progress, and backup intervals above 596 hours no longer
run back to back.
### Changed
- Plugin: faster and lighter on phones. data.json is read once instead of
on every access, no longer holds the text of every note, and a sync-state
save is one write; a remote change costs fewer writes; `.obsidian` is
polled every 30 s and only changed files are read; the author overlay
costs nothing while off; a new connection reads only the revision history
it lacks; obsolete revisions are skipped before download; attachments are
no longer kept in memory.
- Server: expired tokens and idempotency records are pruned hourly, each
pushed payload is hashed once, and backups hard-link unchanged blobs
instead of copying them again.
- README: no longer claims line-level history or live presence, and says
which file types are not synced.
### Removed
- The unused database key (`HAVEMIND_DB_KEY_FILE`, `generate-db-key`); the
live database was never encrypted with it, and the docs now say so.
- Dead code: the unscheduled end-to-end encryption module, unused sync-core
modules, the old server invitation path, the plugin's second secret store
and IndexedDB queue, the join-time bootstrap phase in the plugin, and the
unregistered Activity view.
## [1.5.5], 2026-09-26
### Fixed
- Mobile devices stopped receiving updates at the first changed attachment.
Before applying a remote change the plugin read the file on disk as text to
check for unsaved edits, even when no editor had it open. iOS refuses to read
a PDF as text, so every sync cycle failed there and the device stayed offline.
The disk is now read only when an editor has the file open.
## [1.5.4], 2026-09-26
### Fixed
- A connected device no longer downloads every file in the vault each time
Obsidian starts. The startup identity check fetched the full contents of all
files, PDFs included, before the first pull; on a phone that restarted
before it finished, updates from other devices never arrived. The check now
downloads nothing unless the vault holds a local file sync does not know yet.
### Changed
- The release script publishes the full README to the distribution
repository instead of replacing it with the short plugin README.
## [1.5.3], 2026-09-25
### Changed
- The test suite runs about 17 seconds faster. Three slow tests now prove the
same behaviour with less work: the refresh-token replay cases are listed
explicitly instead of sampled 1000 times, the binary payload ceiling is
checked with a 1 MiB attachment plus an explicit bound for the 25 MB file
cap, and the 1.5 MB encryption round trip no longer fills its payload
through libsodium's slow random generator. No change to plugin or server
behaviour.
## [1.5.2], 2026-09-22
### Changed
- Installation and update guidance now consistently directs users to the
Obsidian Community directory. The plugin no longer documents BRAT as a
distribution path.
## [1.4.10], 2026-09-14
### Fixed
- The adjacent-line merge shipped in 1.4.9 could drop a line. It checked
whether the other side had touched the line the walk was standing on, but not
the rest of the span a multi-line change covers, so a change overlapping on
its second line passed the check and was then skipped over. CI's property
battery found it after 437 random cases, having lost the text "foo" from
ancestor "b\n", local "# h\n- a", remote "b\nfoo". Overlap is now tested
across the whole span, and such a region falls back to a conflict copy.
## [1.4.9], 2026-09-14
### Fixed
- Two people editing neighbouring lines no longer produce a conflict copy. The
merge groups changes that sit within one unchanged line of each other into a
single region, and any region both sides had touched failed outright, even
when each individual line had been rewritten by only one of them. That is the
commonest shape of a shared vault: consecutive table rows, list items, or a
heading and the paragraph under it. Such a region is now resolved line by
line, and only a line claimed by both sides still becomes a conflict copy.
The recovery is deliberately narrow. It applies when every change in the
region replaces ancestor lines one for one; an insertion, a deletion, or a
change in line count still falls back to a conflict copy, because line
ownership alone cannot say where the new text belongs. A deletion next to
someone else's edit stays a conflict for the same reason: combining them
would silently drop a line the other person was working around, and nothing
in the text says whether they meant to keep it.
## [1.4.8], 2026-09-11
### Fixed
- A remote delete could take an unsent local edit with it. The delete path
checked only that the revision owned the file, never that the copy on disk
still matched the last synced version, so a note edited while Obsidian was
closed could be removed with no conflict copy left behind. It now compares
the content first and writes a conflict copy when they differ, the same
guard the rename path already had.
- Renaming onto an occupied path destroyed the original. The destination was
checked only after the source had been deleted, so the operation correctly
reported a conflict while the file the user had been working on was already
gone. The check now runs first.
- The two choice cards on the disconnected screen overlapped, the second
sitting on top of the first card's description, with text running past the
pane edge.
### Added
- The People tab reads the member list from the server, so everyone in a vault
sees the same people. An offline device keeps showing the list it last knew
rather than emptying the panel.
- Activity names the author of a remote change. The author travels with the
revision from the server, so it is never inferred: a change from someone the
roster does not know reads as a remote edit rather than the wrong name.
## [1.4.7], 2026-09-05
### Fixed
- A request could hang indefinitely. Every outgoing call is now bounded by a
timeout, so a stalled network leaves the pane responsive instead of pinned on
a spinner that never resolves.
## [1.4.6], 2026-09-05
### Fixed
- A tap could leave the pane looking unchanged until the next repaint. Every
tap path now repaints immediately.
## [1.4.5], 2026-09-04
### Performance
- Only the activity rows that will actually be drawn are formatted, so a long
history no longer costs work nobody sees.
## [1.4.4], 2026-09-04
### Performance
- The rendered activity list is capped, bounding the work a busy vault can put
on a phone.
## [1.4.3], 2026-09-04
### Performance
- Base64 encoding runs in chunks rather than byte by byte, which is what made
attachment sync slow on mobile.
## [1.4.2], 2026-09-04
### Performance
- The conflict scan is cached between vault changes, and repaints are coalesced
while taps stay immediate.
## [1.4.1], 2026-09-04
### Fixed
- The scroll rule no longer depends on `:has()`, which older mobile webviews do
not support; it uses a class instead.
## [1.4.0], 2026-09-04, Mobile support
### Added
- Mobile support: the pane fills the screen on a phone, with touch targets and
safe areas sized for it (`isDesktopOnly: false`).
### Fixed
- Migration comments restored and applied checksums frozen on the server.
### Internal
- The plugin UI reached its 250-line-per-module ceiling, closing UI-03.
## [1.3.0], 2026-09-03
### Added
- The push channel resumes when the app returns to the foreground, so a phone
that was backgrounded picks changes straight back up.
## [1.2.3], 2026-08-31
### Fixed
- Unloading or reloading the plugin cancels the work still in flight. A sync
retry timer scheduled moments earlier could fire after unload, and a
connection attempt awaiting onboarding I/O ran to completion regardless of
whether the plugin was still there to receive it. Retry timers are now
cancellable and released on teardown, and the connect drive takes an
`AbortSignal` that `onunload` and `disconnect` both trigger.
### Internal
- The generated plugin class list no longer collects CSS custom properties.
`list-plugin-classes.mjs` greps identifier-shaped strings, so
`--havemind-first-run-pad` read as a class the moment a test named it, and
eleven tokens had already entered the list the same way. The list feeds
`stylesheet-conflicts.test.ts`, whose whole job is to fail on a rule for a
class nothing renders, so padding it with tokens weakened that check.
## [1.2.2], 2026-08-31
### Fixed
- The first-run screens no longer run into the edges of the pane. The chooser
and the host path mount their blocks directly on the view, which had given up
its own padding so the header could span the pane, so the heading, subheading,
option rows and the self-hosting link sat flush against the frame while the
hints between them stayed inset, leaving the text on two left edges in a 300px
sidebar.
- The first-run screens can be scrolled. Scrolling and the 16px of air below the
last line come from the tab body on every connected screen, and these screens
render none, so in a pane shorter than its content the overflow could not be
reached at all and the host path's "I've done this, connect" button was out of
reach.
### Added
- A browser preview of the first-run screens (`npm run dev:preview`), which
renders the shipping section renderers and stylesheet outside Obsidian so
width- and height-dependent layout defects are visible before release.
## [1.2.1], 2026-08-27
### Fixed
- The panel's keyboard listener is removed when the plugin unloads.
- Release metadata is checked before publishing, preventing a mismatch between
the plugin package, the manifests and `versions.json`.
- The local test deployment builds once, installs the same artefacts into both
test vaults and verifies their hashes.
## [1.2.0], 2026-08-24, Panel Redesign & Connection Reliability
### Added
- Redesigned Havemind sidebar with Status, Activity, People, and Connect tabs;
the Connect tab brings sync, recovery, server, and disconnect controls into
one place.
- Owner invitation composer has an explicit Close action, so it never traps the
user in the create-connection screen.
### Fixed
- The sidebar tab uses the Havemind hexagon in Obsidian's top bar.
- Server context appears after the overflow-menu actions rather than splitting
them.
- Copy invitation confirms success only after clipboard access succeeds and
provides a manual-copy fallback when it does not.
- Unexpected connect-flow errors are reported in the panel rather than leaving
an unhandled asynchronous error.
## [1.1.8], 2026-08-20
### Added
- The distribution repository now attests its own release artifacts. The
monorepo already attested the same bundle, but that attestation is recorded
against the monorepo, someone installing from the distribution repo could
not verify it there. The release people actually install is now attested
against the repository it is served from.
## [1.1.7], 2026-08-18
### Added
- First release built and attested by the release workflow, so `main.js` and
`styles.css` carry verifiable build provenance.
## [1.1.6], 2026-08-18
### Added
- Release artifacts carry GitHub build-provenance attestations, so `main.js`
and `styles.css` can be verified as built by CI from this repository. Closes
the gap where a release was trusted on account control alone.
### Documentation
- README states what the plugin accesses and why: vault-wide path enumeration
(needed to pair files with revisions and detect renames) and clipboard writes
(the invitation Copy button; the clipboard is never read).
## [1.1.5], 2026-08-18
### Fixed
- The "Self-hosting guide" link actually opens the guide. 1.1.2 corrected the
URL but the link stayed inert: a bare `<a href>` inside a plugin view does not
reliably reach the browser. It now opens the URL explicitly and carries
Obsidian's `external-link` class. Covered by a test that asserts the click
behaviour, not just the URL value.
## [1.1.4], 2026-08-18
### Changed
- Plugin description rewritten to lead with what it does, matching the
convention of the community catalogue.
## [1.1.3], 2026-08-18
### Fixed
- Selecting **Done** in the owner's Create-connection panel returns to the
connection view instead of leaving the composer open. The composer takes
render priority over the status row, so a connected vault previously showed
no "Connected, synced" anywhere and read as if sync had dropped.
## [1.1.2], 2026-08-18
### Fixed
- The "Self-hosting guide" link in the getting-started panel opens the guide
instead of doing nothing. It pointed at a repo-relative path, which Obsidian
resolved against its own internal origin rather than GitHub, so the click went
nowhere.
### Security
- Development dependency advisories cleared (`brace-expansion`, `nanoid`,
`postcss`). None of these ever shipped in the plugin bundle or the server
image; the fix keeps the dependency scan clean.
### Documentation
- The changelog, README and security policy describe the shipped release. They
had been left describing `0.9.0` across four subsequent releases, including
claiming a supported-version range that matched no published build.
## [1.1.1], 2026-08-13
### Added
- Graph view settings (`graph.json`) sync semantically: colour groups, filters
and display preferences mirror between devices, while volatile view state
(zoom, pan, node positions) stays local.
### Changed
- `.obsidian/` appearance settings resolve by last-writer-wins instead of
producing conflict copies. Configuration is not prose; a conflict artifact
for a settings file was noise, not safety.
## [1.1.0], 2026-08-11
### Added
- Author overlay wired into both the editor and reading view, with a working
settings tab.
- Command palette actions for the plugin's commands, with keyboard and
screen-reader access.
### Fixed
- **Security:** the rejoin flow no longer treats knowledge of a
`(membershipId, deviceId)` pair as proof of identity. Rejoining now requires
a per-device secret verified server-side, closing a device-impersonation
path. Devices paired before this release fail closed and must re-pair.
- Owner mutation endpoints (revoke, rejoin-grant) are rate limited.
- Vault storage accounting charges each distinct blob hash once, so a
duplicated attachment no longer counts repeatedly against the quota.
- Status semantics are honest: retrying and deferred states are distinct,
timestamps are human-readable, and synced appearance changes apply
immediately.
- Skipped files are named with their reason in the console behind the
reconcile summary notice, instead of a vague "markdown only".
### Security
- Home LAN addresses redacted from the public tree; the privacy scan widened to
cover RFC1918 `192.168.x.x` ranges, not only Tailscale CGNAT.
## [1.0.1], 2026-08-08
### Fixed
- The plugin no longer detaches its own leaves in `onunload`, per the Obsidian
community catalogue guidelines.
## [1.0.0], 2026-08-08
First stable release. The seven-day pilot closed on 2026-08-07 with zero data
loss across real two-device use from 2026-07-25.
> **Still disposable vaults only.** Content is stored on the server in
> plaintext; the trust boundary is the machine running the server. End-to-end
> encryption remains out of scope, see the README security model.
### Added
- Scheduled server backups with a restic pipeline and a verified restore drill
(the explicit gate for tagging 1.0).
- `.obsidian/` configuration mirroring across devices via adapter polling,
scoped to an explicit appearance allowlist.
- Multi-vault isolation: vault-scoped bootstrap selection, device revocation,
and rejoin-grant device binding scoped to the grant's own vault.
- Corrupt pairing state is detected at connect and offers a reset instead of
failing opaquely.
### Fixed
- **Breaking:** configuration sync is an explicit appearance allowlist. Plugin
code never syncs under any circumstance, `.obsidian/plugins/` is excluded in
full, enforced at two independent layers.
- Backup integrity is verified (`PRAGMA integrity_check`) and prune verifies
all retained snapshots; unsafe backup ids are rejected.
- Repeated configuration-poll failures surface via a throttled notice rather
than failing silently.
### Security
- CI scrubs private infrastructure values, gates releases, and pins actions.
- Dependency advisories for `fast-uri` and `find-my-way` patched.
## [0.9.0], 2026-07-24
First feature-complete build for the two-person technical alpha. Distributed as
a three-file Obsidian artifact (`main.js`, `manifest.json`, `styles.css`) via
GitHub Releases.
> **Alpha, disposable vaults only.** The pilot payload format is plaintext and
> has no end-to-end encryption. Do not connect a vault with real or sensitive
> notes.
### Added
- Two-way sync of Markdown notes with line-level history, and of image
attachments (PNG/JPG/GIF/WebP/SVG) and PDFs up to 25 MB, synced byte-for-byte.
- Append-only history with zero silent overwrites: concurrent edits land as
conflict copies under `Havemind Conflicts/`, both versions preserved.
- Authorship throughout: an Activity panel showing who changed what, a stable
colour per author, and one-click restore of any previous revision.
- Presence roster so the vault owner sees who is connected.
- Rejoin without re-pairing when a device session drops.
- Human-verified onboarding via a 6-digit code, with identity bound
server-side at approval and never trusted from the client afterwards.
- Opaque, append-only server (Fastify + SQLite): content-addressed blob store,
forward-only checksummed migrations, refresh-token rotation with reuse
detection, and per-device rate limiting.
### Notes
- `.obsidian/` and Havemind's own device state are excluded from sync by two
independent guard layers.
- End-to-end encryption is a hard gate before any real vault is connected; the
disposable plaintext pilot will not be upgraded in place.