@formicoidea/labre-core 0.36.0 → 0.38.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/_pkgs/affine-block-frame/frame-manager.d.ts +31 -0
  2. package/dist/_pkgs/affine-block-frame/frame-manager.js +62 -2
  3. package/dist/_pkgs/affine-block-frame/frame-tool.js +6 -2
  4. package/dist/_pkgs/affine-block-surface/extensions/audit.js +12 -34
  5. package/dist/_pkgs/affine-block-surface/extensions/reading-widget.d.ts +25 -13
  6. package/dist/_pkgs/affine-block-surface/extensions/reading-widget.js +58 -24
  7. package/dist/_pkgs/affine-block-surface/extensions/reading.d.ts +83 -7
  8. package/dist/_pkgs/affine-block-surface/extensions/reading.js +51 -42
  9. package/dist/_pkgs/affine-block-surface/extensions/validation.d.ts +321 -16
  10. package/dist/_pkgs/affine-block-surface/extensions/validation.js +945 -62
  11. package/dist/_pkgs/affine-block-surface/framework-background/facts.d.ts +24 -0
  12. package/dist/_pkgs/affine-block-surface/framework-background/facts.js +31 -0
  13. package/dist/_pkgs/affine-block-surface/framework-background/index.d.ts +1 -0
  14. package/dist/_pkgs/affine-block-surface/framework-background/index.js +1 -0
  15. package/dist/_pkgs/affine-block-surface/framework-background/stacking.d.ts +131 -0
  16. package/dist/_pkgs/affine-block-surface/framework-background/stacking.js +184 -0
  17. package/dist/_pkgs/affine-block-surface/utils/sort.d.ts +15 -1
  18. package/dist/_pkgs/affine-block-surface/utils/sort.js +30 -31
  19. package/dist/_pkgs/affine-block-surface/view.js +5 -0
  20. package/dist/_pkgs/affine-gfx-group/element-renderer/utils.js +3 -1
  21. package/dist/_pkgs/affine-gfx-template/index.d.ts +1 -0
  22. package/dist/_pkgs/affine-gfx-template/index.js +1 -0
  23. package/dist/_pkgs/affine-gfx-template/make-snapshot.d.ts +16 -2
  24. package/dist/_pkgs/affine-gfx-template/make-snapshot.js +43 -4
  25. package/dist/_pkgs/affine-gfx-template/services/template-middlewares.js +1 -0
  26. package/dist/_pkgs/affine-gfx-template/services/template.d.ts +6 -0
  27. package/dist/_pkgs/affine-gfx-template/services/template.js +6 -0
  28. package/dist/_pkgs/affine-gfx-template/snapshot-from-action.d.ts +20 -0
  29. package/dist/_pkgs/affine-gfx-template/snapshot-from-action.js +205 -0
  30. package/dist/_pkgs/affine-gfx-template/toolbar/template-panel.js +4 -0
  31. package/dist/_pkgs/affine-gfx-template/toolbar/template-type.d.ts +13 -0
  32. package/dist/_pkgs/affine-inline-link/link-node/configs/toolbar.d.ts +11 -17
  33. package/dist/_pkgs/affine-inline-link/link-node/configs/toolbar.js +55 -14
  34. package/dist/_pkgs/affine-model/elements/cynefin/cynefin.d.ts +9 -20
  35. package/dist/_pkgs/affine-model/elements/cynefin/cynefin.js +7 -28
  36. package/dist/_pkgs/affine-model/elements/edgy/board.d.ts +9 -21
  37. package/dist/_pkgs/affine-model/elements/edgy/board.js +8 -29
  38. package/dist/_pkgs/affine-model/elements/edgy/facets.d.ts +14 -28
  39. package/dist/_pkgs/affine-model/elements/edgy/facets.js +12 -36
  40. package/dist/_pkgs/affine-model/elements/estuarine/estuarine.d.ts +9 -19
  41. package/dist/_pkgs/affine-model/elements/estuarine/estuarine.js +7 -27
  42. package/dist/_pkgs/affine-shared/services/link-preview-service/link-preview-service.d.ts +28 -0
  43. package/dist/_pkgs/affine-shared/services/link-preview-service/link-preview-service.js +20 -3
  44. package/dist/translations.js +8 -2
  45. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { getUniverseRegistry, hostLanguage, queryPivotProperties, } from '../../affine-shared/services/index.js';
1
+ import { getUniverseRegistry, hostLanguage, queryPivotProperties, translateKey, } from '../../affine-shared/services/index.js';
2
2
  import { createIdentifier } from '../../global/di/index.js';
3
3
  import { LifeCycleWatcher, } from '../../std/index.js';
4
4
  import { GfxControllerIdentifier, GfxPrimitiveElementModel, readElementTags, resolvePivotBinding, roleIsA, } from '../../std/gfx/index.js';
@@ -8,7 +8,7 @@ import { effect, signal } from '@preact/signals-core';
8
8
  // reading is a one-way read of pure declaration data that must never pull a
9
9
  // canvas in.
10
10
  import { backgroundPlot, } from '../framework-background/def.js';
11
- import { backgroundTransitionBands } from '../framework-background/facts.js';
11
+ import { backgroundTransitionBands, containingFrame, } from '../framework-background/facts.js';
12
12
  /** Deepest role chain reported by {@link ReadingNodeType.specialises}. */
13
13
  const MAX_ROLE_DEPTH = 32;
14
14
  /** Numeric slack on the two geometry comparisons. Model units. */
@@ -127,7 +127,7 @@ const centreOf = (bound) => [
127
127
  bound.y + bound.h / 2,
128
128
  ];
129
129
  /**
130
- * The frame the subject sits ON — containment of its CENTRE, and nothing else.
130
+ * The frame the subject sits ON — the one that WHOLLY CONTAINS it (PF2.4).
131
131
  *
132
132
  * Deliberately not the "nearest map" attribution the validation engine uses: a
133
133
  * finding has to be filed against some map even when the subject is off in the
@@ -135,40 +135,22 @@ const centreOf = (bound) => [
135
135
  * invented fact. A component beside the map has no phase, and the panel says
136
136
  * nothing rather than something plausible.
137
137
  *
138
- * The centre, not the whole bound: a node overhanging the edge of the map is
139
- * still on it to every reader.
138
+ * The whole bound and not the centre: a node overhanging the edge of the map is
139
+ * not on it yet, which is what the engine already says about it.
140
140
  *
141
141
  * ## Two frames under one component
142
142
  *
143
- * The first match in DOCUMENT order wins — not the smallest, not the topmost.
144
- * Deterministic, and deliberately not clever: z-order is a paint concern and
145
- * "the smallest frame containing it" is a rule nobody declared. Two overlapping
146
- * maps of different sizes are a board that has not decided what it is, and a
147
- * reading that quietly picked one of them by area would be harder to argue with
148
- * than one that picked the first. The day a framework needs nesting, this is the
149
- * one function to change.
143
+ * Ties go to the SMALLER id, which is the tie-break `containingFrame` and the
144
+ * engine share — one function for one question, and an answer that cannot
145
+ * depend on the order a `Y.Map` was rebuilt in.
150
146
  */
151
147
  function frameOf(element, elements, profile) {
152
148
  const frame = profile.frame;
153
149
  if (!frame)
154
150
  return null;
155
- const [cx, cy] = centreOf(element.elementBound);
156
- for (const candidate of elements) {
157
- if (candidate.id === element.id)
158
- continue;
159
- if (candidate.role === undefined)
160
- continue;
161
- if (!roleIsA(candidate.role, frame.backgroundRole, profile.roles))
162
- continue;
163
- const bound = candidate.elementBound;
164
- if (cx >= bound.x &&
165
- cx <= bound.x + bound.w &&
166
- cy >= bound.y &&
167
- cy <= bound.y + bound.h) {
168
- return candidate;
169
- }
170
- }
171
- return null;
151
+ return containingFrame(element.elementBound, elements.filter(candidate => candidate.id !== element.id &&
152
+ candidate.role !== undefined &&
153
+ roleIsA(candidate.role, frame.backgroundRole, profile.roles)), candidate => candidate.elementBound);
172
154
  }
173
155
  /**
174
156
  * The phase, read from the position and the frame's DECLARED zones.
@@ -270,10 +252,14 @@ function readRelations(element, elements, profile) {
270
252
  const side = isSource ? 'supplier' : 'consumer';
271
253
  const [, otherY] = centreOf(other.elementBound);
272
254
  // `y` grows downwards, so "higher on the map" is a SMALLER y. A supplier is
273
- // expected below the subject, a consumer above it.
274
- const contradictsGeometry = side === 'supplier'
275
- ? otherY < subjectY - GEOMETRY_EPSILON
276
- : otherY > subjectY + GEOMETRY_EPSILON;
255
+ // expected below the subject, a consumer above it — but only on a board
256
+ // whose framework CLAIMED that axis says something (`geometry`). Two BPMN
257
+ // tasks side by side contradict nothing.
258
+ const contradictsGeometry = relation.geometry !== 'vertical'
259
+ ? false
260
+ : side === 'supplier'
261
+ ? otherY < subjectY - GEOMETRY_EPSILON
262
+ : otherY > subjectY + GEOMETRY_EPSILON;
277
263
  relations.push({
278
264
  edgeId: edge.id,
279
265
  otherId,
@@ -399,6 +385,16 @@ export function readingProfileFor(element, profiles) {
399
385
  * its words would be inventing it;
400
386
  * - **`values: 'open'`** — the def says any string is a value of this tag, so
401
387
  * everything resolves. That is the def's own claim, not ours.
388
+ *
389
+ * ## Why there is no `translate` here, unlike {@link compareReading}
390
+ *
391
+ * There is nothing to translate. `TagValueDef.label` is documented as "already
392
+ * localized by the host" (ADR 0007): a pack is SEEDED with the wording of the
393
+ * deployment that seeded it, so the label this resolver already matches against
394
+ * IS the localized one. Adding a resolver would be a parameter that could never
395
+ * change an answer. The zone labels are the opposite case — they are declared
396
+ * in the library, in English, with an i18n key beside them — which is why the
397
+ * phase comparison takes one.
402
398
  */
403
399
  export function resolveRecordNature(def, values) {
404
400
  const resolved = [];
@@ -512,11 +508,19 @@ export function readRecord(std, element, profile) {
512
508
  * side. `readRecord` has already resolved what CAN be compared; what could not
513
509
  * is not a difference of opinion, it is a difference of alphabet.
514
510
  *
515
- * The phase comparison accepts either the zone id or the zone's own wording,
516
- * because a host is free to store "Product" where the declaration says
517
- * `product`, and neither spelling is more correct than the other.
511
+ * The phase comparison accepts the zone id, the zone's own English wording, and
512
+ * — when a `translate` is handed in — the wording the HOST's catalogue gives
513
+ * that zone. A French deployment stores "Produit" in its record and reads
514
+ * `product` off the board; without the third spelling the panel would report a
515
+ * permanent, false disagreement on a component nobody had moved. Same shape as
516
+ * the value-id resolution above: a difference of alphabet is not a difference
517
+ * of opinion.
518
+ *
519
+ * `translate` is a PARAMETER rather than a `std` read, so this stays a pure
520
+ * function of its arguments; {@link ReadingManager} owns the single `std` the
521
+ * closure is built from.
518
522
  */
519
- export function compareReading(reading, record) {
523
+ export function compareReading(reading, record, translate) {
520
524
  const fields = [];
521
525
  const recorded = record.nature ?? [];
522
526
  if (recorded.length) {
@@ -532,10 +536,11 @@ export function compareReading(reading, record) {
532
536
  }
533
537
  }
534
538
  if (record.phase && reading.phase) {
535
- const spellings = [
536
- reading.phase.zoneId,
537
- reading.phase.labelFallback ?? '',
538
- ].map(value => value.toLowerCase());
539
+ const { zoneId, labelKey, labelFallback } = reading.phase;
540
+ const localized = translate && labelKey !== undefined
541
+ ? translate(labelKey, labelFallback ?? zoneId)
542
+ : '';
543
+ const spellings = [zoneId, labelFallback ?? '', localized].map(value => value.toLowerCase());
539
544
  if (!spellings.includes(record.phase.toLowerCase())) {
540
545
  fields.push({
541
546
  field: 'phase',
@@ -763,7 +768,11 @@ export class ReadingManager extends LifeCycleWatcher {
763
768
  this.drift$.value = null;
764
769
  return;
765
770
  }
766
- const fields = compareReading(reading, record);
771
+ // The one `std` read the comparison needs, curried into a resolver so
772
+ // `compareReading` stays pure: a host serving French stores "Produit" in
773
+ // its record and would otherwise be told, for ever, that its board says
774
+ // `product` instead.
775
+ const fields = compareReading(reading, record, (key, fallback) => translateKey(this.std, key, fallback));
767
776
  if (fields.length === 0) {
768
777
  if (this.drift$.value?.elementId === elementId)
769
778
  this.drift$.value = null;
@@ -34,7 +34,9 @@ import { ViolationTimeline } from './violation-timeline.js';
34
34
  *
35
35
  * - `blocking-overridable` — the host may block, and must let the user override.
36
36
  * - `warning` — surfaced, never blocking. The sketch always wins.
37
- * - `audit` — collected for reporting, invisible to the drawing user.
37
+ * - `audit` — collected for reporting, invisible to the drawing user, and
38
+ * therefore evaluated ON DEMAND only — unless the level of requirement a
39
+ * frame is on raises it ({@link ValidationMoment}).
38
40
  */
39
41
  export type ViolationSeverity = 'blocking-overridable' | 'warning' | 'audit';
40
42
  /**
@@ -89,6 +91,28 @@ export type ProfileSeverity = ViolationSeverity | 'off';
89
91
  * drawn on it.
90
92
  */
91
93
  export type RuleFamily = 'element-in-background' | 'orientation-against-axis' | 'attachment' | 'no-overlap' | 'relative-order-along-axis' | 'relation-endpoints' | 'element-in-zone' | 'tone-convention' | 'majority-fact' | 'edge-degree' | 'role-count' | 'edge-locality' | 'reachability' | 'label-presence' | 'view-admissibility';
94
+ /**
95
+ * What a verdict on one subject depends on (PF5.3). Ordered: each level
96
+ * includes the previous.
97
+ *
98
+ * - `element` — the subject's own props, plus the frame(s) it is attributed to.
99
+ * A dirty frame already forces a full pass, so frames are free here.
100
+ * - `relations` — the subject, the edges touching it, and the elements at the
101
+ * other end of those edges.
102
+ * - `frame` — every subject of the rule attributed to the same frame: the
103
+ * population facts (a majority, a count, an order within a frame, "is there
104
+ * another one of these on me").
105
+ * - `surface` — anything else. Today's behaviour for every family, and the
106
+ * answer for anything unknown.
107
+ *
108
+ * Declared per FAMILY in {@link RULE_SCOPES} — the evaluator is what decides
109
+ * what a verdict reads, so the family owns the answer. A rule may only WIDEN
110
+ * it ({@link ValidationRule.scope}).
111
+ *
112
+ * Pure DATA: no evaluator reads it, and PF5.3 changes no verdict. It is the
113
+ * precondition of PF5.4, which will compute an incremental closure from it.
114
+ */
115
+ export type RuleScope = 'element' | 'relations' | 'frame' | 'surface';
92
116
  /**
93
117
  * WHEN a rule is evaluated (PF5.14).
94
118
  *
@@ -105,6 +129,17 @@ export type RuleFamily = 'element-in-background' | 'orientation-against-axis' |
105
129
  * interrupt says so once, in its own declaration, and no evaluation path has to
106
130
  * remember. That is what makes "zero real-time cost" provable at the bench
107
131
  * rather than promised in a comment.
132
+ *
133
+ * `audit` severity implies `'on-demand'` (PF7.6) — a finding the canvas never
134
+ * shows has no business costing a frame. A rule may still declare `moment`
135
+ * explicitly, but `'realtime'` on an `audit` rule is IGNORED.
136
+ *
137
+ * The severity that decides this is the EFFECTIVE one, under the
138
+ * {@link ValidationProfile} in force: the framework's default level, and every
139
+ * level a frame on the surface has chosen ({@link momentOf}). So a board nobody
140
+ * raised evaluates none of its pack while the user draws, and the rules
141
+ * `c4.strict` shows come back on the gesture after that board is switched to it
142
+ * — per board, and not once for the pack.
108
143
  */
109
144
  export type ValidationMoment = 'realtime' | 'on-demand';
110
145
  /** `orientation-against-axis` configuration. */
@@ -977,8 +1012,23 @@ export interface ValidationRule extends RuleMessage {
977
1012
  * `'on-demand'` takes the rule OUT of {@link evaluateRules} entirely — not
978
1013
  * out of the canvas affordance, out of the evaluation — and into
979
1014
  * {@link evaluateCheckup}, which only a user gesture calls.
1015
+ *
1016
+ * A rule that is `audit` under every level in force on the surface is
1017
+ * on-demand whatever this field says (PF7.6): declaring `'realtime'` on one
1018
+ * is ignored, not honoured. The reverse never happens — an explicit
1019
+ * `'on-demand'` is kept whatever a level says.
980
1020
  */
981
1021
  moment?: ValidationMoment;
1022
+ /**
1023
+ * WIDER than the family's own dependency scope, when this rule reads more
1024
+ * than its family usually does (PF5.3).
1025
+ *
1026
+ * A rule — or a host shipping one — can only WIDEN {@link RULE_SCOPES}, never
1027
+ * narrow it: the evaluator is what actually reads the surface, and a rule
1028
+ * claiming to read less than its family does would be data that lies. Absent,
1029
+ * the family's scope applies. See {@link scopeOf}.
1030
+ */
1031
+ scope?: RuleScope;
982
1032
  /**
983
1033
  * The role this rule is written on. An element matches when its own role IS
984
1034
  * that role or SPECIALISES it ({@link roleIsA}), so a rule on
@@ -1254,6 +1304,11 @@ export type ExemptionScope = 'element' | 'map';
1254
1304
  * full sweep. An on-demand rule never takes part in that pass, so the frames it
1255
1305
  * measures against have nothing to invalidate — and a framework whose only rules
1256
1306
  * are on-demand has no incremental pass to protect in the first place.
1307
+ *
1308
+ * The DECLARED moment, deliberately, and not the one the levels in force decide
1309
+ * ({@link momentOf}): this is a set of ids and not a verdict, so an audit rule
1310
+ * whose frames it guards costs one extra id and can never make an answer wrong,
1311
+ * where reading the levels here would mean re-reading the surface for it.
1257
1312
  */
1258
1313
  export declare function backgroundElementIds(rules: readonly ValidationRule[], elements: readonly GfxPrimitiveElementModel[]): ReadonlySet<string>;
1259
1314
  /**
@@ -1282,14 +1337,172 @@ export declare function toneOf(value: string | undefined): ColourTone | undefine
1282
1337
  * where exactly that is known; every other caller — the first evaluation, a
1283
1338
  * gesture that must land immediately, the bench, a test — evaluates in full.
1284
1339
  *
1285
- * Only {@link evaluateNoOverlap} honours it. The element-local families are
1286
- * already constant per element and would gain nothing but a way to be subtly
1287
- * wrong.
1340
+ * Since PF5.4 the TWELVE families that are not `'surface'` honour it, and not
1341
+ * one of them computes what it means: {@link runRules} derives the SUBJECTS
1342
+ * each rule must judge from {@link dirtyClosure} — which reads {@link scopeOf}
1343
+ * and nothing else — and hands them down. A family's whole part in this is one
1344
+ * early `continue` in the loop it raises from; its indexes still span the
1345
+ * surface, or an edge whose far end is outside the subset would look dangling
1346
+ * and a majority would be counted over a handful.
1347
+ *
1348
+ * The other three never see `subjects` and are not written to look for it, on
1349
+ * purpose: {@link evaluateNoOverlap} keeps the pair logic it has had since
1350
+ * PF5.13, and `attachment` and `reachability` are declared `'surface'` in
1351
+ * {@link RULE_SCOPES}, so they evaluate in full. Giving them a filter that
1352
+ * cannot fire would read as an invitation to narrow their table entry, which is
1353
+ * exactly the change that would make them wrong.
1288
1354
  */
1289
1355
  export interface IncrementalContext {
1290
1356
  dirty: ReadonlySet<string>;
1291
1357
  previous: readonly Violation[];
1358
+ /**
1359
+ * The frames each element was attributed to at the PREVIOUS pass — the one
1360
+ * fact about a move that the current surface cannot reconstruct, since an
1361
+ * element that has left a frame no longer says where it was.
1362
+ *
1363
+ * The manager's memory ({@link frameMembership}). Absent — every caller but
1364
+ * the debounced path — the closure reads the current attribution alone, which
1365
+ * is safe for everything except a `'frame'`-scope verdict about the frame an
1366
+ * element has just LEFT, and that is precisely why the manager keeps it.
1367
+ */
1368
+ wasIn?: ReadonlyMap<string, readonly string[]>;
1369
+ /**
1370
+ * Filled by {@link runRules}, per rule: the subjects this evaluation must
1371
+ * JUDGE. Absent means judge everything — a full pass for this rule, which is
1372
+ * what a `'surface'` scope, a dirty frame and the crossover guard all resolve
1373
+ * to.
1374
+ *
1375
+ * Never set by a caller: it is an answer about ONE rule, and a context is
1376
+ * handed in for a whole pass.
1377
+ */
1378
+ subjects?: ReadonlySet<string>;
1292
1379
  }
1380
+ /**
1381
+ * What a verdict of each family DEPENDS ON (PF5.3) — read off the evaluator
1382
+ * beside it, one line of justification each.
1383
+ *
1384
+ * `Record<RuleFamily, RuleScope>`, so a family added without an entry is a
1385
+ * BUILD error and never a silent `'surface'`. Nothing reads this table yet:
1386
+ * PF5.4 will compute its dirty-set closure from it, and a family that changes
1387
+ * what its evaluator reads has to change its line here on the same commit.
1388
+ *
1389
+ * Over-approximating is always SAFE — a wider scope re-evaluates more than it
1390
+ * had to. Under-approximating is a stale verdict, so a family whose evidence is
1391
+ * ambiguous is declared wider, not narrower.
1392
+ */
1393
+ export declare const RULE_SCOPES: Record<RuleFamily, RuleScope>;
1394
+ /**
1395
+ * The dependency scope in force for `rule`: the WIDER of its family's and its
1396
+ * own.
1397
+ *
1398
+ * Widening-only by construction rather than by a test — the answer is a
1399
+ * `Math.max` over two positions in {@link SCOPE_ORDER}, so a rule declaring a
1400
+ * narrower scope than its family simply does not get it.
1401
+ *
1402
+ * An unknown family — a serialized rule shipped by a host against a build that
1403
+ * does not have it — resolves to `'surface'`, the safe answer.
1404
+ */
1405
+ export declare function scopeOf(rule: ValidationRule): RuleScope;
1406
+ /**
1407
+ * The frames each element is attributed to, for the FRAME-scope rules
1408
+ * registered — the manager's memory of a pass, handed to the next one as
1409
+ * {@link IncrementalContext.wasIn}.
1410
+ *
1411
+ * The one fact the current surface cannot reconstruct: an element that has left
1412
+ * a frame no longer says where it was, and the frame it left has one fewer
1413
+ * subject to count. Without it a `'frame'`-scope rule has no honest incremental
1414
+ * answer at all, which is exactly what {@link dirtyClosure} falls back to.
1415
+ *
1416
+ * ## Two depths, and why the shallow one is not optional
1417
+ *
1418
+ * The full attribution — which frame every element is on — is built for the
1419
+ * `'frame'` scope only, since no other scope reads it. A framework like Wardley,
1420
+ * whose rules are element, relations and surface, never pays for that walk.
1421
+ *
1422
+ * But every PIVOTAL element records itself under its own id — the same
1423
+ * statement about a frame ("it is on itself"), asked at no cost beyond a role
1424
+ * read and a prop read. Two kinds are pivotal:
1425
+ *
1426
+ * - a **frame** of any rule that frames against a background;
1427
+ * - an element that **declares a level of requirement** (`validationProfile`),
1428
+ * because the levels in force decide the severity AND the moment of every
1429
+ * rule on the board, and they are INHERITED by containment
1430
+ * ({@link inheritChosenProfiles}) — so one of them is never a fact about one
1431
+ * subject.
1432
+ *
1433
+ * Those entries are what let {@link runRules} and {@link dirtyClosure} tell a
1434
+ * dirty element that WAS one of the two from one that was never anything of the
1435
+ * sort. That is the case a deletion makes unknowable, and that a role or a level
1436
+ * changed AWAY makes invisible even while the element is still there. Without
1437
+ * it, an incremental pass keeps verdicts measured against a map the user has
1438
+ * just stopped calling a map, or judged at a level nobody holds any more.
1439
+ *
1440
+ * Real-time rules only, and the DECLARED moment, exactly like
1441
+ * {@link backgroundElementIds} and for the same reason: an on-demand rule never
1442
+ * takes part in an incremental pass, so it has nothing to invalidate.
1443
+ */
1444
+ export declare function frameMembership(rules: readonly ValidationRule[], elements: readonly GfxPrimitiveElementModel[]): ReadonlyMap<string, readonly string[]>;
1445
+ /**
1446
+ * What ONE pass has to re-judge, and what it may keep — derived from the dirty
1447
+ * set and {@link scopeOf}, and from nothing else.
1448
+ */
1449
+ export interface DirtyClosure {
1450
+ /**
1451
+ * The subjects `rule` must judge, or `null` for "judge everything" — a full
1452
+ * pass for this rule, which is what a `'surface'` scope, a touched frame and
1453
+ * a missing memory all resolve to.
1454
+ */
1455
+ subjectsOf(rule: ValidationRule): ReadonlySet<string> | null;
1456
+ /**
1457
+ * The previous findings of `rule` this pass keeps unchanged, given the
1458
+ * subjects it judged. Call it ONLY with the set {@link subjectsOf} answered.
1459
+ */
1460
+ carried(rule: ValidationRule, subjects: ReadonlySet<string>): Violation[];
1461
+ }
1462
+ /**
1463
+ * The closure of a change (PF5.4): for each rule, which subjects a pass must
1464
+ * re-judge for its answer to be the one a FULL pass would give.
1465
+ *
1466
+ * Framework-agnostic by construction — it reads roles only through
1467
+ * {@link backgroundsOf}, ends only through {@link rawEndpointIds}, and geometry
1468
+ * only through `elementBound`. It never asks what anything IS.
1469
+ *
1470
+ * ## The three closures
1471
+ *
1472
+ * - `'element'` — the dirty set itself. A verdict about an element's own props
1473
+ * changes when those props change and at no other time.
1474
+ * - `'relations'` — the dirty set, plus the ends of every dirty edge, plus every
1475
+ * edge touching one of those ends. Two hops, on purpose: a dirty edge reaches
1476
+ * its ends, and its ends reach their SIBLING edges — which is precisely what
1477
+ * the duplicate and exclusivity verdicts of `relation-endpoints` read.
1478
+ * - `'frame'` — the dirty set, plus every frame a dirty element is or WAS
1479
+ * attributed to, plus everything attributed to one of those frames. The frame
1480
+ * ids are in the set themselves, since `role-count` and `majority-fact` raise
1481
+ * their findings ON the frame.
1482
+ *
1483
+ * ## Where it refuses to answer
1484
+ *
1485
+ * Each of these returns `null`, i.e. a full pass for that rule, and each is a
1486
+ * case where a narrower answer would be a GUESS:
1487
+ *
1488
+ * - **a frame of the rule is dirty** — moving, resizing or deleting one
1489
+ * re-attributes every finding measured against it. The deletion is the case a
1490
+ * "is a current background dirty" test cannot see on its own, so the previous
1491
+ * findings are asked too, exactly as {@link evaluateNoOverlap} asks them.
1492
+ * - **a dirty element is GONE, for a `'relations'` rule** — a removal reports an
1493
+ * id and nothing else, so the ends of a deleted edge cannot be recovered, and
1494
+ * the nodes it used to bind would keep a degree nobody counts any more.
1495
+ * - **no `wasIn`, for a `'frame'` rule** — see {@link frameMembership}.
1496
+ *
1497
+ * ## Cost
1498
+ *
1499
+ * Lazy throughout: a board of element-scope rules alone never walks an edge,
1500
+ * and one whose frames nothing touched never builds a membership map. The
1501
+ * relations closure is two passes over the elements, shared by every
1502
+ * relations-scope rule; the frame membership is one pass per frame ROLE, shared
1503
+ * by every rule framing against it.
1504
+ */
1505
+ export declare function dirtyClosure(elements: readonly GfxPrimitiveElementModel[], dirty: ReadonlySet<string>, previous: readonly Violation[], wasIn?: ReadonlyMap<string, readonly string[]>): DirtyClosure;
1293
1506
  /** The exceptions an element carries, always an array, never a copy to keep. */
1294
1507
  export declare function elementExceptions(element: GfxPrimitiveElementModel): readonly ValidationException[];
1295
1508
  /** Whether `element` is excused from `ruleId`. */
@@ -1307,10 +1520,33 @@ export declare function hasException(element: GfxPrimitiveElementModel, ruleId:
1307
1520
  * whole surface is re-judged from scratch. Handed in, the pair-wise family
1308
1521
  * re-tests only the couples a change can have affected; the answer is the same,
1309
1522
  * it is just reached without walking the grid against itself.
1523
+ *
1524
+ * ## Two ways to hand one in, and two contracts
1525
+ *
1526
+ * - With a `previous` that IS the last verdict — the manager's debounced path —
1527
+ * the answer is exactly what a full pass would give, whole. That is PF5.4's
1528
+ * contract and the fuzz asserts it finding for finding.
1529
+ * - With `previous: []` — a caller SEEDING the pass with its own dirty set, e.g.
1530
+ * the host's selection (PF7.9) — nothing is carried, so the answer is the
1531
+ * fresh verdicts on the closure of the seed and nothing else. For a seed `S`:
1532
+ * `seeded ⊆ full`, and `seeded ∩ touching(S) = full ∩ touching(S)` where
1533
+ * `touching(S)` is the findings naming an element or a frame of `S`. A partial
1534
+ * answer, never a wrong one.
1310
1535
  */
1311
1536
  export declare function evaluateRules(rules: readonly ValidationRule[], elements: readonly GfxPrimitiveElementModel[], profiles?: readonly ValidationProfile[], incremental?: IncrementalContext): Violation[];
1312
- /** The registered rules a check-up runs — and nothing else ever evaluates. */
1313
- export declare function onDemandRules(rules: readonly ValidationRule[]): readonly ValidationRule[];
1537
+ /**
1538
+ * The registered rules a check-up runs on THIS surface — and nothing else ever
1539
+ * evaluates.
1540
+ *
1541
+ * The same question {@link evaluateCheckup} answers by running them, asked
1542
+ * without running anything: a host listing what a check-up would walk, and the
1543
+ * manager sizing one ({@link ValidationManager.checkupRulesFor}), get exactly
1544
+ * the set that pass will use — which means asking it the same way, against the
1545
+ * levels the surface's own frames have CHOSEN ({@link momentOf}) and not
1546
+ * against the registry in the abstract. Hand it no elements and the framework
1547
+ * defaults govern, which is what an empty board is.
1548
+ */
1549
+ export declare function checkupRules(rules: readonly ValidationRule[], elements: readonly GfxPrimitiveElementModel[], profiles?: readonly ValidationProfile[]): readonly ValidationRule[];
1314
1550
  /**
1315
1551
  * Run the ON-DEMAND rules over the surface (PF5.14). The exact mirror of
1316
1552
  * {@link evaluateRules}, for the moment the drawing path never touches.
@@ -1326,8 +1562,30 @@ export declare function onDemandRules(rules: readonly ValidationRule[]): readonl
1326
1562
  * never reach the timeline, the bracket or the badge. A framework declares them
1327
1563
  * `audit` on top of that, which is the severity for "collected, invisible to the
1328
1564
  * drawing user" — belt and braces, and the braces are structural.
1565
+ *
1566
+ * ## A SEED, i.e. "audit this much of the board" (PF7.9)
1567
+ *
1568
+ * `seed` is a set of element ids the caller wants judged — the host's selection,
1569
+ * typically. It reaches the same pipeline as any other incremental pass, with
1570
+ * `previous` deliberately EMPTY: a seed is not a change to build on, it is a
1571
+ * question about a part of the board, so nothing is carried over and the answer
1572
+ * is exactly the fresh verdicts on the closure of the seed ({@link dirtyClosure}).
1573
+ * `wasIn` is the CURRENT attribution, which is the honest memory here: nothing
1574
+ * moved between the two, so where an element is and where it was are the same
1575
+ * fact.
1576
+ *
1577
+ * The contract, for a seed `S` and against the same call without one:
1578
+ *
1579
+ * - `seeded ⊆ full` — a seeded run never invents a verdict; and
1580
+ * - `seeded ∩ touching(S) = full ∩ touching(S)`, where `touching(S)` is the
1581
+ * findings naming an element or a frame of `S` — a seeded run never MISSES one
1582
+ * that is about the seed.
1583
+ *
1584
+ * Everything between the two is what the caller chose not to ask about. A seed
1585
+ * containing a frame asks about that whole map: the frame guards in
1586
+ * {@link dirtyClosure} send every rule measured against it through a full pass.
1329
1587
  */
1330
- export declare function evaluateCheckup(rules: readonly ValidationRule[], elements: readonly GfxPrimitiveElementModel[], profiles?: readonly ValidationProfile[]): Violation[];
1588
+ export declare function evaluateCheckup(rules: readonly ValidationRule[], elements: readonly GfxPrimitiveElementModel[], profiles?: readonly ValidationProfile[], seed?: ReadonlySet<string>): Violation[];
1331
1589
  /**
1332
1590
  * One check-up, as the panel reads it: what it found, when it was asked for,
1333
1591
  * and how far it has got.
@@ -1359,7 +1617,15 @@ export interface CheckupRun {
1359
1617
  backgroundId: string;
1360
1618
  /** Epoch ms, taken at the moment of the gesture. */
1361
1619
  at: number;
1362
- /** The remarks so far — complete once {@link done} reaches {@link total}. */
1620
+ /**
1621
+ * The remarks so far — complete once {@link done} reaches {@link total}.
1622
+ *
1623
+ * "Complete" is about the RULES walked, not about the board: a run given a
1624
+ * seed ({@link ValidationManager.runCheckup}) is partial by construction, and
1625
+ * says nothing about the elements the caller did not ask about. The seed is
1626
+ * not recorded here because the only thing that can produce one is the caller
1627
+ * that already holds it.
1628
+ */
1363
1629
  results: readonly Violation[];
1364
1630
  /** Rules evaluated so far. */
1365
1631
  done: number;
@@ -1568,6 +1834,11 @@ export declare const VERDICT_PROPS: string[];
1568
1834
  * it would hand back precisely what declaring the second moment bought — and a
1569
1835
  * framework that wants its naming checked while the user types says so by
1570
1836
  * declaring the rule real-time, which is the one place that decision belongs.
1837
+ *
1838
+ * The DECLARED moment, like {@link backgroundElementIds} and for the same
1839
+ * reason: this set is computed ONCE per manager, before any surface exists to
1840
+ * read a level from, and watching a prop nothing turns out to need costs a
1841
+ * debounced pass that finds nothing — never a wrong verdict.
1571
1842
  */
1572
1843
  export declare function verdictPropsOf(rules: readonly ValidationRule[]): ReadonlySet<string>;
1573
1844
  /**
@@ -1716,6 +1987,17 @@ export declare class ValidationManager extends InteractivityExtension {
1716
1987
  * luck of no shipped profile using `'off'` yet.
1717
1988
  */
1718
1989
  private _backgrounds;
1990
+ /**
1991
+ * Where every element WAS, frame by frame, at the last evaluation — the other
1992
+ * half of the memory {@link _backgrounds} is, and for the same kind of reason.
1993
+ *
1994
+ * A `'frame'`-scope verdict is about a frame's whole content, so an element
1995
+ * that has just LEFT one changes the answer there as much as it does where it
1996
+ * landed — and the surface, which only knows where things are now, cannot say
1997
+ * where anything was. See {@link frameMembership}: empty, and free, unless a
1998
+ * frame-scope rule is registered.
1999
+ */
2000
+ private _membership;
1719
2001
  private _rules;
1720
2002
  /** Registered rules, resolved once. Empty when every framework is flagged off. */
1721
2003
  private get _activeRules();
@@ -1755,13 +2037,24 @@ export declare class ValidationManager extends InteractivityExtension {
1755
2037
  /**
1756
2038
  * Evaluate now. Exposed so a host (and the bench) can drive it directly.
1757
2039
  *
1758
- * `incremental` is the DEBOUNCED path's privilege and nobody else's: it is
1759
- * the only caller that knows exactly which elements moved since the last
1760
- * verdict. Everything else — a gesture that must land on the spot, a surface
1761
- * arriving, a host asking — pays for a full pass, because a full pass is
1762
- * always right and a wrong dirty set is a board that lies.
2040
+ * An incremental pass is the privilege of WHOEVER KNOWS WHAT CHANGED — the
2041
+ * debounced path by default, and since PF7.9 any caller willing to say so:
2042
+ * `true` runs the debounced path's own accumulated dirty set, an object seeds
2043
+ * the same pipeline with the caller's, `false` (the default) pays for a full
2044
+ * pass. Nothing about the guarantee is relaxed by opening it: a full pass is
2045
+ * always right, and a wrong seed is still a board that lies — so a caller that
2046
+ * is not certain hands in nothing rather than a guess.
2047
+ *
2048
+ * The seed may contain FRAME ids, and that is a request rather than a mistake:
2049
+ * a frame among the dirty ids means "re-judge this board", and it lands on the
2050
+ * {@link _backgrounds} guard below, which is exactly a full pass.
2051
+ *
2052
+ * A pending debounce is never lost: what it had accumulated is folded INTO the
2053
+ * seed and its timer cleared, so one pass answers both questions.
1763
2054
  */
1764
- evaluate(incremental?: boolean): void;
2055
+ evaluate(seed?: boolean | {
2056
+ dirty: ReadonlySet<string>;
2057
+ }): void;
1765
2058
  /** The registered rule with this id, if its framework is enabled. */
1766
2059
  ruleOf(ruleId: string): ValidationRule | undefined;
1767
2060
  /**
@@ -1801,7 +2094,10 @@ export declare class ValidationManager extends InteractivityExtension {
1801
2094
  * already ticked on the element stay written, unread, until it comes back.
1802
2095
  */
1803
2096
  nudgesFor(element: GfxPrimitiveElementModel): readonly QualityNudge[];
1804
- /** The on-demand rules a check-up on `element` would walk (PF5.14). */
2097
+ /**
2098
+ * The on-demand rules a check-up on `element` would walk (PF5.14) — under the
2099
+ * levels the surface's frames are on right now (PF7.6).
2100
+ */
1805
2101
  checkupRulesFor(element: GfxPrimitiveElementModel): readonly ValidationRule[];
1806
2102
  /**
1807
2103
  * Whether `element` has a Map quality panel to open at all: a framework of
@@ -1877,10 +2173,19 @@ export declare class ValidationManager extends InteractivityExtension {
1877
2173
  * remounted. So the run is finished with {@link CheckupRun.error} and whatever
1878
2174
  * it had managed to collect.
1879
2175
  *
2176
+ * ## A SEED, i.e. "audit this much of it" (PF7.9)
2177
+ *
2178
+ * `seed` narrows the QUESTION, never the answer's honesty: the run judges the
2179
+ * closure of those ids ({@link evaluateCheckup}) and carries nothing, so what
2180
+ * comes back is `full ∩ about-the-seed` — the same verdicts a whole-board
2181
+ * check-up would have produced there, and none of the ones it would have
2182
+ * produced elsewhere. A host that has a selection can therefore ask about it;
2183
+ * one that has not passes nothing and gets the whole map, as before.
2184
+ *
1880
2185
  * @returns the finished run, or `null` if it was superseded or there was
1881
2186
  * nothing to run.
1882
2187
  */
1883
- runCheckup(element: GfxPrimitiveElementModel): Promise<CheckupRun | null>;
2188
+ runCheckup(element: GfxPrimitiveElementModel, seed?: ReadonlySet<string>): Promise<CheckupRun | null>;
1884
2189
  /**
1885
2190
  * The profile `element` is actually checked against — the one it names, or
1886
2191
  * its framework's default. `undefined` when it is not a root instance of any