Skip to content

chore(ts): strict mode migration, resolved colors/constants, graph event typing#280

Draft
draedful wants to merge 13 commits intomainfrom
chore/typescript-strict-colors-constants
Draft

chore(ts): strict mode migration, resolved colors/constants, graph event typing#280
draedful wants to merge 13 commits intomainfrom
chore/typescript-strict-colors-constants

Conversation

@draedful
Copy link
Copy Markdown
Collaborator

@draedful draedful commented Apr 6, 2026

Summary

TypeScript strict baseline for library sources, resolved graph colors/constants API, safer canvas/React patterns, and clearer graph event typing.

Highlights

  • Strict config: root tsconfig with strict (stories/tests excluded where appropriate); publish typecheck may still use strict: false until remaining errors are cleared.
  • Colors/constants: TResolvedGraphColors, merge helpers, public API returns fully merged colors.
  • Graph events: GraphEventListener, graph.on/off use explicit listener type; React useGraphEvents / TGraphEventCallbacks derived from GraphCallbacksMap without casts.
  • Fixes: getPort without assertion, drag/click instanceof MouseEvent, Block.subscribe view cleanup on rebind, orphan connection port ids, BlockState signal init without undefined, Group click guard, addEventListeners unsubscribe fix, etc.

Notes

  • Run npm run typecheck:strict to see remaining strict-only diagnostics outside publish config.

Made with Cursor

Summary by Sourcery

Tighten TypeScript typing across the graph core, colors/constants configuration, and event APIs while hardening canvas/React integration and selection/connection behavior under strict mode.

New Features:

  • Expose fully resolved graph colors/constants via new resolved types and merge helpers for both core and public APIs.
  • Provide typed graph event listener helpers for both core and React hooks, derived from a central GraphEventsDefinitions mapping.

Bug Fixes:

  • Ensure ports, blocks, anchors, groups, and connections handle missing or orphaned state safely, avoiding undefined access and stale view bindings.
  • Fix drag and DOM event listener lifecycles, including proper unsubscribe behavior and safer MouseEvent handling in canvas interactions.
  • Resolve edge cases in multipoint connection labels, ELK layout conversion, and selection resolution where optional data or coordinates could be missing.
  • Guard React layers, portals, and tests against absent DOM/canvas elements and null graph instances to prevent runtime errors.

Enhancements:

  • Enable TypeScript strict mode for library sources, adding stricter nullability, initialization, and generic constraints throughout the codebase.
  • Introduce resolved graph colors/constants signals and update layers, canvas components, and GraphCanvas CSS bindings to consume the normalized runtime shape.
  • Refine connection, block, port, and selection store types to avoid undefined state, add explicit view-component rebinding semantics, and improve computed IDs/port IDs.
  • Strengthen Layer, GraphLayer, and BlockGroups abstractions with safer canvas/context acquisition, typed graph events, and explicit error reporting when required resources are missing.
  • Improve React hooks (graph, layer, events, anchors) with stricter generics, defaults, and null-safe behavior for graph instances and refs.
  • Add a dev-only logging helper and centralize development diagnostics for missing entities and unexpected rebinding cases.
  • Document the strict-mode migration status and remaining diagnostics in a dedicated analysis markdown file.

Build:

  • Turn on strict TypeScript compilation in the root tsconfig while excluding tests and stories, and add dedicated strict and stories typecheck npm scripts.
  • Configure Storybook to use a non-strict stories tsconfig for react-docgen-typescript analysis.

Documentation:

  • Add a strict-mode analysis document summarizing current TypeScript errors, their categories, and a migration plan.

Tests:

  • Update unit tests and React hook tests to work under stricter types, including safer canvas/context setup, null checks, and more precise expectations around selected components and portals.

draedful added 10 commits April 6, 2026 13:17
…tion

- Add TResolvedGraphColors and merge helpers; wire Graph, Layer, events, API
- Accept RecursivePartial for color/constant updates; fix useGraph view config
- Export TResolvedGraphColors; add TS strict mode analysis doc

Made-with: Cursor
…hrow

- Block, Blocks, BaseConnection, BlockConnection: types and guards
- Anchor optional store binding; PublicGraphApi updateConnection no-op if missing
- Add logDev for NODE_ENV !== production

Made-with: Cursor
… unknown

- BatchPath2D, BlockConnections, MultipointConnection, EventedComponent
- GraphComponent drag/root events; settings legacy canDrag map
- Tests: Graph stub via CoreComponent chain; Resolver/ReactLayer/camera
- Story: mergeResolvedGraphConstants; BaseSelectionBucket type guard

Made-with: Cursor
- Harden Graph, Layers emitter typing, Layer canvas/html lifecycle
- Improve CoreComponent children updates and generic unmount
- Tighten GraphComponent drag coords, MultipointConnection, groups
- Update strict-mode analysis doc

Made-with: Cursor
- Tighten ConnectionState signals, ids, port computeds, view component
- Fix ConnectionsStore map updates and selection event typing
- Harden PortState signal init and meta merge; optional port owner
- Type getCoord, addEventListeners, dragListener, and text helpers

Made-with: Cursor
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Apr 6, 2026

Reviewer's Guide

Migrates the core graph library to TypeScript strict mode by tightening types across core components, graph configuration, events and React bindings; introduces resolved color/constants types, safer canvas/layer handling, and stronger typing for graph events and selections while fixing several latent runtime edge cases.

Sequence diagram for typed React graph event handling

sequenceDiagram
  actor ReactUser
  participant GraphCanvas
  participant ReactLayer
  participant ReactHook as useGraphEvents
  participant Graph
  participant DOMEmitter

  ReactUser->>GraphCanvas: render GraphCanvas with graph and callbacks
  GraphCanvas->>ReactLayer: create ReactLayer via useLayer
  GraphCanvas->>ReactHook: useGraphEvents(graph, callbacks)
  ReactHook->>Graph: graph.on(eventName, GraphEventListener)
  Graph-->>ReactHook: returns unsubscribe function

  DOMEmitter->>Graph: dispatch CustomEvent<GraphEventsDefinitions[EvName]>
  Graph->>GraphEventListener: invoke listener(event)
  GraphEventListener->>ReactUser: callback(detail, event)

  ReactUser->>GraphCanvas: unmount GraphCanvas
  GraphCanvas->>ReactHook: cleanup effect
  ReactHook->>Graph: off(eventName, GraphEventListener)
Loading

Class diagram for resolved graph colors and constants

classDiagram
  class Graph {
    -Layers layers
    -Signal_TResolvedGraphColors_ $graphColors
    -Signal_TGraphConstants_ $graphConstants
    -TGraphConfig config
    +Graph(config: TGraphConfig, rootEl: HTMLDivElement, graphColors: RecursivePartial_TGraphColors_, graphConstants: RecursivePartial_TGraphConstants_)
    +get graphColors() TResolvedGraphColors
    +get graphConstants() TGraphConstants
    +setColors(colors: RecursivePartial_TGraphColors_) void
    +setConstants(constants: RecursivePartial_TGraphConstants_) void
  }

  class Layers {
    +rootSize: Signal_TLayersRootSize_
    +attach(root: HTMLDivElement) void
    +start() void
    +on_update_size_(listener: function) void
  }

  class TLayersRootSize {
    +width: number
    +height: number
    +dpr: number
  }

  class TGraphConfig {
    +blocks: any
    +connections: any
    +configurationName: string
    +viewConfiguration: any
  }

  class TGraphColors {
    <<type>>
    +canvas: TCanvasColors
    +block: TBlockColors
    +anchor: TAnchorColors
    +connection: TConnectionColors
    +connectionLabel: TConnectionLabelColors
    +selection: TSelectionColors
  }

  class TResolvedGraphColors {
    <<type>>
    +canvas: TCanvasColors
    +block: TBlockColors
    +anchor: TAnchorColors
    +connection: TConnectionColors
    +connectionLabel: TConnectionLabelColors
    +selection: TSelectionColors
  }

  class TGraphConstants {
    <<type>>
    +block: any
    +connection: any
    +text: any
  }

  class RecursivePartial_TGraphColors_ {
    <<type>>
  }

  class RecursivePartial_TGraphConstants_ {
    <<type>>
  }

  class createInitialResolvedGraphColors {
    <<function>>
    +createInitialResolvedGraphColors() TResolvedGraphColors
  }

  class mergeResolvedGraphColors {
    <<function>>
    +mergeResolvedGraphColors(current: TResolvedGraphColors, patch: RecursivePartial_TGraphColors_) TResolvedGraphColors
  }

  class mergeResolvedGraphConstants {
    <<function>>
    +mergeResolvedGraphConstants(current: TGraphConstants, patch: RecursivePartial_TGraphConstants_) TGraphConstants
  }

  class PublicGraphApi {
    -graph: Graph
    +getGraphColors() TResolvedGraphColors
    +updateGraphColors(colors: RecursivePartial_TGraphColors_) void
    +getGraphConstants() TGraphConstants
    +updateGraphConstants(constants: RecursivePartial_TGraphConstants_) void
  }

  Graph --> Layers : uses
  Graph --> TGraphConfig : config
  Graph --> TResolvedGraphColors : graphColors
  Graph --> TGraphConstants : graphConstants
  Graph ..> RecursivePartial_TGraphColors_ : setColors input
  Graph ..> RecursivePartial_TGraphConstants_ : setConstants input

  createInitialResolvedGraphColors ..> TResolvedGraphColors : returns
  mergeResolvedGraphColors ..> TResolvedGraphColors : returns
  mergeResolvedGraphColors ..> RecursivePartial_TGraphColors_ : patch

  mergeResolvedGraphConstants ..> TGraphConstants : returns
  mergeResolvedGraphConstants ..> RecursivePartial_TGraphConstants_ : patch

  PublicGraphApi --> Graph : wraps
  PublicGraphApi ..> TResolvedGraphColors : exposes
  PublicGraphApi ..> TGraphConstants : exposes

  TLayersRootSize <.. Layers : rootSize value
Loading

Class diagram for graph events and React event hooks

classDiagram
  class GraphEventsDefinitions {
    <<interface>>
    +camera-change: function
    +constants-changed: function
    +colors-changed: function
    +state-change: function
    +connection-selection-change: function
    +click: function
    +dblclick: function
    +block-drag-start: function
    +block-drag: function
    +block-drag-end: function
    +blocks-selection-change: function
    +block-anchor-selection-change: function
    +block-change: function
  }

  class UnwrapGraphEvents {
    <<type>>
  }

  class UnwrapGraphEventsDetail {
    <<type>>
  }

  class GraphEventListener {
    <<type>>
    +GraphEventListener_EventName_(event: UnwrapGraphEvents_EventName_) void
  }

  class Graph {
    -eventEmitter: EventTarget
    +on_EventName_(type: EventName, cb: GraphEventListener_EventName_, options: AddEventListenerOptions) function
    +off_EventName_(type: EventName, cb: GraphEventListener_EventName_) void
  }

  class GraphComponent {
    +onGraphEvent_EventName_(eventName: EventName, handler: GraphEventListener_EventName_, options: AddEventListenerOptions) function
  }

  class Layer {
    +onGraphEvent_EventName_(eventName: EventName, handler: GraphEventListener_EventName_, options: AddEventListenerOptions) function
  }

  class GraphReactHandler_N_ {
    <<type>>
    +GraphReactHandler_N_(data: UnwrapGraphEventsDetail_N_, event: UnwrapGraphEvents_N_) void
  }

  class TGraphEventCallbacks {
    <<type>>
    +click: GraphReactHandler_click_
    +dblclick: GraphReactHandler_dblclick_
    +onCameraChange: GraphReactHandler_camera-change_
    +onBlockDragStart: GraphReactHandler_block-drag-start_
    +onBlockDrag: GraphReactHandler_block-drag_
    +onBlockDragEnd: GraphReactHandler_block-drag-end_
    +onBlockSelectionChange: GraphReactHandler_blocks-selection-change_
    +onBlockAnchorSelectionChange: GraphReactHandler_block-anchor-selection-change_
    +onBlockChange: GraphReactHandler_block-change_
    +onConnectionSelectionChange: GraphReactHandler_connection-selection-change_
    +onStateChanged: GraphReactHandler_state-change_
  }

  class GraphCallbacksMap {
    <<const object>>
    +click: click
    +dblclick: dblclick
    +onCameraChange: camera-change
    +onBlockDragStart: block-drag-start
    +onBlockDrag: block-drag
    +onBlockDragEnd: block-drag-end
    +onBlockSelectionChange: blocks-selection-change
    +onBlockAnchorSelectionChange: block-anchor-selection-change
    +onBlockChange: block-change
    +onConnectionSelectionChange: connection-selection-change
    +onStateChanged: state-change
  }

  class useGraphEvent {
    <<hook>>
    +useGraphEvent_EvName_(graph: Graph, event: EvName, cb: function, debounceParams) void
  }

  class useGraphEvents {
    <<hook>>
    +useGraphEvents(graph: Graph, events: Partial_TGraphEventCallbacks_) void
  }

  class bindReactGraphCallback {
    <<function>>
    +bindReactGraphCallback(graph: Graph, key: GraphCallbackKey, cb: TGraphEventCallbacks_key_) function
  }

  GraphEventsDefinitions <.. UnwrapGraphEvents : based on
  GraphEventsDefinitions <.. UnwrapGraphEventsDetail : based on

  GraphEventListener ..> UnwrapGraphEvents : param

  Graph --> GraphEventListener : uses in on_off_
  Graph --> GraphEventsDefinitions : events typed by

  GraphComponent --> Graph : via context.graph
  GraphComponent ..> GraphEventListener : handler type

  Layer --> Graph : via props.graph
  Layer ..> GraphEventListener : handler type

  TGraphEventCallbacks --> GraphReactHandler_N_ : mapped
  GraphCallbacksMap --> TGraphEventCallbacks : keys align

  useGraphEvent --> Graph : subscribes
  useGraphEvent ..> UnwrapGraphEvents : event type
  useGraphEvent ..> UnwrapGraphEventsDetail : detail type

  useGraphEvents --> Graph : subscribes
  useGraphEvents --> TGraphEventCallbacks : callbacks map
  useGraphEvents --> bindReactGraphCallback : uses

  bindReactGraphCallback --> Graph : calls on
  bindReactGraphCallback --> GraphCallbacksMap : lookup
  bindReactGraphCallback --> GraphEventListener : constructs listener
Loading

File-Level Changes

Change Details Files
Safer CoreComponent children lifecycle and stronger typing
  • Make CoreComponent children map and previous children array strongly typed via ComponentDescriptor and Record<string, CoreComponent
undefined>
  • Introduce resolveChildDescriptorKey helper to consistently compute child keys from descriptors
  • Guard unmounts against undefined children and ensure refs are assigned from mounted instances with correctly typed this.$
  • Make static CoreComponent.unmount generic and typed, and add missing type annotations for arrays and methods
  • ConnectionState and PortsStore now maintain non-undefined signal state and handle orphan ports
    • Initialize ConnectionState.$state as a Signal in constructor instead of signal(undefined) and adjust id/source/target port accessors to synthesize ids for orphan endpoints
    • Allow ConnectionState.viewComponent to be optional and expose a typed getViewComponent
    • Update PortsStore.createPort to accept optional owner components and set owner only when present, and make PortState.$state a non-undefined Signal<TPort> with safer metadata merging
    src/store/connection/ConnectionState.ts
    src/store/connection/port/Port.ts
    src/store/connection/port/PortList.ts
    Graph configuration now exposes fully resolved colors/constants and normalized updates
    • Introduce TResolvedGraphColors plus helper functions to create and merge resolved colors, and use them for Graph.$graphColors, graphColors getter and LayerContext.colors
    • Add mergeResolvedGraphConstants for constants updates and change Graph constructor and setColors/setConstants to accept RecursivePartial patches
    • Adjust PublicGraphApi to return resolved colors, accept partial colors/constants updates, and tighten types of other update methods with dev logging for missing entities
    src/graphConfig.ts
    src/graph.ts
    src/api/PublicGraphApi.ts
    src/services/Layer.ts
    src/index.ts
    src/react-components/GraphCanvas.tsx
    src/services/LayersService.ts
    src/graphEvents.ts
    src/stories/main/GraphEditor.stories.tsx
    Graph events and React bindings have explicit listener and callback typing
    • Define GraphEventListener helper type and use it in Graph.on/off, Layer.onGraphEvent, GraphComponent.onGraphEvent and tests
    • Refactor React graph events API: introduce GraphReactHandler and GraphCallbackKey, redefine TGraphEventCallbacks via GraphCallbacksMap, and wire useGraphEvent/useGraphEvents to derive types from GraphEventsDefinitions without casts
    • Update selection and connection events to use the correct id types and fix related tests to assert on object shape instead of mocked component casts
    src/graph.ts
    src/graphEvents.ts
    src/services/Layer.ts
    src/components/canvas/GraphComponent/index.tsx
    src/react-components/events.ts
    src/react-components/hooks/useGraphEvents.ts
    src/components/canvas/GraphComponent/GraphComponent.test.ts
    src/store/connection/ConnectionList.ts
    src/services/selection/BaseSelectionBucket.ts
    src/services/selection/Resolver.integration.test.ts
    Canvas, layers, and geometry utilities are stricter and guard against missing DOM/canvas state
    • Make Layer.canvas/html/root optional fields with typed getters returning possibly undefined, then override in GraphLayer and BlockGroups to guarantee a canvas and assert context acquisition
    • Ensure unmountLayer checks for canvas and 2D context before clearing, and update canvas size/resetTransform to early return when canvas is missing
    • Strengthen text measurement utilities with explicit numeric typings, null-safe canvas context acquisition, deterministic lineHeight and wrapping logic, and safer return types
    • Improve dragListener typings and event listener cleanup by consistently casting handlers to EventListener when attaching/removing
    src/services/Layer.ts
    src/components/canvas/layers/graphLayer/GraphLayer.ts
    src/components/canvas/groups/BlockGroups.ts
    src/utils/functions/text.ts
    src/utils/functions/dragListener.ts
    Blocks, groups, anchors and connections handle missing state and mouse events safely
    • Make Block.connectedState non-null via controlled subscribe lifecycle, introduce unsetViewComponent on BlockState and update subscribe to clean up previous view binding and log when state is missing
    • Guard Group and Anchor event handlers against non-MouseEvent events, make Group subscription resilient to missing groupState, and avoid calling methods on undefined connectedState
    • Initialize Block view flags (shouldRenderText, hidden) with defaults and make Block.updateViewState compare typed keys to avoid unnecessary updates
    • Normalize MultipointConnection behaviour when no points exist, make label rendering robust to undefined paddings and dimensions, and initialise BaseConnection.bBox
    src/components/canvas/blocks/Block.ts
    src/store/block/Block.ts
    src/components/canvas/groups/Group.ts
    src/components/canvas/anchors/index.ts
    src/components/canvas/connections/MultipointConnection.ts
    src/components/canvas/connections/BaseConnection.ts
    src/components/canvas/connections/BlockConnections.ts
    React hooks/components and story/test infrastructure updated for strict mode
    • Narrow various React refs and callbacks to nullable/typed refs, make useLayer props optional with defaults, and ensure hooks like useGraph only call updateSettings when config.settings is defined
    • Adjust ReactLayer, GraphLayer, GraphPortal and GraphLayer (React wrapper) refs and getHTML/getCanvas signatures for stricter nullability and correct LayerPublicProps defaults
    • Introduce src/stories/tsconfig.json with strict off for stories, and wire it via Storybook reactDocgenTypescriptOptions and a dedicated typecheck:stories script
    • Tighten tests to avoid casting away types (e.g. check objectContaining instead of MockComponent casts) and fix mocks to satisfy new constructor signatures and nullable HTML/Canvas return types
    src/react-components/hooks/useLayer.ts
    src/react-components/GraphLayer.tsx
    src/react-components/GraphPortal.tsx
    src/react-components/GraphCanvas.tsx
    src/react-components/Block.tsx
    src/react-components/Anchor.tsx
    src/react-components/hooks/useBlockAnchorState.ts
    src/react-components/hooks/useGraph.ts
    src/react-components/layer/ReactLayer.tsx
    src/react-components/layer/ReactLayer.test.ts
    src/react-components/hooks/useLayer.test.ts
    src/services/camera/defaultGetCameraBlockScaleLevel.test.ts
    src/store/connection/port/PortList.test.ts
    src/stories/tsconfig.json
    .storybook/main.ts
    package.json
    Utility functions and components made strict-safe with explicit types and safer listeners
    • Strengthen getXY/getCoord/addEventListeners with explicit argument and return types, safer touch handling, and stable listener references for unsubscribe
    • Refine Blocks to use typed blockComponents settings, guard font scale against non-numeric values, and always stringify block ids for keys
    • Tighten EventedComponent internal listener map initialization to avoid undefined maps and make events getter return a concrete Map
    • Add a dev-only logDev helper used in places like Anchor/Block/connection update to report missing state without throwing
    src/utils/functions/index.ts
    src/components/canvas/blocks/Blocks.ts
    src/components/canvas/EventedComponent/EventedComponent.ts
    src/utils/devLog.ts
    TypeScript configuration and strict-mode analysis docs
    • Set root tsconfig.json to strict, narrow includes/excludes to library source only, and add tsconfig.publish.json plus new npm scripts for strict and stories typechecking
    • Add a Russian-language analysis document summarizing strict-mode diagnostics and migration strategy under docs/analysis/typescript-strict-mode-analysis.md
    tsconfig.json
    tsconfig.publish.json
    docs/analysis/typescript-strict-mode-analysis.md

    Tips and commands

    Interacting with Sourcery

    • Trigger a new review: Comment @sourcery-ai review on the pull request.
    • Continue discussions: Reply directly to Sourcery's review comments.
    • Generate a GitHub issue from a review comment: Ask Sourcery to create an
      issue from a review comment by replying to it. You can also reply to a
      review comment with @sourcery-ai issue to create an issue from it.
    • Generate a pull request title: Write @sourcery-ai anywhere in the pull
      request title to generate a title at any time. You can also comment
      @sourcery-ai title on the pull request to (re-)generate the title at any time.
    • Generate a pull request summary: Write @sourcery-ai summary anywhere in
      the pull request body to generate a PR summary at any time exactly where you
      want it. You can also comment @sourcery-ai summary on the pull request to
      (re-)generate the summary at any time.
    • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
      request to (re-)generate the reviewer's guide at any time.
    • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
      pull request to resolve all Sourcery comments. Useful if you've already
      addressed all the comments and don't want to see them anymore.
    • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
      request to dismiss all existing Sourcery reviews. Especially useful if you
      want to start fresh with a new review - don't forget to comment
      @sourcery-ai review to trigger a new review!

    Customizing Your Experience

    Access your dashboard to:

    • Enable or disable review features such as the Sourcery-generated pull request
      summary, the reviewer's guide, and others.
    • Change the review language.
    • Add, remove or edit custom review instructions.
    • Adjust other review settings.

    Getting Help

    @gravity-ui-bot
    Copy link
    Copy Markdown
    Contributor

    Preview is ready.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Labels

    None yet

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    2 participants