Skip to content

Report editor: stale-write 409 is silent and unrecoverable under the one-save model #271

Description

@richardmhope

Summary

PR #267 removed the always-visible "Save draft" button, making the debounced autosave the only save path. But saveNow() treats an optimistic-lock 409 exactly like a transient network blip:

// src/iceberg/static/js/tags.js:180-184
const res = await fetch(form.action, { method: 'POST', ... });
if (!res.ok) return false;   // <-- 409 indistinguishable from a network error

The server correctly raises 409 on a stale version (src/iceberg/web/reports.py:321-323), but the editor just leaves the header chip on "Unsaved changes" and every subsequent autosave re-posts the same stale version, 409ing forever with no error state and no recovery affordance.

Failure scenario

Notebook access is deliberately role-wide (#65), so two writers in one report is a supported case (also: the same user in two tabs). Writer A saves. Writer B's every keystroke thereafter is never persisted; B sees only a small amber "Unsaved changes" and eventually navigates away — all of B's work is lost, with no conflict message, no reload prompt, nothing. Before this PR, clicking "Save draft" did a full-form POST and the app-level 409 handler (main.py:186-189) surfaced the conflict; that escape hatch is now gone.

Suggested fix

Inspect res.status === 409 in saveNow() and drive the save-state chip to a distinct conflict state, ideally with a reload/merge affordance. Add a test exercising the 409 surfacing.

Found in Fable review of PR #232 → HEAD.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfable-reviewCorrectness findings from Claude Fable code reviewpriority: highDo first / blocks go-public

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions