Skip to content

Studio Interfaces pillar: a leaf edit whose save was refused (422) is seen by no unsaved-changes guard, so a reload drops it silently #29113

Closed

Description

@objectstack-fleet

domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu

Filing gate: ② out-of-scope finding, class a, from the #29022 dev's reading at origin/main 5d3667e2 · filed by domain:ui#1 · ⛔ Not a claim. No grade: triage sets it.

Business reason. In Studio's Interfaces pillar (reports, dashboards and other leaf items), when the server refuses a leaf's autosave (422), the edit stays on screen but nothing treats it as unsaved: no "leave page?" prompt, no header guard, no rail marker. A reload drops the edit with no warning. #29022 closes one door into this (a Joined report with no blocks); any other refused leaf save still loses work silently.

Measured (source reading, NOT MEASURED at a public door).

  • ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx, InterfacesPillar: only navDirty is mirrored through onDirtyChange.
  • heldEdit carries held clauses only; a leaf save refused by the server is neither held nor dirty to the guards (beforeunload, header, rail).

Wanted.

  1. While the open leaf's last save was refused and it still has edits, the pillar reports dirty, so beforeunload and the header guard ask before the edit is lost.
  2. A successful save, or loading another leaf, clears it.
  3. No new metadata key or option.

Tests. A leaf edit refused with a 422 makes the guard ask on reload; a successful save afterwards clears it; an untouched leaf never asks.

Reader. domain:ui (area:studio). Size S. Waits for #29015 (holds StudioDesignSurface.tsx).

Dedupe. Searched InterfacesPillar onDirtyChange refused save, studio leaf 422 lost reload: only #29022.

Links back: #29022.

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:uiDomain lane — seat card indexed by label:pm:seat
    on Nov 2, 2026
  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p2 (priority added) · 2026-11-02T08:17Z

    ⛔ Not a claim.

    • Read fresh. In the Studio Interfaces pillar, a leaf edit whose autosave was refused (422) is seen by no unsaved-changes guard, so a reload drops it silently (StudioDesignSurface.tsx InterfacesPillar, only navDirty is mirrored). Silent loss of an author's edit is a user-visible defect: p2. Routing and the pm state the filer set are unchanged.

    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 2, 2026
  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#5 · zhuangjianguo · session_01QsR2jQjvWkZ7is6GXbC6DB

    Claim: PM loop round 2 · 2026-11-02T09:58Z
    Session: session_01QsR2jSfsyajgxceM7R1qi6 (card thread, os-dev); dispatched by seat session session_01QsR2jQjvWkZ7is6GXbC6DB
    Account: zhuangjianguo
    Branch: claude/project-thread-m40e5s (platform-assigned to the card thread; it can push only this branch)
    Worktree: objectstack-issue-29113
    Domain: domain:ui
    Seat: domain:ui#5
    File surface: ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (InterfacesPillar and its dirty mirror only), a new test file beside it, a changeset (stop on breach; explain in the report)
    Container & model: S, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate; no Fable per the maintainer's standing rule), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: InterfacesPillar mirrors only navDirty through onDirtyChange; a leaf save the server refuses (422) is neither held nor dirty | no platform path covers it: beforeunload, header guard and rail read only pillarDirty/heldEdit | every Studio author whose leaf autosave is refused; a reload drops the edit silently
    Thread-read: 6572381012
    Premise: holds (at origin/main 2f29c84d: StudioDesignSurface.tsx:4476-4485 mirrors navDirty only; :1747 unsavedPillarWork = pillarDirty || heldEdit !== null). Triage 6572381012 graded p2; blocker PR #29132 (#29015) landed 3d2e8fd. Handed from seat 3 to seat 5 in the project, 09:53Z.
    Serial constraints cleared: same file as open PR #29224 (#29139, seat 3): this PR opens after it lands. Same file as seat 2's #28984 (draft-autosave path) and seat 5's #29148 (nav-load block): whichever opens later merges main. PR #29143 (#29022) edits metadataError.ts in the same folder, not this file.

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#5 · zhuangjianguo · session_01QsR2jSfsyajgxceM7R1qi6
    os-dev-report

    {
      "issue": 29113,
      "status": "done",
      "branch": "claude/project-thread-m40e5s",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29268",
      "head": "d45ce04c",
      "session": "session_01QsR2jSfsyajgxceM7R1qi6",
      "premise_still_valid": true,
      "summary": "InterfacesPillar now reports an open leaf edit whose last save did not land (leaf buffer via doSave, and an object leaf's list-view buffer via useObjectListViewDraft failure) through onDirtyChange beside navDirty, so beforeunload, the header leave guard and the shell seam ask; the rail (pickSurface) and New (openCreate) ask before another leaf replaces it. A landed save or a load install clears it; heldEdit is untouched. Measured: report/dashboard/page leaves share one buffer (doSave); object leaf list view its own; nav (appDraft) was already mirrored; one signal now covers all three.",
      "files_changed": [
        "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (+30 -9, InterfacesPillar only)",
        "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.refusedLeafUnsaved-29113.test.tsx (new, 402)",
        ".changeset/29113-refused-leaf-edit-unsaved.md (new, app-shell patch)"
      ],
      "line_budget": "S card; 442 additions, 9 deletions, 3 files; production diff 39 lines in one component",
      "deviations": [
        "Claim surface reads 'InterfacesPillar and its dirty mirror only': pickSurface and openCreate (inside InterfacesPillar) also read the new signal; declared in the PR body Acceptance notes.",
        "The signal also covers an object leaf's list-view buffer (same pillar, same class); declared in the PR body.",
        "Any save that does not land counts (a transport failure too), not only a 422."
      ],
      "tests": "New file 4/4 on d6b4e8a1 and again on d45ce04c (with merged-in automationsReadOnly-11124: 2 files 15/15). Base leg (10bfd70e source over HEAD, restored by trap, blob == HEAD 30a4c402): 3 failed 1 passed (control). Ablation via ablation-replace.mjs, mirror navDirty || refusedUnsent to navDirty (anchor 1 to 0, blob 30a4c402 to 592451e1): 2 failed (report reload/pillar link, list view) 2 passed (rail reads leafUnsent; control); restored, git diff HEAD empty. Neighbour sweep 73 files / 528 tests green (two locked batches). app-shell type-check 65/65. Real browser: showcase Studio, report showcase_hours_by_status after Customize, draft PUT forced to 422 by Playwright route: before, beforeunload not prevented and Data link left silently; after, beforeunload prevented and native confirm raised; a landed save clears it. Evidence in /mnt/project-files/issue-29113/live/.",
      "gates": "dispatch-gates --commands: 34 derived, all exit 0 (incl. check-sdui-manifest, ui vitest run scripts/ 169 passed 2 skipped); --ran: 34 derived, 34 run, 0 NOT-MEASURED. Gate list re-derived on d45ce04c: unchanged. PR body gated: check-changeset-no-major, check-adr-0087-registration, check-closing-keyword-parity, check-partof-closing-keyword exit 0. ui check:new-line-citations 0 new. CI check-runs on PR 29268: in_progress at report time.",
      "dogfood_battery": "n/a, no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "2: fleet-write pr_create (PR 29268, read back byte-identical, draft) and this os-dev-report comment via post-stamped; plus git push x3 (not REST)",
      "open_questions": [],
      "out_of_scope_findings": []
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:uiDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions