Skip to content

feat(lint): page-component field bindings + non-dashboard chart bindings (#3583) - #3684

Merged
os-zhuang merged 1 commit into
mainfrom
claude/app-metadata-validation-assessment-xskbcg
Jul 27, 2026
Merged

feat(lint): page-component field bindings + non-dashboard chart bindings (#3583)#3684
os-zhuang merged 1 commit into
mainfrom
claude/app-metadata-validation-assessment-xskbcg

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

#3583 方案的 Phase 2:剩下的两类引用(页面字段绑定、仪表盘之外的图表轴),同 PR 接入 validate/lint/compile

先说一个必须修的缺陷

上个 PR(#3657)里的 quick-actions 检查走的是 page.components —— PageSchema 根本没有这个键。组件实际挂在 regions[].components[]slots 下,子树嵌在无类型的 properties 包里(children / items[].children / body / footer),而不是组件上的 children(PageComponentSchema.strict(),不允许该键)。

也就是说那段检查在解析后的 stack 上一个组件都没访问到,而它的测试恰好也是照着这个臆想的形状写的,所以全绿。现在遍历抽成一个共享且有测试的模块;在 showcase 上它访问到 194 个组件,旧形状只有 46 个。

validate-page-field-bindings

PageComponent.properties 是无类型包,所以 highlights 条、KPI 卡、details 分区都能写一个对象上不存在的字段,组件静默跳过、页面少渲染一项。

  • 对象绑定按 dataSource.objectproperties.objectpage.object,多对象页面逐元素判定,而不是拿一个页面级猜测硬套。
  • record:related_listcolumns/sort/filter 解析到关联对象(objectName),add-picker 解析到 picker 自己的对象。
  • 严重级 warning,与 FORM_FIELD_UNKNOWN 一致 —— 页面消费方是降级而非崩溃。

描述表是手写的,这是刻意的:Zod schema 说不出哪个 z.string() 是字段名(statusFieldagentId 一样都是字符串),而且 PageComponent.type 接受任意字符串,所以未知类型一律静默跳过。表里还覆盖了 props schema 没描述、但真实页面确实这么写的形状(record:detailssections[].fieldshideFields、picker 的 labelField)—— 只按 schema 形状写规则,在真实语料上会一条都查不出来。

validate-chart-bindings

把 ADR-0021 的轴校验从仪表盘扩到:报表图表(report.chart + blocks[].chart)、列表视图图表(三条容器路径)、以及数据集绑定的页面图表组件。

  • 轴指向原始字段而非已声明 measure → error(cutover 之后结果行按 measure 名索引,所以序列返回空)。
  • 轴指向已声明但本图表未 values 选中的 measure → warning(查询根本不返回它)。
  • 报表需要单独处理:ReportChartSchemaxAxis/yAxis 收窄成裸字符串,仪表盘规则的 Array.isArray(yAxis) 守卫会静默跳过它 —— 直接把老规则指向报表会一条都查不出来。

故意不做:react <ObjectChart> 是对象绑定而非数据集绑定,而仓库里没有任何地方定义它的 aggregate 把结果列叫什么,所以「轴该解析成什么」没有可辩护的答案。猜一个就是在制造误报。留作后续(ADR-0078:先验证再强制)。

验证

  • 误报地板:三个真实应用零 finding。
  • 并且先确认了这不是空转:探针显示遍历确实访问到 194 个组件、11 个有对象绑定、1 个报表图表、2 个列表图表 —— 遍历若静默为空,零误报就是假通过(这正是上面那个缺陷的教训)。
  • 测试:lint 435 / cli 637 全过;eslint、nul-bytes、api-surface 干净。

关联:#3583 · #3640(方案)· #3657(Phase 1)· ADR-0021 / ADR-0072 / ADR-0078


Generated by Claude Code

…ngs (#3583)

Phase 2 of the #3583 assessment: the two remaining reference classes the
HotCRM audit found, both wired into validate/lint/compile together.

validate-page-field-bindings — `PageComponent.properties` is an untyped
bag, so a highlights strip, KPI card, or details section can name a field
the bound object lacks and the component silently skips it. Object
binding follows dataSource.object -> properties.object -> page.object, so
a multi-object page is judged per element instead of against one page-wide
guess; `record:related_list` resolves columns/sort/filter against the
RELATED object and its add-picker against the picker's own. Advisory,
matching FORM_FIELD_UNKNOWN. The descriptor table is hand-written on
purpose: a Zod schema cannot say which `z.string()` prop is a field name,
and `PageComponent.type` accepts arbitrary strings, so unknown types are
skipped silently. It also covers shapes the props schemas do not describe
but real pages author anyway (record:details `sections[].fields` and
`hideFields`, the picker's `labelField`) — linting the schema shape alone
would find nothing on the actual corpus.

validate-chart-bindings — extends ADR-0021 axis checking past dashboards
to report charts (report.chart + blocks[].chart), list-view charts (three
container paths), and dataset-bound page chart components. An axis naming
a raw field instead of a declared measure is an error (post-cutover rows
are keyed by measure NAME, so the series returns empty); a declared but
unselected measure is a warning. Reports needed their own handling:
ReportChartSchema narrows xAxis/yAxis to bare STRINGS, which the dashboard
rule's Array.isArray guard skips silently. The react <ObjectChart> block
is object-bound rather than dataset-bound and nothing in the repo defines
what its aggregate names the result column, so it is left out rather than
guessed at (ADR-0078: verify, then enforce).

Fixes a defect in the rule shipped by #3657: its page walk read a
top-level `page.components` array that PageSchema does not have —
components live under regions[].components[] and slots, and sub-trees
nest inside the untyped properties bag (children, items[].children, body,
footer), not a `children` key on the component (PageComponentSchema is
strict). The quick-actions check was visiting nothing on a parsed stack,
and its tests encoded the invented shape so they passed. Traversal is now
one shared, tested module; on the showcase app it reaches 194 components
where the old shape found 46. Source-authored pages (html/react/jsx) are
skipped — their regions hold a derived cache the source wins over.

Verified zero false positives on app-crm, app-showcase and app-todo with
the fuller traversal, after confirming the walk actually visits their 194
components, 1 report chart and 2 list charts (a silently-empty walk would
pass vacuously). Suites: lint 435, cli 637 passing; eslint, nul-bytes and
api-surface clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBks5G7AkwrvgF2kL5rfkT
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 27, 2026 2:26pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/xl labels Jul 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/lint.

19 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via packages/cli)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli)
  • content/docs/automation/hook-bodies.mdx (via packages/cli)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli)
  • content/docs/permissions/authorization.mdx (via @objectstack/lint)
  • content/docs/plugins/packages.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli)
  • content/docs/releases/v16.mdx (via @objectstack/cli)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review July 27, 2026 14:41
@os-zhuang
os-zhuang merged commit 17749fc into main Jul 27, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/app-metadata-validation-assessment-xskbcg branch July 27, 2026 14:42
os-zhuang added a commit that referenced this pull request Jul 28, 2026
…nd validate its axes (#3701) (#3725)

#3684 extended ADR-0021 axis checking to report charts, list-view charts and
dataset-bound page chart components, but had to leave the react <ObjectChart>
block out: it is OBJECT-bound (objectName + an inline aggregate), and nothing
in the repo said what the aggregated result columns were called.

Record the convention rather than invent one: an object-bound aggregate returns
rows keyed by the RAW FIELD NAMES — `groupBy` for the category column, `field`
for the value column, the literal `count` for a fieldless count, plus
`<field>__comparison` under a comparison overlay. The deliberate opposite of
the dataset path, whose rows are keyed by the declared measure name.

spec: ChartAggregateSchema/ChartGroupBySchema/ChartAggregateFunctionSchema
replace the '{ field, function, groupBy }' description string and reject a
non-count function with no field; chart-aggregate.ts records the convention
and exports chartAggregateCategoryKey/chartAggregateValueKey/
chartAggregateResultKeys. The <ObjectChart> contract now names the props the
block actually reads (chartType/xAxisKey/series join the overlay; ChartConfig's
inert xAxis/yAxis/series leave dataProps per ADR-0078).

lint: validate-react-page-props now reads attribute VALUES for <ObjectChart> —
react-chart-field-unknown, react-chart-aggregate-invalid,
react-chart-axis-unknown, react-chart-axis-inert. Static literals only; state-
driven props, spreads, inline data, and cross-package objects are skipped.

The end state remains runtime-honors-ChartConfig: tracked in #3729 (framework
contract/lint flip) + objectui#2880 (renderer implementation), after which the
overlay dialect retires.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants