Skip to content

fix(notes): make the install default notes structure take effect - #150

Merged
Valtora merged 1 commit into
mainfrom
fix/install-notes-default-feedback
Jul 27, 2026
Merged

fix(notes): make the install default notes structure take effect#150
Valtora merged 1 commit into
mainfrom
fix/install-notes-default-feedback

Conversation

@Valtora

@Valtora Valtora commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Pull Request

Description

Clicking Use as install default on an install-scoped notes structure appeared to do nothing. The click was reaching the API and the value was being stored, but the Install default badge and the link label are rendered from is_install_default on GET /notes-templates, which was only fetched when the section mounts. A save that succeeded end to end therefore left the row byte-identical, so the control read as dead. By contrast the per-user default's tick renders from settings.notes_template_id, which persistNow updates optimistically, which is why one control looked healthy and the other looked broken.

The section now awaits the save and refetches the list, in a finally block so the row reflects what the server actually stored whether the save succeeded or failed. That required persistNow in AISettings to return its promise. It still catches and reports failures, so every other caller is unchanged.

A second defect followed from the first. The client held the value optimistically and the autosave layer skips any save whose serialised payload matches the last one it stored, so every click after the first emitted no HTTP request at all, which matches the "no corresponding request reaches the API" in the report. That trap only springs when the API returns 200 without having stored anything, which ConfigManager allowed: _save_config caught write errors, logged them, and returned normally, so _persist_install_wide_ai_settings reported success on a write that never happened. Install-wide settings live only in config.json, so on a deployment whose data/ directory is not writable by the container user, every such save silently reverted on the next reload. The write now raises and the endpoint returns an explicit error. The first-run bootstrap write stays best effort so an unwritable directory cannot block startup.

Two smaller hardening changes come with it. The install-wide write now reloads before its read-modify-write, so a config edited out of band is not reverted by a stale in-memory copy, matching what telemetry._write_config already did. And install_notes_template_id is validated against the database, since it is a foreign key held in a flat file with nothing enforcing it and resolution degrades silently to the built-in structure, so a wrong id was previously accepted and then quietly did nothing.

No new dependencies.

Refs #149

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that changes existing behaviour)
  • Documentation update

Checks run

  • Backend tests: source .venv/bin/activate && pytest (1087 passed)
  • Python quality: python scripts/check.py (Ruff lint, format check, whitespace, file size, held pins, mypy, doc and Alembic validators all passed)
  • Frontend lint: cd frontend && npm run lint
  • Frontend unit tests: cd frontend && npm run test (293 passed, 51 files)
  • Frontend build: cd frontend && npm run build
  • Docs validation: python3 scripts/validate_docs.py
  • Alembic validation: python3 scripts/validate_alembic.py

Migration impact

  • No database migration in this PR.
  • Adds an Alembic migration.

Documentation impact

  • No documentation change required.
  • Updated the relevant guide(s) in the same PR.

docs/DEPLOYMENT.md now states that install-wide settings are written to data/config.json rather than the database, that the directory must be writable by the API container's user, and that a failed write is now reported instead of silently reverting.

Security impact

  • No security-sensitive change.
  • Touches auth, tokens, encryption, capture ownership, or exposure.

The new admin-only validation narrows what an administrator can set; it does not widen any boundary. The non-admin path is unchanged and covered by a test asserting the key is still dropped before it reaches the config.

Manual verification

Not a capture change, so no browser smoke set applies.

Verified against a live deployment before writing the fix, to establish that the backend half was already correct. Driving the real settings router in-process against the running instance's database and a copy of its config.json, a POST carrying install_notes_template_id wrote the value, survived reload, and came back on the next GET, twice in succession. That is what isolated the defect to the missing refetch rather than the save.

Both new defects are pinned by tests that fail without the change:

  • NotesTemplatesSection.test.tsx asserts the badge appears and the link flips after a save, that clearing works, that a rejected save leaves the server's answer on screen, and that a non-admin is never offered the control.
  • test_install_notes_default.py asserts the value reaches the config, that a failed write is a reported error rather than a silent success, that a personal-scoped or unknown id is refused, that clearing is allowed, and that a non-admin write is dropped.

Still worth doing on a real deployment before merge, since automated tests cannot cover the browser path:

  1. As Owner, go to Settings > AI > Notes structure and click Use as install default. The badge should appear and the link should flip without a reload.
  2. Reload and confirm both persist.
  3. Click Remove as install default and confirm it reverts.
  4. Optionally chmod a-w data/config.json, click the link, and confirm an explicit error appears instead of a silent success. Restore the permission afterwards.

Clicking "Use as install default" appeared to do nothing. The click was
reaching the API and the value was being stored, but the badge and the
link label are rendered from is_install_default on the template list,
which was only fetched when the section mounts. A save that succeeded
therefore left the row byte-identical, so the control read as dead.

The section now awaits the save and refetches the list, in a finally
block so the row reflects what the server actually stored whether the
save succeeded or failed. That required persistNow in AISettings to
return its promise; it still catches and reports failures, so every
other caller is unchanged.

A second defect followed from the first. The client held the value
optimistically, and the autosave layer skips any save whose serialised
payload matches the last one it stored, so every click after the first
emitted no request at all. That trap only springs when the API returns
200 without having stored anything, which ConfigManager allowed:
_save_config caught write errors, logged them, and returned normally,
so _persist_install_wide_ai_settings reported success on a write that
never happened. Install-wide settings live only in config.json, so on a
deployment whose data directory is not writable by the container user
every such save silently reverted on the next reload. The write now
raises and the endpoint returns an explicit error. The first-run
bootstrap write stays best effort so an unwritable directory cannot
block startup.

Two smaller hardening changes come with it. The install-wide write now
reloads before its read-modify-write, so a config edited out of band is
not reverted by a stale in-memory copy, matching what the telemetry
writer already did. And install_notes_template_id is validated against
the database, since it is a foreign key held in a flat file with
nothing enforcing it and resolution degrades silently to the built-in
structure, so a wrong id was previously accepted and then did nothing.

Refs: #149
@Valtora
Valtora merged commit 3fca533 into main Jul 27, 2026
19 of 20 checks passed
@Valtora
Valtora deleted the fix/install-notes-default-feedback branch July 27, 2026 21:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant