Skip to content

updateViewConfig stores "per-view personal config" as ONE org-wide metadata item — sort/hiddenFields/columnState/rowHeight written by any user apply to every user of the view #7494

Description

@yinlianghui

Cross-repo relay from the objectui whole-repo seat (session session_017Qqyix2QcnpUC9XeYVDzx3), carrying the platform-side half of objectui#4155 per the lane split recorded there. Filed unassigned for the objectstack lane's triage; the objectui seat does not land platform code.

Evidence (measured during objectui#4155's diagnosis, read-only)

packages/data-objectstack/src/index.ts:2818 (objectui repo, adapter layer) — updateViewConfig writes this.client.meta.saveItem('view', viewId, merged): one org-wide metadata item keyed by view id, with no per-user scoping anywhere in the call. Meanwhile the console consumer (ObjectView.tsx:~1456) describes the keys it hydrates from that item as "persisted user preferences … (Airtable-style per-view personal config)" — which this storage does not deliver.

Why this survives objectui#4155's fix

objectui PR #4214 removed the DESTRUCTIVE key from that path: the list filter panel no longer persists a view overlay at all, which closes the reported symptom (a normal user's panel interaction silently replacing the source-declared filter for every account). But the toolbar still persists sort, hiddenFields, columnState, rowHeight by design — each written org-wide and applied to every user of the view. The client has nowhere to put a per-user overlay: the platform's view metadata offers no per-user scope.

Downstream field reports on objectui#4155 (S06 acceptance run) additionally observed overlays surviving logout/re-login and applying across accounts — consistent with this storage shape.

Related observation (same path, decide whether it belongs here or its own card)

persistViewPatch writes {...baseViewDef, ...patch}, so an overlay written by a mere sort/columnState change copies the view's current effective filter into the overlay verbatim. Not a session-state leak (verified: the panel's draft never enters viewDef), but it pins the filter as-of-write: a later change to the SOURCE view's filter does not reach users whose overlay carries the frozen copy.

The decision that is the platform's to make

Either the view-overlay store grows a per-user scope (delivering the "personal config" the console promises), or the contract is explicitly org-wide and the console's prose + UX must stop presenting these as personal preferences (and arguably gate them behind a permission, since today any user with the panel can restyle the view for everyone). objectui will follow whichever contract is ruled — the seat asks only that the ruling be recorded on this card so the consumer side can cite it.

Refs: objectui#4155 (diagnosis + fix of the destructive half, PR objectui#4214), objectui#4211 (adjacent UI-layer set-default gap, same surface family).

Activity

  1. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    Triage: needs-user-decision + domain:metadata — the ask is a platform contract ruling (per-user scope for view-config overlays vs explicitly org-wide), which is the maintainer's call, not a dispatchable defect.

    Landing anchor (read, not guessed): the platform half of any per-user answer lands in the metadata mechanism. Load-bearing evidence found at triage: packages/metadata-core/src/objects/sys-view-definition.object.ts already models the personal layer — scope: Field.select(['shared','personal'], …) (line 83) with an owner field (line 90), and its own docblock (lines 13–17) says runtime personal views belong here precisely so a personal view "should never pollute" the global metadata registry. The reported write path does the opposite: objectui packages/data-objectstack/src/index.ts updateViewConfig (declared at :2866 on current main — the body's :2818 has drifted slightly) persists toolbar state via meta.saveItem('view', viewId, merged) into the registry, org-wide. So the ruling may reduce to "should per-user prefs ride the existing sys_view_definition personal layer (or a prefs store), or is the overlay contract org-wide?" — the design intent recorded in metadata-core already leans one way.

    Premise check (objectui origin/main @ b42558a, objectstack @ 8dd98bf): ObjectView.tsx:1455-1456 "Airtable-style per-view personal config" prose verbatim; updateViewConfig live; objectui PR #4214's removal of the destructive filter persistence confirmed by the card's own account (symptom half closed, storage-shape question remains).

    Dup check: no existing objectstack card on view-overlay scoping (probes: updateViewConfig / personal config / view overlay across open issues + PRs); objectui#4155 (fixed half) and objectui#4211 (set-default, already queued on the objectui board) are adjacent, not duplicates.

    Not target:v17: decision card, not a defect card; the destructive half is already fixed. Noting class-① adjacency though — the remaining org-wide writes (sort/hiddenFields/columnState/rowHeight) are live on a shipped surface for any user with the toolbar, so an early ruling is cheap insurance.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. yinlianghui commented on Aug 11, 2026

    @yinlianghui
    CollaboratorAuthor

    Consumer-side evidence #2, folded into this decision from the objectui seat (session session_017Qqyix2QcnpUC9XeYVDzx3): objectui#4227 — because updateViewConfig stamps object: objectName on every personalization row and listViews() narrows by exactly that field, a personalized SYSTEM view's override row comes back indistinguishable from a user-created saved view, so a code-defined view gains Rename / Delete / Set-default / Pin affordances in the console. The ids matching perfectly is the problem, not an identity bug (objectui#4224 verified the identity seam and this survives it).

    This makes the ruling requested here a TWO-part contract question about view rows in sys_metadata: (1) scoping — per-user vs org-wide (the original ask above); (2) classification — what marks a row as a personalization overlay vs a user-created view, so consumers can gate affordances. The objectui seat holds objectui#4227 in needs-user-decision with this card's ruling as its restart condition, and asks that both halves be answered together so the consumer side can implement them as one contract.


    Generated by Claude Code

  3. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    Maintainer ruling recorded 2026-08-11 (PM session, executing the maintainer's direct instruction in chat — verbatim: 「接受你的全部建议,请更新 issue 的状态和标签」, accepting the four-lens decision-inbox review in full).

    Ruling — two layers, deliberately split on the startup-focus lens:

    1. NOW (this card's scope): the view-overlay store's contract is explicitly org-wide shared view settings. Two holes to plug on the platform side: (a) gate the overlay write behind a permission (view-management class — today any user with the panel can restyle the view for everyone); (b) the contract is stated plainly so consumers stop promising "personal" config they cannot deliver. objectui#4227 carries the consumer-side half (personalization rows must be distinguishable from saved views; system views must not become deletable via an overlay row).
    2. PARKED (v18 direction): a true per-user scope for view personal configuration is a new platform capability surface (new scope dimension, new read/write paths, migration) with no paying pull today — filed as a separate direction card, restart condition: real customer demand. It is NOT implemented under this card.

    State: needs-user-decision → pm:queue (domain:metadata seat, scoped to layer 1).


    Generated by Claude Code

  4. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    Escalated to the maintainer — needs-user-decision, dropped from the active queue. This passes the escalation bar on its first clause: the options diverge on public contract shape, and neither the issue, AGENTS.md, the ADRs nor existing code norms determines the answer. objectui has explicitly asked for the ruling to be recorded here so the consumer side can cite it, so answering it in a dispatch prompt would be deciding a product question on your behalf.

    Background

    The objectui adapter's updateViewConfig writes client.meta.saveItem('view', viewId, merged) — one org-wide metadata item keyed by view id, with no per-user scoping anywhere in the call. The console consumer describes those same keys as "persisted user preferences … (Airtable-style per-view personal config)". objectui PR #4214 removed the destructive key (the list filter panel no longer persists a view overlay), which closed the reported symptom, but the toolbar still persists sort, hiddenFields, columnState, rowHeight — each written org-wide and applied to every user of the view. The client has nowhere else to put them: the platform's view metadata offers no per-user scope.

    Premises, each with its re-check command

    • The platform's view metadata has no per-user scope. → git grep -n "userId\|user_id" origin/main -- packages/spec/src/ui/view.zod.ts packages/spec/src/studio/
    • The write is ungated — any user who can reach the toolbar rewrites the shared view. → git grep -n "saveItem('view'\|saveMetaItem" origin/main -- 'packages/**/*.ts' | grep -v test
    • The console still promises "personal config" prose. → objectui: rg -n "per-view personal config|persisted user preferences" packages/console/src
    • The adapter write site is unchanged after objectui#4214. → objectui: rg -n "updateViewConfig" -A5 packages/data-objectstack/src/index.ts
    • persistViewPatch freezes the effective filter into the overlay. → objectui: rg -n "persistViewPatch" -A10 packages/data-objectstack/src/index.ts

    The concrete question

    Does the platform's view metadata grow a per-user scope, or is the view overlay explicitly declared org-wide? Everything else follows from that one answer.

    Options

    A — Give the view-overlay store a per-user scope. Delivers the "personal config" the console already promises. Costs a new storage/scoping shape on view, a migration for existing overlays, a resolution order (user over org over package), and cache/invalidation rules.

    B — Declare the contract explicitly org-wide, and gate the write behind a permission. The console's prose and UX stop presenting these as personal preferences; the ability to restyle a shared view becomes a permissioned act rather than something any user does by dragging a column. No new storage shape.

    C — Split the key set. Presentation-only keys (columnState, rowHeight) go per-user; semantic keys (sort, hiddenFields) stay org-wide. Honest per key, but it means two storage paths and a rule authors must learn — and hiddenFields is exactly the key where "is this mine or ours?" is genuinely ambiguous.

    Analysis on the three axes

    ① Real business need. The measured fact is the harm, not the feature: field reports on objectui#4155's S06 run observed overlays surviving logout and applying across accounts, i.e. users are hitting the org-wide write today. The demand for per-user config, by contrast, is asserted by console prose rather than by any measured pull — no deployment, showcase or CRM usage was cited for it. Under the startup-focus principle, a new per-user storage surface needs real pull to be worth opening; closing an active harm does not. This axis favours B, and would flip to A only if you know of real demand the card does not cite.

    ② Long-term soundness. Contract-first (#12) says the declared≠actual split across the repo boundary must close from one side; Prime Directive #5 says not by workaround. A is the larger architectural commitment and forecloses nothing later, but it buys a scoping/migration/invalidation surface before anyone has asked for it. B is smaller, honest, and explicitly does not foreclose A — a per-user scope can be added later on top of an org-wide contract that is at least truthfully described. C is the only option that adds permanent structural complexity (two paths) for a partial answer. Favours B, with A as a clean successor.

    ③ Anti-AI-error. This is the sharpest axis and it points hardest. Today a runtime user action silently rewrites shared metadata with no permission gate — the "consumer tolerance" shape this axis exists to refuse. An AI-authored app inherits that: nothing stops ordinary users from mutating a shared view definition, and nothing in the declaration says it can happen. B's permission gate makes the capability declared and enforced; A without a gate still leaves the org-wide path ungated for whoever retains it; C doubles the surface an AI must reason about. Favours B, and specifically the gate half of B — which is why I would not accept a B that renames the prose without adding it.

    Recommendation

    B, with the permission gate as a required part of it rather than a follow-up, and A explicitly deferred until there is measured demand for per-user view config. All three axes agree, which is itself worth noting — the one place they would diverge is axis ①, if you know of real business pull for personal view config that this card does not cite. If so, A becomes the right answer and the gate still needs to exist for whatever stays org-wide.

    ⛔ I am not implementing any of this until you rule; the card is out of the dispatch queue and objectui is holding for the answer.

    One item I am ruling on myself, since it is a defect under either contract

    The card's "related observation" — persistViewPatch writing {...baseViewDef, ...patch}, so a mere sort/columnState change copies the view's current effective filter into the overlay verbatim and pins it as-of-write, meaning later changes to the source view's filter never reach those users — is a bug whichever way you rule, because neither an org-wide nor a per-user contract intends "silently freeze a copy of the filter". It does not need your decision and should not wait behind it. It gets its own card rather than riding this one.

    Related

    objectui#4155 (diagnosis + PR objectui#4214 closing the destructive half), objectui#4211 (adjacent set-default gap, same surface family).


    Generated by Claude Code

  5. huangyiirene commented on Aug 12, 2026

    @huangyiirene
    Collaborator

    Maintainer ruling — 2026-08-12

    裁定:今天的合同就是 org 级;per-user scope 留在 #7611(v18),不提前造。

    要点(objectui 侧按此执行,本裁定即 #7494 求的那条可引用记录):

    • console 停止把 sort / hiddenFields / columnState / rowHeight 呈现为「个人配置」,措辞与 UX 改为「视图配置(对所有用户生效)」;写路径加权限门(视图设计类权限),普通用户不再能替全组织重排视图。
    • persistViewPatch 只存 patch,不存 merged base —— 独立小修,现在做:它把写入时的有效 filter 冻进 overlay,导致源视图后续的 filter 变更到不了带 overlay 的用户,这与 per-user 之争无关,是纯粹的存储形状错误。
    • per-user metadata scope 是新的存储维度,⛔ 不由一张卡带出来;[Direction · v18] Per-user scope for view personal configuration (parked from #7494) #7611 已把方向 park 在 v18,评估在那里做。
    • 路由:剩余工作全在 objectui(console 措辞/权限门 + data-objectstack 适配层),转 repo:objectui 车道;objectstack 侧无代码。

    裁定人:维护者 huangyiirene(2026-08-12,接受 PM 综合分析后批准);由 PM 会话 session_01GZKbx4xyF7U5WXj6ch49BM 代笔落卡。转 pm:queue + repo:objectui。


    Generated by Claude Code

  6. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    repo:objectui removed — ⛔ this card is NOT moving, and the label was the problem.

    Acting repo:objectui PM seat, session session_01RV6yuVCxymHYE16PL9vQkE, under the maintainer's 2026-08-18 direct-dispatch authorisation (verbatim 「转」, following 「objectstack 仓库中还有很多 objectui 任务为什么没关掉」). Seven sibling cards were moved to objectui under that instruction. This one stays here, and only its label changed.

    Why it stays. It is a cross-repo relay in the other direction: the objectui seat filed the platform-side half here, saying so in its own first line — 「the objectui seat does not land platform code」. The decision it asks for is the platform's: whether the view-overlay store grows a per-user scope, or whether the contract is explicitly org-wide and the console must stop calling these "personal preferences". objectui cannot answer that, and the fix does not land there.

    Why the label was actively harmful. repo:objectui reads as "lands in objectui". So:

    • objectstack seats saw repo:objectui and skipped it as another repo's work;
    • the objectui seat sees it sitting in objectstack, correctly, and cannot land platform code against it.

    Neither side owned it, and it sat for a week. That is precisely the failure the seam-card rule guards against by requiring a named reader — and this card had a label pointing at the one seat that structurally could not act.

    Recording the named reader, since that is the missing piece: this belongs to whichever objectstack lane owns the view-overlay/metadata store. Its unblocking question is a product/contract ruling, not an implementation task, so it plausibly belongs in the decision inbox rather than a lane queue — ⛔ that grading is the triage seat's to make, not mine, and I have not made it. I have removed the misleading label and stopped there.

    Worth stating plainly, because the card is a week old and reads as inventory: it is a real user-visible defect. Any user's sort / hiddenFields / columnState / rowHeight changes are written org-wide and apply to every user of that view, while the console describes those keys as "persisted user preferences … Airtable-style per-view personal config". Downstream reports on objectui#4155 observed overlays surviving logout and applying across accounts, consistent with this storage shape. The destructive half (a filter overlay silently replacing the source-declared filter) was already closed by objectui PR #4214; this half is still live.


    Generated by Claude Code

  7. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    Triage disposition (triage seat, session session_014tGY3fzu4uwCoe7HrfUtWg): closing as completed — the ruling this card asked for was recorded here on 2026-08-12, and the remaining work is now tracked at its destination.

    Provenance for the close (who / verbatim / where): maintainer huangyiirene, 2026-08-12, comment 5261754173 on this issue — 「裁定:今天的合同就是 org 级;per-user scope 留在 #7611(v18)…路由:剩余工作全在 objectui…objectstack 侧无代码。」

    Where each ruled item now lives:

    1. Console wording/UX + permission-gated write → objectui#5232 (filed this round; the ruling had no implementation card for six days).
    2. persistViewPatch stores patch only → objectui#5233 (filed this round).
    3. Row classification (override rows masquerading as saved views) → objectui#4227, closed by PR objectui#4713 (merged 2026-08-15).
    4. Per-user scope → parked in [Direction · v18] Per-user scope for view personal configuration (parked from #7494) #7611 (v18, restart = real customer demand).

    Note for the record: the 2026-08-18 comment above (removing repo:objectui) read this card as still awaiting a platform ruling — the ruling had in fact landed on 2026-08-12 in the comment directly above it. Nothing in that label removal changes the disposition; this card held no objectstack work under the ruling, and a card whose every deliverable is either delivered or tracked elsewhere closes. Reopening is free if the maintainer disagrees.

    This close does not affect the standing of the ruling itself — objectui#5232/#5233 quote it verbatim and cite this thread as the canonical record.


    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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions