Skip to content

Releases: nodummd/nodum

v3.11.0 — the nodum CLI

Choose a tag to compare

@maqboolthoufeeq maqboolthoufeeq released this 18 Sep 13:17
6ced181

The compose stack gets a command line of its own, contributed by @ziuus in #87.

nodum stack …

pip install -e cli
nodum stack up                 # start dev
nodum stack up --env prod      # production (needs deploy/.env.prod)
nodum stack ps
nodum stack logs -f api
nodum stack exec api uv run alembic upgrade head
nodum stack migrate --env staging
nodum stack down -v

cli/ is a third top-level component alongside back/ and web/: standard library only, with its own pyproject.toml, uv.lock, tests and CI workflow. cli-lint and cli-test run as part of make verify.

Everything sits under nodum stack <verb>, leaving the top-level nodum <noun> space free for an API client later. Every verb delegates to deploy/compose.sh through a single subprocess boundary, so engine detection, environment-file resolution and the staging/prod subdomain preflight keep one source of truth.

Behaviour worth knowing:

  • migrate runs compose run --rm migrate, which returns the migration's own exit code without stopping a live postgres.
  • restart is up -d --force-recreate; plain up -d does nothing to a running stack.
  • clean and reset need a terminal and the environment name typed back. --force does not bypass that on staging or prod.
  • --env and --root work before or after the verb. After a service name in exec they are refused, because there they would silently target dev.

Also in this release

  • Version constants corrected. APP_VERSION is maintained by hand in two places and both had drifted — the backend read 3.9.1 (it serves the OpenAPI document) and the web app 3.3.0 (shown in Settings → General and in the landing page's SoftwareApplication data). Both now read 3.11.0.
  • Branch protection. main and dev carry rulesets: pull requests only, merge commits, no deletion or force-push.
  • A test that never tested anything. The malformed-import end-to-end check matched the import dialog's own instructions, so it passed whether or not the error it was asserting on ever appeared. It now waits for the error toast itself, which also means the toast is verified to render (#94, #95, #97).

Full changelog: v3.9.12...v3.11.0

v3.10.0

Choose a tag to compare

@maqboolthoufeeq maqboolthoufeeq released this 18 Sep 11:51
1978c27

What's Changed

Full Changelog: v3.9.1...v3.10.0

v3.9.12

Choose a tag to compare

@maqboolthoufeeq maqboolthoufeeq released this 17 Sep 01:19
1978c27

What's Changed

Full Changelog: v3.9.1...v3.9.12

v3.9.1 — Graph names follow the viewport

Choose a tag to compare

@maqboolthoufeeq maqboolthoufeeq released this 01 Sep 14:51
e6a50c2

A hotfix on v3.9.0: zooming into a synced vault showed dots with no names.

Names follow the viewport

The graph kept a pool of label elements chosen once — the 1200 most-connected nodes in the vault. A node outside that slice had no label element at all, so no amount of zooming would ever name it. A synced mailbox is thousands of one-link leaves hanging off daily notes, which is precisely the outside of that slice, so zooming into a cluster showed unnamed dots.

The pool now follows the viewport: what is on screen (plus a margin) gets a label element, most-connected first, still capped at 1200 so the DOM stays bounded. Zoom in and the handful in front of you are all named; zoom out and the cap picks hubs, which is what a distant view should say anyway.

Measured on a 1456-note vault, names drawn while zooming in:

fit view +3 +6
v3.9.0 54 30 15
v3.9.1 61 51 30

Search matches and the note you are typing in sort to the front of that ordering, so the exemptions added in v3.9.0 survive the cap — and the on-demand search-label path that v3.9.0 shipped untested is retired, because an on-screen match is now always a candidate.

Rebuilding the set costs a read-back of every point position, so it runs at most every 220 ms, and only when the view has moved, the layout is still settling, or the data changed.

Shorter names

Names are cut to 30 characters plus an ellipsis. A graph label is a map label, and the whole title is already one hover away in the tooltip.

Labels also no longer grow with the 2.2× node scale when you zoom in. Every pixel a name gains in width is a neighbour that loses its own name to the claim grid — backwards, because zooming in is exactly when you want more names.

Also fixed

  • Label colours went stale. The render loop holds the closures from the render that built it, so a group query or a folder colour changed the palette without any newly created label ever seeing it.

Full changelog: v3.9.0...v3.9.1

v3.9.0 — Import & sync in one place

Choose a tag to compare

@maqboolthoufeeq maqboolthoufeeq released this 01 Sep 11:36
5c60059

Import and live sync become one thing you can find, and the knowledge graph becomes readable at the size a synced vault actually reaches.

Import & sync in one place

Import data now lives in the sidebar's tab strip — one door for every source, in the vault it will write to, rather than buried in Settings.

  • Everything is chosen before the redirect. How much history (including future only), which calendars, which Gmail labels, whether message bodies are stored, which senders to exclude, which links to write — all decided on a setup screen, carried through the OAuth state, and applied to the very first sync. Previously the first backfill ran on defaults and your choices arrived too late to matter.
  • Where notes land is yours to pick. A folder named after the source at the vault root (the default), an existing folder chosen from a collapsible tree, or a new one you type.
  • Progress you can see and stop. A floating card counts records as they arrive, survives a reload or a trip to another tab, and offers Pause, Resume, and Keep future only — which stops the history import, keeps what has already landed, and leaves new items syncing.
  • The Connections settings tab is gone. It was a second, worse copy of all of this.

A graph you can read

The graph drew every name at full strength, which on a few hundred notes is a wall of overlapping text.

  • Names are drawn most-connected first, each claiming the screen space it occupies; a name that would land on space already taken is not drawn. On a 494-label vault the fit view went from every name stacked to 48 legible ones — the hubs and the nodes standing on their own.
  • Zoom hands the space back. Zoom in and more names appear until, close in, everything is named. Zoom out and they fade off entirely.
  • Search is the exemption. Zoomed out past the point where names are drawn, what you searched for is still named — and only that. So is the note you are typing in.
  • Geometry decides rather than a quota, so a fifteen-note vault still shows all fifteen names at the fit view.

Privacy Policy and Terms of Service

Both now live on the marketing site and are linked from the footer — required for Google OAuth verification, and honest about what nodum.md is: one instance of an open-source project, run by one person on their own server, offered free and without warranty.

Fixes

  • Calendar connected but synced nothing. The bind-mounted back/.env leaked POSTGRES_PORT=15432 into the Celery container, so the worker dialled a port that only exists on the host. 113 events landed the moment it was fixed.
  • Gmail could not be connected while Calendar was. include_granted_scopes merges old scopes into a new consent, so inferring the provider from the grant re-stamped the existing connection. The requested provider now rides the OAuth state.
  • Pause could be silently undone by a run already in flight or queued behind it.
  • Reconnect is offered when the token encryption key has changed, instead of a dead-end error.
  • The graph came back blank on any workspace restored with the graph tab already open. cosmos.gl 3.x brings its GPU device up asynchronously; the data effect ran in the same commit that constructs the engine, so the first apply always threw and nothing ever retried it.
  • The graph's zoom fade was dead code — the reference zoom was never recorded for small graphs, so every zoom read as "exactly the fit view".
  • Reconnecting no longer wipes settings such as the chosen calendars.

Dependencies

21 bumps: the web minor group (15), the backend minor group (6), uvicorn[standard], and the claude-code-action pin. make verify and the full Playwright suite were run locally against the merged tree with all of them installed.

Operator notes

No new environment variables since v3.8.0. The v3.8.0 notes still apply: GOOGLE_SYNC_CLIENT_ID, GOOGLE_SYNC_CLIENT_SECRET and OAUTH_ENCRYPTION_KEY must reach both the API and the Celery worker, the OAuth consent screen must be published (a project left on Testing expires every refresh token after seven days), and Gmail stays behind GOOGLE_SYNC_GMAIL_ENABLED, off by default, because its scopes are Google-restricted.

Full changelog: v3.8.0...v3.9.0

v3.8.0 — Live Google sync

Choose a tag to compare

@maqboolthoufeeq maqboolthoufeeq released this 31 Aug 13:51
244d1cd

Two features, and a long hardening pass over the larger of them.

Live one-way sync from Google

Connect a Google account and calendar events and mail threads arrive as notes that keep themselves current — and that participate in the graph rather than sitting beside it.

  • Calendar — one note per event series in Calendar/YYYY/MM, so a weekly standup is one note rather than one a week. Meetings you declined are not synced.
  • Gmail — one note per thread in Mail/YYYY/MM. Self-hosted only; see below.
  • The ## Notes contract — sync owns everything above that heading and nothing below it. Updates take a row lock, so someone typing underneath as a sync lands cannot lose it. Delete a synced note and it is never recreated; delete the heading and sync stops touching that note rather than risk overwriting you.
  • Rationed links — one date wikilink per note in the vault's own daily-note format, and a person only once they have a note. A link to a note that does not exist is a grey dot, and one per correspondent buries everything you actually wrote.
  • Connection settings — which calendars to sync, the folder they land in, how many appearances before someone gets their own note, and for Gmail which labels are in scope and whether message bodies are stored.

One-click import from twenty apps

Obsidian, Notion, Evernote, Apple Notes, Google Keep, OneNote and others, through Import data in the workspace.

Operator action required

  • New environment variables. GOOGLE_SYNC_CLIENT_ID, GOOGLE_SYNC_CLIENT_SECRET and OAUTH_ENCRYPTION_KEY must reach the API and the Celery worker — they are in the shared x-backend-env anchor for that reason. Empty leaves the feature off, which is the safe default. See docs/OWNER-SETUP.md §1b.
  • Publish the OAuth consent screen. A Google Cloud project left on Testing expires every refresh token after exactly seven days, so sync works for a week and then dies with an opaque invalid_grant. Nodum recognises that case and says so, but cannot fix it.
  • Gmail is self-hosted only. Its scopes are Google-restricted, obliging any hosted multi-user service to pass a CASA assessment by an accredited lab, renewed annually. It sits behind GOOGLE_SYNC_GMAIL_ENABLED, off by default, and a test fails the build if a future adapter reaches for a restricted scope without a flag.
  • No write scopes. Sync requests read-only access and could not create, edit or delete anything in a connected account if asked to.

Behaviour changes

  • The Gmail label filter now applies on every sync rather than only the first import. Previously, once a backfill finished, a connection set to INBOX wrote a note for every thread in the account.
  • Declined calendar events no longer become notes. Ones you have not answered, or accepted tentatively, still do.
  • Stored mail bodies keep only what each message added; the quoted history below a reply is dropped.

Hardening

The sync feature went through a review loop after its first version passed its tests. Almost everything found was a failure that reported nothing — silent thread loss in Gmail's incremental walk, a filter that only applied to the first import, one broken connection ending the sweep for every account on the instance, a disconnect in one vault killing another's connection, remote text able to forge frontmatter properties or reopen the user region, and a settings endpoint that validated its keys but not its values. Each fix is mutation-checked: the bug is put back and the test must fail for the right reason.

Three structural rules now fix bug classes rather than instances — an AST rule requiring transport handling on every HTTP client, a closed status vocabulary checked across the Python/TypeScript boundary, and a guard that every sync setting reaches the containers that read it.

Verification

make verify green. 289 backend unit tests, 285 integration, 23 Playwright specs across the connection surface, 91% coverage of the sync feature.

Not covered: no test drives a real Google API — that needs a registered OAuth client and is operator setup.

Also

make e2e-up brings up the stack the Playwright suite expects, mirroring CI. There was previously no documented way to run e2e locally.

v3.7.0 — public API response contract

Choose a tag to compare

@maqboolthoufeeq maqboolthoufeeq released this 21 Aug 22:30
fd4b4bd

Breaking for external API clients. Every /api/public/v1 response now carries ok:

{ "ok": true, "data": { "id": "…", "title": "From the API" } }
{ "ok": false, "error": {
    "code": "VALIDATION_FAILED",
    "details": "body.title: Field required",
    "message": "Request validation failed." } }
  • Error codes are stable SCREAMING_SNAKE: NOT_FOUND, FORBIDDEN, CONFLICT, ALREADY_EXISTS, VALIDATION_FAILED, UNAUTHORIZED, RATE_LIMITED, PAYLOAD_TOO_LARGE, METHOD_NOT_ALLOWED, INTERNAL_ERROR.
  • details carries the specifics (which field, which scope, server_updated_at=…) and falls back to message, so the shape never varies.
  • Rate-limit (429) and body-size (413) responses — produced before the mount — speak the same dialect.
  • The app's own /api/v1 is unchanged.

Also in this release: Developers link in the site navbar, and a deploy preflight that understands running behind an external TLS terminator.

Migrating: branch on body.ok instead of pairing HTTP status with shape, and uppercase any error-code comparisons.

Full changelog: v3.6.1...v3.7.0

v3.6.2

Choose a tag to compare

@maqboolthoufeeq maqboolthoufeeq released this 21 Aug 19:02
dd74d19

What's Changed

Full Changelog: v3.6.1...v3.6.2

v3.6.1 — subdomain hotfix

Choose a tag to compare

@maqboolthoufeeq maqboolthoufeeq released this 21 Aug 17:06
6daebb2

Patch release for deployments enabling the v3.6.0 subdomains:

  • Section redirects now honor X-Forwarded-Proto — behind the TLS-terminating proxy they emitted http://.
  • /api-reference joins the apex redirects (the proxy matcher excluded api, unintentionally catching api-reference; it now excludes api/).
  • smoke.sh follows section redirects and, when a target host has no DNS record, says so by name with both remedies instead of printing a bare 308. /docs is covered too.
  • CI: the full-stack Playwright suite now runs only on release PRs (main ← dev) and manual dispatch.

Deploying with subdomains: set both NODUM_SUBDOMAIN_ADDRESSES and NODUM_ENABLE_SUBDOMAIN_REDIRECTS in .env.prod (DNS records required) and ./compose.sh prod up -d --build.

Full changelog: v3.6.0...v3.6.1

v3.6.0 — subdomains, community hub, dev tooling

Choose a tag to compare

@maqboolthoufeeq maqboolthoufeeq released this 21 Aug 15:14
283101c

Highlights

  • Host-based sections — docs., developers. (the interactive API reference at its root), community. and forum. subdomains, served by the same app. Additive: the apex keeps working; enable with NODUM_SUBDOMAIN_ADDRESSES + NODUM_ENABLE_SUBDOMAIN_REDIRECTS once DNS records exist.
  • Forum & community split, Obsidian-style — the discussion product now lives at /forum (old URLs redirect permanently); /community is a hub that routes the forum's categories, shows the latest announcements and showcases live, points at the ways to contribute, and links the extension surfaces that already exist (public API, MCP, Web Clipper) with plugins on the roadmap.
  • GitHub in the navbar — an icon button beside Log in.
  • Agentation in dev — annotate the running app and paste agent-ready feedback; production builds contain zero bytes of it.

Verification

Every constituent PR green in CI; full Playwright sweeps during the cycle; make prod-verify (prod compose, migrations, smoke incl. forum, hub, public API and the attachment round-trip) passed on the exact release candidate.

Full changelog: v3.5.0...v3.6.0