/** * Grid fallback layout for the schema canvas. * * Dagre's network-simplex lays DISCONNECTED graphs (no relations between * entities — the common fresh-tenant case) as one long vertical column, which * reads as "tidy just stacks cards". When there are no edges (or every node is * its own component), a simple fixed-column grid is the honest tidy. * * Same return contract as computeDagreLayout: a NEW nodes array with updated * `position` fields; unrelated properties preserved. */ import type { Node } from '@xyflow/react'; export interface GridLayoutOptions { /** Columns in the grid (default 4). */ columns?: number; /** Space BETWEEN columns, on top of the widest card (default 60). */ gutterX?: number; /** Space BETWEEN rows, on top of the tallest card in that row (default 80). */ gutterY?: number; } /** * Lay cards out in a grid that they actually FIT IN. * * WHY THIS IS NOT `(i % columns) * 320, floor(i / columns) * 220`. * * It was, and that is the whole of operator report D-6: "Selecting 'tidy' icon * just puts the schema cards into a column. Super un-usefull." * * MEASURED on crm.crm7.app 2026-08-27, at the canvas's opening zoom of 0.75 * where field rows are still drawn: 44 cards sitting on the persisted output of * this function, on x = 0/320/640/960 and y = 0/220/440/880/1320 — and **69 of * the 946 card pairs physically overlapping**. Card heights ran from 603px to * 1275px against a 220px row pitch, and card width is 340px against a 320px * column pitch. Every row buried the row beneath it and every column clipped * its neighbour. Cards stacked five deep on a 220px pitch do not read as a * grid; they read as a column, which is exactly the word the operator used. * * WHY IT SURVIVED EVERY CHECK. Tidy ends with a `fitView`, which for 44 cards * settles at zoom 0.28 — below `LOD_FIELDS_VISIBLE` (0.7) in EntityNode, so the * cards collapse to header-only and stop overlapping. The damage is invisible * at the zoom Tidy leaves you at and appears the moment you zoom in far enough * to read anything. A screenshot taken straight after pressing Tidy shows a * clean grid. Measure at the OPENING zoom, not the fitted one. * * The fix is not new thinking: `computeDefaultGridPositions` below already * solves this exact problem, and its docblock already records the same bug * being measured on 2026-08-06. This function was added afterwards and repeated * the mistake the file was already documenting. A row's pitch comes from the * TALLEST card in that row; a column's pitch clears the WIDEST card anywhere, * so columns stay aligned down the whole canvas. * * `gapX`/`gapY` are gone rather than renamed. They meant PITCH — the distance * between origins — and the replacements mean GUTTER — the space left over. * Keeping the old names for the new meaning would silently change what every * existing caller's number does. Nothing outside this package could call it: * it is not re-exported from `utils/index.ts` or the package root. * * Same return contract as `computeDagreLayout`: a NEW nodes array with updated * `position` fields; unrelated properties preserved. */ export declare function computeGridLayout = Record>(nodes: Node[], options?: GridLayoutOptions): Node[]; /** * Default (never-arranged) placement for entity cards. * * WHY THIS IS NOT A ONE-LINER * * The previous inline version computed the row pitch from the CURRENT card's * own field count: * * y = 100 + floor(i / 4) * (220 + fieldCount * 8) * * which is wrong twice. Cards sharing a row got DIFFERENT y values, so rows were * staggered rather than aligned; and the space below a row was decided by * whichever card happened to land at index `i` instead of by the TALLEST card in * that row — so a single wide-schema entity silently overlapped everything * beneath it. Measured live 2026-08-06 on a 44-entity canvas: 5 drag handles * were physically covered by another card, so those cards could not be grabbed * at all even once the drag filter was fixed. * * A card's height is driven by its field count (EntityNode renders one row per * field), so the row pitch has to come from `max(fieldCount)` across the row and * accumulate down the canvas. */ /** Height of one FieldRow (`h-7`) in px. */ export declare const FIELD_ROW_HEIGHT = 28; /** Header + footer + padding on an EntityNode, independent of field count. */ export declare const CARD_CHROME_HEIGHT = 140; /** Widest an EntityNode is expected to render; column pitch must clear it. */ export declare const CARD_MAX_WIDTH = 340; export interface DefaultGridOptions { columns?: number; /** Left/top origin of the grid. */ originX?: number; originY?: number; /** Space between columns, on top of CARD_MAX_WIDTH. */ gutterX?: number; /** Space between rows, on top of the measured row height. */ gutterY?: number; } export declare function estimateCardHeight(fieldCount: number): number; /** * Positions for cards that have never been arranged. Returns a map keyed by id * so callers can apply it per entity without depending on array order. */ export declare function computeDefaultGridPositions(entities: { id: string; fieldCount: number; }[], options?: DefaultGridOptions): Map; /** * True when the graph is effectively disconnected for layout purposes — dagre * would produce a single-column stack that reads as broken. Heuristic: no * edges at all, or every node is its own weak component. */ export declare function isDisconnectedGraph = Record>(nodes: Node[], edges: { source: string; target: string; }[]): boolean; //# sourceMappingURL=gridLayout.d.ts.map