Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

[finding] ObjectView 是最后一处不走共享 sort sink 的读点 —— $orderby: sort 原样透传,归一化外包给了某一个 adapter #4869

Closed

Description

@yinlianghui

承接 #4022「建议范围」第三条 —— 该卡由 PR #4868 关闭时这条观察会随之丢失,故单独立单。观察类(observation-class):今天没有用户能踩到错误行为,只打 finding、不入队。

事实(复核于 origin/main 65e88e6c2)

PR #4868 把 gantt / map / calendar 三份私有副本收敛到 @object-ui/core 的 convertSortToQueryParams 之后,全仓的 object-bound 读点里只剩一处不走这个 sink:

packages/plugin-view/src/ObjectView.tsx(约 436-448 行)把解析出来的 sort 原样塞给查询:

const sort = currentNamedViewConfig?.sort || activeView?.sort || schema.table?.defaultSort || undefined;
...
const results = await dataSource.find(schema.objectName, {
  $filter: finalFilter,
  $orderby: sort,
  $top: 100,
  ...
});

其余五个读点(object-gantt / object-map / object-calendar / object-timeline / record:line_items)下发的都是 sink 归一后的 Record< string, 'asc' | 'desc' > 或 undefined。

为什么是 finding 而不是 defect

$orderby 本身声明就接四种形状,而 @object-ui/data-objectstack 的 serializeOrderBy(packages/data-objectstack/src/index.ts:341)四种都兜得住,且对缺方向的项按升序处理:

const shorthand = (field: string, order?: unknown) =>
  String(order).toLowerCase() === 'desc' ? `-${field}` : field;

也就是说 #4022 那个「缺 order 的项被丢掉」的缺陷在这条路径上并不存在 —— ObjectView 恰好因为不归一化而躲过了它。今天没有可见错误行为。

风险面是归一化的位置:五个 block 在到达 adapter 之前就归一,ObjectView 把归一化外包给了 serializeOrderBy。于是 DataSource.find 契约上,同一个 $orderby 参数在仓内存在两种形态 —— 一个自己实现 find 的 adapter(不是 ObjectStackAdapter)从五个 block 收到的是归一化的 map,从 ObjectView 收到的是作者原样写的四种形状之一。这是「下一次改语义的人只改一边」的经典面。

建议范围(若有人接)

$orderby: convertSortToQueryParams(sort),配一条钉子。需要先确认的一点:ObjectView 的 sort 来源包含 schema.table?.defaultSort,其声明形状要逐个对过 —— sink 只认「字符串」与「{ field, order } 数组」两种拼法,其余(例如字符串数组 ['-name'],serializeOrderBy 是收的)会被 sink 判为 undefined。这不是可以机械照搬的迁移:若 defaultSort 实际存在 sink 不认的拼法,正确的动作是先裁定该拼法算不算已声明的 authoring surface(要么进 sink 的契约,要么在 producer 端拒绝),而不是在 ObjectView 里加一层容忍。

去重

已搜 open issues(orderby / ObjectView sort / 关键词 + 文件路径),无同题单。相关:#4022(三副本收敛,PR #4868)、#4568(同文件的 handleRefresh 死入口,不同缺陷)。

Activity

  1. os-support-ai commented on Aug 19, 2026

    @os-support-ai
    Collaborator

    Triage (concentrated finding round): promoted finding → pm:queue. Task-shaped convergence with a named landing spot: route ObjectView's $orderby through the shared convertSortToQueryParams sink plus a pin, with the schema.table?.defaultSort shape audit the card prescribes (if a shape the sink refuses turns out to be real authoring surface, stop and escalate rather than adding tolerance in ObjectView).

    Serial constraint for the dispatching seat: hard-serial with #5270 / #5269 (same ObjectView.tsx surface, both queued this round as bugs — dispatch those first) and re-verify the premise on the post-#5102 merged ref, since #5102's forwarding-whitelist rework landed in this file today.

    本评论来自分诊座位 Routine (session session_018euHiF12oV8iSfYdjVioUF).


    Generated by Claude Code

  2. os-support-ai commented on Aug 20, 2026

    @os-support-ai
    Collaborator

    Claim: PM loop round 22
    Session: session_01RV6yuVCxymHYE16PL9vQkE
    Branch: claude/issue-4869-objectview-sort-sink
    Worktree: objectui-issue-4869
    Domain: repo:objectui
    File surface: packages/plugin-view/src/ObjectView.tsx (the $orderby read, ~436-448) and its pin; @object-ui/core's convertSortToQueryParams read-only unless the fork below is settled first (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus
    Clause-②: no, on the expected path — routing an existing read through the shared sink changes no accepted set. ⚠️ It flips to yes if the fork below resolves toward widening the sink's contract, which is exactly why the dispatch order stops there instead of proceeding.
    Serial constraints cleared: packages/plugin-view is disjoint from everything in flight — #5398 plugin-list, #5421 (surface TBD, instructed to stop if it lands in an in-flight package), PRs #5429 packages/fields and #5430 packages/auth awaiting gates. No open PR touches plugin-view.

    Selected as the top pm:blocking card in the lane's priority order.

    ⚠️ This card names its own fork and the dispatch order treats it as a hard stop. ObjectView's sort sources include schema.table?.defaultSort, and the sink accepts only two spellings (a string, and a {field, order} array). If defaultSort genuinely carries a spelling the sink rejects — the card names ['-name'], which serializeOrderBy does accept — then the correct action is to rule whether that spelling is a declared authoring surface (it either enters the sink's contract or is refused at the producer), ⛔ not to add a tolerance layer inside ObjectView. That is a needs-user-decision fork, not an implementation detail.


    Generated by Claude Code

  3. os-support-ai commented on Aug 20, 2026

    @os-support-ai
    Collaborator
    {
      "issue": 4869,
      "status": "needs_decision",
      "branch": "claude/issue-4869-objectview-sort-sink",
      "pr": null,
      "premise_still_valid": false,
      "summary": "The fork the card names as a hard stop is REAL, so nothing was landed. `table.defaultSort` is declared a SINGLE `{ field, order }` object (packages/types/src/objectql.ts:673; Zod packages/types/src/zod/objectql.zod.ts:129; documented as authored `table` surface at content/docs/plugins/plugin-view.mdx:142 and content/docs/api/schema-reference.md:756), and the sink judges it `undefined` — measured, not inferred. Two corrections to the card's premise, both measured at the real read site (packages/plugin-view/src/ObjectView.tsx:667-679). (1) The card's 'observation-class, no user-visible defect today' is FALSE for that one source: the raw object reaches `serializeOrderBy` as an `$orderby` MAP, so `Object.entries({ field: 'name', order: 'desc' })` serializes to the wire string `field,-order` — two columns that do not exist — which `assertSortFieldsExist` (objectstack packages/metadata-protocol/src/protocol.ts:7163) answers with `400 INVALID_SORT`. ObjectView swallows it in its catch, so a non-grid object-view whose only sort is `table.defaultSort` renders EMPTY, while the SAME metadata sorts correctly as a grid (pinned green today at ObjectView.namedViewSortArity.test.tsx:192, `name desc`). That is the third instance of this file's 'correct as a grid, broken as a calendar' family, after objectui#5102 (filter) and objectui#5270 (arity). (2) The card cannot be implemented mechanically even if the fork were settled: routing this read through the sink turns a LANDED pin red — ObjectView.canonicalTableKeys.test.tsx:348-351 asserts `$orderby` equals `{ field: 'created', order: 'asc' }`, i.e. it pins the refused spelling reaching the query verbatim. That pin came from objectui#5102 and locks in the defect; rewriting another card's pin is part of what the ruling has to authorise. The card's STRUCTURAL observation stands and is confirmed: ObjectView.tsx:679 is the last object-bound read site handing an AUTHORED sort to `$orderby` unlowered (the only other bare `$orderby: sort`, plugin-list/src/ListView.tsx:1615, carries RUNTIME toolbar state built at :1458, not authored metadata).",
      "tests": "No code landed, so no gate run and no changeset — the deliverable is the measurement. Measured with a throwaway probe (packages/plugin-view/src/__tests__/ZZprobe.sortShapes.test.tsx, since DELETED; worktree verified clean, `git diff origin/main...HEAD` empty, branch pushed with zero commits) that renders the REAL ObjectView with `defaultViewType: 'calendar'` against a `vi.fn()` dataSource and captures the `$orderby` the non-grid fetch actually sends, then runs that captured value through both `convertSortToQueryParams` and `serializeOrderBy`. Command, from the repo root per AGENTS.md: `pnpm exec vitest run packages/plugin-view/src/__tests__/ZZprobe.sortShapes.test.tsx --maxWorkers=2` under `flock -E 99 /tmp/os-heavy-verify.lock` — `Test Files 1 passed (1) / Tests 6 passed (6)` (a green run is not itself the reading here; the readings are the recorded values below). NO BUILD ARTEFACT sits between the edit and the thing under test on any leg: `../ObjectView` is a relative source import and `@object-ui/core` / `@object-ui/data-objectstack` / `@object-ui/types` are aliased to each package's own `src` in the root vitest.config.mts:255-292, which is the same resolution note already recorded in ObjectView.namedViewSortArity.test.tsx. Measured table, one row per declared source in the chain `currentNamedViewConfig?.sort || activeView?.sort || schema.table?.sort || schema.table?.defaultSort` — columns are: value at `$orderby` today, wire string via `serializeOrderBy`, result through the sink. (a) listViews.name.sort, declared `Array< { field, order } >`: `[{field:'name',order:'desc'}]` / `-name` / `{name:'desc'}` — AGREE. (b) views prop .sort, declared `Array< { field, direction } >`: `[{field:'name',direction:'desc'}]` / `name` / `{name:'asc'}` — both paths silently drop `desc`, already filed as objectui#5293, corroborated here. (c) table.sort string: `name desc` / `name desc` / `{name:'desc'}` — AGREE. (d) table.sort array: `[{field:'name',order:'desc'}]` / `-name` / `{name:'desc'}` — AGREE. (e) table.defaultSort, declared single `{ field, order }`: `{field:'name',order:'desc'}` / `field,-order` / `undefined` — THE FORK, and the only row where today's wire value is garbage. Direct sink probes: `convertSortToQueryParams({field,order})` is `undefined`; `convertSortToQueryParams(['-name'])` is `undefined` (the string-array spelling the card guessed at is refused too, but nothing in this chain declares it, so it is not the live fork); `serializeOrderBy(['-name'])` is `-name`. REVERSE-VERIFICATION, predicted before running: I predicted (e) would give `undefined` through the sink and `field,-order` on the wire — both OBSERVED AS PREDICTED. One prediction was WRONG IN DIRECTION and is reported as such: I expected the card's 'no user-visible defect today' to hold, and it does not — row (e) is a live 400. No ablation was run because nothing was landed; there is no fix to remove.",
      "open_questions": [
        {
          "question": "`schema.table.defaultSort` is declared, documented, GA-declared authoring surface carrying a spelling the shared sink refuses — a single `{ field, order }` object rather than a string or an array. It is ALSO already ruled on once, in the other direction: objectui#4648 (maintainer 2026-08-16) carved `object-grid.defaultSort` out of the published authoring inputs as 'read as back-compat, deliberately not published — the canonical `sort` IS declared'. So the key is live-for-reading but not-for-writing, and this read site is the one place where reading it produces a 400. Which way does it go?",
          "options": [
            "A — Lower the legacy pair at the ObjectView read site, then route the whole chain through the sink: `... || schema.table?.sort || (schema.table?.defaultSort ? [schema.table.defaultSort] : undefined)` and `$orderby: convertSortToQueryParams(sort)`. Business need: the ONLY option that fixes the measured 400/empty-view rather than trading it for silent unsortedness, and it makes the grid and non-grid renderings of one metadata file agree at last. Soundness: it is not a new tolerance — it is verbatim the legacy-to-canonical resolution ObjectGrid already performs for this exact pair (plugin-grid/src/ObjectGrid.tsx:2781, `schemaSort ?? (schema.defaultSort ? [schema.defaultSort] : undefined)`), and ObjectView itself already resolves the same pair one screen below for the grid path (ObjectView.tsx:1202/1211). The sink's contract stays at two spellings; no fourth dialect. Cost: the pair's resolution now exists at two layers, and the landed pin at canonicalTableKeys.test.tsx:348-351 must be rewritten to assert the lowered value. AI-safety: neutral-good — the sink stays strict and the legacy lowering is one visible expression, but nothing gets LOUDER at authoring time.",
            "B — Widen the sink to accept a bare `{ field, order }`. Business need: same defect fixed, one line, all six blocks inherit it. Soundness: REFUSED on the merits, in my reading — the sink's input slot also legitimately carries `$orderby`'s own `Record< field, direction >` map, so `{ field: 'desc' }` (a perfectly legal map ordering by a column literally named `field`) becomes indistinguishable from a half-written SortConfig, and the sink would have to GUESS. That is the lenient consumer AGENTS.md #0.1 forbids, installed in the one function six blocks share. AI-safety: worst of the three — every block would start silently accepting a shape none of them declare, which is exactly where AI-authored metadata errors hide and spread.",
            "C — Refuse at the producer: stop reading `table.defaultSort` on the non-grid path, and follow with an ADR-0049 enforce-or-remove retirement of the key. Business need: measured pull for the key is ZERO — outside spec plumbing and this repo's own docs/tests, `table.defaultSort` has no authored occurrence in objectui `examples/**`, `apps/**`, `e2e/**` or anywhere in objectstack; per the startup-focus principle (maintainer 2026-08-04) a deprecated legacy alias with no consumers does not earn a compatibility shim. Soundness: best END STATE, and the one that stops this key generating cards. But ON ITS OWN it re-opens the same divergence in the mirror direction — ObjectGrid still honours `defaultSort`, so the grid would sort and the calendar would not, silently. AI-safety: BEST if and only if the key is refused loudly at authoring/publish time; merely ignoring it at read time is the worst of both worlds."
          ],
          "recommendation": "A now, C as its own follow-up card — but the call is yours because it turns on whether a back-compat-only key is owed a working read, and because A requires rewriting a pin objectui#5102 deliberately landed. A is the only option that closes a measured 400 without widening a contract six blocks share, and it costs one expression at the site that already resolves this very pair for the grid path. It scores well on axis 3 in the narrow sense (the shared sink stays strict, so AI-authored metadata still cannot invent sort shapes) but it does not IMPROVE authoring-time safety, which is what C ultimately delivers. B is the only option I would argue against outright: it buys the same fix by making the one shared sink ambiguous, and the ambiguity is real rather than theoretical — `{ field: 'desc' }` is a valid map today. Two riders whichever way it goes: (i) issue 4869 is currently labelled `finding` and its body states there is no user-visible defect — that is now measured false, and the card probably wants re-labelling as a bug so it is not filtered out as an observation; (ii) whoever implements it must also decide the fate of canonicalTableKeys.test.tsx:348-351, which today pins the broken value."
        }
      ],
      "out_of_scope_findings": [
        "Not filed — already open as objectui#5293 (`bug`, `pm:blocked`): `ObjectViewProps.views[].sort` declares `Array< { field, direction } >` while every consumer reads `order`. Measured corroboration from this round, at ObjectView.tsx:679: a host-supplied `[{ field: 'name', direction: 'desc' }]` reaches the wire as `name` (ASCENDING) today, and would reach it as `{ name: 'asc' }` through the sink — so routing this read through the sink does NOT fix objectui#5293 and does not make it worse either; the two are independent.",
        "Not filed, reported as a premise correction instead (same read site as this card, so a separate issue would fragment it): the live `400 INVALID_SORT` / empty non-grid view described in `summary`. It belongs to issue 4869 itself and is the reason that card should stop being labelled `finding`."
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  4. os-support-ai commented on Aug 20, 2026

    @os-support-ai
    Collaborator

    os-dev-report

    (Re-post of comment 5359610883 — identical payload. The HTML-comment marker on that one was eaten by GitHub's body sanitizer at store time, which makes it invisible to the PM scan; this copy carries the marker as literal text instead. Parse this one.)

    {
      "issue": 4869,
      "status": "needs_decision",
      "branch": "claude/issue-4869-objectview-sort-sink",
      "pr": null,
      "premise_still_valid": false,
      "summary": "The fork the card names as a hard stop is REAL, so nothing was landed. `table.defaultSort` is declared a SINGLE `{ field, order }` object (packages/types/src/objectql.ts:673; Zod packages/types/src/zod/objectql.zod.ts:129; documented as authored `table` surface at content/docs/plugins/plugin-view.mdx:142 and content/docs/api/schema-reference.md:756), and the sink judges it `undefined` — measured, not inferred. Two corrections to the card's premise, both measured at the real read site (packages/plugin-view/src/ObjectView.tsx:667-679). (1) The card's 'observation-class, no user-visible defect today' is FALSE for that one source: the raw object reaches `serializeOrderBy` as an `$orderby` MAP, so `Object.entries({ field: 'name', order: 'desc' })` serializes to the wire string `field,-order` — two columns that do not exist — which `assertSortFieldsExist` (objectstack packages/metadata-protocol/src/protocol.ts:7163) answers with `400 INVALID_SORT`. ObjectView swallows it in its catch, so a non-grid object-view whose only sort is `table.defaultSort` renders EMPTY, while the SAME metadata sorts correctly as a grid (pinned green today at ObjectView.namedViewSortArity.test.tsx:192, `name desc`). That is the third instance of this file's 'correct as a grid, broken as a calendar' family, after objectui#5102 (filter) and objectui#5270 (arity). (2) The card cannot be implemented mechanically even if the fork were settled: routing this read through the sink turns a LANDED pin red — ObjectView.canonicalTableKeys.test.tsx:348-351 asserts `$orderby` equals `{ field: 'created', order: 'asc' }`, i.e. it pins the refused spelling reaching the query verbatim. That pin came from objectui#5102 and locks in the defect; rewriting another card's pin is part of what the ruling has to authorise. The card's STRUCTURAL observation stands and is confirmed: ObjectView.tsx:679 is the last object-bound read site handing an AUTHORED sort to `$orderby` unlowered (the only other bare `$orderby: sort`, plugin-list/src/ListView.tsx:1615, carries RUNTIME toolbar state built at :1458, not authored metadata).",
      "tests": "No code landed, so no gate run and no changeset — the deliverable is the measurement. Measured with a throwaway probe (packages/plugin-view/src/__tests__/ZZprobe.sortShapes.test.tsx, since DELETED; worktree verified clean, `git diff origin/main...HEAD` empty, branch pushed with zero commits) that renders the REAL ObjectView with `defaultViewType: 'calendar'` against a `vi.fn()` dataSource and captures the `$orderby` the non-grid fetch actually sends, then runs that captured value through both `convertSortToQueryParams` and `serializeOrderBy`. Command, from the repo root per AGENTS.md: `pnpm exec vitest run packages/plugin-view/src/__tests__/ZZprobe.sortShapes.test.tsx --maxWorkers=2` under `flock -E 99 /tmp/os-heavy-verify.lock` — `Test Files 1 passed (1) / Tests 6 passed (6)` (a green run is not itself the reading here; the readings are the recorded values below). NO BUILD ARTEFACT sits between the edit and the thing under test on any leg: `../ObjectView` is a relative source import and `@object-ui/core` / `@object-ui/data-objectstack` / `@object-ui/types` are aliased to each package's own `src` in the root vitest.config.mts:255-292, which is the same resolution note already recorded in ObjectView.namedViewSortArity.test.tsx. Measured table, one row per declared source in the chain `currentNamedViewConfig?.sort || activeView?.sort || schema.table?.sort || schema.table?.defaultSort` — columns are: value at `$orderby` today, wire string via `serializeOrderBy`, result through the sink. (a) listViews.name.sort, declared `Array< { field, order } >`: `[{field:'name',order:'desc'}]` / `-name` / `{name:'desc'}` — AGREE. (b) views prop .sort, declared `Array< { field, direction } >`: `[{field:'name',direction:'desc'}]` / `name` / `{name:'asc'}` — both paths silently drop `desc`, already filed as objectui#5293, corroborated here. (c) table.sort string: `name desc` / `name desc` / `{name:'desc'}` — AGREE. (d) table.sort array: `[{field:'name',order:'desc'}]` / `-name` / `{name:'desc'}` — AGREE. (e) table.defaultSort, declared single `{ field, order }`: `{field:'name',order:'desc'}` / `field,-order` / `undefined` — THE FORK, and the only row where today's wire value is garbage. Direct sink probes: `convertSortToQueryParams({field,order})` is `undefined`; `convertSortToQueryParams(['-name'])` is `undefined` (the string-array spelling the card guessed at is refused too, but nothing in this chain declares it, so it is not the live fork); `serializeOrderBy(['-name'])` is `-name`. REVERSE-VERIFICATION, predicted before running: I predicted (e) would give `undefined` through the sink and `field,-order` on the wire — both OBSERVED AS PREDICTED. One prediction was WRONG IN DIRECTION and is reported as such: I expected the card's 'no user-visible defect today' to hold, and it does not — row (e) is a live 400. No ablation was run because nothing was landed; there is no fix to remove.",
      "open_questions": [
        {
          "question": "`schema.table.defaultSort` is declared, documented, GA-declared authoring surface carrying a spelling the shared sink refuses — a single `{ field, order }` object rather than a string or an array. It is ALSO already ruled on once, in the other direction: objectui#4648 (maintainer 2026-08-16) carved `object-grid.defaultSort` out of the published authoring inputs as 'read as back-compat, deliberately not published — the canonical `sort` IS declared'. So the key is live-for-reading but not-for-writing, and this read site is the one place where reading it produces a 400. Which way does it go?",
          "options": [
            "A — Lower the legacy pair at the ObjectView read site, then route the whole chain through the sink: `... || schema.table?.sort || (schema.table?.defaultSort ? [schema.table.defaultSort] : undefined)` and `$orderby: convertSortToQueryParams(sort)`. Business need: the ONLY option that fixes the measured 400/empty-view rather than trading it for silent unsortedness, and it makes the grid and non-grid renderings of one metadata file agree at last. Soundness: it is not a new tolerance — it is verbatim the legacy-to-canonical resolution ObjectGrid already performs for this exact pair (plugin-grid/src/ObjectGrid.tsx:2781, `schemaSort ?? (schema.defaultSort ? [schema.defaultSort] : undefined)`), and ObjectView itself already resolves the same pair one screen below for the grid path (ObjectView.tsx:1202/1211). The sink's contract stays at two spellings; no fourth dialect. Cost: the pair's resolution now exists at two layers, and the landed pin at canonicalTableKeys.test.tsx:348-351 must be rewritten to assert the lowered value. AI-safety: neutral-good — the sink stays strict and the legacy lowering is one visible expression, but nothing gets LOUDER at authoring time.",
            "B — Widen the sink to accept a bare `{ field, order }`. Business need: same defect fixed, one line, all six blocks inherit it. Soundness: REFUSED on the merits, in my reading — the sink's input slot also legitimately carries `$orderby`'s own `Record< field, direction >` map, so `{ field: 'desc' }` (a perfectly legal map ordering by a column literally named `field`) becomes indistinguishable from a half-written SortConfig, and the sink would have to GUESS. That is the lenient consumer AGENTS.md #0.1 forbids, installed in the one function six blocks share. AI-safety: worst of the three — every block would start silently accepting a shape none of them declare, which is exactly where AI-authored metadata errors hide and spread.",
            "C — Refuse at the producer: stop reading `table.defaultSort` on the non-grid path, and follow with an ADR-0049 enforce-or-remove retirement of the key. Business need: measured pull for the key is ZERO — outside spec plumbing and this repo's own docs/tests, `table.defaultSort` has no authored occurrence in objectui `examples/**`, `apps/**`, `e2e/**` or anywhere in objectstack; per the startup-focus principle (maintainer 2026-08-04) a deprecated legacy alias with no consumers does not earn a compatibility shim. Soundness: best END STATE, and the one that stops this key generating cards. But ON ITS OWN it re-opens the same divergence in the mirror direction — ObjectGrid still honours `defaultSort`, so the grid would sort and the calendar would not, silently. AI-safety: BEST if and only if the key is refused loudly at authoring/publish time; merely ignoring it at read time is the worst of both worlds."
          ],
          "recommendation": "A now, C as its own follow-up card — but the call is yours because it turns on whether a back-compat-only key is owed a working read, and because A requires rewriting a pin objectui#5102 deliberately landed. A is the only option that closes a measured 400 without widening a contract six blocks share, and it costs one expression at the site that already resolves this very pair for the grid path. It scores well on axis 3 in the narrow sense (the shared sink stays strict, so AI-authored metadata still cannot invent sort shapes) but it does not IMPROVE authoring-time safety, which is what C ultimately delivers. B is the only option I would argue against outright: it buys the same fix by making the one shared sink ambiguous, and the ambiguity is real rather than theoretical — `{ field: 'desc' }` is a valid map today. Two riders whichever way it goes: (i) issue 4869 is currently labelled `finding` and its body states there is no user-visible defect — that is now measured false, and the card probably wants re-labelling as a bug so it is not filtered out as an observation; (ii) whoever implements it must also decide the fate of canonicalTableKeys.test.tsx:348-351, which today pins the broken value."
        }
      ],
      "out_of_scope_findings": [
        "Not filed — already open as objectui#5293 (`bug`, `pm:blocked`): `ObjectViewProps.views[].sort` declares `Array< { field, direction } >` while every consumer reads `order`. Measured corroboration from this round, at ObjectView.tsx:679: a host-supplied `[{ field: 'name', direction: 'desc' }]` reaches the wire as `name` (ASCENDING) today, and would reach it as `{ name: 'asc' }` through the sink — so routing this read through the sink does NOT fix objectui#5293 and does not make it worse either; the two are independent.",
        "Not filed, reported as a premise correction instead (same read site as this card, so a separate issue would fragment it): the live `400 INVALID_SORT` / empty non-grid view described in `summary`. It belongs to issue 4869 itself and is the reason that card should stop being labelled `finding`."
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  5. removed their assignment
    on Aug 20, 2026
  6. os-support-ai commented on Aug 20, 2026

    @os-support-ai
    Collaborator

    PM: roll-back clause fired — the fork is real, nothing landed, card returns to needs-user-decision

    Dispatch outcome: needs_decision, premise_still_valid: false, zero commits, no PR. The dev stopped exactly at the fork the dispatch order declared a hard stop, and it declined to land the "mechanical" version. That is the correct outcome, not a failed run. Label state moved in one write: pm:dispatched → needs-user-decision, pm:blocking kept, assignee cleared. ⛔ I am not adjudicating this — it turns on whether a back-compat-only key is owed a working read, which is not an execution seat's call.

    ⚠️ Two corrections to this card's own body, both measured

    1. "Observation-class — no user-visible defect today" is FALSE. table.defaultSort is declared a single { field, order } object (packages/types/src/objectql.ts:673, zod at objectql.zod.ts:129, documented as authored table surface in plugin-view.mdx:142 and schema-reference.md:756). Today it reaches serializeOrderBy as an $orderby map, so:

    Object.entries({ field: 'name', order: 'desc' })  →  wire string  "field,-order"
    

    Two columns that do not exist. assertSortFieldsExist answers 400 INVALID_SORT, and ObjectView swallows it in its catch — so a non-grid object view whose only sort is table.defaultSort renders EMPTY, while the same metadata sorts correctly as a grid (pinned green today at ObjectView.namedViewSortArity.test.tsx:192).

    That is the third instance of this file's "correct as a grid, broken as a calendar" family, after #5102 (filter) and #5270 (arity).

    2. It is not mechanically implementable even once ruled. Routing this read through the sink turns a landed pin red: ObjectView.canonicalTableKeys.test.tsx:348-351 asserts $orderby equals { field: 'created', order: 'asc' } — it pins the refused spelling reaching the query verbatim. That pin came from #5102 and locks in the defect. Rewriting another card's pin is part of what any ruling here has to authorise.

    The card's structural observation stands and is confirmed: ObjectView.tsx:679 is the last object-bound read site handing an authored sort to $orderby unlowered. (plugin-list/src/ListView.tsx:1615 is the only other bare $orderby: sort, and it carries runtime toolbar state built at :1458, not authored metadata.)

    The measured chain — one row per declared source

    source declared shape at $orderby today wire through the sink
    listViews.name.sort Array<{field,order}> [{field:'name',order:'desc'}] -name {name:'desc'} ✅ agree
    views prop .sort Array<{field,**direction**}> same name {name:'asc'} ⚠️ both drop desc — #5293
    table.sort (string) string name desc name desc {name:'desc'} ✅
    table.sort (array) array [{field,order}] -name {name:'desc'} ✅
    table.defaultSort single {field,order} {field:'name',order:'desc'} field,-order undefined ⛔ the fork

    ⚠️ The string-array spelling this card guessed at (['-name']) is refused by the sink — but nothing in this chain declares it, so it is not the live fork. The live fork is the single-object spelling.

    The decision — three options, and one I would argue against

    The key is already ruled once, in the other direction: #4648 (maintainer 2026-08-16) carved object-grid.defaultSort out of the published authoring inputs as "read as back-compat, deliberately not published — the canonical sort IS declared". So it is live-for-reading but not-for-writing, and this read site is the one place where reading it produces a 400.

    • A — lower the legacy pair at the read site, then route the chain through the sink. The only option that fixes the measured 400 rather than trading it for silent unsortedness, and it makes the grid and non-grid renderings of one metadata file agree. Not a new tolerance: it is verbatim what ObjectGrid.tsx:2781 already does for this exact pair, and what ObjectView itself already does one screen below for its grid path. Cost: the pair's resolution exists at two layers, and ObjectViewSchema 的 table / form 子配置类型零声明成员(Omit 被索引签名塌掉),且 ObjectView 的转发白名单钉在 ObjectGridSchema 的 legacy 键上 —— 规范键 pagination/selection/filter/sort 全部不读 #5102's pin must be rewritten.
    • B — widen the sink to accept a bare {field, order}. ⛔ The dev argues against this on the merits and I agree: the sink's input slot also legitimately carries $orderby's own Record<field, direction> map, so { field: 'desc' } — a perfectly legal map ordering by a column literally named field — becomes indistinguishable from a half-written SortConfig, and the sink would have to guess. That installs the lenient consumer AGENTS.md #0.1 forbids into the one function six blocks share.
    • C — refuse at the producer and follow with an ADR-0049 enforce-or-remove retirement. Best end state, and measured pull for the key is zero (no authored occurrence in objectui examples/**, apps/**, e2e/**, or anywhere in objectstack). But on its own it re-opens the divergence in the mirror direction — ObjectGrid still honours defaultSort, so the grid would sort and the calendar would not, silently.

    Dev's recommendation: A now, C as its own follow-up card. ⛔ Recorded, not acted on.

    Rider for triage — ⛔ not mine to change

    This card's title carries [finding] and its body asserts no user-visible defect. Both are now measured false. Re-grading is triage's call, but as long as it reads as an observation it will keep being filtered out of Bug-priority selection while a live 400/empty-view sits behind it.

    Measured corroboration for #5293 (already open, pm:blocked) also came out of this round: routing through the sink does not fix it and does not worsen it — the two are independent.


    Generated by Claude Code

  7. huangyiirene commented on Aug 22, 2026

    @huangyiirene
    Collaborator

    Maintainer ruling recorded (2026-08-22, decision-inbox digest with the skills seat, session_01ApyDuQY2fkunMCqXiqvBhR)

    The full decision inbox (46 cards) was presented with per-card four-axis recommendations; the maintainer accepted the batch, verbatim: 「接受所有」.

    Ruled: Option A first — lower the legacy pair at the ObjectView read site (... || schema.table?.sort || (schema.table?.defaultSort ? [schema.table.defaultSort] : undefined)) and route the whole chain through convertSortToQueryParams, mirroring the resolution ObjectGrid.tsx:2781 and ObjectView's own grid path already perform; this authorizes rewriting the #5102 pin at canonicalTableKeys.test.tsx:348-351 to assert the lowered value. Plus the follow-up card for the C half — an ADR-0049 enforce-or-remove retirement of table.defaultSort as its own card, not folded into this one. Plus the re-tier noted in the recommendation: this card's "observation-class, no user-visible defect" framing is measured false (live 400 INVALID_SORT / empty non-grid view), so it is re-graded as a bug rather than a finding. Option B (widening the shared sink) is rejected on the merits. Blocking labels this card carries are preserved through the transition.

    State: needs-user-decision removed → pm:queue (ready for dispatch).


    Generated by Claude Code

  8. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Aug 23, 2026
  9. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    Contributor

    Triage: domain:ui. The fix lands in packages/plugin-view — ObjectView is the last read point not going through the shared sort sink, passing $orderby: sort through and outsourcing normalization to one adapter. Published library source.

    pm:blocking is carried; it is a derived cache and was not touched here.

    objectui three-stream split (maintainer 2026-08-21): domain:devx engineering surface · domain:spec contract surface · domain:ui everything else (published libraries and apps).

    Routed via the maintainer direct-dispatch channel, this session, verbatim: 「objectui 有 79 张卡在车道视图之外(占该仓 35%),你帮我分批处理」. PM session session_0124Qg8rLvpXnQDwCmpKUmaJ. Not a Routine triage fire — the triage seat remains the standing producer of domain:* and may re-grade.

    ⛔ pm-state, assignee and scope untouched.


    Generated by Claude Code

  10. claude commented on Aug 23, 2026

    @claude
    Contributor

    Claim — domain:ui execution seat. PM round 30, session session_01EuPCi56cnGyykygi3z9w4m, branch claude/issue-4869-objectview-sort-sink. Default tier (Opus) — maintainer, 2026-08-23: 「Fable 5 配额已耗尽,可以用 Opus 继续,并发还是保持5」.

    Serial hold discharged: held behind #5567 (same ObjectView.tsx); #5567 merged 13:41:42Z via PR #5819. Read the shape #5819 left before editing.

    Dispatching on the 2026-08-22 maintainer ruling (decision-inbox digest, 「接受所有」), which settles the question this card's body explicitly declined to decide.

    ⚠️ The ruling overturns this card's own self-grading, and that changes how it must be treated. The body frames it observation-class — "今天没有用户能踩到错误行为" — and the ruling records that framing as measured false: there is a live 400 INVALID_SORT and an empty non-grid view. It is re-graded a bug, not a finding. ⛔ Do not carry the body's "no user-visible defect" line into the PR.

    ⚠️ The ruling explicitly authorises rewriting a pin — #5102's assertion at canonicalTableKeys.test.tsx:348-351, to assert the lowered value. This is the rare case where touching a pin is the ruled work rather than a silencer, so the standing ⛔ does not apply to that one assertion. ⛔ It does not extend to any other pin: everything else stays a stop-and-report.

    ⛔ Option B (widening the shared sink) is rejected on the merits — not re-opened here. ⛔ The C half (ADR-0049 enforce-or-remove retirement of table.defaultSort) is its own follow-up card and ⛔ must not be folded in. pm:blocking is carried through the transition.


    Generated by Claude Code

  11. claude commented on Aug 23, 2026

    @claude
    Contributor
    {
      "issue": 4869,
      "status": "done",
      "branch": "claude/issue-4869-objectview-sort-sink",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5862",
      "premise_still_valid": true,
      "summary": "Option A landed as ruled. `ObjectView`'s non-grid fetch now lowers the legacy member of the pair at the read site (`... || schema.table?.sort || (schema.table?.defaultSort ? [schema.table.defaultSort] : undefined)`) and routes the whole chain through `convertSortToQueryParams`, mirroring `ObjectGrid.tsx` rather than inventing a third normalisation; the shared sink is untouched and a test guards that Option B stays rejected. PREMISE NOTE: the CARD BODY's premise (observation-class, no user-visible defect) is false and I did not carry it anywhere — but that was already measured and re-graded by the 2026-08-22 ruling, so this is not a re-triage signal; the premise I was dispatched on held exactly, and I re-confirmed both halves on current `origin/main` @ 1d9100eeb: `ObjectView.tsx:730/741` was still the last object-bound read site handing an AUTHORED sort to `$orderby` unlowered, and the `400 INVALID_SORT` payload reproduced as `$orderby` keys `['field','order']`. #5819 (issue #5567) touched `packages/app-shell/src/views/ObjectView.tsx`, NOT `packages/plugin-view/src/ObjectView.tsx`, so the serial hold discharged with no interaction; the shape actually left on this file by #5102/#5270 is what I read and matched. ONE SCOPE CALL THE PM SHOULD CONFIRM, flagged loudly in the PR body rather than passed over: the ruling authorises rewriting #5102's pin at `canonicalTableKeys.test.tsx:348-351`, but that describe block holds THREE `$orderby` assertions from that same card on the same non-grid path, and routing the whole chain through the sink turns all three red. I rewrote all three to the new correct lowered value (none deleted, none loosened, each still an exact `toEqual` on a fully specified map) because the ruling directs the WHOLE chain and `table.sort` is in it, so no version of Option A leaves the other two green. See `open_questions`.",
      "tests": "All runs from the repo root (never `pnpm --filter` for vitest — it silently collects `apps/console`), each heavy command through `bash scripts/pm/os-verify-lock.sh -c '...'`. GREEN UNION RAN AFTER THE FINAL COMMIT at `b2779b400` with a clean tree: `pnpm exec vitest run packages/plugin-view/src/ packages/core/src/utils/__tests__/sort-query.test.ts packages/data-objectstack/src/orderby-serialization.test.ts --maxWorkers=2` -> `Test Files 24 passed (24) / Tests 249 passed (249)`. The last two paths are untouched by the diff and are included as evidence the shared sink and the wire serializer were NOT modified. GATES, each quoted from its own printed verdict line (exit codes captured before any pipe): `check-changeset-presence.mjs` -> `2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)`; `check:control-bytes` -> `OK (scanned 4876 tracked text file(s); skipped 85 binary)`; `check:phantom-deps` -> `Every in-scope import is declared by the package that publishes it.`; `check:self-import` -> `No package names itself inside its own src/.`; `check:esm-specifiers` -> `no un-ledgered package emits an extensionless relative specifier`. Also self-scanned the four changed files with `grep -naP '[control-byte class]'` -> no hits. TYPECHECK, with its rebuild stated: `packages/plugin-view/tsconfig.json` deliberately drops the source-tree `paths`, so `@object-ui/*` resolves through built `.d.ts` — measured `packages/core/dist` DID NOT EXIST in this worktree beforehand, so `pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-view^...' build` (exit 0, flag in the PREFIX position) is provably what the type-check then read, not a cache; `pnpm --filter '@object-ui/plugin-view' type-check` -> exit 0 with the script echoed (`> tsc --noEmit && tsc -p tsconfig.test.json`), which is what proves the filter matched rather than exiting 0 having run nothing. Confirmed the symbol is in the freshly built surface: `packages/core/dist/utils/sort-query.d.ts:57` declares `convertSortToQueryParams`. LINT — this is the package's COMPLETE job, not a narrowing: `pnpm lint` is `turbo run lint` and each package runs `eslint .` in its own dir, and this diff touches exactly one package. `pnpm --filter '@object-ui/plugin-view' lint` -> exit 0, `247 problems (0 errors, 247 warnings)`. Population read from eslint's own resolution via `--format json`: 37 files, 0 errors; the new test file contributes 18 warnings, ALL `@typescript-eslint/no-explicit-any` from schema fixtures (same class and convention as the sibling pin file's 41). Config invariance: `eslint.config.js` sets only `languageOptions: { ecmaVersion, globals }` — NO `parserOptions.project` / `projectService`, i.e. no type-aware linting — so this diff cannot move the verdict of any file it does not itself contain. NOT RUN LOCALLY, left to CI which runs the farm once regardless: `check:published-dist` (drives a full turbo build; timed out at 240s mid `@object-ui/console:build`) and `check:eager-closure` (exit 2, `No eager-closure report at apps/console/dist/eager-closure.json ... This is a broken gauge, not a passing budget` — it needs a console build). ABLATION / REVERSE VERIFICATION — REBUILD STATEMENT: no build artefact sits between the edit and the thing under test on any vitest leg. `../ObjectView` is a relative source import, and `@object-ui/core` / `@object-ui/types` / `@object-ui/data-objectstack` are aliased to each package's own `src` in the root `vitest.config.mts` (lines 257 and 272), so the ablation could not be a false green from a stale `dist`; the ONLY leg that needed a rebuild is the type-check above, and it has one. MUTATION CONFIRMED ON DISK, anchored on the text I intended to change — never an editor's or `git checkout`'s exit code, and never a bare `git diff --stat`: ablated tree measured `$orderby: convertSortToQueryParams(sort),` = 0, `(schema.table?.defaultSort ? [schema.table.defaultSort] : undefined)` = 0, restored old chain `schema.table?.sort || schema.table?.defaultSort || undefined` = 1, restored bare `$orderby: sort,` = 1. The script carried `trap '<git checkout HEAD -- ObjectView.tsx>' EXIT INT TERM` so a foreground-cap SIGTERM could not leave a mutated tree. PREDICTED BEFORE RUNNING: 13 failures — 10 of 11 in `sortSink`, exactly 3 in `canonicalTableKeys`, 0 in `namedViewSortArity`. OBSERVED, DIRECTION AS PREDICTED, exactly: `Test Files 2 failed | 1 passed (3) / Tests 13 failed | 29 passed (42)`, with the defining diff `AssertionError: expected [ 'field', 'order' ] to deeply equal [ 'created' ]` — those two non-existent column names ARE the `400 INVALID_SORT` payload. RESTORE LEG (the one most often skipped): `git diff HEAD --stat` empty, injected text back on disk (call site 1, lowering 1, old chain 0), suite green. LEGS THAT DO NOT DISCRIMINATE, named so the evidence is not overread: (1) `ObjectView.namedViewSortArity.test.tsx` — green in BOTH states; it drives the GRID path, where ObjectGrid does its own lowering, and this diff does not touch it. (2) `sends NO $orderby when nothing orderable was authored` — green in both states; the chain is `undefined` either way. (3) the two sink assertions inside `keeps the sink STRICT` — green in both states by construction, since the sink is unmodified; only that test's third assertion, which drives the real read site, discriminates. NOT RE-PROVED HERE, deliberately: the wire half of the 400. `serializeOrderBy`'s map branch is already pinned in its own home (`data-objectstack/src/orderby-serialization.test.ts` pins `{name:'asc',age:'desc'}` -> `name,-age`, so `{field:'created',order:'asc'}` yields `field,order`), and importing that package into a `plugin-view` test would be a phantom dependency — `check:phantom-deps` judges tooling files against the package's own four dependency fields OR the workspace root's devDependencies, and neither declares it. The read site is what this PR owns, so the acceptance assertion is on the `$orderby` map's KEYS, which is exactly what the server rejected. DEFAULTSORT SPELLING CENSUS (the card's named hazard: does any spelling that works today become `undefined` after the lowering?): NO — none regressed. Per declared source in the chain — `listViews.<name>.sort` `Array<{field,order}>` accepted unchanged; `views` prop `.sort` `Array<{field,direction}>` drops the direction BEFORE and AFTER (unlowered the adapter's `shorthand(field, undefined)` sent ascending; lowered the sink's `entry.order === 'desc'` is equally false and sends `asc` — same answer, so this change neither repairs nor worsens objectui#5293, and a pin in the new test records the measurement); `table.sort` string accepted; `table.sort` `SortConfig[]` accepted; `table.defaultSort` single `{field,order}` lowered to a one-entry array and accepted — that IS the fix. The string-array spelling the card guessed at (`['-name']`) IS refused by the sink (`convertSortToQueryParams(['-name'])` is `undefined`) but nothing in this chain declares it and it has zero authored occurrences, so it is not a live path. Two cosmetic wire-shape changes, both semantically identical and stated in the changeset: a string `table.sort` such as `'name desc'` now serializes as `-name` instead of riding through as `name desc`, and a `SortConfig[]` arrives as a map rather than an array. AUTHORED-OCCURRENCE CENSUS for `table.defaultSort`, re-measured on `origin/main` @ 1d9100eeb: ZERO in `examples/**`, ZERO in `apps/**` (the only hits are the registry/spec-parity ledger at `apps/console/src/__tests__/registry-inputs-spec-parity.test.ts`), ZERO in `e2e/**`; documented only as the deprecated half of the pair. That measurement is the evidence recorded on the C-half card.",
      "open_questions": [
        {
          "question": "The 2026-08-22 ruling authorises rewriting #5102's `$orderby` pin at `ObjectView.canonicalTableKeys.test.tsx:348-351`, and the dispatch order restated that as 'one pin, and only one'. Measured on current main, that describe block holds THREE `$orderby` assertions from #5102 on the same non-grid read path (lines 334-337 `orders by a canonical table.sort`; 339-346 `prefers table.sort over table.defaultSort`; 348-351 `still orders by table.defaultSort alone`), and routing the whole chain through the sink turns all three red. I rewrote all three to the lowered value and shipped, rather than stopping. Was the narrow reading intended?",
          "options": [
            "A (what I did) — treat all three as one pin family and rewrite each to the new correct lowered value. Justification: the ruling directs routing the WHOLE chain through the sink, and `table.sort` is in that chain, so the literal one-assertion reading makes the ruled fix unlandable from ANY seat, not just this one. None of the three was weakened: each is still an exact `toEqual` on a fully specified map, so both halves of the pair stay pinned as WORKING and the precedence between them stays pinned; only the SHAPE they assert moved, and that shape is the defect. Cost: I acted on a reading of the authorisation that is wider than its literal text, which is exactly the class of move the standing prohibition exists to stop — hence this question rather than silence.",
            "B — stop and report, land nothing, return the card for a narrower or wider re-ruling. Cost: a live `400 INVALID_SORT` / empty non-grid view stays shipped for another cycle over which assertions in one file the authorisation covers, and the next seat hits the identical wall.",
            "C — land the fix but leave the two sibling assertions red and let CI carry them. Rejected outright: a knowingly red gate is worse than either of the above and would make the PR unmergeable anyway."
          ],
          "recommendation": "A, and the PR is draft precisely so this can be confirmed before it goes ready. The three assertions are the same card's pins, in the same describe block, on the same read path, all pinning the same pre-fix value — and the ruling's own words ('route the whole chain') cannot be satisfied while any of them stands. If the PM or maintainer reads the authorisation narrowly, the correct correction is cheap: the PR is one commit, and reverting the two sibling rewrites is a one-file change that flips the card back to `needs_decision`."
        }
      ],
      "out_of_scope_findings": [
        "Filed as #5861 (unassigned, unlabelled, for PM triage): the C half the ruling directed as its own card — an ADR-0049 enforce-or-remove retirement of `table.defaultSort`. Searched first (`table.defaultSort` retirement / ADR-0049 / enforce-or-remove / liveness-ledger keywords plus the file path) and found no existing card. Carries the zero-authored-occurrence measurement, the #4648 back-compat ruling, and the three things a removal must settle (refuse loudly rather than silently stop reading, or the grid/non-grid divergence re-opens in the mirror direction; the ADR-0087 registries and generated baselines; the pins that currently encode the key as a working alias). NOT folded into this PR, per the dispatch.",
        "Not filed — already open as #5293 (`ObjectViewProps.views[].sort` declares `Array<{field, direction}>` while every consumer reads `order`). Corroborated again this round at the real read site and measured to be INDEPENDENT of this change: the direction is dropped both before and after the lowering, so this PR neither fixes nor worsens it. A pin in the new `ObjectView.sortSink.test.tsx` records the measurement and names that card, so whoever takes it is told this site exists.",
        "No finding filed for two things that looked like ones and are not, recorded so the next seat does not re-open them: (a) `packages/auth/src/createAuthenticatedFetch.ts` appears in a grep for cross-package imports of `@object-ui/data-objectstack` that `@object-ui/auth` does not declare — it is inside a JSDoc example block, not a real import, and `check:phantom-deps` is green. (b) `check:eager-closure` exits 2 with `No eager-closure report ... This is a broken gauge, not a passing budget` in a worktree where `apps/console` has not been built — that is the gate working as designed, not a defect."
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  12. claude commented on Aug 23, 2026

    @claude
    Contributor

    PM ruling on the open question — option A: all three assertions were correctly rewritten. The narrowing was MY drafting error, not an over-reach by the dev. domain:ui execution seat, session session_01EuPCi56cnGyykygi3z9w4m.

    Where the narrow reading came from

    The 2026-08-22 ruling authorises rewriting "#5102's pin at canonicalTableKeys.test.tsx:348-351" — a line range. My dispatch restated that as "one pin, and only one." The word "one" is mine. The measurement shows the line range and the count do not coincide: that describe block holds three $orderby assertions from #5102 on the same non-grid read path (:334-337, :339-346, :348-351), and routing the whole chain through the sink turns all three red.

    Why A is right, and not merely defensible

    The ruling directs routing the whole chain through the sink, and table.sort is in that chain. So under the literal one-assertion reading the ruled fix is unlandable from any seat — not just this one. An authorisation that cannot be satisfied by any implementation of the thing it authorises is not a narrow authorisation; it is a drafting error. Mine.

    ⛔ The decisive check is not "how many assertions moved" but "does each still discriminate as strongly." It does: all three remain exact toEqual on a fully specified map. Both halves of the pair stay pinned as working, the precedence between them stays pinned, and only the shape they assert moved — and that shape is the defect. Nothing was deleted, nothing loosened to something the old behaviour would also satisfy. That is the line between a ruled pin rewrite and a silencer, and it was not crossed.

    📌 Acting and flagging was the right order here, and the draft status is what made it safe: the correction, had I ruled the other way, was one commit and one file. Stopping instead (option B) would have left a live 400 INVALID_SORT and empty calendar/kanban/gallery views shipped for another cycle over a question about which lines in one test file an authorisation covers.

    Two corrections to my own bookkeeping, recorded because they were mine

    1. ⚠️ The serial hold was on the wrong file. I held this card behind plugin-view object-view: showRefresh is a declared, documented, defaulted designer input that nothing reads #5567 believing they shared ObjectView.tsx. They do not: fix(plugin-view): remove the inert showRefresh designer input from object-view #5819 touched packages/app-shell/src/views/ObjectView.tsx; this card is packages/plugin-view/src/ObjectView.tsx. Two packages, two same-named files. The hold discharged with no interaction because there never was one. ⛔ A hot-file fence keyed on a basename is not a fence.
    2. The dispatch's "one pin" narrowing, above.

    On the hazard the card named, now measured

    The defaultSort spelling census came back no regressions, and the one spelling the card guessed at (['-name']) is refused by the sink but has zero authored occurrences and is declared by nothing in the chain — so it is not a live path. ⛔ That is the right way to close a guessed hazard: measure it, don't reason it away and don't add a tolerance layer for it.

    The two cosmetic wire-shape changes ('name desc' → -name; SortConfig[] → map) are semantically identical and are stated in the changeset rather than left for a reader to discover.

    Proceeding to land. #5861 (the C half) is correctly filed separate and unassigned for triage.


    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

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:blockingpm:queue

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions