/** * What contains what on a canvas, and what a folded container draws instead of * its contents (#473). * * Two questions, one module, because they are the same question asked twice. A * VIEW says which relationships nest (ADR 0101); a PATTERN says which subjects * are parts of an instance (ADR 0123). Both produce a parent-of map over the * same node ids, and folding reads that one map. Answering them apart would * mean two trees that can disagree about who owns a node. * * Imports nothing but the `NestingKind` type, and that from `./nesting.js`, * which itself imports nothing. The same weight argument that split * `nesting.ts` out of `projection.ts` applies here and harder: this module is * reached from `yarramate/adapter/visual-graph`, the runtime-neutral subpath a * Durable Object imports, where `node:module` and Ajv are not available at any * price. `test/visual-app-browser-safety.test.ts` is what holds that line. * * Everything here is a pure function over plain data. The canvas adapts its own * shapes to {@link FoldInput}; nothing in this file knows what cytoscape is. */ import type { NestingKind } from './nesting.js'; /** * A view names which relationships nest, in precedence order (ADR 0101). The * short names a projection is authored in resolve to the kind identities the * graph carries, in one place, so the schema's vocabulary and the canvas's * cannot drift. */ export declare const NESTING_KIND_IDS: Readonly>; /** * Whether a view draws pattern instances folded by default (#473). * * Lives here rather than in `projection.ts` for the reason `nesting.ts` exists: * the browser needs the VALUE, and `projection.ts` drags Ajv and the projection * schema in behind it. `projection.ts` re-exports both. */ export type FoldMode = 'instances' | 'none'; /** * What a view folds when it does not say: nothing. Folding hides detail, and a * view that hid detail without being asked would be a surprise its author never * wrote down. */ export declare const DEFAULT_FOLD: FoldMode; /** One node, reduced to what containment needs to know about it. */ export interface FoldNode { readonly id: string; /** The kind as authored, profile-qualified or not. Unused by the rules here. */ readonly kind: string; /** * The core-vocabulary kind this resolves to. Every rule below reads THIS and * never `kind`: a profile's `mule-api-operation` is an `applicationService` * and must be treated as one, and the label it happens to carry is not a * fact about what it is. */ readonly coreKind: string; } /** One relationship, reduced to what containment needs to know about it. */ export interface FoldEdge { readonly id: string; readonly kind: string; readonly from: string; readonly to: string; } /** How a pattern's wiring relates a slot to the instance that declares it. */ export type SlotWiring = 'owned' | 'context' | 'unwired'; /** One bound slot, as {@link foldTree} needs it. */ export interface FoldMembership { readonly member: string; readonly slot: string; readonly instance: string; readonly wiring?: SlotWiring; } export interface FoldInput { readonly nodes: readonly FoldNode[]; readonly edges: readonly FoldEdge[]; readonly memberships: readonly FoldMembership[]; readonly nesting: readonly NestingKind[]; } /** * Two parents claiming one child at the same precedence. Returned rather than * resolved: picking a winner would hide a real modelling anomaly behind a * layout that looks deliberate. The caller renders the child unnested and says * so, which is what composition alone already did. */ export interface NestingConflict { readonly child: string; readonly claims: readonly { readonly edgeId: string; readonly kind: string; readonly from: string; }[]; } export interface FoldTree { readonly parentOf: ReadonlyMap; readonly consumedEdgeIds: ReadonlySet; readonly conflicts: readonly NestingConflict[]; /** Ids left unnested because their parent chain loops. */ readonly cycleMembers: readonly string[]; } /** * The parent-of map a view's nesting kinds imply. * * The compiler's `YM501` rule rejects one pair declaring both composition and * aggregation; it does not reject two different compositions naming one child, * which a single-parent field cannot represent either, nor a composition chain * that loops. Both are real modelling anomalies, surfaced here rather than * silently resolved: affected subjects come back unnested and every edge naming * them stays an ordinary line, so the conflicting claims remain visible. */ export declare function nestingTree(edges: readonly FoldEdge[], nesting: readonly NestingKind[], coreKindOf: (id: string) => string): FoldTree; /** * The containment tree: what a view nests, plus what a pattern owns. * * A slot member joins the tree only when all of these hold, and each condition * is a different way of getting the answer wrong: * * - **Held inside one box.** A member's HOLDERS are every instance whose slots * name it. One holder puts the member in that holder. Several put it in their * lowest common ancestor, which is the level at which the holders diverge and * therefore the innermost box that contains all of them. Holders with no * common ancestor leave the member outside, because there is no one box it * sits within and a single-parent tree would have to pick. * * This AMENDS ADR 0143's "Exclusive" rule, which kept every shared subject * outside (#473 phase 3, ADR 0145, Nabeel's decision of 2026-09-05). The * original reasoning was that two owners force a silent choice; it is only * true when the owners sit in different boxes. Where both already sit under * one box there is nothing to choose, and the old rule left 14 of the * reference Landscape's 30 data objects outside the single application whose * own parts were the things binding them. * - **`owned` or `unwired`, never `context` alone.** A context slot names * something the instance USES and does not contain — the upstream API it * calls, the plane it runs on. Folding those would swallow half the landscape * into whichever box happened to reference it. At least one binding must be * `owned` or `unwired` for the member to fold at all. * - **Not a ruling.** See {@link RULING_CORE_KINDS}. * * A view's own nesting wins where both apply: the view is the more specific * statement, and a reader who wrote `nesting: [composition]` meant it. */ export declare function foldTree(input: FoldInput): FoldTree; /** An edge that stands for one or more relationships hidden inside a fold. */ export interface LiftedEdge { readonly id: string; readonly kind: string; readonly from: string; readonly to: string; readonly count: number; readonly relationshipIds: readonly string[]; } /** The id a lifted edge takes. Deterministic, so a re-render is stable. */ export declare const liftedEdgeId: (from: string, to: string, kind: string) => string; /** * What the canvas draws once some instances are folded. * * A folded instance KEEPS its own node — it is still a subject, still * selectable, still the thing a question is about — and gains what it is * standing in for. Its descendants leave the output, and every edge with an end * inside it is lifted to the box. * * Lifted edges of one kind between one ordered pair merge into a single edge * carrying `count` and the ids it stands for, so seven `serving` relationships * between two applications draw as one line labelled ×7 rather than as seven * lines the reader has to count. An edge whose ends fold into the SAME box * vanishes: it is internal, and the box is the statement now. */ export declare function foldGraph(graph: { readonly nodes: readonly N[]; readonly edges: readonly E[]; }, tree: Pick, folded: ReadonlySet): { readonly nodes: (N & { readonly folded: boolean; readonly insideIds: readonly string[]; })[]; readonly edges: (E | LiftedEdge)[]; };