Resolve per-type graph styles via data() mappers, not per-type selectors - #2112
Draft
kmcginnes wants to merge 2 commits into
Draft
Resolve per-type graph styles via data() mappers, not per-type selectors#2112kmcginnes wants to merge 2 commits into
kmcginnes wants to merge 2 commits into
Conversation
The schema view emitted one Cytoscape selector per vertex/edge type (~20k contexts at 10k labels), making style application O(elements × contexts) and locking the main thread so the view never rendered. Precompute each element's resolved style values onto ele.data() (ge_* fields) at the element-construction seams and read them back through a single node rule + single edge rule using data() mappers, plus two gated rules (node[__iconUrl], edge[ge_lineDashPattern]). Context count is now O(1) in the number of types. A live 10k sync confirms the schema view renders with cytoscape style self-time dropping from 87.6% to ~0%. The schema view merges its label into the base node/edge rules rather than replacing them, so the ge_* mappers survive; label-text-color resolution falls back to the default color instead of throwing on an empty labelColor. useBackgroundImageMap moves to core/icons so the element-enrichment seam consumes it without a core-to-modules import. See docs/adr/20260813-element-data-style-mappers.md. Refs #2104.
This was referenced Aug 14, 2026
…#2118) `LabelPreview` inlined its own `new Color(...).isDark()` with no guard for the empty `labelColor` an imported style file can carry, so the preview could throw where the canvas does not. Both now call `labelTextColorFor`, memoized because parsing a color is the only non-trivial work here. Also reads the fallback from `appDefaultEdgeStyle.labelColor` rather than repeating the hex, and looks up line dash patterns through a `Map` so a `lineStyle` colliding with `Object.prototype` cannot resolve to a function.
This was referenced Aug 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The Schema View locked up and never rendered on databases with very large label sets (~10k node types + ~10k edge types). Root cause:
createGraphStylesemitted one Cytoscape selector per type (node[type="…"],edge[type="…"]), so Cytoscape resolved every element's style by scanning ~20k selectors — O(elements × contexts) ≈ 4×10⁸, pinning the main thread. A trace attributed ~88% of self-time to the Cytoscape style engine; layout was never reached.This moves per-type styling off the stylesheet and onto element data: each element's resolved style values are precomputed onto
ele.data()asge_*fields, and a single staticnoderule + singleedgerule read them back throughdata(…)mappers (plus two gated selectors for the optional cases,node[__iconUrl]andedge[ge_lineDashPattern]). Style-context count drops from ~20k to O(1) in the number of types. The type→style mapping now happens once per element at build time instead of per element × per selector at style time — the same pattern the pre-existingnode[__iconUrl]mapper already used, generalized.Both surfaces that share
useGraphStylesare covered: the explorer graph (renderedEntities.ts) and the Schema View (useSchemaGraphData.ts). The Schema View merges its label into the basenode/edgerules rather than replacing them, so thege_*mappers survive. Supporting change:useBackgroundImageMapmoves intocore/iconsso the element-enrichment seam consumes it without acore → modulesimport.How to read
vertexStyleData/edgeStyleDataproducers that own the per-element precompute (isDarklabel color, dotted→dashed remap + dash pattern, border-opacity)noderule + oneedgerule ofdata(ge_*)mappers, replacingcreateGraphStylesge_*onto element datage_*mappers)core/icons(file move + import repoint)docs/adr/20260813-element-data-style-mappers.mdrecords the decision, the O(1) rationale, and the two lockstep constraints (producer/consumer must move together; a stylesheet consumer must merge into — not replace — the base rules).Validation
pnpm checkspasses (types + lint + format);pnpm testpasses (222 files, 2672 tests).graphElementStyleData, O(1) style-context-count guards on both the explorer and schema stylesheets, a guard that the schema label-merge retains thege_*mappers, and an empty-labelColorcrash guard.Related Issues
Check List
pnpm checkspasses with no errors.pnpm testpasses with no failures.