Skip to content

feat(spec): let an inline lookup action param declare its reference target (#3405) - #3406

Merged
os-zhuang merged 2 commits into
mainfrom
fix/action-param-inline-lookup-3405
Jul 22, 2026
Merged

feat(spec): let an inline lookup action param declare its reference target (#3405)#3406
os-zhuang merged 2 commits into
mainfrom
fix/action-param-inline-lookup-3405

Conversation

@baozhoutao

@baozhoutaobaozhoutao commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Closes part 1 + 2 of #3405. Companion UI change: objectstack-ai/objectui#2786

问题

ActionParamSchema 没有任何键可以指定内联 record-picker 参数要搜哪个对象。作者很自然地写了字段 schema 同名的键:

params: [{name: 'inspector',label: '质检员',type: 'lookup',reference: 'sys_user',required: true},]

schema 是 .stripreference 作为未知键被静默丢弃,不报错。下游的动作参数弹窗看到一个没有目标的 picker,降级成「粘贴记录 ID(UUID)」文本框。作者写对了,配置被吃掉了,用户拿到一个真人没法用的控件。

真机场景(PLAT-DEF-005,天顺 EHR):质检主管点【指派】/【转派】要选质检员,得先跑到别处把人的 UUID 复制出来再粘回去。同一套引用字段在新建/编辑弹窗里是正常的搜索式选择器。

讽刺的是 schema 里已经有一段「Widget config for inline params」的注释,把 multiple / accept / maxSize 专门补给了内联参数 —— 唯独漏了 picker 需要的那一个。file/image 类内联参数能配全,lookup 配不全。

改动

  • ActionParamSchema 增加 reference,键名对齐 FieldSchema.reference,让同一个拼法在两处都成立 —— 租户已经写出来的代码原样就合法,不需要为平台再学第二种拼法。位置就放在现有的内联 widget config 那一组。
  • 内联 lookup / master_detailreference → 解析期报错,错误 path 指向 reference,message 同时给出两条出路(补 reference 或改成字段引用式)。挂在现有 .refine() 链上。
    字段引用式参数不受影响 —— 它从被引用字段的元数据继承目标,那在解析期不可见。
  • app-showcase 的动作参数 gallery 补上以前表达不出来的内联 picker 标本。

验证

objectstack validate(app-showcase):

  • 删掉 reference✗ ["reference"] ActionParam with type "lookup"/"master_detail" requires "reference" (the target object) when declared inline …
  • 加回去 → ✓ Validation passed

真机 UI(framework showcase :5311 + objectui console 指过去):GET /api/v1/meta/objects/showcase_field_zoop_account 带上了 reference: showcase_account;弹窗渲染出搜索式选择器,GET /api/v1/data/showcase_account?top=50&search=华宁 服务端过滤命中,选中后按记录 ID 回填。全程不接触 UUID。

@objectstack/spec 全量测试:256 文件 / 6822 用例通过。

破坏性说明

内联 lookup 参数漏配 reference 的既有元数据,之前是静默降级成文本框,现在会在 validate / 解析期报错。这正是本 PR 的意图 —— 那些参数本来就是坏的,只是坏得没声音。修法就是错误信息里给的两条。

🤖 Generated with Claude Code

@vercel

vercelBot commented Jul 22, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 22, 2026 8:41pm

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests protocol:ui tooling size/s labels Jul 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

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

  • content/docs/ai/agents.mdx(via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx(via @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/spec)
  • content/docs/api/environment-routing.mdx(via @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/spec)
  • content/docs/automation/approvals.mdx(via packages/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via packages/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/cli.mdx(via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via packages/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via packages/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/spec)
  • content/docs/permissions/authorization.mdx(via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via @objectstack/spec)
  • content/docs/permissions/positions.mdx(via @objectstack/spec)
  • content/docs/permissions/rls.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx(via @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v16.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/spec)
  • content/docs/ui/actions.mdx(via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)
  • content/docs/ui/translations.mdx(via @objectstack/spec)
  • content/docs/ui/views.mdx(via @objectstack/spec)

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.

… target (#3405)
`ActionParamSchema` had no way to name the object an inline record-picker param
should search. Authors reasonably wrote the same key the field schema uses —
`{ name: 'inspector', type: 'lookup', reference: 'sys_user' }` — and the schema
stripped it as an unknown key, silently. Downstream the param dialog saw a
picker with no target and degraded it to a "paste the record id (UUID)" text
input, so the authored intent was dropped and the user was handed a control a
human cannot reasonably operate (found on a QC dispatch assign/transfer action).
- Added `reference` to `ActionParamSchema`, spelled to match
`FieldSchema.reference` so one spelling works in both places. It joins the
existing inline widget config (`multiple` / `accept` / `maxSize`), which had
covered the file/image params but not the picker ones.
- A `lookup` / `master_detail` param declared inline with no `reference` is now
a parse-time error pointing at the missing key, instead of degrading at
render time. Field-backed params are unaffected: they inherit the target from
the referenced field's metadata, which is not visible at parse time.
- app-showcase's action-param gallery gains the inline picker specimen it could
not previously express, next to the other widget types.
Verified: `objectstack validate` on app-showcase fails with the new message when
`reference` is removed and passes with it, and the param renders a working
searchable picker in the console (see objectui's companion change).
Refs #3405
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang pushed a commit that referenced this pull request Jul 22, 2026
The `ui/action` reference doc regeneration is generated output and releases
nothing on its own — the `@objectstack/spec` release is declared by the
changeset in #3406. This empty-frontmatter changeset satisfies the
"Check Changeset" gate, which diffs added .changeset files against this PR's
base (the #3406 head) and otherwise sees none.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011W4a1eVS3aaYVRxKut68hK
…ce` (#3410)
Regenerates content/docs/references/ui/action.mdx so it reflects the new
`reference` key on ActionParamSchema, fixing the check:docs CI step on #3406.
Includes an empty changeset (docs-only regeneration releases nothing).
@os-zhuang
os-zhuang merged commit d99aeb3 into mainJul 22, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the fix/action-param-inline-lookup-3405 branch July 22, 2026 20:46
os-zhuang added a commit that referenced this pull request Jul 24, 2026
…t (spec-owned), typed action handlers, file-as-reference (#3412)
* docs(adr): ADR-0104 — field runtime value-shape as a first-class contract
Design for three coupled gaps with one root cause (the runtime value shape
of a field is nobody's contract):
- D1: spec-owned valueSchemaFor(field) + semantic type classes; converge the
four hand-duplicated type lists (record-validator, import-coerce,
driver-sql, verify); reconcile the three dead value-schema exports; wire
the field-zoo oracle to the contract.
- D2: typed action handlers — declared params validated at REST/MCP dispatch,
typed ctx.params via FieldValue<T>, file params become fileId references.
- D3: file-as-reference — Field.file/image values store sys_file ids instead
of inline {url} blobs, joining the existing lifecycle/GC/authz machinery.
- D4: three-phase rollout (non-breaking convergence -> param enforcement ->
protocol-major file migration).
Generalizes the #3405/#3406 silently-stripped-param incident; relates #3407,
#1878/#1891.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SHpGw3GBA9aFpfwVArRWfd
* chore: empty changeset — ADR-0104 is a design doc, releases nothing
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SHpGw3GBA9aFpfwVArRWfd
* docs(adr): ADR-0104 — add risks/migration-hazards and performance-budget sections
Nine named risks (legacy-row stranding, warn-first rollout, unknown-key
allowlist, GC freeze during migration, public-posture inventory, sub-key
read breakage, external-URL retirement, cross-repo sequencing, spec purity)
and the performance budgets/benchmark gates for each phase, so the phase
PRs inherit them as acceptance items.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SHpGw3GBA9aFpfwVArRWfd
* docs(adr): ADR-0104 accepted — implementation staged per D4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SHpGw3GBA9aFpfwVArRWfd
* chore: retrigger CI after GitHub Actions hosted-runner incident
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SHpGw3GBA9aFpfwVArRWfd
---------
Co-authored-by: Claude <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jul 28, 2026
…ng them (#3405) (#3746)
* feat(spec): reject unknown keys on an action param instead of stripping them (#3405)
Closes part 3 of #3405 — the item deferred out of #3406 as "evaluate
separately".
Parts 1 and 2 gave an inline record-picker param a `reference` key and made a
targetless one a parse error. That fixed the symptom. The mechanism that caused
it stayed: `ActionParamSchema` was zod-default `.strip`, so any key it does not
declare was discarded silently and the param went on parsing. An author wrote a
correct, clearly intended `reference: 'sys_user'`, the key was eaten, and the
dialog rendered a text box asking a human to paste a UUID — no error anywhere.
The next mis-spelled key would have failed the same way, just as quietly.
An action param is now `.strict()`, with an error map that makes the rejection
fixable rather than merely loud:
- Case/underscore slips (`help_text` → `helpText`, `default_value` →
`defaultValue`) resolve through the shared `findClosestMatches`, bounded by
the same length-relative distance `suggestKey` uses in `data/object.zod.ts` —
a flat distance of 3 suggests `visible` for `wibble`.
- Semantic near-misses edit distance cannot reach are named explicitly, in the
`FIELD_TYPE_ALIASES` style: `reference_to` / `referenceTo` / `targetObject` →
`reference` (the runtime field shape spells it the first way, objectui's
resolved param the second), and `visibleWhen` / `visibleOn` / `visibility` →
`visible`. That last one is why this matters beyond typos: ADR-0089 made
`visibleWhen` canonical on view/page schemas, so borrowing it here used to
strip a param's capability gate and render it unconditionally.
Follows ADR-0078 (no-silently-inert-metadata) and ADR-0049 (enforce-or-remove),
and matches the precedent set by ADR-0089 D3a for the view/page schemas.
Verification: spec 258 files / 6716 tests pass; `tsc --noEmit` clean;
app-showcase, app-crm and app-todo all `validate` clean — no existing metadata
in the repo carried an undeclared param key. Planting `visibleWhen` on
showcase's inline picker param reproduces the new error with the
`visibleWhen` → `visible` prescription, and removing it validates again.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LkvDs4y4gveyB5NJZKxaZt
* fix(spec): keep the schema's own JSDoc first in action.zod.ts (#3405)
`scripts/build-docs.ts` `getFileDescription()` takes the FIRST `/** */` block
in a module, verbatim, as the description of its generated reference page.
Placing the new `ACTION_PARAM_KEYS` / error-map helpers above the "Action
Parameter Schema" JSDoc therefore replaced the public authoring guide on
`content/docs/references/ui/action.mdx` with an internal note about why a key
list is kept beside the schema — which is what `check:docs` caught.
Moved the helpers back below that JSDoc (after the `lazySchema` import, where
they were originally). The generated doc is byte-identical to main again:
`check:docs` reports 250 generated files in sync.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LkvDs4y4gveyB5NJZKxaZt
* fix(spec): regenerate skill references, keep the error map internal (#3405)
Two more generated-artifact gates behind `check:docs`, both tripped by the same
commit:
- `check:skill-refs` — importing `shared/suggestions.zod.ts` from
`ui/action.zod.ts` pulls it into the transitive reference set of the
objectstack-data / -ui / -platform skills. Regenerated via `gen:skill-refs`;
the diff is the one expected line per skill, and `action.zod.ts` still
resolves to "Action Parameter Schema", confirming the JSDoc-order fix held.
- `check:api-surface` — `actionParamUnknownKeyError` was exported, which added
it to the package's public API. It has no caller outside its own module, so
the export was unnecessary: unlike `strictVisibilityError`, which is shared
across the view/page schemas, this map is wired into exactly one schema.
Made it module-private; the public API surface is now unchanged by this PR.
All ten `check:*` gates in packages/spec pass locally, alongside 258 files /
6716 tests and a clean `tsc --noEmit`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LkvDs4y4gveyB5NJZKxaZt
---------
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:uisize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@baozhoutao@os-zhuang