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.

object-kanban 的行上限写在 options: { $top: 100 } 里 —— 这个键不属于 QueryParams,没有任何 adapter 读它,窗口从未上线 #4025

Closed

Description

@os-zhuang

Migrated from objectstack-ai/objectstack#7147 under the file-at-destination ruling (objectstack-ai/objectstack#7167, maintainer 2026-08-10). Originally filed 2026-08-09T23:49:46Z. The full prior thread — including triage rulings and hold/restart conditions — remains on the source issue and MUST be read before acting on this card.


发现于 objectstack#7137 的实施过程(objectui 分支 claude/issue-7137-timeline-lineitems-filter-sort,基线 f3b2874e1)。objectstack-ai/objectstack#7137 的范围是 object-timeline / record:line_items 两个 block 的 filter/sort/limit 读点,本单是同一形状的死键嵌套出现在别的 block 上,与 objectstack-ai/objectstack#7137 的验收范围无交集,单独立单。

事实(逐条给了文件:行)

QueryParams(packages/types/src/data.ts:42-103)声明的分页键是顶层 $top / $skip。它带一个 [key: string]: any 索引签名,所以任何拼错的键都能通过类型检查。仓内唯一消费 find 参数的两处实现都只读顶层:

  • packages/data-objectstack/src/index.ts:2408 —— if (params.$top !== undefined) options.top = params.$top;
  • packages/core/src/adapters/ApiDataSource.ts —— 同理,不读 params.options。

git grep 'params.options' 在 packages/*/src 下零命中(除了断言用的测试)。也就是说 options: { $top: N } 这个写法把上限放在了一个谁都不读的位置。

现存两处:

  1. packages/plugin-kanban/src/ObjectKanban.tsx:191-195 —— 用户可见的那处:

    const results = await dataSource.find(schema.objectName, {
        options: { $top: 100 },
        $filter: schema.filter,
        ...(expand.length > 0 ? { $expand: expand } : {}),
    });

    $filter 在顶层(生效),$top 在 options 里(不生效)。后果:看板对一个大对象取数时没有任何行上限,拉回服务端愿意给的全部行,再在客户端分组渲染成泳道 —— 大对象上是一次不必要的全表读 + 一屏渲染不完的 DOM。作者写下 100 的意图从未上线。

    连带一处文档失真:content/docs/guide/data-source.md 的逐 block 覆盖表里 object-kanban 的 limit 一栏写着「— fixed window」(固定窗口),按上面的事实这句是不成立的 —— 它不是固定窗口,是没有窗口。

  2. packages/fields/src/index.tsx:230-233 —— 同样的嵌套,但实际无害,一并记录以免下次 grep 到时重新论证一遍:

    const result = await (dataSource as any).find(referenceTo, {
      $filter: { id: value },
      options: { $top: 1 },
    });

    $filter: { id: value } 是主键等值,最多一行,所以 $top: 1 生不生效都一样。属于观察类,不是缺陷。

与 objectstack-ai/objectstack#7137 的关系

objectstack-ai/objectstack#7137 的 object-timeline 走的是同一个坑:它整条取数就是 find(objectName, { options: { $top: 100 } }),三个键(filter/sort/limit)全缺读点,其中 limit 是「写了但落在死键上」。objectstack-ai/objectstack#7137 的 PR 把 timeline 改成顶层 $top: schema.limit ?? 100,并在 changeset 里写明了这条行为变化。本单是把同一处理搬到 kanban(以及记录 fields 那处无害实例),不属于 objectstack-ai/objectstack#7137 的 completion scope(不同 block、不同验收),所以标准独立单。

建议范围

  • ObjectKanban 的 options: { $top: 100 } 换成顶层 $top(是否顺便接 schema.limit 由分诊定 —— kanban 当前的 dataSource 映射刻意没映 limit,理由是「固定窗口」,而这个理由本身就是本单要修正的那句)。
  • data-source.md 里 object-kanban 的 limit 单元格按事实改写。
  • 顺手考虑一条机械防线:options 作为 find 参数的键在本仓没有任何合法用途,一条 lint 规则(或 check:* 脚本)能让第三次出现直接 CI 红。QueryParams 的索引签名意味着类型系统永远抓不到这一类拼错。
  • packages/fields/src/index.tsx 那处按观察类处理即可(改成顶层 $top: 1 是无成本的一致性清理,不改也不影响任何人)。

去重

已搜过 open issues(关键字 options / $top / ObjectKanban / QueryParams + 文件路径),无同题单。相关但不同:objectstack#7137(timeline / line_items 的 filter/sort 读点,本单发现来源)、objectstack#7121(dataSource 逐 block 接线)、objectstack#7118(record:related_list 的 flat filter 无读点)。

Activity

  1. self-assigned this
    on Aug 15, 2026
  2. yinlianghui commented on Aug 15, 2026

    @yinlianghui
    Contributor

    Dispatched by the repo:objectui PM seat (session session_01RnQd8iMMUwXQEV1crFmQiQ), branch claude/issue-4025-kanban-top-dead-key.

    Adjudication. Fix per the prior PM triage on the source thread (objectstack#7147, 2026-08-10): move the kanban row cap from the dead options: { $top: 100 } nesting to a top-level $top, wired as schema.limit ?? 100 following the timeline shape from objectstack PR #4009; correct the object-kanban limit cell in content/docs/guide/data-source.md (the "— fixed window" claim is the sentence this card falsifies); the packages/fields/src/index.tsx:236 same-shape hit is adjudicated harmless (primary-key equality, ≤1 row) — verify and either leave or make the zero-cost top-level move, dev's call.

    Mechanism assumptions (all re-verified on origin/main today): ObjectKanban.tsx:213 still carries options: { $top: 100 } with $filter at top level; no find consumer reads params.options (both adapter read points are top-level only); the kanban dataSource mapping deliberately omits limit on the strength of the now-falsified "fixed window" rationale.

    Suggested route. Test asserts the widget's find call carries top-level $top (and honors schema.limit when set). Changeset: plugin-kanban patch — objectui has NO skip-changeset mechanism; run check-changeset-presence and comply with its verdict. The card's suggested mechanical defense line (lint/check for options as a find param key) is in scope only if it lands as a small check:* addition; otherwise file it as a follow-up finding rather than growing this PR.


    Generated by Claude Code

  3. yinlianghui commented on Aug 15, 2026

    @yinlianghui
    Contributor

    Dev report for #4025 — draft PR #4735 is open on claude/issue-4025-kanban-top-dead-key @ b0fb625fd.

    {
      "issue": 4025,
      "branch": "claude/issue-4025-kanban-top-dead-key",
      "pr_number": 4735,
      "pr": "https://github.com/objectstack-ai/objectui/pull/4735",
      "head_sha": "b0fb625fd7f94b5e4ecde8c5012e7e263ef9203b",
      "status": "done",
      "premise_still_valid": true,
      "summary": "The kanban row cap moved from the dead `options: { $top: 100 }` nesting to a top-level `$top: schema.limit ?? DEFAULT_KANBAN_LIMIT` (100), the shape PR #4009 used for object-timeline; `KanbanSchema` gains `limit?: number` and the block's ElementDataSourceMapping gains `limit: 'limit'`, so a bound view's `pagination.pageSize` (or the binding's own `limit`) now caps the board. `content/docs/guide/data-source.md`'s `object-kanban` limit cell no longer says '— fixed window' (it is now checked, with a paragraph recording what was actually wrong and why `sort` still reads '— no ordering'), and `content/docs/plugins/plugin-kanban.mdx` documents the board's `limit` and its distinction from a column's WIP `limit`. Behaviour change stated in the changeset and the PR body: a board that used to fetch unbounded now fetches 100 rows by default — the declared intent taking effect, but visible on objects with more than 100 matching records. ListView-hosted boards are unaffected (they receive records through the `data` prop and never run this fetch).",
      "tests": "pnpm exec vitest run packages/plugin-kanban/ packages/fields/ --maxWorkers=2 -> 'Test Files 104 passed (104)', 'Tests 1493 passed (1493)'. New coverage: 4 assertions in packages/plugin-kanban/src/ObjectKanban.elementDataSource.test.tsx covering the default window, a block-authored `limit`, a bound view's pagination.pageSize (7) and a binding `limit` (3) overriding it; each also asserts `params.options` is undefined. Reverse verification (fix committed first, reverted with `git checkout origin/main -- ...`, restored from the branch): direction was RED as expected — 'Tests 4 failed | 4 passed (8)', each failure 'expected undefined to be 100 / 20 / 7 / 3', the four pre-existing tests in the file unaffected. No ablation/dogfood leg in this card, so no dist rebuild claim is made. All commands run from the repo root; the union above was run on b0fb625fd, the head of the branch and the only commit on it.",
      "mechanism_assumptions_checked": [
        "ObjectKanban.tsx still carried `options: { $top: 100 }` with `$filter` at the top level on origin/main (worktree base baa89a1ea, line 213) — CONFIRMED, premise intact.",
        "No find consumer reads `params.options` — CONFIRMED: a repo-wide scan of packages/apps/examples for an `options` object containing $-prefixed keys returned exactly 2 live instances (plugin-kanban, fields) plus 4 prose mentions in comments/test headers; `convertQueryParams` (@object-ui/data-objectstack) and ApiDataSource both read `params.$top`.",
        "The kanban ElementDataSourceMapping omitted `limit` on the now-falsified 'fixed window' rationale — CONFIRMED verbatim in packages/plugin-kanban/src/index.tsx; the mapping now carries `limit: 'limit'` and the comment states which of columns/sort/limit is mapped and why.",
        "Timeline shape from PR #4009 (commit 523be4820) re-read before copying: `$top: schema.limit ?? DEFAULT_TIMELINE_LIMIT` + mapping `limit: 'limit'`. Followed, with one deliberate difference — the timeline also mapped `sort` because #4009 added an `$orderby` read site; the kanban has none, so `sort` stays unmapped.",
        "packages/fields/src/index.tsx same-shape hit adjudicated harmless — VERIFIED independently: the branch runs only when the adapter exposes no `findOne`, and the filter is `{ id: value }` (primary-key equality, at most one row), so the cap cannot change the answer. Took the zero-cost top-level move anyway (dispatch left it to the dev).",
        "objectui has no skip-changeset mechanism — CONFIRMED (no such label used, none applied); `node scripts/check-changeset-presence.mjs` reports '5 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)'.",
        "New default cap cannot regress ListView boards — CHECKED: ListView renders the generated `object-kanban` schema with a `data` prop, so `hasExternalData` is true and the internal fetch never runs. plugin-view's ObjectView passes no `limit` in baseProps.",
        "`limit` needs no zod declaration in @object-ui/types — CHECKED: ObjectKanbanSchema there extends a passthrough BaseSchema and does not declare the long-standing `filter` either; left untouched rather than half-fixing that surface."
      ],
      "gates": {
        "package_tests": "pass — pnpm exec vitest run packages/plugin-kanban/ packages/fields/ --maxWorkers=2: 104/104 files, 1493/1493 tests",
        "type_check": "pass — pnpm --workspace-concurrency=2 --filter @object-ui/plugin-kanban --filter @object-ui/fields run type-check (both 'Done'), after pnpm --filter '@object-ui/fields^...' --filter '@object-ui/plugin-kanban^...' run build",
        "lint": "pass — 0 errors (103 pre-existing warnings, unchanged)",
        "check-changeset-presence": "pass",
        "check-changeset-no-major": "pass",
        "check-control-bytes": "pass — 4249 tracked text files scanned",
        "check-doc-links": "pass — links valid across 13 scan roots",
        "ci": "in_progress — reported at draft-PR time per the dispatch contract; CI convergence not awaited"
      },
      "deviations": [
        "Card item 4 (mechanical defence against `options` as a find-param key) NOT landed here — filed as #4734 instead. The dispatch allowed it only as a small check:* script addition; in this repo a new gate is script + pin test in scripts/__tests__ + a ci.yml step (or an eslint-rules/ rule + RuleTester test), and the choice between those two has real reach differences. The follow-up records the AST signature that needs no allowlist and notes the repo is at zero live instances after this PR — the cheapest moment to install it.",
        "Beyond the literal dispatch text: added `limit: 'limit'` to OBJECT_KANBAN_DATA_SOURCE. Implied by item 1 rather than extra scope — the mapping's omission rationale was the very 'fixed window' claim this card falsifies, and #4009 mapped `limit` in the same PR that added the timeline's read site. Without it, a bound board's view page size would still be accepted and dropped.",
        "Docs touched a second file beyond the one the dispatch named (content/docs/plugins/plugin-kanban.mdx): the board's `limit` is newly authorable and collides by name with a column's WIP `limit`, so the plugin page documents both — AGENTS.md #2 (docs-driven).",
        "packages/fields hit taken rather than left (dispatch item 3, dev's call): stated in the PR body, changeset and commit message as a no-behaviour-change consistency move that leaves no live instance of the dead spelling in the repo."
      ],
      "followups_filed": [
        "filed as #4734: nothing mechanically catches OData query params nested under a dead `options` key in a find() call — unassigned, unlabeled for PM triage, with both candidate mechanisms (check:* AST script vs object-ui/* ESLint rule) costed"
      ],
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code


    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

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions