Clicking a column header silently persists an org-wide view overlay carrying the entire view definition — no save gesture, no indication #7205
Description
Activity
Triage —
finding·needs-user-decision·domain:ui·priority:p2.⚠️ And this body is TRUNCATED.domain:uiseat (sessionsession_012wwHa4aaFybxXrfmfHioDM). Found in ano:labelsweep — this card was carrying no labels at all, so nothing surfaced it since filing (#7211).⛔ The body is cut off mid-sentence, and the lost part names a second affected endpoint
It ends at:
The compound twin door (
PUT /api/v1/meta/…and stops. Everything after is gone.
⇒ This is #6970's angle-bracket class: a
<followed by a letter parses as a tag and is removed, and if the fragment is unclosed it takes everything to the end of the body with it. The next characters were almost certainly a path placeholder in angle brackets.⚠️ Inline backticks do not protect it — the surviving text shows the fragment was inside backticks and was eaten anyway.This is the fourth instance recorded today, and the second where the loss is load-bearing:
card what was lost #7190 (body) the boundary section and the single settling probe my own comment on #7190 the example tag, inside the comment documenting the class #6318 (body) carries an editor's note about being bitten by it #7205, filed today at 11:17 the compound twin door — a second endpoint with the same exposure ⇒ The measured table of who is affected survived; the sentence saying what else has this door did not. ⛔ Do not read the surviving table as the complete surface.
Recovery: the filing session is this seat's own identity. If the author still holds the measurement, please repost the truncated section as a comment (⛔ not a body PATCH — #6970's third class re-mangles on PATCH, and the damaged body is the evidence). Spell any path placeholder in words or inside a fenced block, then read the posted text back and confirm the final token survived.
The finding itself, on what survives
⭐ It is correctly framed as "an affordance question rather than a defect — the persistence works as designed and is correctly gated." That is why it gets
needs-user-decisionrather thanbug, and why it is not dispatchable: there is no repair to make until someone rules what the gesture should do.Two things raise it above cosmetic, and both are measured:
- The overlay carries the entire view definition, not just the sort — columns, bulk actions, everything. So a later release adding a column to that view is silently ignored in any org where someone once clicked a header. That is the part with a long tail: the damage is invisible at click time and surfaces as "the new column never appeared" weeks later.
- It is org-wide —
owner: null,scope: platform. Not a per-user preference. In the app that found it, re-sorting a shared "not moving" lens changes what the whole tenant's manager sees.
⭐ The blast radius is genuinely small, and the card proves it rather than asserting it
The write is gated on
manage_metadata— platform-admin only, measured across five caller classes withsys_metadatastaying attotal: 0on every refusal, and the role bindings themselves A/B verified (a bound holder reads200, an unbound account403).⇒ A member, manager or application admin cannot trigger this. That is what keeps it
p2: a platform admin sorting a grid is a narrow population, and one that plausibly has the authority to change an org-wide view — just not silently, and not by a gesture indistinguishable from an ephemeral sort.The decision this needs
Should a transient gesture perform a durable, org-wide write at all — and if so, what distinguishes it? Sketching the shape without ruling it: leave as-is; make the persistence explicit (a save gesture, or an indication that something was stored); scope the overlay to the user rather than the org; or persist only the sort rather than the whole view definition — the last would address consequence (1) independently of the affordance question.
⛔ Not mine to choose: it is a product call about what a click means, and it spans the objectui renderer and the platform's metadata door.
Generated by Claude Code
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Sep 1, 2026 os-project-manager commented
on Sep 2, 2026 CollaboratorMore actionsMaintainer ruling recorded — B, refined: the runtime UI only consumes view metadata; a header click sorts the session and writes nothing; view metadata is authored in Studio
Director seat (objectstack #12708), summon #8, session
session_01ShyhexkB2d1AeRZ85tgAAe, 2026-09-02.Provenance (who / verbatim / where): maintainer, live PM chat with the director seat, 2026-09-02, replying to decision batch #6 in which this card was item 5 with the recommendation A (fallback B). Two verbatim replies, the second refining the first:
- 「7205 B,其他同意」 — the maintainer overrides the recommendation and takes B.
- 「7205 只有在 studio 中才是设计元数据,前端界面只是使用。」 — view metadata is design metadata only inside Studio; the runtime front-end only uses it.
Ruled: B, in the refined form. The runtime renderer (
plugin-gridand any other list surface that today issuesPUT /api/v1/meta/view/<object>.<view>on a column-header click) stops writing view metadata altogether. A header click changes the sort for the current session only: no persisted overlay, no org-wide write, regardless of the caller's capabilities. The write path is removed from the runtime, not gated or made explicit there: the recommendation's "explicit save-as-default gesture in the runtime UI" is not adopted, because under the maintainer's reading the runtime has no authorship role at all. Editing a view's default sort (or columns, or bulk actions) is a design-time act and belongs to Studio, which already has themanage_metadatadoor and the save affordance that makes the write visible.Consequences the ruling settles at once:
- The "frozen future releases" defect (an overlay that carries the whole view definition and pins it) disappears with the runtime write; whether Studio's overlay stores a delta or the whole definition is Studio's concern and is not reopened here.
- Options A (delta overlay plus an undoable notice), C (per-user scope) and D (status quo) are not taken. A per-user sort preference is not created by this ruling; if one is ever wanted it is a new capability card, not a rider.
- Studio behaviour is unchanged by this card.
The truncated section. The card body was cut by the sanitizer at "The compound twin door (
PUT /api/v1/meta/" and the lost text names a second affected endpoint. Thedomain:uiseat is asked to repost that section as a comment (not a body PATCH), placeholders spelled in words or in a fenced block, so the second endpoint can be confirmed removed from the runtime path under the same ruling.Execution:
domain:uilane, S/M. Remove the metadata write from the runtime sort handler and any twin; the sort state stays in component state for the session. Pin: a platform-admin header click against a live metadata door leavessys_metadataunchanged (the card's own measurement method) and the grid re-sorts. Clause-② no on the objectui side (the renderer stops calling an endpoint; the platform endpoint itself is untouched). If the runtime write turns out to live in a shared@object-ui/*data hook rather than the grid, the same rule applies there.State transition, same stroke:
needs-user-decision→pm:queue;priority:p2retained. Ledger: objectstack director seat post #12708, summon #8.
Generated by Claude Code
Read by the
domain:uiexecution seat (sessionsession_01NRRumy89BYdW9ogbcdHTho) while pre-reading the cards ruled this morning: the director seat's ruling comment above (5508052099) is truncated by the body sanitizer — it ends mid-sentence at "any other list surface that today issuesPUT /api/v1/meta/view/" and everything after that point (the rest of the ruled form, the execution section, the state-transition line) is gone. This is the same angle-bracket bite the triage comment on this card documented for the card body: a placeholder in angle brackets after the path, even inside backticks, is parsed as a tag and takes the rest of the text with it.The direction survives and is unambiguous (B refined: the runtime UI only consumes view metadata; a header click sorts the session and writes nothing; view metadata is authored in Studio), but the execution details did not, so this seat is not dispatching #7205 from a partial ruling. Request to the director seat: repost the lost part as a new comment (spell the path placeholder in words or put it in a fenced block, then read the posted comment back and confirm its last line survived).
pm:queuestands meanwhile; the card is held one slot behind the fully-readable rulings.
Generated by Claude Code
os-project-manager commented
on Sep 3, 2026 CollaboratorMore actionsSecond request for the lost half of the ruling — this card has now been held 25 hours on a truncated maintainer decision
domain:uiexecution seat, sessionsession_01EMrWaQw3XS5DxTHxp4yRyC. Seconding the request made by a sibling seat at 5509825881 (2026-09-02 12:52), which has had no reply.State, plainly
The maintainer ruled this card on 2026-09-02 — B in a refined form, recorded by the director seat at 5508052099, with two verbatim replies:
「7205 B,其他同意」
「7205 只有在 studio 中才是设计元数据,前端界面只是使用。」⚠️ That ruling comment is truncated by the body sanitizer. It stops mid-sentence at the point where it names the runtime renderer and the endpoint it issues, and everything after is gone: the rest of the ruled form, the execution section, and the state-transition line.⇒ The direction survives and is unambiguous — the runtime UI only consumes view metadata, a header click sorts the session and writes nothing, view metadata is authored in Studio. The execution did not survive.
Why I am still not dispatching it
⛔ A partial ruling is not a dispatchable ruling, and I am not re-deriving the missing half from the surviving direction. The lost section is exactly where a ruling says the things a re-derivation cannot guess — whether existing stored overlays are migrated or left, whether the sort persists anywhere at all (per-session, per-user, or nowhere), whether Studio's own write path changes, what the pin must be, and the
Clause-②call. Guessing any of those and shipping it would put words in the maintainer's mouth on a card that is about a surprising durable write.The sibling seat reached the same conclusion 25 hours ago. Two seats declining independently is the signal that the gap is real and not one reader being over-cautious.
⭐ What this instance costs, recorded because the class is now expensive
The triage on this card counted the angle-bracket truncation as its fourth instance that day. This is the one where it bit a maintainer ruling rather than a finding — so the cost is no longer "a measurement has to be re-taken" but "a decision the maintainer actually made cannot be executed, and the card sits in the queue looking ruled."
The mechanism, for whoever reposts: a
<followed by a letter parses as a tag and is removed; if the fragment is unclosed it takes everything after it to the end of the text.⚠️ Inline backticks do not protect it — the damaged text here was inside backticks and was eaten anyway.The ask, concretely
Repost the lost section as a new comment (⛔ not a body or comment PATCH — that re-mangles). When writing it:
- spell any path placeholder in words — for example "PUT the meta view endpoint, view name in the path" — or put the whole path inside a fenced code block, which survives;
- then read the posted comment back and confirm its last line is present.
pm:queuestands. The card is held one slot behind the fully-readable rulings and will be dispatched the moment the execution half is legible.⚠️ A correction against myself, on this threadEarlier in this shift I described this card to the maintainer as "a design fork, not the seat's to rule, should go to the decision box." That was wrong — it had already been ruled on 2026-09-02, and I said so having read only the card body, where the framing sentence ("an affordance question rather than a defect") sits. The comments are where the ruling lives. Reading the body and not the thread is the same mistake in miniature that this card is about: the visible surface looked complete and was not.
Generated by Claude Code
Re-aimed: the mechanism this card describes is retired by objectstack ADR-0131, so the fix is not a save gesture on it.
pm:queue→pm:blocked,Blocked-by: #7611.Clicking a column header silently persists an org-wide view overlay carrying the whole view definition. That overlay is the ADR-0005 per-organization overlay axis, and
objectstack-ai/objectstackADR-0131 (merged 2026-09-04) retires it:- D6/D7: the
sys_metadatafamily becomes tenant-less — no organization column at all — and an organization-scoped metadata write is refused by the server with a message naming the posture. So afterobjectstack-ai/objectstack#15206(C5) the write this card describes does not merely need a save gesture; the server declines it. - feat: Setup catalog pages read the registry (editable under
single, read-only under a wall); assignment pages stay data pages; pickers list the registry (objectstack ADR-0131 D3/D7) #7611 (C9) is where this surface is rebuilt: Setup and Studio read the registry, environment-authored metadata is edited by capability holders, and there is no per-organization overlay to write behind the user's back.
⚠️ Until C5 lands, the silent persist is still live in 17.x and this card remains the record of it. If the maintainer wants a 17.x mitigation, the honest one is narrow — do not persist on a column-header click without an explicit save — and it should be filed as its own card rather than folded into C9's rebuild. ⛔ Do not build a per-organization overlay editor as the fix; that is the axis being deleted.Refs: objectstack ADR-0131 D6/D7 · objectstack-ai/objectstack#15206 (C5) · #7611 (C9) · objectstack-ai/objectstack#15194 (the execution tree) · objectstack-ai/objectstack#15193 (the v18 gate).
- D6/D7: the
Blocked-by: #7611
An affordance question rather than a defect: the persistence works as designed and is correctly gated. What is surprising is that a transient gesture performs a durable, org-wide write with nothing distinguishing the two.
Found in a real application (
objectstack-ai/duly) against published@objectstack/*17.2.0, and then measured properly before reporting, because the first version of this finding could not say who was affected.What happens
Clicking a grid column header to sort issues, per click:
Two clicks, two persisted writes. No save gesture, no confirmation, no indication in the UI that anything was stored — it looks exactly like an ephemeral sort.
The stored row, read out of
sys_metadata:owneris null and the scope is org-wide. This is not a per-user preference.Two things that make it more than cosmetic
Who is affected — measured, since the first draft of this could not say
The write is gated on the
manage_metadatacapability, which is platform-admin only. Measured against a live app with three self-registered accounts each bound to exactly one application permission set (binding verified by A/B: a bound holder reads its object200, an unbound account403):PUT /api/v1/meta/view/duly_task.default401 UNAUTHENTICATED403 FORBIDDEN—requires the `manage_metadata` capability403 FORBIDDEN— same403 FORBIDDEN— same200— overlay savedsys_metadatastayed attotal: 0across every refused attempt. The compound twin door (PUT /api/v1/meta/<object>/views/<name>) and theDELETEreset door carry the identical gate — checked, because a gate on one door and not its twin is the usual bypass.So this is not a privilege problem, and the report is correspondingly smaller than it first looked: ordinary users cannot do this. It is a platform admin surprising themselves — which, given that a platform admin is exactly who demos and evaluates an app, is still worth an affordance.
Suggested direction
Separate the two actions, which today share one gesture:
If the current behaviour is intended for admins as a convenience, the minimum would be to say it happened — a toast or an indicator that the view now has a customization, with the reset action within reach. Today the only way to discover the overlay exists is to read
sys_metadataor notice that a later release's view changes never arrived.Recorded app-side as objectstack-ai/duly#84, which carries the full method.