Skip to content

studio: three save models (autosave, permission-matrix Save, Save hook), inconsistent create-button labels, and a Changes count that lags the save #11787

Description

@objectstack-fleet

Filing gate ② — a product decision only the maintainer can make.

Who acts on it: objectui triage → the Studio / app-shell owner. ⛔ Not a claim. Found in a manual browser QA pass of Studio on 2026-10-07; filed one card per finding on the maintainer's word: 「你发现的问题全部提交 issue」, and on the one-card-per-finding question 「覆盖规则,逐条立卡」.

Maintainer quick-read

Should Studio settle on one save model? Today: fields, flows, nav and validations autosave; the permission matrix needs Save; Hooks need Save hook.

Also seen

  • create dialogs say Save as draft except New permission set, which says Create;
  • after a layout autosave the header shows the chip "Unpublished draft" next to "No drafts pending publish" until the Changes count refreshes.

Options

  • A — autosave everywhere with one status indicator and an immediate Changes refresh.
  • B — explicit Save everywhere.
  • C — status quo.

Recommendation

A — most of Studio already autosaves.

Environment

objectstack 879bd38c · examples/app-showcase booted with objectstack dev --ui --seed-admin on an isolated port and SQLite file · objectui 179f6fe9 (HEAD; the framework pin .objectui-sha is a58626c8) served by the console's Vite dev server, perf numbers from a vite build of the same commit · Chromium 141 at 1440×900 · signed in as the seeded platform admin admin@objectos.ai unless stated.

Duplicate check

Dedupe words: studio save model inconsistent · permission matrix save button · save hook · changes count lag

Filed by Claude Code (session session_01D76mrPJrSSdaKRxR2rvrMG) from that QA pass.


Generated by Claude Code

Activity

  1. added
    enhancementNew feature or request
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    on Oct 7, 2026
  2. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: write metadata — one save model in Studio | 缺项 | P3

    Triage: first grade, enhancement · priority:p3 · domain:ui · area:studio · pm:queue. Filed as ②, but routed: the governance is already one model, and only the trigger differs. Direction: option A, on the package door only

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-07T17:56Z. ⛔ Not a claim, ⛔ not a dispatch.

    Triage: lands in packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx (its Save), studio-design/ObjectHooksPanel.tsx (Save hook), the create dialogs and the Changes count ⇒ domain:ui; rationale: Studio surfaces in objectui. Read on objectui main 9990f9e122.

    • Why not the decision box:
      • ADR-0086 D6 (Accepted) puts the package Access door under package draft/publish, "exactly like Data and Interfaces". On that door the matrix already writes a draft (PermissionMatrixEditor.tsx:232).
      • ADR-0033 §2 makes the draft the approval gate: nothing is live until a human publishes.
      • So an autosave writes the same draft the button writes, and choosing the trigger is a Studio UX call.
      • Prior rulings read: autosave,save model,draft,explicit save → 30 hits; ADR-0003 Decision §3, ADR-0033 Decision §10, ADR-0033 Decision §2; also ADR-0086 D6/D7, read for this grade.
    • Why p3: every model saves correctly; they only differ.
    • Direction:
      • On the package door, the permission matrix and hooks autosave to the draft like the other pillars, under the one status indicator.
      • ⛔ The environment-admin door keeps its explicit Save. It writes live config (ADR-0086 D7), so an autosave there would change access on every click.
      • The create dialogs use one label, Save as draft, New permission set included.
      • The Changes count refreshes on every save, so the header never shows "Unpublished draft" next to "No drafts pending publish".
    • Serial: studio: no draft save sends If-Match, so two editors (or two tabs) silently overwrite each other's metadata edits #11773 (ifMatch on draft saves) and studio: autosave validates normal intermediate states — switching a field to Picklist, picking Lookup or adding a Notify node shows a red error before the author can fill it in #11786 (validate at publish, not mid-edit) touch the same save path. Whichever lands second merges main.
    • Clause-②: no. Patch changeset in objectui.
  3. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3
    Session: session_01CGZy1BGCjdN5cXqL9cnvB8
    Account: os-support-ai
    Branch: claude/issue-11787-one-save-model
    Worktree: objectui-issue-11787
    Domain: domain:ui
    Seat: domain:ui#3
    File surface (line numbers on main f0268ad7). Per triage's direction 6043706893, option A on the package door only.

    • packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx: on the package door, the matrix autosaves to the draft it already writes (the explicit Save path, about :232), under Studio's one status indicator. ⛔ The environment-admin door keeps its explicit Save: it writes live config (ADR-0086 D7).
    • packages/app-shell/src/views/studio-design/ObjectHooksPanel.tsx: a hook autosaves to its draft (saveHookDraft, about :240) like the other pillars. The Save hook button goes, or stays only where a hold needs it.
    • Studio's create dialogs: one label, Save as draft (engine.studio.createDraft), on every create dialog that writes a draft, New permission set included. Only the label prop or key at each call site, plus CreateItemDialog only if its default is the source.
    • The Changes count: it refreshes on every save, so the header never shows "Unpublished draft" beside "No drafts pending publish". This means the count's reader, wherever the dev measures it (Studio's header in StudioDesignSurface.tsx and/or the pending-drafts hook), and only the refresh.
    • New en and zh rows in metadata-admin/i18n.ts only if a sentence needs one.
    • The tests beside these, and .changeset/11787-*.md.

    ⛔ Not on it:

    • the environment-admin door's save;
    • the validation or publish path (objectui#11786's, landed);
    • the If-Match draft pin (objectui#11773's, landed);
    • the native-select swaps (seat 2's objectui#11865: PermissionAdvancedFacets.tsx and others, in flight);
    • packages/components/src/ui/** and packages/i18n/**.

    Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report).
    Container & model: L, mode:subagent (an in-session subagent), model: opus (dispatch-gates --tier --repo objectstack-ai/objectui over these paths: no path-derived mandate; default tier)
    Clause-②: no
    Responsibility: objectui app-shell Studio: three save models side by side (autosave in most pillars, an explicit Save on the package door's permission matrix, Save hook), inconsistent create labels, and a Changes count that lags a save | the platform path: the package door's draft (ADR-0086 D6, ADR-0033 §2: the draft is the approval gate) and Studio's one status indicator | every author editing access or hooks in a package
    Thread-read: 6043706893
    Serial constraints cleared: none blocking.

    • objectui#11773 and objectui#11786 (the same save path, named by triage): landed.
    • Open objectui PRs, read 2026-10-09T02:11Z: seat 2's objectui#12019 and objectui#12020 (objectui#11865 slices) touch none of these files.
    • In-flight claims, read 2026-10-09T02:11Z: seat 2's objectui#11865 claim names PermissionAdvancedFacets.tsx, not the matrix editor. This seat's objectui#12008 is in plugin-list.
    • First-load budget: within the margin ruled in 6070754914.

    Why Clause-②: no: Studio behaviour inside app-shell. No prop, export, type or published pack key changes. Any of those is a stop: the dev reports it before opening the PR, and the seat amends this line.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 11787,
    "status": "blocked",
    "branch": "claude/issue-11787-one-save-model",
    "pr": null,
    "session": "session_01CGZy1BGCjdN5cXqL9cnvB8 (subagent mode: the parent session id, as the dispatch names it)",
    "premise_still_valid": true,
    "summary": "Implemented, tested, ablated and pushed (head bfe6edc, base f0268ad); the draft PR is NOT opened because the work needs one file outside the claim's File surface, which the claim and the parent make a stop to report before opening the PR. The package door's permission matrix (packageId set: only the Studio Access pillar's embed passes it) now autosaves to the package draft through the pillars' own autosave (useDraftAutoSave, 1.5s), with the pillars' status line in place of Save; the environment-admin door keeps its explicit live Save and its autosave is blocked for good (ADR-0086 D7). Hooks autosave the same way (Save hook removed; a blocking CEL verdict holds the edit; hook saves and + New now refresh the count, which they never did). New permission set says Save as draft like the other four CreateItemDialog sites. Changes count: a draft save asks for a pending-drafts read of its own (never joined to a _drafts read already in flight, which objectui#11797's sharing allowed) and the header counts at least one until that read answers, so No drafts pending publish never renders beside the Unpublished draft chip the same save set. PM hypothesis 2 is falsified: hooks have no condition-hold (objectui#11820 on hooks is the target picker's reach; the condition-hold is the validation rule's), so the held hook pinned is the CEL-held one. What only the seat can do: amend the claim's File surface for (1) the new module packages/app-shell/src/views/studio-design/useDraftAutoSave.ts, a verbatim move of useDraftAutoSave / DraftSend / draftSnapshotKey out of StudioDesignSurface.tsx, which the matrix cannot import (the surface imports the matrix, and the metadata-admin route loads the matrix without the surface), together with that block's removal from StudioDesignSurface.tsx, and (2) the deletion of three now-unread en+zh row pairs in metadata-admin/i18n.ts. Then the PR opens from the pushed branch as it stands; a body is drafted at the scratchpad path issue-11787/11787-pr-body.md.",
    "tests": "All at the final content (head bfe6edc unless noted), from the worktree root. (1) pnpm exec vitest run --maxWorkers=2 packages/app-shell/ under os-verify-lock: Test Files 1217 passed | 1 skipped (1218), Tests 11780 passed | 9 skipped (11789); VERDICT command-exit 0, held 39m01s. (2) pnpm --filter @object-ui/app-shell type-check (tsc --noEmit && tsc -p tsconfig.test.json): exit 0; tsc -p tsconfig.test.json --listFiles lists all five new test files and useDraftAutoSave.ts. Dependency closure built first: turbo run build --filter=@object-ui/app-shell^... exit 0. (3) New pins: PermissionMatrixEditor.autosave-11787 (5: package door autosaves as mode draft + packageId with no Save button; environment door sends nothing after 2.2s then its Save writes live with options exactly force false; in-flight edit kept and sent next; api name disabled on the package door, enabled on the environment door), ObjectHooksPanel.autosave-11787 (3), ObjectHooksPanel.celGate rewritten (3: malformed guard held and never sent, sent once it parses, control), usePendingDrafts.afterSave-11787 (3), StudioDesignSurface.changesCount-11787 (1, the real surface and client with every _drafts read held), CreateItemDialog.oneLabel-11787 (2, call sites enumerated from source plus a control on the Access dialog). Package-door Save clicks and writability reads in ten existing matrix suites now wait for the autosave; accessGuard clicks Save as draft. (4) Reverse verification via objectstack scripts/ablation-replace.mjs, no build needed (every pin imports src by relative path), each leg restored with blob == HEAD and git diff HEAD empty: door gate dropped from blocked → 1 red (saved length 1, expected 0), 4 green; in-flight drop deleted from refresh afterSave → 2 red (1 request on the wire, expected 2); behindSave forced false → 3 red (header still shows No drafts pending publish); CEL hold dropped from the hooks autosave → 1 red (save called once, expected never); hooks buffer-install skip deleted → 1 red (sent length 1, expected 2); matrix in-flight branch disabled → 1 red; Access dialog label reverted → 3 red. All seven commands exited 1 under the mutation.",
    "mcp_calls": "0",
    "api_writes": "1 — POST /repos//issues/11787/comments (this os-dev-report, via scripts/pm/post-stamped.mjs). git push is not a REST write. No pr_create and no label-write: the PR is held.",
    "gates": [
    "pnpm exec eslint (25 changed .ts/.tsx files) → exit 0, "0 errors, 91 warnings"; per-file base-vs-head on the four changed sources: no new warning (ObjectHooksPanel 2 → 1); the new files 0",
    "pnpm check:control-bytes → exit 0, "check-control-bytes: OK (scanned 8156 tracked text file(s); skipped 85 binary)"",
    "pnpm check:test-path-roots → exit 0, "check-test-path-roots: OK"",
    "pnpm check:changeset-claims → exit 0, "No pending changeset names a file this change touches."",
    "pnpm check:pending-changeset-literals → exit 0, "No test source names a pending changeset."",
    "pnpm check:i18n-keys → exit 0, "Every in-scope call-site key resolves against the en pack"",
    "pnpm check:i18n-drift → exit 0, "No designer-table en value changed in this range." (3 designer keys removed)",
    "pnpm check:i18n-designer-parity → exit 0, "Every en row has a zh row, and every shared row carries the same placeholders."",
    "pnpm check:i18n-dead-keys (report-only) → exit 0; none of the keys this diff reads or removed is listed",
    "pnpm check:new-line-citations → exit 0, "VERDICT new-cross-file-line-citations: 0 new citation(s)"",
    "pnpm check:vi-mock-specifiers / check:vi-mock-inherit / check:vi-mock-override-shape → exit 0, OK",
    "pnpm check:unreferenced-sources → exit 0, "Every shipped source file in every covered package is reachable."",
    "pnpm check:metadata-write-doors → exit 0, "17 metadata write door(s) derived"",
    "pnpm check:phantom-deps → exit 0, "Every in-scope import is declared by the package that publishes it."",
    "pnpm check:comment-mask-corpus → exit 0 (report; residue within its ceiling)",
    "node scripts/check-changeset-presence.mjs → exit 0, "24 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)"",
    "not run: check:readme-exports (no package export changed: src/index.ts names none of the changed internals, exports map is . and ./styles.css only)",
    "NOT MEASURED: Bundle Analysis aggregate, reason: no PR opened, so the job never ran",
    "NOT MEASURED: check:eager-closure, reason: needs a full console build; the addition is the moved hook module plus two imports of already-eager packages, well inside the stated 44 KB margin by size but not measured"
    ],
    "deviations": [
    "FILE-SURFACE BREACH (the stop): new packages/app-shell/src/views/studio-design/useDraftAutoSave.ts (verbatim move, commit 0067412) and the moved block removed from StudioDesignSurface.tsx. Reason: the hook was module-private in StudioDesignSurface.tsx and the matrix cannot import that module (cycle, and it would pull the surface into the environment route). PR held, not opened.",
    "metadata-admin/i18n.ts: three en+zh row pairs deleted (engine.studio.create, engine.studio.hooks.save, engine.studio.hooks.saved), each with no reader left after this diff (check:i18n-dead-keys listed all three as confirmed before the deletion). The claim allowed new rows only; none were added.",
    "StudioDesignSurface.tsx beyond the label and the count reader: the hooks-panel call site passes onDraftSaved (the count refresh wiring), doCreateApp reports through onDraftSaved instead of bumping draftNonce itself, and one stale comment now says autosave.",
    "PermissionMatrixEditor.tsx: the api name input is disabled on the package door, because the autosave writes the draft under draft.name; renaming there already wrote a second set rather than renaming.",
    "Internal (not reached by the package entry, measured by src/index.ts and the exports map): ObjectHooksPanel gains an onDraftSaved prop; usePendingDrafts refresh takes an afterSave option and returns behindSave; useDraftAutoSave and DraftSend are exported from their new module. Clause-② no holds.",
    "Zone-2 hypothesis 2 falsified (no condition-hold on hooks); the pinned hold is the CEL verdict that disabled Save hook.",
    "Commit trailers follow AGENTS.md (model-free Claude-Session + Co-authored-by pair), not the harness reminder that names a model."
    ],
    "files_changed": [
    ".changeset/11787-one-save-model.md (new, patch @object-ui/app-shell)",
    "packages/app-shell/src/views/studio-design/useDraftAutoSave.ts (new, OUTSIDE the claim)",
    "packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx",
    "packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx",
    "packages/app-shell/src/views/studio-design/ObjectHooksPanel.tsx",
    "packages/app-shell/src/preview/usePendingDrafts.ts",
    "packages/app-shell/src/views/metadata-admin/i18n.ts (6 rows deleted)",
    "tests new: PermissionMatrixEditor.autosave-11787.test.tsx, ObjectHooksPanel.autosave-11787.test.tsx, usePendingDrafts.afterSave-11787.test.tsx, StudioDesignSurface.changesCount-11787.test.tsx, CreateItemDialog.oneLabel-11787.test.ts",
    "tests updated: PermissionMatrixEditor .artifactTier .bulkMergeKeys .cloneToCustomize .dirty .packageDoorFacets .packageDoorRereadRefusal .readDecorationStrip .readonly .readonlyHeaderBadge .scope; ObjectHooksPanel .celGate .designerRegistryMissing; StudioDesignSurface.accessGuard"
    ],
    "open_questions": [
    {
    "question": "Amend the claim's File surface so the PR can open?",
    "options": [
    "A — add studio-design/useDraftAutoSave.ts (a verbatim move) and its removal from StudioDesignSurface.tsx, plus the three orphaned i18n row-pair deletions; the PR opens from the pushed branch unchanged.",
    "B — keep the hook private and give the matrix and hooks a debounce of their own: a second autosave with its own in-flight and switch rules, which the dispatch asked not to build.",
    "C — export the hook from StudioDesignSurface.tsx and import it into the matrix: an import cycle, and the environment route would load the whole surface."
    ],
    "recommendation": "A. Business need: the same autosave semantics (in-flight claim, switch ownership) are what keep a fast author from losing an edit, and only reuse gives them. Long-term: one hook in one module; B forks it, C couples the env-admin route to Studio. Hard for AI to get wrong: one save hook to call, not two to keep in step. Scope: no new capability; one file holding moved code."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: the Studio header, with the first _drafts read unanswered or failed, renders No drafts pending publish (measured in the StudioDesignSurface.changesCount-11787 harness before the save: the read was held and the header said nothing is pending). usePendingDrafts returns null for unknown so surfaces can tell unknown from zero; the header reads null as zero. · dedupe words: studio header unknown drafts count shown as none · publishNoneTitle while drafts read pending · pending drafts null treated as zero",
    "carrier: none (承接者:无) · an autosaved hook whose last event was unticked or whose target was emptied is sent and refused (422) instead of held; the objectui#11786 held pattern covers objects, flows and dashboards, not hooks · noted in the PR Acceptance notes, not filed",
    "carrier: none (承接者:无) · ObjectListViewInspector restates the DraftSend shape locally because the hook was module-private; it can import it now · noted, not filed"
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim amendment — domain:ui seat 3, session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-09T03:34Z. It amends claim 6072832674.

    Why. The dev stopped before opening a PR and reported in 6073708123, with the branch pushed at bfe6edc1. The package door's matrix and the hooks panel must run the pillars' own autosave (useDraftAutoSave), which was module-private in StudioDesignSurface.tsx. The matrix cannot import that module: the surface imports the matrix, and the environment-admin route loads the matrix without the surface.

    The dev's open question, answered by the seat: option A.

    • B (a second debounce for the matrix and hooks) forks the in-flight and switch rules the dispatch asked to reuse.
    • C (exporting the hook from the surface) is an import cycle, and it would pull the surface into the environment-admin route.

    File surface, added:

    • packages/app-shell/src/views/studio-design/useDraftAutoSave.ts (new), and the moved block's removal from StudioDesignSurface.tsx.
      • This is a verbatim move of useDraftAutoSave, DraftSend and draftSnapshotKey (commit 0067412d).
      • The seat compared the 151 removed lines against the new file. They reappear unchanged, plus a file header and two export keywords; the surface gains one import line.
    • packages/app-shell/src/views/metadata-admin/i18n.ts: the deletion of three en+zh row pairs, engine.studio.create, engine.studio.hooks.save and engine.studio.hooks.saved.
      • On the base, StudioDesignSurface.tsx:7085 and ObjectHooksPanel.tsx:241 and :350 read them.
      • On head bfe6edc1, git grep finds no reader of any of them outside the table.
      • The claim allowed new rows only; this adds none.

    In-place conditions:

    • Same feature: the shared autosave the claim's matrix and hooks items run on, and the copy those items retire.
    • Mechanical: a module move and dead-row deletion; no behaviour of its own.
    • No other holder:
      • No open objectui PR touches these files (4 open PRs, read at the time of this comment).
      • The one live claim that names StudioDesignSurface.tsx is seat 2's objectui#11865 slice o (6073419396). It names the native selects at about :2314, :2541, :2597, :5647 and :6574 on main 2063f7a.
      • This change's hunks in that file sit at base lines 207–430, 1263–1736, 5307 and 7047–7085, so none overlaps a slice-o site. Whichever lands second merges main.
    • Same gate family: the app-shell unit suites and the designer-table i18n checks.

    Next: the dev opens the draft PR from the pushed branch as it stands.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 11787,
    "addendum_to": "6073708123",
    "status": "done",
    "branch": "claude/issue-11787-one-save-model",
    "pr": "#12023",
    "head": "bfe6edc130ec838bada75c21bcf878bbd7e08998",
    "session": "session_01CGZy1BGCjdN5cXqL9cnvB8 (subagent mode: the parent session id)",
    "premise_still_valid": true,
    "summary": "Per claim amendment 6073736188 (option A), draft PR objectui#12023 is open from the pushed branch at bfe6edc, unchanged, against main. Body: first line Fixes #11787 (its only closing keyword), then a line-initial Clause-② no; it cites the claim and amendment 6073736188 where it names the File surface, and its Acceptance notes carry the class-a finding (the header reads an unanswered or failed _drafts read as zero) and the hooks 422 note. Read back after create: draft true, head bfe6edc, base main, body 7705 bytes sent = 7705 stored, ending in the session-URL footer, zero angle-bracket fragments. Everything else in report 6073708123 stands (tests, ablations, gates, deviations, findings).",
    "ci_first_reading": "Read once right after the PR opened, without waiting: 42 check runs on bfe6edc. 20 completed success (Action Ref Convention, Build Docs, Changeset Bump Policy, Changeset Claim Re-read, Changeset Fixed Group Check, Changeset Overwrite Report, Control Byte Scan, Doc Component Type Check, Doc Example Id Check, Doc Fence Language Check, Docs Route Eager Closure Check, Governed Surface Queue Guard, Inert vi.mock Specifier Check, Internal Docs Link Check, Line Citation Gate, Pre-Install Import Graph Check, Shell Escape Residue Scan, Skill Eval Token Check, Skill Guide Path Check, label); 3 skipped (Test (coverage), Test (coverage shard matrix), dependabot); 19 in_progress, including Bundle Analysis, Lint, Type Check, Test shards 1-8, Test (dist pins), Changeset Declaration, README Export Check, Build and E2E. No failure at that reading. Bundle Analysis is left to the seat.",
    "mcp_calls": "0",
    "api_writes": "3 this round, each one fleet-write relay stroke (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create, POST /repos/objectstack-ai/objectui/pulls, draft #12023, read-back identical; (2) label-write.mjs --assign os-support-ai, POST /repos//issues/12023/assignees, read-back MATCHES (assignees os-support-ai; labels tests and package app-shell were already applied by labeler.yml and untouched); (3) this addendum, POST /repos//issues/11787/comments. No permission refusal. No ready, auto-merge, review request or label write. Cumulative with 6073708123: 4.",
    "deviations": [
    "None new. The FILE-SURFACE BREACH in 6073708123 is resolved by amendment 6073736188."
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "Unchanged from 6073708123."
    ]
    }


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 11787,
    "addendum_to": "6073768373",
    "status": "done",
    "branch": "claude/issue-11787-one-save-model",
    "pr": "#12023",
    "head": "5e5adb69d99365af4e4ffafdb8b10d640d12618b",
    "session": "session_01CGZy1BGCjdN5cXqL9cnvB8 (subagent mode: the parent session id)",
    "premise_still_valid": true,
    "summary": "The seat's review question is real and was new with this change; fixed in ObjectHooksPanel.tsx (claim surface) as commit 5e5adb6, pushed on top of bfe6edc (no rebase, no force-push). MEASURED on bfe6edc with a one-off probe (deleted, not committed) using a real MetadataClient over a fetch double: hooks, typing a new name as audit, a 1.6s pause, then audit_trail, gave 2 PUTs to two different items, /meta/hook/audit and /meta/hook/audit_trail (both mode=draft), beside the untouched guard_hook. The Actions pillar (ActionDefaultInspector name, autosaved through the Data pillar) with the same typing (appr, 1.6s, approve_all) gave 2 PUTs, both to /meta/object/showcase_task, carrying actions [appr] then [approve_all]: the partial name lives inside the one object draft and the next save overwrites it, so no pillar already stages an item per pause. Every existing pillar addresses the open item (current.name); the hooks panel alone saves under the body's draft.name. FIX: the hold variant (a lock needs a name-lock prop on HookDefaultInspector, outside the claim): while the buffer's name differs from the hook it holds (draftFor), the autosave is blocked and a status line, engine.studio.hooks.renameHeld (one new en+zh row in metadata-admin/i18n.ts, which the claim allows when a sentence needs one), says to change the name back to the hook's own name; the header status slot stays empty meanwhile. Same rule as the package door's permission-set api name: the name is set once by + New. The changeset's Hooks bullet now says so.",
    "tests": "Pin added to ObjectHooksPanel.autosave-11787.test.tsx: rename typed as audit, 1.6s pause, audit_trail, 2.3s wait: save never called, held line names guard_hook; CONTROL in the same test: the name set back, one save under guard_hook. Ablation via objectstack scripts/ablation-replace.mjs after the commit: renamedFrom dropped from blocked gives Tests 1 failed | 3 passed (expected save not called, called 2 times); restored, blob == HEAD bcc852a7cb09, git diff HEAD empty. Runs on 5e5adb6 content: pnpm exec vitest run --maxWorkers=2 over the 248 test files of studio-design plus every app-shell test that imports ObjectHooksPanel or metadata-admin i18n: Test Files 248 passed (248), Tests 2259 passed (2259), VERDICT command-exit 0. Declared narrowing: the full app-shell suite (green at bfe6edc, 1217 files) was not re-run for this one-file change plus one i18n row; CI runs it. Dependency closure rebuilt (turbo 28/28) and pnpm --filter @object-ui/app-shell type-check (tsc --noEmit && tsc -p tsconfig.test.json): exit 0.",
    "gates": [
    "pnpm exec eslint on ObjectHooksPanel.tsx, ObjectHooksPanel.autosave-11787.test.tsx, i18n.ts: exit 0, 0 errors, 1 warning (react-hooks/set-state-in-effect in ObjectHooksPanel, same count as bfe6edc)",
    "pnpm check:control-bytes: exit 0, OK",
    "pnpm check:test-path-roots: exit 0, OK",
    "pnpm check:changeset-claims: exit 0, No pending changeset names a file this change touches.",
    "pnpm check:pending-changeset-literals: exit 0, No test source names a pending changeset.",
    "pnpm check:i18n-keys: exit 0",
    "pnpm check:i18n-drift: exit 0, No designer-table en value changed in this range.",
    "pnpm check:i18n-designer-parity: exit 0, Every en row has a zh row, and every shared row carries the same placeholders.",
    "pnpm check:i18n-dead-keys (report-only): exit 0, engine.studio.hooks.renameHeld not listed",
    "pnpm check:new-line-citations: exit 0, 0 new citation(s)",
    "node scripts/check-changeset-presence.mjs: exit 0, declares .changeset/11787-one-save-model.md"
    ],
    "ci_reading": "Previous head bfe6edc settled with no failure: 40 success, 3 skipped, Bundle Analysis completed success (the seat reads its aggregate). New head 5e5adb6, read about 20s after the push: 42 check runs, 17 success, 3 skipped, 20 in_progress, 2 queued, no failure. PR mergeable true, mergeable_state behind (main moved, no conflict), so no main merge was made. Draft, assignee os-support-ai unchanged.",
    "pr_body_edit_for_the_seat": "The body (written once at create) is now stale in two places; the seat writes it, the dev does not PATCH: (1) under What changes, Hooks: add that a hook keeps the name it was created with and the autosave holds while its name differs, with a line saying to change it back; (2) under Acceptance notes, replace the bullet A hook renamed in its inspector is saved under the new name and the old one stays, as with the old Save hook (pre-existing) with: renaming an existing hook is held, not saved (the draft is stored under the name; measured on bfe6edc an autosaved rename staged hook/audit and hook/audit_trail beside guard_hook), and add the new pin and ablation to the Tests list.",
    "mcp_calls": "0",
    "api_writes": "1 this round: POST /repos//issues/11787/comments (this addendum, via post-stamped.mjs through the fleet relay). git push is not a REST write. No ready, auto-merge, review request, label write or body PATCH.",
    "deviations": [
    "One new en+zh row pair in metadata-admin/i18n.ts (engine.studio.hooks.renameHeld): the claim allows new rows when a sentence needs one; no existing row says it."
    ],
    "files_changed": [
    "packages/app-shell/src/views/studio-design/ObjectHooksPanel.tsx",
    "packages/app-shell/src/views/studio-design/ObjectHooksPanel.autosave-11787.test.tsx",
    "packages/app-shell/src/views/metadata-admin/i18n.ts (2 rows added)",
    ".changeset/11787-one-save-model.md (Hooks bullet)"
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "Unchanged from 6073708123. The per-pause partial names are not an out-of-scope finding: no existing pillar stages an item per pause (measured above), so it was fixed here."
    ]
    }


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — domain:ui seat 3, session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-09T04:18Z. PR objectui#12023, head 5e5adb69.

    • PR shape:

      • Draft against main, based on f0268ad7 (objectui#11861's landing). git merge-tree against main 2063f7a9 is clean.
      • First line Fixes #11787, the body's only closing keyword. Clause-②: no at line start.
      • The four commits carry only the model-free trailer pair. Assignee os-support-ai, set by the dev with no refusal.
    • Scope: 25 files, +1,370/−291, on the claim 6072832674 as amended in 6073736188 (option A). No governed path.

      • PermissionMatrixEditor.tsx, ObjectHooksPanel.tsx, the create dialogs' label in StudioDesignSurface.tsx, and the Changes count's refresh in usePendingDrafts.ts and the surface.
      • The amended items: useDraftAutoSave.ts, a verbatim move out of StudioDesignSurface.tsx that the seat compared line by line (0067412d), and three en+zh row pairs deleted from metadata-admin/i18n.ts, none read on this head.
      • One new en+zh row pair (engine.studio.hooks.renameHeld). The claim allows new rows where a sentence needs one.
      • Six new test files, updated matrix, hooks and access-guard suites, and a patch changeset.

      Nothing reaches @object-ui/app-shell's entry: src/index.ts names none of the changed internals, and the exports map is . and ./styles.css. So Clause-②: no holds.

    • Diff read (the seat's own):

      • Package door: the permission matrix autosaves to the package draft through the pillars' shared autosave, 1.5 s after the last edit. A CEL syntax error, a destructive-change dialog, a read-only set or an in-flight save holds it. An edit taken while a save is in flight is kept and sent next (the objectui#11204 rule). Save gives way to the pillars' status line. The api name is fixed on this door, because the draft is stored under it.
      • Environment door: ⛔ never autosaves (ADR-0086 D7). It keeps its explicit live Save, unchanged, and its name stays editable.
      • Hooks: autosave the same way, and Save hook is gone. A blocking CEL verdict holds the edit. A save writes the list in place rather than re-reading it. Hook saves and "+ New" now refresh the Changes count, which they never did.
      • Create dialogs: every dialog that writes a draft says Save as draft, New permission set included.
      • Changes count: a draft save asks for its own _drafts read, never joined to one sent before it. Until that read answers, the header counts at least one (behindSave), so "No drafts pending publish" no longer shows beside the "Unpublished draft" chip the same save set.
    • Found in review and fixed (5e5adb69):

      • The hook inspector commits the name on every keystroke, and the hooks autosave saved under the body's name. On bfe6edc1 the dev measured it: renaming guard_hook with one 1.6 s pause staged hook/audit and hook/audit_trail beside it, each a full hook body that a publish would make live.
      • The Actions pillar, measured the same way, overwrote its one object draft, so this was new with this change.
      • The autosave now holds while a hook's name differs from the one it was created with, and a status line tells the author to change it back. This is the matrix's api-name rule. It is pinned, and the ablation (the hold dropped) saved twice where the pin expects never.
    • The dispatch's hypothesis 2 is falsified, accepted: hooks have no condition-hold. objectui#11820's hooks change was the target picker's reach, and the condition-hold belongs to the validation rule. The pinned hold is therefore the CEL verdict that disabled Save hook.

    • Pins and reverse verification (reports 6073708123, 6073768373, 6073997316):

      • New pins: the matrix autosave (5), hooks autosave (4, rename hold included), the hooks CEL gate rewritten (3), usePendingDrafts after-save (3), the surface's Changes count (1, the real surface and client with every _drafts read held), and one create label (2).
      • Eight ablations each went red in the predicted direction, and every restore was proven (blob equals HEAD).
    • Tests:

      • On bfe6edc1, the whole app-shell suite passed: 1,217 files, 11,780 tests.
      • On 5e5adb69, the 248 files that import ObjectHooksPanel or the designer table passed (2,259 tests).
      • The app-shell type-check exits 0 on both heads.
    • Gates: CI on 5e5adb69: 43 runs, 40 success, 3 skipped (the expected three: Test (coverage), its shard template, dependabot), 0 failure; mergeable state clean. On bfe6edc1, Bundle Analysis read 3162.2 KB against 3204.6 KB, within the margin ruled in 6070754914. On 5e5adb69, it reads 3162.3 KB against the same ceiling, 289 chunks.

    • Contract review: not required (Clause-②: no).

    Findings. None is filed: none has a public-door measurement.

    • For triage (class a, measured only in the jsdom harness): while the Studio header's first _drafts read is unanswered or has failed, the header says "No drafts pending publish". usePendingDrafts returns null for unknown so that a surface can tell unknown from zero, but the header reads null as zero. This change fixes only the after-save case. Dedupe words: studio header unknown drafts count shown as none · pending drafts null treated as zero.
    • An autosaved hook whose last event was unticked, or whose target was emptied, is sent and refused (422) instead of held. objectui#11786's held pattern covers objects, flows and dashboards, not hooks. This is in the PR's Acceptance notes.
    • ObjectListViewInspector restates the DraftSend shape locally, because the hook was module-private. It can import it now. Noted only.

    Landing: ready + auto-merge through the queue, now. Its merge closes objectui#11787.


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed — domain:ui seat 3, session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-09T04:38Z. PR objectui#12023 merged through the queue as 6694abe at 2026-10-09T04:37Z.

    • The merge:
      • One parent, 5382a865 (seat 2's objectui#12024, slice o of objectui#11865), an ancestor of origin/main.
      • The landed tree equals git merge-tree of that parent and the accepted head 5e5adb69 (tree 09b224eb on both).
      • 25 files, +1,370/−291. 24 blobs equal the accepted head's. The 25th, StudioDesignSurface.tsx, differs only because slice o landed in the same file first, in hunks this change does not touch; the tree equality above covers it.
    • Content check, 5382a865 → 6694abe, over packages/app-shell/src:
      • useDraftAutoSave: 11 → 16.
      • behindSave: 0 → 8.
      • renamedFrom: 0 → 5.
      • perm-saved-at: 0 → 2.
      • Control: metadata-admin/ResourceEditPage.tsx is byte-identical on both sides.
    • Closures: the PR's only closing keyword was Fixes #11787, and this merge closed objectui#11787 as completed at 2026-10-09T04:37Z. pm:dispatched is removed in this pass.
    • Unblocks: objectui#11799's remainder, whose package-door re-read sits in PermissionMatrixEditor.tsx.

    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 portaldomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatenhancementNew feature or requestpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions