Skip to content

Extract Web API abstraction into spark_web package#25

Merged
kevin-sakemaer merged 17 commits intomainfrom
claude/server-safe-web-api-vW7Lg
Feb 12, 2026
Merged

Extract Web API abstraction into spark_web package#25
kevin-sakemaer merged 17 commits intomainfrom
claude/server-safe-web-api-vW7Lg

Conversation

@kevin-sakemaer
Copy link
Copy Markdown
Contributor

Summary

This PR extracts the Web API abstraction layer from spark into a new standalone spark_web package. This provides a server-safe Web API that mirrors the MDN Web API with identical naming, working on both the Dart VM (server) and dart2js/dart2wasm (browser).

Key Changes

New spark_web package:

  • Created a complete Web API abstraction library with platform-specific implementations
  • Core types (core.dart): EventTarget, Event, MouseEvent, KeyboardEvent, InputEvent, FocusEvent, WheelEvent, DragEvent, PointerEvent, TouchEvent, AnimationEvent, TransitionEvent, CustomEvent, MutationObserver, MutationRecord
  • DOM types (dom.dart): Node, Element, HTMLElement, and 40+ specific element types (input, button, canvas, video, audio, dialog, table, etc.)
  • Window & APIs (window.dart): Window, Document, Console, Navigator, Storage, Location, History, Crypto, Performance, CustomElementRegistry
  • Canvas (canvas.dart): CanvasRenderingContext2D with full 2D drawing API
  • CSS (css.dart): CSSStyleDeclaration, CSSStyleSheet, DOMTokenList
  • Collections (collections.dart): NodeList, NamedNodeMap, DOMTokenList

Browser implementation (browser/):

  • Wraps native package:web types with comprehensive type checking and conversion
  • wrapNode(), wrapElement(), wrapEvent() utilities for converting native objects
  • Full implementations of all DOM, window, and canvas types

Server implementation (server/):

  • No-op implementations for all types, allowing code to compile and run on Dart VM
  • Storage backed by Map for localStorage/sessionStorage
  • Crypto.randomUUID() via dart:math
  • Console backed by print()
  • Factory functions for creating platform-appropriate instances

Updated spark package:

  • Replaced conditional imports of stubs.dart with spark_web imports
  • Removed stub files: stubs.dart, adopted_styles_web.dart, adopted_styles_stub.dart, js_callback_web.dart, js_callback_stub.dart, query_stubs.dart, query_web.dart
  • Updated web_component.dart to use spark_web instead of conditional imports
  • Updated vdom_web.dart to import package:web directly (no longer needs stubs)
  • Updated extensions.dart to use spark_web
  • Updated spark_component.dart to pass native DOM objects to VDOM engine
  • Updated pubspec.yaml to depend on spark_web
  • Updated CI workflow to include spark_web in change detection

Implementation Details

  • MDN-aligned API: All types and methods match MDN Web API documentation exactly, enabling developers to use MDN as reference
  • Conditional compilation: Uses dart:js_interop availability to detect platform (browser vs server)
  • Type safety: Comprehensive type hierarchy with proper inheritance relationships
  • Wrapping strategy: Browser implementation wraps native package:web objects; server implementation provides sensible defaults
  • Event system: Full event type hierarchy with proper subclass relationships (PointerEvent → MouseEvent → Event, etc.)
  • Global singletons: window and document available as top-level exports in spark_web.dart
  • Raw access: .raw property on all types provides access to underlying native objects on browser (null on server)

https://claude.ai/code/session_01S4gf6evvE9dAHPDFt6e8pa

claude and others added 14 commits February 11, 2026 07:10
…N naming

Introduces `spark_web`, a cross-platform Web API layer that mirrors MDN naming
exactly (Element, Node, Document, Window, etc.) so developers can rely on MDN
docs. On the browser, types wrap real `package:web` objects. On the server,
types are no-ops or provide Dart-native fallbacks (Storage backed by Map,
Crypto via dart:math, btoa/atob via dart:convert).

Key changes:
- New `packages/spark_web/` package with abstract interfaces, browser
  implementations (wrapping package:web), and server implementations
- Migrated spark_framework to use spark_web instead of the old stubs.dart
  noSuchMethod pattern — provides real type safety
- vdom_web.dart continues using package:web directly for performance
  (browser-only, behind conditional export)
- Deleted stubs.dart (563 lines of noSuchMethod-based stubs)
- Updated js_callback_stub, query_stubs, extensions, and tests

https://claude.ai/code/session_01S4gf6evvE9dAHPDFt6e8pa
- Fix innerHTML/outerHTML to handle JSAny <-> String conversion
- Fix hidden property to handle JSAny? <-> bool conversion
- Fix MutationObserverInit to pass non-nullable bool params with defaults
- Fix Console type ($Console in package:web) and use top-level console
- Fix setTimeout/setInterval to pass delay as JSAny?
- Export createMutationObserver/createEvent from spark_web barrel
- Remove Request/Response from hide list (no longer in spark_web)
- Remove unused imports (dart:js_interop, dart:async)

https://claude.ai/code/session_01S4gf6evvE9dAHPDFt6e8pa
Adds the Clipboard API matching MDN (navigator.clipboard):
- readText() → Future<String>
- writeText(data) → Future<void>

Browser impl wraps package:web Clipboard via navigator.clipboard.
Server impl stores text in memory (useful for testing).

https://claude.ai/code/session_01S4gf6evvE9dAHPDFt6e8pa
README includes usage examples, architecture overview, and a comprehensive
API coverage table tracking implemented vs TODO MDN Web APIs organized by
category (DOM Core, Elements, CSS, Window, Networking, Observers, etc.).

https://claude.ai/code/session_01S4gf6evvE9dAHPDFt6e8pa
Adds all missing event types:
- FocusEvent, WheelEvent, PointerEvent, TouchEvent, DragEvent
- AnimationEvent, TransitionEvent, CustomEvent

Adds all missing HTML element types:
- HTMLCanvasElement, HTMLMediaElement, HTMLVideoElement, HTMLAudioElement
- HTMLDialogElement, HTMLDetailsElement, HTMLSlotElement, HTMLIFrameElement
- HTMLTableElement, HTMLTableSectionElement, HTMLTableRowElement, HTMLTableCellElement
- HTMLHeadingElement, HTMLUListElement, HTMLOListElement, HTMLLIElement
- HTMLPreElement, HTMLHRElement, HTMLBRElement
- HTMLProgressElement, HTMLMeterElement, HTMLOutputElement

All types include abstract interfaces, server stubs, and browser
implementations wrapping package:web. DOM Core and DOM Elements
sections are now 100% complete.

https://claude.ai/code/session_01S4gf6evvE9dAHPDFt6e8pa
- Delete js_callback_web.dart / js_callback_stub.dart — inline browser
  event listener logic directly into vdom_web.dart
- Delete query_stubs.dart / query_web.dart — unused top-level query
  functions superseded by WebComponent.query() instance methods
- Delete adopted_styles_web.dart / adopted_styles_stub.dart — replace
  with cross-platform code using spark_web's CSSStyleSheet API
- Delete orphaned html/query_stubs.dart
- Add createCSSStyleSheet() factory to spark_web for constructable
  stylesheets on both server and browser
- Move stylesheet caching into web_component.dart using spark_web types
- Update adopted_styles_test to import from web_component.dart

7 files deleted, net -195 lines. All 342 tests pass.

https://claude.ai/code/session_01S4gf6evvE9dAHPDFt6e8pa
…ransfer abstractions

Replace all dynamic return types (except the intentional `raw` escape
hatch) with fully typed alternatives:

- Add Touch, TouchList, DataTransfer abstract interfaces to core.dart
- Add BrowserTouch, BrowserTouchList, BrowserDataTransfer wrappers
- Add ServerTouch, ServerTouchList, ServerDataTransfer no-op impls
- TouchEvent.touches/targetTouches/changedTouches now return TouchList
- DragEvent.dataTransfer now returns DataTransfer?
- CustomEvent.detail now returns Object? instead of dynamic
- Console methods now take Object? instead of dynamic
- History.state/pushState/replaceState now use Object?
- CustomElementRegistry.define/get now use Object/Object?
- HTMLCanvasElement.getContext returns Object?, toDataURL quality is num?

The only remaining `dynamic` in spark_web is on the `raw` getter —
the intentional escape hatch to the underlying platform object.

https://claude.ai/code/session_01S4gf6evvE9dAHPDFt6e8pa
…nd Canvas

- Add ElementDefinitionOptions class for CustomElementRegistry.define()
  with extends_ property (maps to JS 'extends' keyword)
- Add RenderingContext base type and CanvasRenderingContext2D with full
  typed API (drawing, paths, text, styles, transforms, pixel manipulation)
- Add TextMetrics and ImageData abstract types
- HTMLCanvasElement.getContext now returns RenderingContext? instead of Object?
- Browser implementations wrap package:web natives with proper type
  conversions
- History.state, Console, CustomEvent.detail remain Object? as they are
  inherently "any" in the web spec

https://claude.ai/code/session_01S4gf6evvE9dAHPDFt6e8pa
The contextId parameter is now a CanvasContextType enum instead of a
free-form string. The enum currently supports canvas2d, with comments
guiding contributors on how to add webgl, webgl2, webgpu, etc.

https://claude.ai/code/session_01S4gf6evvE9dAHPDFt6e8pa
Provides query() and queryAll() stubs that user @component classes can
mix in. This lets the user write `query('#my-el')` in their base class
without IDE errors. The generated code inherits the real implementations
from WebComponent, so the stubs are never called at runtime.

Usage:
  @component(tag: 'my-widget')
  class MyWidget with Queryable {
    Element render() => div(onClick: (_) {
      final el = query('#greeting');
    }, ['Click']);
  }

https://claude.ai/code/session_01S4gf6evvE9dAHPDFt6e8pa
Users no longer need `with Queryable` or any boilerplate. The top-level
query() and queryAll() functions serve as stubs for the user's @component
source file. In the generated code, WebComponent.query() (the instance
method) naturally shadows the top-level function.

Usage — zero ceremony:
  @component(tag: 'my-widget')
  class MyWidget {
    Element render() => button(onClick: (_) {
      final el = query('#greeting'); // just works
    }, ['Click']);
  }

https://claude.ai/code/session_01S4gf6evvE9dAHPDFt6e8pa
The generator now emits:
  import 'package:spark_framework/spark.dart' hide query, queryAll;

This ensures the generated class resolves query() and queryAll() to the
inherited WebComponent instance methods instead of the top-level stubs.
User base files still see the stubs for IDE type-checking.

https://claude.ai/code/session_01S4gf6evvE9dAHPDFt6e8pa
Both spark and spark_generator depend on spark_web, so changes in
packages/spark_web/ should trigger their test jobs.

https://claude.ai/code/session_01S4gf6evvE9dAHPDFt6e8pa
@kevin-sakemaer kevin-sakemaer marked this pull request as ready for review February 12, 2026 12:55
kevin-sakemaer and others added 3 commits February 12, 2026 13:58
The openapi tests create temp projects that depend on spark_framework
via path. Since spark_framework now depends on spark_web (also a local
package), the temp pubspecs need a dependency_overrides entry pointing
to spark_web's local path so dart pub get can resolve it.

https://claude.ai/code/session_01S4gf6evvE9dAHPDFt6e8pa
@kevin-sakemaer kevin-sakemaer merged commit aea06da into main Feb 12, 2026
5 checks passed
@kevin-sakemaer kevin-sakemaer deleted the claude/server-safe-web-api-vW7Lg branch February 12, 2026 18:05
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