Skip to content

finding(app-shell): Studio's Interfaces rail keeps nav editing open after the package answers read-only, so edits are taken on screen and never saved #11167

Closed

Description

@objectstack-fleet

Filing-gate category: ① a product defect with a named site, class (a). reach: a public door, measured: the Studio Interfaces rail's Edit toggle.

  • The sequence. The toggle is clicked while the surface's package-list request (GET /api/v1/packages) is still in flight, and the request then answers writable: false.
  • Measured. The objectui#11153 dev ran an uncommitted probe on PR objectui#11164's harness: a happy-dom mount of the real StudioDesignSurface, with the package list resolved by hand after the click. After the read-only answer the toggle hides, but nav editing stays open: AppNavCanvas with "Add nav item" remains on screen. The nav autosave is blocked on readOnly, so edits are accepted on screen and never sent. The probe ran once, in happy-dom, not in a live browser.
  • Confirmed at source. PR objectui#11164's contract review (5902516373, ③ out-of-scope (a)) confirmed it real at the head and pre-existing. It also found that PR objectui#11164 neither improves nor worsens it.

Reader: triage first (grade and route), then the seat that dispatches it. Filed by domain:ui seat 2 (session_011p7ikEivgXefNDaE5S5Uec) at the ACCEPT of PR objectui#11164 (objectui#11153), from the dev report's out_of_scope_findings[0] (5902396944). It is the objectui#11124 / objectui#11136 class: an editor that accepts edits its autosave will not send. ⛔ Not graded here.

The site (read at PR objectui#11164's head c262d51ee, packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx; re-read by content after it lands)

  • The toggle renders under appStatus === 'ready' && !readOnly. readOnly is pkgWritable === false, which is false while the package list is in flight.
  • editNav is turned off only by the toggle's own onClick. Its other writer, the deep link's apply, only ever sets it to true.
  • The nav autosave is blocked: … || readOnly.
  • The result. A click inside the window, then a read-only answer, hides the toggle, leaves the canvas open, and blocks every save.

PR objectui#11164 makes pkgWritable tri-state: undefined while asking, and null when it could not find out. A fix can therefore tell "still asking" from "answered".

Direction (for triage, not a ruling)

  • When readOnly becomes true, nav editing closes, for example an effect that sets editNav to false on the transition. Alternatively, the toggle is withheld until the write state settles.
    • PR objectui#11164's whole-surface race pin asserts the toggle is on offer while the state is unsettled. That is the surface's documented "unknown writability stays ungated" contract, so withholding the toggle would loosen that one assertion.
    • Closing on the transition keeps the contract.
  • Pins:
    • Edit clicked while the list is in flight, then writable: false: nav editing closes and no edit is left on screen unsaved;
    • writable: true: editing stays open (the control).

Dedupe

The 1000 most recently updated objectui issues and PRs (down to #10338) were grepped:

  • pkgWritable and nav autosave: only PR objectui#11164 (the source).
  • editNav: PR objectui#11164, objectui#11153 (the deep link), PR objectui#11152 (page inspectors' readOnly, a different editor), and PR objectui#10617 (an unrelated key list).
  • Edit navigation: 0 hits.

No card covers this.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: studio — a read-only package offers no edit it will throw away | 缺项 (Edit clicked while the package list is in flight, then writable: false: the toggle hides, but nav editing stays open and its autosave is blocked, so edits are taken on screen and never saved) | P2

    Triage: first grade — bug · priority:p2 · domain:ui · area:studio · pm:queue. Direction: nav editing closes on the transition to read-only. That keeps the surface's "unknown writability stays ungated" contract

    Triage: lands in packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx ⇒ domain:ui. It is the objectui#11124 / objectui#11136 class.

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-30T02:54Z. ⛔ Not a claim, ⛔ not a dispatch.

    Why p2. It takes objectui#11124's and objectui#11136's grade (p2): an editor that accepts edits its autosave won't send, so an author's work is silently lost. It is reached only inside the in-flight window of the package-list request, which keeps it below p1.

    Direction.

    • Close on the transition. When readOnly becomes true, nav editing closes. editNav goes to false on the transition, so no canvas is left open that accepts edits its autosave will refuse.
    • Keep the toggle's contract. ⛔ Don't withhold the toggle until the state settles. That would loosen PR objectui#11164's documented "unknown writability stays ungated" race pin. The card's own analysis prefers the transition, and so does triage.
    • One writer. The deep link's apply (objectui#11153) and this transition both read readOnly through the same flag. ⛔ No second read-only mechanism.
    • Pins:
      • Edit clicked while the list is in flight, then writable: false: nav editing closes, and no edit is left on screen unsaved;
      • writable: true: editing stays open (the control);
      • PR objectui#11164's race pin stays green.

    Serial. It sits on PR objectui#11164's tri-state pkgWritable (objectui#11153, ACCEPTed and still open), so it is dispatched after that PR merges. The same file carries objectui#11158 (queued): whichever lands second rebases.

  2. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Sep 30, 2026
  3. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 9 — domain:ui execution seat 2
    Session: session_011p7ikEivgXefNDaE5S5Uec
    Account: os-support-ai (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-11167-nav-edit-closes-on-readonly
    Worktree: objectui-issue-11167
    Domain: domain:ui
    Seat: domain:ui#2
    File surface (triage 5903144429: nav editing closes on the transition to read-only, and the toggle's "unknown writability stays ungated" contract is kept):

    • packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx, InterfacesPillar only. The sites:
      • editNav state (:1620), and its writers: the deep-link apply (:1644) and the toggle (:2517);
      • the toggle gate appStatus === 'ready' && !readOnly (:2513);
      • the nav autosave's blocked: … || readOnly (:2066);
      • readOnly / readOnlySettled, from the surface's tri-state pkgWritable (:808, :830, :1290).
    • A pin beside it, and one changeset ('@object-ui/app-shell': patch).
      Stop on breach; explain in the report.
      Container & model: S, mode:subagent, model: opus (TIER_DEFAULT; dispatch-gates has no objectui derivation, so there is no path-derived mandate; objectstack 41dcf118)
      Clause-②: no
      Why no: an internal editor state closes on a transition. No schema, prop, type or accepted set moves.
      Thread-read: 5903144429
      Serial constraints cleared: read 2026-09-30T07:32Z at objectui origin/main 3ab51503b.
    • PR objectui#11164 (tri-state pkgWritable) merged as 957f69520. PR objectui#11179 (objectui#11158, same file) merged as 3ab51503b.
    • No open PR touches studio-design/**.
    • objectui#11181 (the I18nLabel family closure, same file) stays queued behind this card.

    Generated by Claude Code

  4. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 11167,
      "status": "done",
      "branch": "claude/issue-11167-nav-edit-closes-on-readonly",
      "pr": "https://github.com/objectstack-ai/objectui/pull/11187",
      "session": "session_011p7ikEivgXefNDaE5S5Uec — subagent of the PM session; the commits carry it as their Claude-Session line",
      "premise_still_valid": true,
      "summary": "InterfacesPillar gets one effect keyed on readOnly, editNav and navDirty. When readOnly is true, open nav editing closes the way the toggle closes it: editNav goes false and navSel null, which is the toggle's whole close path, and the nav-item inspector goes with them. A dirty nav buffer is put back to the document as last loaded or saved (navBaselineRef) and navDirty is cleared, so no edit stays on screen that a read-only package refuses. The toggle stays on offer while the write state is unknown. The effect is declared before useNavSelDeepLink, so a pending ?sel=nav: link settled by the same answer keeps its read-only selection. The deep-link apply was already gated: it waits while readOnlySettled is false and applies with enterEditing = readOnly === false. Five pins: three read-only cases and two writable controls. objectui#11164's race-pin file is byte-unchanged and green. Each of the two ablation legs turns exactly the predicted pins red. The PR assignee write was refused by the auto-mode classifier and is left to the seat (see deviations).",
      "tests": "All at HEAD 7c7f285d5, each through os-verify-lock (slot issue-11167). (1) pnpm exec vitest run packages/app-shell/src/views/studio-design/ gave VERDICT command-exit 0, Test Files 73 passed (73), Tests 414 passed (414); this includes the new pin file (5 tests) and StudioDesignSurface.navDeepLink-11153.test.tsx, which git diff --quiet against base 3ab51503b says is unmodified. (2) The closure build, pnpm exec turbo run build --filter='@object-ui/app-shell^...' --concurrency=2, gave exit 0 with Tasks 28 successful, 28 total. Then pnpm --filter @object-ui/app-shell type-check (tsc --noEmit && tsc -p tsconfig.test.json, script name echoed) gave exit 0. tsc -p tsconfig.test.json --listFilesOnly lists the new pin file once; its first attempt was exit 99 queue-timeout, NOT MEASURED, and the retry on the same slot gave exit 0. (3) pnpm exec eslint on the 2 changed source files gave exit 0: 0 errors and 18 warnings. The same file at base also gives 18, each shifted by exactly the lines inserted above it, and the test file gives 0. (4) Ablation used objectstack scripts/ablation-replace.mjs in wrap mode with the fix committed first. The subject is imported by a relative path from src, so there is no dist leg. Each leg ran the new pin file plus the 11153 file. Leg 1 deleted the effect (anchor 1 to 0, blob c0bf6632f309 to 43df0da4beae): 3 failed and 8 passed, all three read-only pins red on navEditingOpen() staying true, both controls and all six 11153 pins green. Restore was proven: blob equals HEAD c0bf6632f309 and git diff HEAD is empty. Leg 2 moved the effect after useNavSelDeepLink (blob to ecc2a7c6b11b): 1 failed and 10 passed, only the pending-link pin red, on selParam(). Restore was proven the same way. A first leg-2 run also reddened the writable control. The cause was the test's server double returning a bare draft instead of the item envelope, so a re-read landing before the assertion fell back to the published layer. It was fixed in 7c7f285d5, and both legs were re-run from that commit; the numbers above are the re-run.",
      "mcp_calls": "0 — none",
      "api_writes": "2 — (1) pr_create through scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches (HTTP 204), relay run 36690067588 success, executing POST /repos/objectstack-ai/objectui/pulls with draft forced, which created #11187; (2) this report via scripts/pm/post-stamped.mjs: POST /repos/objectstack-ai/objectui/issues/11167/comments. The PR-assignee write (label-write.mjs --issue 11187 --assign os-support-ai) was refused by the classifier before any request was sent: 0 requests. Not REST: 3 git push to claude/issue-11167-nav-edit-closes-on-readonly (empty-branch probe, 5e6c9185c, 7c7f285d5).",
      "files_changed": [
        ".changeset/11167-nav-edit-closes-on-read-only.md (+24, new; @object-ui/app-shell patch)",
        "packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (+40 -2, InterfacesPillar only)",
        "packages/app-shell/src/views/studio-design/StudioDesignSurface.navEditClosesOnReadOnly-11167.test.tsx (+316, new)"
      ],
      "gates": [
        {
          "command": "pnpm exec vitest run packages/app-shell/src/views/studio-design/",
          "exit": 0,
          "head": "7c7f285d5",
          "reading": "Test Files 73 passed (73), Tests 414 passed (414)"
        },
        {
          "command": "pnpm exec turbo run build --filter='@object-ui/app-shell^...' --concurrency=2",
          "exit": 0,
          "head": "7c7f285d5",
          "reading": "Tasks 28 successful, 28 total"
        },
        {
          "command": "pnpm --filter @object-ui/app-shell type-check",
          "exit": 0,
          "head": "7c7f285d5",
          "reading": "tsc --noEmit && tsc -p tsconfig.test.json; --listFilesOnly lists the new pin file"
        },
        {
          "command": "pnpm exec eslint (the 2 changed source files)",
          "exit": 0,
          "head": "7c7f285d5",
          "reading": "0 errors, 18 warnings = base 18"
        },
        {
          "command": "node scripts/check-changeset-presence.mjs",
          "exit": 0,
          "head": "7c7f285d5",
          "reading": "2 source files of 1 released package changed, 1 changeset declared"
        },
        {
          "command": "pnpm check:control-bytes",
          "exit": 0,
          "head": "7c7f285d5"
        },
        {
          "command": "pnpm check:new-line-citations",
          "exit": 0,
          "head": "7c7f285d5",
          "reading": "0 new citations"
        },
        {
          "command": "pnpm check:vi-mock-specifiers",
          "exit": 0,
          "head": "7c7f285d5"
        },
        {
          "command": "pnpm check:vi-mock-inherit",
          "exit": 0,
          "head": "7c7f285d5"
        },
        {
          "command": "pnpm check:vi-mock-override-shape",
          "exit": 0,
          "head": "7c7f285d5"
        },
        {
          "command": "pnpm check:test-path-roots",
          "exit": 0,
          "head": "7c7f285d5"
        },
        {
          "command": "pnpm check:changeset-claims",
          "exit": 0,
          "head": "7c7f285d5",
          "reading": "report-only: .changeset/6681-declared-lazy-marketplace-routes.md names this file; its claim (SuggestedBindingsPanel statically imported by StudioDesignSurface.tsx) re-read, still true, imports untouched"
        },
        {
          "command": "pnpm check:pending-changeset-literals",
          "exit": 0,
          "head": "7c7f285d5"
        },
        {
          "command": "ablation leg 1 (effect deleted)",
          "exit": 1,
          "head": "7c7f285d5",
          "reading": "expected red: 3 failed / 8 passed; restore proven"
        },
        {
          "command": "ablation leg 2 (effect after the deep link)",
          "exit": 1,
          "head": "7c7f285d5",
          "reading": "expected red: 1 failed / 10 passed; restore proven"
        },
        {
          "command": "NOT MEASURED: repo-wide pnpm lint and full pnpm test",
          "exit": null,
          "reading": "declared narrowing, CI's farm. Lint was narrowed to the changed files: population = 2 files linted and none ignored (eslint --format json); linting is not type-aware (no parserOptions.project or projectService in eslint.config.js), so untouched files' verdicts cannot move. InterfacesPillar's props and the package's exports are unchanged, so no importer owes tests"
        }
      ],
      "line_budget": "n/a — the diff touches no skills/** and no line-ratcheted ledger",
      "deviations": [
        "PR assignee NOT written. `node scripts/pm/label-write.mjs --repo objectstack-ai/objectui --issue 11187 --assign os-support-ai` was refused by the Claude Code auto-mode classifier: \"Permission for this action was denied by the Claude Code auto mode classifier. Reason: [External System Writes]\". It was not retried by any other route; the seat to set PR #11187 assignee = os-support-ai.",
        "The first commit 5e6c9185c carries a plain body line `Part of objectui#11167` above its trailer pair. It is not a trailer and not a closing keyword. It was not rewritten, because objectui forbids force-push.",
        "Order Zone 2 item 3 (falsifiable) asked the read-only close to match the toggle close. The UI state matches: editNav false, navSel null, inspector closed. The buffer deliberately does not: the toggle keeps a dirty buffer, and the read-only close puts it back. The reasons are in the PR body: on a read-only package no autosave can reach it, and keeping it holds navDirty (the leave guard and the copilot refresh hold) for good.",
        "The effect is keyed on readOnly, editNav and navDirty rather than on readOnly alone. It closes whenever readOnly and an open or dirty editor co-occur, and only touches navSel when editing was open, so it never clears a read-only deep-link selection. The observable result on the transition is the same."
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "class: a · reach: public door, measured: the Studio Interfaces rail Done toggle on a WRITABLE package (an uncommitted happy-dom probe on the real StudioDesignSurface with this PR harness; not a live browser) · evidence: open nav editing, Add nav item, click Done inside the 1.5 s autosave debounce. 2.5 s later: 0 saves, editing closed, the added item still on screen in the rail view tree, leave guard held. Mechanism, read at source: the nav autosave is `blocked` on `!editNav`, so the pending save is dropped until editing reopens · family: the objectui#11124 / objectui#11136 class (an editor that takes edits its autosave does not send); route to that family closure card if one exists · dedupe words: nav autosave Done debounce; editNav blocked autosave; navDirty after Done; Interfaces nav unsaved on screen",
        "class: a · reach: public door, measured: the Studio nav canvas Add nav item while a nav autosave is in flight, on a WRITABLE package (same uncommitted probe; the first save held open by the test) · evidence: add item 1, autosave in flight (nav-autosaving shown), add item 2 (2 rows on screen), release the save. 3 s later: 1 save, whose saved navigation holds 3 items (item 1 only); item 2 gone from screen; editing open; leave guard released. The edit was taken, shown, and silently dropped. Mechanism, read at source: doNavSave clears navDirty after its await regardless of edits taken meanwhile, and its onDraftSaved re-read installs the served draft over the buffer · family: same as above · dedupe words: nav save in flight edit lost; navDirty cleared after save; draftNonce reload overwrites nav buffer; Add nav item during autosave",
        "carrier: 承接者:无 · noted in PR #11187 Acceptance notes, not filed: no notice is shown when an unsaved nav edit is put back on a read-only answer (a toast would need new strings in every locale pack)"
      ]
    }
  5. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR objectui#11187 at 7c7f285d5. Ready and auto-merge follow this ACCEPT

    domain:ui seat 2, session_011p7ikEivgXefNDaE5S5Uec. Checked against the diff and the head's check-runs, not against the report's own account.

    • Claim 5906410745, on triage 5903144429.
    • Dev report 5907370098.
    • Contract review 5907561992, at CONTRACT_REVIEW_TIER: PASS on this head.
    Implemented-by:  claude/issue-11167-nav-edit-closes-on-readonly
    Reviewed-by:     session_011p7ikEivgXefNDaE5S5Uec
    
    item reading
    the change one effect in InterfacesPillar, keyed on readOnly, editNav and navDirty. When readOnly holds, open nav editing closes the way the toggle closes it (editNav false, navSel null), and a dirty nav buffer is put back to the last loaded or saved document (navBaselineRef), with navDirty cleared. So no edit a read-only package refuses stays on screen. The toggle stays on offer while writability is unknown. The race-pin file of PR objectui#11164 is byte-unchanged and green
    order the effect is declared before useNavSelDeepLink, so a pending ?sel=nav: link settled by the same answer keeps its read-only selection. Ablation leg 2 pins that order
    tests 5 pins: three read-only cases and two writable controls. Leg 1 (effect deleted) turns exactly the three read-only pins red. Leg 2 (effect after the deep link) turns exactly the pending-link pin red. studio-design/: 73 files, 414 tests
    changeset '@object-ui/app-shell': patch; Clause-②: no
    CI head 7c7f285d5: 43 runs, 40 success, 3 expected skips, 0 failure
    size 3 files, InterfacesPillar only
    closing keywords Fixes #11167 only. The commit body line Part of objectui#11167 is not a closing keyword
    governed none of the 3 paths is on objectui's governed surfaces

    Deviations, answered.

    • The buffer put-back diverges from the toggle, which keeps a dirty buffer. It is right, and the card requires it: on a read-only package no autosave can reach the buffer, and keeping it would hold navDirty (the leave guard and the copilot refresh hold) for good.
    • The three-key effect: right. It never clears a read-only deep-link selection.
    • The PR assignee is unset. The dev's label-write --assign on PR objectui#11187 was refused by its session's permission layer. The seat does ⛔ not re-route a write a subagent's permission layer refused. It has been surfaced to the maintainer and is not a landing gate.

    Out of scope, one line each:

    • Two measured class-a sites in the nav autosave: (1) Done inside the debounce leaves the edit unsent; (2) an Add while a save is in flight is silently dropped. filed objectui#11189 as the objectui#11124 / objectui#11136 family's nav closure card, one card for both sites, as the review judged.
    • No notice is shown when an unsaved nav edit is put back on a read-only answer: Acceptance notes. It is a residual, not a defect, and a toast needs new strings in every locale pack.

    Next on this file: objectui#11181 and objectui#11189 both edit StudioDesignSurface.tsx's InterfacesPillar, so they run one after the other, after this PR lands.

    domain:ui seat 2 · ACCEPT · 2026-09-30T08:46Z


    Generated by Claude Code

  6. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Closed completed: PR objectui#11187 landed as 37d166280, verified by content

    domain:ui seat 2, session_011p7ikEivgXefNDaE5S5Uec. The merge is on main, and each file was checked blob-for-blob against the reviewed head (the verification is in this seat's landing watch). The PR's closing line is Fixes #11167, but the merge-queue merge did not close this card within 3 minutes, so the seat closes it here, with the same meaning. pm:dispatched was removed when the landing was verified.

    domain:ui seat 2 · close · 2026-09-30T09:04Z


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions