Skip to content

fix(newsletter): stop dismissal re-pop loop and add DB write observability#2324

Open
peppescg wants to merge 2 commits into
mainfrom
fix/2321-db-write-observability
Open

fix(newsletter): stop dismissal re-pop loop and add DB write observability#2324
peppescg wants to merge 2 commits into
mainfrom
fix/2321-db-write-observability

Conversation

@peppescg

Copy link
Copy Markdown
Collaborator

What & why

First step of #2321. The newsletter modal re-pops and won't stay dismissed because the dismissal is never persisted when the SQLite write fails silently — users then submit junk emails to make it go away (the "newsletter spam"). This PR stops the worst symptom and, crucially, adds the telemetry we're missing so we can measure how often this actually happens before investing in deeper fixes.

It deliberately does not change the read-only DB behavior or add filesystem mitigations — those are follow-ups (PR2/PR3 in #2321), gated on the data this PR collects.

Changes

1. Stop the in-session re-pop loop (only when persistence fails)

  • setNewsletterDismissedAt now returns whether the dismissal was actually persisted (false = read-only DB or a failed write).
  • The renderer suppresses the modal for the rest of the session only when it wasn't persisted. When the write succeeds, behaviour is unchanged (the stored state handles suppression). This kills the "dismiss → invalidate → refetch empty dismissedAt → re-show" loop observed in Sentry (one install dismissed the modal 9 times in 5 minutes).

2. Observe DB write failures in Sentry, split by bucket

  • Bucket A (read-only DB: on-disk schema newer than the app) — captured in the migrator with applied vs known schema. This path is currently completely silent (not even a local log).
  • Bucket B (the write threw) — captured in the settings writer with the native SQLITE_* code.
  • os.name / release are attached automatically by @sentry/electron, so we can see which OS/versions are affected.

Testing

  • pnpm type-check ✅, eslint on changed files ✅
  • main/src/tests/newsletter.test.ts ✅ (added coverage for the new boolean contract: persisted/read-only/throws)
  • main/src/db/__tests__ (migrator + writers) ✅
  • Added a renderer regression test (modal stays gone when the dismissal can't be persisted). Renderer (jsdom-via-Electron) tests don't run in my local sandbox; they'll run in CI.

Refs #2321

Copilot AI review requested due to automatic review settings June 18, 2026 18:42
@peppescg peppescg self-assigned this Jun 18, 2026
…ility

The newsletter modal re-pops because the dismissal is never persisted when the
SQLite write fails silently. First step of #2321:

- setNewsletterDismissedAt now reports whether the dismissal was actually
  persisted. The renderer suppresses the modal for the rest of the session only
  when it wasn't — stopping the in-session re-pop loop on a read-only DB or a
  failed write, while leaving the normal (persisted) flow unchanged.
- Instrument DB write failures in Sentry, separating Bucket A (read-only DB:
  on-disk schema newer than the app, captured in the migrator with applied vs
  known schema) from Bucket B (the write threw, captured with the SQLite error
  code). os.name / release are attached automatically. Today these failures are
  invisible — Bucket A doesn't even produce a local log.

Refs #2321

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@peppescg peppescg force-pushed the fix/2321-db-write-observability branch from 9391d43 to 06ca195 Compare June 18, 2026 18:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses the newsletter modal “dismiss → re-show” loop when the dismissal cannot be persisted to SQLite, and adds main-process Sentry telemetry to observe (a) “DB forced read-only due to schema newer than app” and (b) “SQLite write threw” failures.

Changes:

  • Changed the dismissal IPC contract so setNewsletterDismissedAt returns boolean indicating whether the dismissal was persisted.
  • Updated the renderer modal to suppress re-showing within the same session when persistence fails, plus added a regression test for this behavior.
  • Added DB write failure telemetry (Bucket A via migrator; Bucket B via settings writer) with schema / SQLite code context.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
renderer/src/common/mocks/electronAPI.ts Updates mock return type for setNewsletterDismissedAt to boolean.
renderer/src/common/components/newsletter-modal.tsx Uses persisted/not-persisted result to avoid in-session re-pop loop.
renderer/src/common/components/tests/newsletter-modal.test.tsx Adds regression coverage for “cannot persist dismissal” session behavior.
preload/src/api/app.ts Updates preload API typing for setNewsletterDismissedAt to return Promise<boolean>.
main/src/tests/newsletter.test.ts Updates unit tests for new boolean contract and read-only / throw scenarios.
main/src/newsletter.ts Makes setNewsletterDismissedAt return persisted status; skips on read-only.
main/src/db/writers/settings-writer.ts Captures and reports thrown write failures (Bucket B) before rethrowing.
main/src/db/telemetry.ts Adds captureDbReadOnly and captureDbWriteFailure Sentry helpers.
main/src/db/migrator.ts Captures schema-newer read-only condition (Bucket A) when disabling writes.

Comment thread main/src/db/telemetry.ts
Comment thread renderer/src/common/components/newsletter-modal.tsx
Address PR review: the schema versions were interpolated into the
captureMessage string, which Sentry uses for grouping and would fragment
the signal into a separate issue per schema combo. Use a fixed message and
keep the versions in extras.

Refs #2321

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants