Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/24796-list-view-binding-explanations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@objectstack/lint': patch
---

docs(lint): `os explain` covers `list-view-object-unknown` and `list-view-object-outside-closure`, and the `packageScope` docblock says what a whole-collection miss now means

Clause-②: no (no verdict, severity, accept set or served body moves: explanation text and comments only)

- **`os explain list-view-object-outside-closure`** explains the package closure a write into a package is judged against (the package's own objects, its declared `dependencies`' objects, platform objects and provenance-less tenant objects), why a list view bound outside it is refused, and the remedy: declare the dependency, or bind the view inside the closure.
- **`os explain list-view-object-unknown`** explains why a write that names no package can say an object exists nowhere (it is judged against the whole collection the deployment serves), why an empty collection is not judged, and why `os validate` / `os build` / `os lint` still skip the binding.
- **`os explain list-view-field-unknown`** no longer says an unresolvable binding always skips the list view: it does on the CLI, and the runtime save door refuses it under one of the two ids above.
- **`AuthoringRuleContext.packageScope`** (in the published `.d.ts`) no longer says an object missing from a whole snapshot "may come from another package": that is true only on the CLI. At the runtime door with no closure in force the snapshot is the whole served collection, and such a binding is refused `list-view-object-unknown`.
11 changes: 9 additions & 2 deletions packages/lint/src/authoring-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,8 +382,15 @@ export interface AuthoringRuleContext {
* force; ABSENT on the three CLI commands and on a write that names no
* package, whose snapshots carry the whole collection. It is what lets a
* rule tell "outside what this package declared it may reference" from
* "not in this stack": an object missing from a narrowed snapshot is the
* first, one missing from a whole one may come from another package. Read
* "exists nowhere": an object missing from a narrowed snapshot is the
* first. What an object missing from a whole snapshot means depends on who
* built it. On the CLI the stack is one app, so the object may come from a
* dependency package the stack does not carry, and "exists nowhere" cannot
* be decided. At the runtime door with no closure in force the snapshot is
* the whole collection the host serves, so the object exists nowhere, and
* [#24668] a list view bound to it is refused `list-view-object-unknown`
* (the reference-integrity suite reads a set `runtimeWriteType` beside an
* absent `packageScope` as that whole collection). Read
* by two rules: `validateListViewFieldRefs` (through the reference-integrity
* suite), which refuses a list view bound outside the closure instead of
* skipping it, and [#24669] `validateFlowTriggerReadiness`, which refuses a
Expand Down
64 changes: 63 additions & 1 deletion packages/lint/src/rule-explanations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3142,7 +3142,9 @@ const LIST_VIEW_FIELD_UNKNOWN_EXPLANATION: RuleExplanation = {
'view bound to a provider other than `object`, and the positions other rules own: `sort` ' +
'(`sort-field-unknown`), `searchableFields` (`searchable-field-unknown`), the `chart` ' +
'bindings, action names, `conditionalFormatting` predicates and `addRecord.formView`. An ' +
'unresolvable `data.object` skips the whole list view, so one typo yields one finding.',
'unresolvable binding skips the whole list view on `os validate`, `os build` and `os lint`, so one ' +
'typo yields one finding; the runtime save door refuses it once instead, as `list-view-object-unknown` ' +
'or, for a write into a package, `list-view-object-outside-closure`.',
],
};

Expand Down Expand Up @@ -3176,6 +3178,64 @@ const LIST_VIEW_FIELD_DOTTED_EXPLANATION: RuleExplanation = {
],
};

/** Which keys a list view's binding is read from, and what stays unjudged (both binding ids write it out). */
const LIST_VIEW_BINDING_SCOPE =
'One finding per list view, at the key the binding was read from: the view\'s `object`, or a list ' +
'view\'s own `data.object` where it sets one. The view\'s field references stay unjudged while the ' +
'binding does not resolve, so a wrong name is reported once per list view rather than once per ' +
'position. Not judged: a draft save, and a stored view until its next save — the door judges ' +
'writes only.';

const LIST_VIEW_OBJECT_OUTSIDE_CLOSURE_EXPLANATION: RuleExplanation = {
rule: 'list-view-object-outside-closure',
covers: 'what a package closure holds, and why the view refuses',
paragraphs: [
'A view written into a package at the runtime save door is judged against that package\'s ' +
'closure, never the whole deployment: the package\'s own objects, the objects of the packages its ' +
'manifest declares in `dependencies`, platform objects, and tenant-authored objects that carry no ' +
'package. The declared `dependencies` bound what the package MAY reference, so a list view bound ' +
'to an object outside that closure is not resolvable by declaration — whether the object belongs ' +
'to a package the manifest never declared or exists nowhere at all, because the closure is all ' +
'the door can see.',
'An error, so the publish is refused (`422`): it is the verdict the dataset door gives a base ' +
'object outside the same closure (`object-reference-unknown`). Judging the view\'s field ' +
'references against an object the package may not reference would judge the package against the ' +
'wrong set; declaring the dependency puts the object, and the field judgement with it, inside the ' +
'closure.',
LIST_VIEW_BINDING_SCOPE,
'To fix it, declare a dependency on the package that defines the object in the `dependencies` of ' +
'the written package\'s manifest, or bind the view to an object inside the closure; when the name ' +
'is a typo, the verdict names the nearest object in reach. A write that names no package is ' +
'judged against the whole collection instead (`list-view-object-unknown`), and `os validate`, ' +
'`os build` and `os lint` judge neither: a stack there is one app, and an unresolved binding may ' +
'be a dependency package\'s object the stack does not carry.',
],
};

const LIST_VIEW_OBJECT_UNKNOWN_EXPLANATION: RuleExplanation = {
rule: 'list-view-object-unknown',
covers: 'why the save door can say an object exists nowhere',
paragraphs: [
'A view written at the runtime save door with no package named (an overlay write) is judged ' +
'against the whole collection the deployment serves: every installed package\'s objects, enabled ' +
'or not, the platform\'s, and the objects saved at run time — the same merge the object metadata ' +
'listing answers from. Nothing narrows it, so a list view bound to an object missing from it is ' +
'bound to an object that exists nowhere: there is no other package for it to come from.',
'An error, so the publish is refused (`422`): left in, the view would be stored with none of its ' +
'field references checked and fail only when it renders. The nearest object name rides the ' +
'`fix:` line, never the verdict, so a stored view\'s verdict does not change when an unrelated ' +
'write creates an object with a similar name.',
LIST_VIEW_BINDING_SCOPE,
'To fix it, bind the view to an object that exists, or save the object before you publish the ' +
'view. Not judged: a write into a package, which is judged against that package\'s closure ' +
'instead (`list-view-object-outside-closure`); an empty collection, which means the host supplied ' +
'none, because no deployment serves zero objects; and `os validate`, `os build` and `os lint`, ' +
'where a stack is one app and an unresolved binding may be a dependency package\'s object the ' +
'stack does not carry, so the list view is skipped and the binding left to the object-reference ' +
'checks.',
],
};

// ── Translation bundles (`validate-translation-references.ts`, `validate-translatable-sections.ts`) ──

/** How a bundle key is read at all (the two reference ids write it out). */
Expand Down Expand Up @@ -4785,6 +4845,8 @@ export const RULE_EXPLANATIONS: Readonly<Record<string, RuleExplanation>> = Obje
[FILTER_EMPTY_NODE_EXPLANATION.rule]: FILTER_EMPTY_NODE_EXPLANATION,
[LIST_VIEW_FIELD_UNKNOWN_EXPLANATION.rule]: LIST_VIEW_FIELD_UNKNOWN_EXPLANATION,
[LIST_VIEW_FIELD_DOTTED_EXPLANATION.rule]: LIST_VIEW_FIELD_DOTTED_EXPLANATION,
[LIST_VIEW_OBJECT_OUTSIDE_CLOSURE_EXPLANATION.rule]: LIST_VIEW_OBJECT_OUTSIDE_CLOSURE_EXPLANATION,
[LIST_VIEW_OBJECT_UNKNOWN_EXPLANATION.rule]: LIST_VIEW_OBJECT_UNKNOWN_EXPLANATION,
[TRANSLATION_TARGET_UNKNOWN_EXPLANATION.rule]: TRANSLATION_TARGET_UNKNOWN_EXPLANATION,
[TRANSLATION_OPTION_KEY_UNKNOWN_EXPLANATION.rule]: TRANSLATION_OPTION_KEY_UNKNOWN_EXPLANATION,
[TRANSLATION_SECTION_NAME_MISSING_EXPLANATION.rule]: TRANSLATION_SECTION_NAME_MISSING_EXPLANATION,
Expand Down
5 changes: 4 additions & 1 deletion packages/lint/src/runtime-gate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1117,7 +1117,10 @@ export function runRuntimeAuthoringRules(args: {
// [#24563] The closure the snapshots' `objects` were narrowed to, stated
// exactly when the builder above narrowed them (the same predicate), so a
// rule can tell an object outside the package's declared reach from one
// a whole-collection snapshot merely does not carry.
// that exists nowhere. [#24668] Absent here, the snapshots carry the whole
// collection the host serves, so an object missing from them exists
// nowhere in this deployment; only the CLI, whose stack is one app, still
// cannot decide that.
...(packageScopeInForce(args.packageScope) ? { packageScope: args.packageScope } : {}),
// [#20611] Spelled against the CANDIDATE, the one snapshot that holds the
// written item. The baseline pass shares the set and cannot match it: the
Expand Down
17 changes: 17 additions & 0 deletions packages/lint/src/validate-list-view-field-refs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
validateListViewFieldRefs as validateListViewFieldRefsUnrecorded,
LIST_VIEW_FIELD_UNKNOWN,
LIST_VIEW_FIELD_DOTTED,
LIST_VIEW_OBJECT_OUTSIDE_CLOSURE,
LIST_VIEW_OBJECT_UNKNOWN,
listViewWalkedPositions,
type ListViewFieldRefFinding,
} from './validate-list-view-field-refs.js';
Expand Down Expand Up @@ -1089,6 +1091,21 @@ describe('[#22161] one-line verdicts — list-view-field-unknown, list-view-fiel
'`gantt.quickFilters[].field`',
'`gantt.tooltipFields[]`',
],
// The two binding verdicts carry their reasoning only here: the closure's
// limbs and which door judges which snapshot.
[LIST_VIEW_OBJECT_OUTSIDE_CLOSURE]: [
'declares in `dependencies`',
'platform objects',
'`object-reference-unknown`',
'`list-view-object-unknown`',
'a draft save',
],
[LIST_VIEW_OBJECT_UNKNOWN]: [
'the whole collection the deployment serves',
'no deployment serves zero objects',
'`list-view-object-outside-closure`',
'a draft save',
],
};
for (const [rule, list] of Object.entries(facts)) {
const entry = explainRule(rule);
Expand Down
Loading