cytoscape.js v4 HEAD · 824a7351 · built 2026-08-05 21:16Z

Cytoscape.js v4 — executive summary

A week-by-week summary of the v4 rewrite: the columnar model and WebGPU renderer specified in #3486.

This is a derived document. The development record is PLAN.md, which carries every round's plan, what it found, and the controls that proved it. This file is the readable-in-five-minutes version and is rewritten from that record — see Maintaining this file at the end.

  • Scope: the v4 prototype, which begins 2026-07-22. The v4 branch also carries earlier v3-era work (a TypeScript migration through June and mid-July) that PLAN.md does not cover and this summary does not describe.
  • Status: not released. cytoscape@3 remains the shipping library.
  • Last updated: 2026-08-05, covering work through round 46.6, with round 52 scoped.

Where it stands

v4 is feature-complete against its own scope and is in release preparation. The public API keeps v3's shapecy.add(), eles.filter(), node.position(), the traversal and algorithm surfaces — while several v3 mechanisms were removed by decision rather than reimplemented, each recorded with its rationale.

Automated tests 2,013 unit · 247 module · 24 soak · 283 browser
Documented API 362 members over 48 sections, gated at 100%
Visual regression 43 golden images + live v3-vs-v4 pixel-parity scenes
Benchmarks 31 suites; 13× faster than v3 on CPU work, 27× on rendering (geometric means)
Style parity v4 accepts 153 of v3's 291 style property names; the rest are dropped by decision
Bundle 660 KiB minified, 179 KiB gzipped — 1.4× v3 on the wire, of which 24% is WebGPU shader source v3 has no equivalent of

The headline case: a 19,607-node / 464,657-edge network initialises in 1.7 s against v3's 19.1 s, and holds 33 ms frames where v3 takes 4,460 ms.

Three things are deliberately unbuilt and need a decision before 4.0: border-style/outline-style porting (round 38), the error/warning policy (round 40), and the documentation site (round 46).


Week 1 — 22–24 July: the foundation

105 commits. Rounds 1–9.

The base pass established the architecture and proved it end to end before any parity work began.

  • A columnar, CPU-canonical model. Elements live in typed-array columns with stable slots, coalesced dirty spans and a CSR adjacency index. Reads stay synchronous, so the public API does not become async.
  • A WebGPU renderer with SDF node shapes, compute culling, indirect draws and GPU picking. Text was pulled into scope early, so that labelled rendering — the realistic case — could be assessed for performance rather than assumed.
  • A co-signed model↔renderer contract (src/contract.mts) fixing the column and flag layout both halves agree on. Changing it is a deliberate, single-file act.
  • Selectors replaced by structured queries. v3's selector language is gone; v4 takes objects and plain functions. This is the largest single break for existing apps and the reason the migration guide leads with it.
  • A serializable mapper DSL for style, evaluated on the GPU for paint channels.

Decision taken this week: GPU geometry and the read-staleness contract — what a synchronous read is allowed to observe while the device is mid-frame.


Week 2 — 27 July – 2 August: parity

201 commits — the densest week of the project. Rounds 9.4–27, plus four design sittings.

The work turned from architecture to matching v3's visible behaviour, and the measurement infrastructure that makes such claims checkable was built alongside it.

Rendering and style parity

  • Curved edges in full: bundled and unbundled bezier, segments, taxi, haystack, self-loops, and the endpoint vocabulary.
  • Compound nodes: auto-sizing parents, nesting, compound loop edges.
  • Background images, multiline labels and label bounding boxes, node charts (v3's 101 pie/stripe properties reduced to one data-driven chart family).
  • Style transitions, animation, and geometry tweens.

Infrastructure that pays for itself later

  • A visual regression harness: golden images plus live v3-vs-v4 parity diffs rendering both libraries in one run. The distinction matters — goldens answer "did this change?", parity answers "is this right?", and an early arrow-sizing bug passed the goldens while failing parity.
  • Benchmarks with an HTML report, and renderer benchmarks driven on a real GPU.

Decisions taken this week (four sittings)

  • z-index dropped outright rather than reimplemented.
  • The animation queue removed — concurrency by channel, promises for sequencing.
  • display/visibility split into a structural tier and a paint-only tier.
  • The event vocabulary and extension contract fixed, making cy.layout({ impl }) the whole extension story.

Week 3 — 3–5 August: hardening and release preparation

129 commits. Rounds 28–48, plus the round-46.5 status site.

With the feature ledger closed, the work moved to what was unpinned rather than unbuilt — contracts, documentation, packaging and robustness.

Contracts made explicit

  • The error contract: every throw site the test suite never reached went from 34 to 0, and it is now gated at zero tolerance.
  • The documented contract: @throws, @param and @returns are complete and gated, so a doc comment that is silent or wrong about failure fails the build. These ship as hover text in the type declarations.

Performance, measured then fixed

  • A benchmark sweep found five slow paths and logged rather than fixed them, because a measurement round measures. The next round fixed all five: three now sit at parity with v3, style reads went 5.8× → 2.3×, and a per-layout-run cost fell from 333 µs to 795 ns.
  • Two of those findings were corrected while being fixed — one was an artefact of the test loader rather than the library. The standing rule that came out of it: check a hot-path finding against the built bundle before rewriting anything.

Release preparation

  • The repository was restructured so v4 is the package at the root and v3 lives whole and still-buildable in v3/. Behaviour-neutral, and verified by comparing every moved file against its original rather than by trusting a green test suite.
  • Packaging gates, a migration guide whose property tables are measured against both libraries rather than remembered, a generated API reference, and a soak tier that found four defects — including a corrupt payload that made a load never return, and an identity bug that made union() silently drop elements across two instances.
  • A status site (round 46.5): a deployable preview of the branch — the debug harness on WebGPU, the benchmark archive with full machine provenance, the API reference and the project documents. Its fixtures ship in v4's own binary wire format, which is what makes them small enough to host.

What remains before 4.0

needs
border-style / outline-style (round 38) a scope decision on three sub-questions
Error / warning policy (round 40) a design sitting
Documentation site (round 46) prose written by hand; the generated model is ready
Cross-platform validation (round 49) macOS/Metal, Windows/D3D12, WebKit, real-device touch
Release engineering (round 50) the release workflows are still v3's and are marked as not yet adapted
Release bake (round 51) alpha/beta cycle, external-consumer smoke, then 4.0.0
Optional: WGSL minification (round 52) scoped and measured, not scheduled — worth 10% of the download, best landed before the alpha

How this project works, in three habits

These explain most of what the record contains, and are worth knowing before reading it.

  1. A control for every claim. A test is run once with the behaviour it checks deliberately broken, to prove it can fail. This has repeatedly caught tests that asserted nothing — and on several occasions a control that failed to fail revealed dead code or a check that discriminated on nothing.
  2. Measure, don't remember. Statements about the code are re-verified rather than inherited. Three consecutive rounds were handed a "fact" from the record that had quietly stopped being true.
  3. Decisions are written down when taken, with their rationale, which is why the migration guide could be compiled rather than reconstructed.

Maintaining this file

PLAN.md is the source; this file is derived from it. Rewrite this summary when a round closes — not by appending, but by re-reading the record for the week in question and restating it.

  • Organise by calendar week, newest week last, with the commit count and the rounds it covers.
  • Translate, do not excerpt. The audience is a reader who will not open PLAN.md. Say what changed for users of the library and what was decided; leave the implementation, the file names and the round numbering to the record itself, except where a round number is the only handle on an open question.
  • Quote numbers only if they are current. Test tallies, member counts and benchmark figures go stale; re-run the relevant command rather than copying a figure forward. The commands are in AGENTS.md.
  • Keep the open-questions table honest. An item leaves it when the decision is made, not when the work is scheduled.
  • Update Last updated and the round it covers.

A rewrite that only appends a new week has not been done properly: earlier weeks routinely need correcting once later rounds reveal what a decision actually meant.