Skip to content

fix(app-shell): the Studio canvas runs a report or widget over a draft dataset before the package publishes - #27758

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-fbc3or
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-fbc3or

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

Requested by Bill · project thread

Fixes #27585

Clause-②: yes (widening)
Metadata-protocol: none

维护者速读

  • 改了什么:Studio 画布上的报表与仪表盘组件查询数据集时带上草稿标记,草稿数据集在包发布前就能出数;queryDataset 新增可选参数 previewDrafts。
  • 为什么改:作者在 Studio 新建数据集并绑定报表或图表后,画布提示「数据集在此环境中未定义」,直到发布才显示,而旁边的选择器早已能读到这份草稿。
  • 风险与回滚:只影响 Studio 内的报表/仪表盘预览;控制台运行时和 Setup 编辑器的请求一字节不变。回滚即还原本 PR。
  • 先看:canvasDataSource.ts(画布数据源)、data-objectstack/src/index.ts 的 queryDataset 参数一段、canvasDataSource-27585.test.tsx。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/metadata-admin/previews/canvasDataSource-27585.test.tsx,预期 5 个测试全过。
  • 需要你做的:无,席位落地。

What changes

The defect. In the Studio, a report or a dashboard widget bound to a dataset that is still a draft said the dataset "is not defined in this environment" until the package published. queryDataset decided the draft world only from the page URL's ?preview=draft, and the Studio canvas is a draft window that does not carry that flag, so its query went to the published world, where a draft-only dataset does not exist. The dataset pickers beside the canvas already read the draft-overlaid catalog (#27518).

@object-ui/data-objectstack: queryDataset(dataset, selection, options?). A new optional { previewDrafts?: boolean }. true puts previewDrafts: true on the one dataset-query request, exactly what the URL flag already put there; the server then resolves the dataset from the draft overlay. It is the same explicit option listViews(objectName, { previewDrafts }) takes. Absent, the page URL decides as before; given, it wins over the URL. A caller that passes nothing sends a byte-identical request.

@object-ui/app-shell: the canvas data source. ReportPreview and DashboardPreview render through useCanvasDataSource() (new, internal, previews/canvasDataSource.ts). In the Studio, keyed on the signal the dataset pickers read (StudioDatasetAuthoringContext, provided by the Interfaces pillar around the canvas), it hands the runtime renderers the adapter with its queryDataset asking for previewDrafts; every other member is the adapter's own, bound to it. Outside the Studio (Setup's editors) it hands back the adapter unchanged. The shape follows the Form tab preview's data source in StudioDesignSurface.tsx (formPreviewSource): the preview hands the runtime renderer a data source rather than threading a flag through plugin-report and plugin-dashboard, so every dataset query those renderers make (the report, a dataset widget, a dataset-bound chart, a filter bar's options) carries it with no change in either plugin. One source per adapter is kept in a module WeakMap, so its identity is the adapter's and never a useMemo result (ui/AGENTS.md #10).

The ruling. "⛔ The canvas resolves draft datasets the same way the draft preview resolves other draft metadata. It does not add a second lookup." The draft preview resolves draft metadata with one request-level flag the server honours (?preview=draft on /meta, previewDrafts on the dataset query). The canvas now sends that same flag on the same request. The dataset is still named and only the server resolves it: no client-side read of the draft definition, no inline post of it.

Measurements (the order's mechanism assumptions)

  • (a) The server's dataset door honours the flag for a draft-only dataset: yes (code read, not run live). packages/rest/src/analytics-routes.ts, the POST /analytics/dataset/query handler: previewDrafts is body.previewDrafts === true or ?preview=draft, admitted only for a caller mayReadPendingDrafts admits; the by-name lookup reads getMetaItems({ type: 'dataset', previewDrafts }), the draft-overlaid list in which draft-only items surface, then hands previewDrafts on to svc.queryDataset. It is the one owner of that path in the tree. No server file changes.
  • (b) The canvas's draft signal. useStudioDatasetAuthoring() (studio-design/datasetCreate.tsx), the context the Interfaces pillar provides around both the inspector and the canvas. DashboardWidgetInspector and ReportDefaultInspector read it to ask useDatasetCatalog for previewDrafts since Studio cannot report or chart on the package's own objects: the report and dashboard-widget dataset pickers list only pre-existing sys_/showcase_ datasets, and Studio offers no way to create a dataset #27518; the canvas previews now read the same one.
  • (c) The console runtime stays published. DashboardView and ReportView render with the plain adapter and pass no option, so queryDataset still follows the page URL only: no flag on a runtime page, the flag inside the app draft preview, as before.

Tests

  • ui/packages/app-shell/src/views/metadata-admin/previews/canvasDataSource-27585.test.tsx (new). Through the real ObjectStackAdapter (fetch stubbed) and the real plugin-report / plugin-dashboard renderers. The stub server answers the dataset by name only when the request carries previewDrafts: true, and otherwise answers the door's own 404 NOT_FOUND, which is what the door does for a draft-only dataset.
    • In the Studio context, the report preview and the dashboard preview post { datasetName: 'draft_sales', previewDrafts: true } on every dataset query and draw the draft's rows.
    • Without the context, the same previews post no previewDrafts and draw no rows.
    • datasetDraftsSource is one source per adapter, binds every other member to the adapter, and merges previewDrafts into a caller's own options.
  • ui/packages/data-objectstack/src/queryDataset.test.ts, new block "the draft world (objectstack#27585)": the option with no URL flag sends previewDrafts: true; neither sends nothing; the URL flag alone still sends it; an explicit previewDrafts: false wins over the URL flag.
Targeted run, ablations and type-check (head 27e684e, before the main merge)
  • pnpm --dir ui exec vitest run --reporter=verbose packages/data-objectstack/src/queryDataset.test.ts packages/app-shell/src/views/metadata-admin/previews/canvasDataSource-27585.test.tsx packages/app-shell/src/views/metadata-admin/previews/ReportPreview.dataset.test.tsx packages/app-shell/src/views/metadata-admin/previews/DashboardPreview.loneWidget.test.tsx packages/app-shell/src/views/metadata-admin/previews/DashboardPreview.i18nTitle-10847.test.tsx · VERDICT command-exit 0 · Test Files 5 passed (5) · Tests 56 passed (56). The three existing preview test files are unchanged and green: outside the Studio the previews call queryDataset(name, selection) exactly as before.
  • Ablation 1, through scripts/ablation-replace.mjs: canvasDataSource.ts anchor return inStudio && adapter replaced with a marker that always hands back the adapter (anchor 1 to 0, blob a3c4bd40de49 to 3f04dfb10d35). The two Studio tests went red (Tests 2 failed, 3 passed); restored to blob a3c4bd40de49.
  • Ablation 2: index.ts anchor const previewDrafts = options?.previewDrafts !== undefined replaced with a marker that ignores the option (blob a258fe17a1ec to 36c53a39804e). Red: the two option tests in queryDataset.test.ts and the two Studio tests (Tests 4 failed, 36 passed); restored to blob a258fe17a1ec.
  • pnpm --filter @object-ui/data-objectstack run type-check && pnpm --filter @object-ui/app-shell run type-check (the latter also compiles the tests through tsconfig.test.json) · VERDICT command-exit 0.
  • The ui vitest resolves @object-ui/* to src, so neither ablation needed a dist build.

Acceptance notes

  • Who gets drafts. The server serves the draft world only to a caller it admits to drafts (mayReadPendingDrafts); anyone else gets the published dataset, as if the flag were absent. A Studio author is admitted by construction.
  • Not in this PR, and not the same option. The card's "related, code-read only" note (Setup's dataset designer field catalog, useDatasetFields, reading objects without the draft overlay) is a metadata read, not a dataset query; this option does not reach it. Left as the card noted it.
  • Overlap. feat(rest,console): one request opens a record page #27713 also edits ui/packages/data-objectstack/src/index.ts (the record-page open request, another region). This diff in that file is the queryDataset docblock and its option only; whichever lands second rebases.
  • Assignee and reviewers. None set by this PR's author, by the dispatch rule: the seat asks once its review is in.

Verification

Derived gates (dispatch-gates --commands, on the merged head)

Head 41685152 (the main merge). Each line is the command and the exit code captured before any pipe.

node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0

node scripts/pm/dispatch-gates.mjs --ran reconciliation: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN.

After the merge, on the same head: the six dataset/preview test files (the five above plus queryDataset.forbidden-10899.test.ts) · VERDICT command-exit 0 · Test Files 6 passed (6) · Tests 59 passed (59); eslint over the six touched source and test files: 0 errors (the warnings are pre-existing ones in the edited files; the two new files carry none).

CI runs every other family on this PR; its check-runs are the record.


Generated by Claude Code

…dget before the package publishes (#27585)

`queryDataset` takes an explicit `previewDrafts` option (the caller's answer
wins over the page URL), and the Studio's report and dashboard previews hand
the renderers a data source whose dataset queries carry it, keyed on the same
signal the dataset pickers read.

Claude-Session: https://claude.ai/code/session_01Utp6CYWA2yJcJwFWLpBa7Q
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 41685152df3bb709cf8afd0e25240ba301143952
Local-runs: none

Face: the Clause-②: yes (widening) declaration alone (PR body, changeset, and the claim 6509080036, which expected it). None of the seven files is under packages/spec/** or a governed path, so no CONTRACT_REVIEW_TIER face applies.

① Derived judgments

  • Widened, correctly declared: @object-ui/data-objectstack (published; no private) exports ObjectStackAdapter, whose public queryDataset(dataset, selection) gains a third optional parameter options?: { previewDrafts?: boolean }.
    • The surface grows, so yes (widening) is the honest line.
    • Nothing becomes required. A caller that passes nothing sends a byte-identical request: absent ⇒ the page URL's ?preview=draft decides, as before. That is pinned in queryDataset.test.ts.
    • The DataSource interface's narrower signature stays assignable, because an extra optional parameter is compatible.
  • Precedence is stated and pinned: an explicit previewDrafts wins over the URL in both directions (false keeps the published world even under ?preview=draft). This mirrors the existing listViews(objectName, { previewDrafts }) option on the same class, so it adds no new idiom.
  • Not widened: canvasDataSource.ts (datasetDraftsSource, useCanvasDataSource) is internal to @object-ui/app-shell, which is private. It is not exported from the package root, which the header states.
  • One lookup, as triage requires ("does not add a second lookup"): the canvas still names the dataset and the server resolves it from the draft overlay. The server door was measured by code read (analytics-routes.ts: body.previewDrafts or ?preview=draft, gated by mayReadPendingDrafts, then getMetaItems({ type: 'dataset', previewDrafts })). A caller who may not read drafts gets the published dataset, so the option grants nothing the server does not already gate.
  • Proxy soundness (seat read):
    • The draft source is one Proxy per adapter in a WeakMap, so its identity is stable across renders and fetches keyed on the data source do not refire.
    • Every other method is bind-ed to the real adapter, so private state and caches stay the adapter's.
    • No consumer in plugin-dashboard, plugin-report or plugin-charts keys an effect on a method's identity (seat grep at origin/main).
  • Metadata-protocol: none holds: nothing an author writes changes.

② Semver level

patch on @object-ui/data-objectstack and @object-ui/app-shell, following the repo's rule that the level follows Metadata-protocol: (kind none ⇒ patch). The changeset names the new optional parameter, its absent-case behaviour and its precedence.

③ Boundary flags

  • No open_questions. The dev reported premise valid. The useDatasetFields note on the card is not reached by this option and is left alone, as the order allowed.
  • Server measured by code read, not live. That is acceptable: no server file changes, and the server answers the option exactly as it already answers ?preview=draft.
  • Overlap: the spec seat's draft PR feat(rest,console): one request opens a record page #27713 edits another region of data-objectstack/src/index.ts. Whichever lands second rebases.
  • The PR author is the relay (objectstack-fleet[bot]).

Implemented-by: claude/project-thread-fbc3or
Reviewed-by: session_01Utp6CY2n1JmZZKrHgtp9jC

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

席位核对

ACCEPT · head 41685152df3bb709cf8afd0e25240ba301143952 · 2026-10-29T22:51Z

  1. Shape: draft → main, Fixes #27585, attribution block on top. Seven files:
    • data-objectstack/src/index.ts (queryDataset only) and queryDataset.test.ts;
    • app-shell previews/{ReportPreview,DashboardPreview}.tsx, the new internal canvasDataSource.ts and its test;
    • one changeset.
      No assignee, no reviewer. No packages/spec/**, no server file.
  2. Premise measured:
  3. Gates (check-runs on this head): 44 success, 11 skipped, 0 red.
    • Named runs: Lint & Repo Gates 123774371346, TypeScript Type Check 123773662655, Test UI 123777735309, Lint · PM dispatch-gates 123771543775.
    • The dev's derived gates: 35/35 run, 0 NOT-MEASURED. Targeted 59/59 on the merged head. Two ablations went red and were restored.
  4. Contract review: owed for Clause-②: yes (widening) (the new optional parameter on the published ObjectStackAdapter.queryDataset). TIER_DEFAULT declaration review PASS on this head: comment 6509997108. Metadata-protocol: none.
  5. Accepted deviations / out of scope:
    • The card's "Related" note (useDatasetFields without the draft overlay) is not reached by this option and stays unfiled, as the order allowed.
    • The server was measured by code read, not live; the review accepts that.
    • Overlap: the spec seat's draft PR feat(rest,console): one request opens a record page #27713 edits another region of data-objectstack/src/index.ts. Whichever lands second rebases.
  6. Landing: not governed (check-governed-merges --pr 27758: NOT governed), +316 / −13 < 3000. Not Tier H, so no reviewer request. The seat lands it: ready + auto-merge now.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 29, 2026 22:52
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 29, 2026 22:52
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 29, 2026
Merged via the queue into main with commit 968cacf Oct 29, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-fbc3or branch October 29, 2026 23:09
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/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Studio report/widget canvas says a draft dataset "is not defined in this environment" until the package publishes

1 participant