**Last updated: October 2nd, 2026**

> 📘 Upgrading from 2.0.0-beta.1?
>
> See the [2.0.0-beta.2 changelog](/docs/2.0/changelog-2-0-0-beta-2) for changes since `2.0.0-beta.1`

## Highlights

- Photon, a new analytic vector renderer: paths, strokes, SVG, and text render directly in pixel shaders at any zoom or transform. Enabled by default on GPU Views.
- A new type-safe JavaScript API (`ultralight::js`), native DOM API (`ultralight::dom`), and declarative data bindings API (`ultralight::dom::data`).
- HTML5 `<video>` and `<audio>` playback (WebM, VP9, Opus).
- GPU-accelerated HTML5 Canvas support, `OffscreenCanvas`, canvas filters, and all SVG filters on the GPU.
- Block-compressed `.dds` textures in `<img>` and CSS (Pro edition and above).
- Text editing, IME composition, multi-format clipboard, and native editing conventions per platform.
- AppCore: a flexible panel layout replaces Overlay; windows gain custom chrome, native backdrop materials, per-pixel transparency, popups, and light/dark OS theming.
- Profiler and render-trace diagnostics (Perfetto), a `GCListener`, memory profiles, and automatic idle reclamation.
- The SDK ships as a CMake package (`find_package(Ultralight)`), and the reference platform code (GPU drivers, font loaders, file systems, clipboards, audio outputs) is now Zlib-licensed source in the package's `platform/` folder.

## Full Changelog (since v1.4)

### Major Improvements

**Graphics & Compositor:**

- Rewrite CPU renderer for better performance and accuracy
- Rewrite compositor to closely match upstream WebKit (now enabled by default)
- Apply the standard compositing policy on both renderers, so `will-change`, `translateZ(0)`, and painted canvases promote layers as they do in Safari and Chrome
- Add a per-View render FPS cap (`ViewConfig::max_render_fps` / `View::set_max_render_fps()`) that throttles `requestAnimationFrame`, CSS animations, smooth scroll, and paint as one rate
- Add display-locked frame pacing: animation clocks sample the physical display's presentation time, and `Renderer::RefreshDisplay()` accepts a target timestamp, a declared refresh rate, and a custom clock
- Add `View::set_visible()` to suspend off-screen Views (pauses animations, fires `visibilitychange`, and optionally throttles JavaScript timers)
- Render composited text sharply under animated transforms instead of resampling a raster
- Improve support for CSS, SVG, clipping, backgrounds, and filters across the board
- Improve box-shadow performance (closed-form Gaussian on both renderers, significantly faster on GPU) and memory usage
- Route rounded borders, ellipses, and rounded rects through an analytic primitive so they render resolution-independently
- Unify shader pipeline (HLSL is now ground-truth across all GPU renderers; stock shaders for every program ship in `platform/shaders`)
- Add single-channel (A8) render targets and explicit blend state per draw to the GPU driver interface
- Add `GPUDriver::GetDeviceCaps()` so drivers can report MSAA support, texture limits, compressed formats, and partial redraw
- Drop MSAA from compositor intermediate render targets (a 1080p blur filter drops from about 152 MB to about 17 MB of VRAM) and from the analytic vector path

**Photon (analytic vector graphics and text):**

- Add Photon, an analytic vector rendering pipeline for the GPU renderer: path fills, strokes, dashes, clips, and text are evaluated directly in the pixel shader from cached curve data instead of being tessellated
- Enable Photon by default (`Config::enable_photon`, `Config::enable_photon_text`); text at or above `Config::photon_text_min_px` (16 px) renders analytically on both renderers
- Render text on the CPU renderer from the same unhinted outlines with fractional placement, so both renderers produce near-identical text
- Render text under rotation, skew, and non-uniform scale sharply at every size
- Add analytic Gaussian box shadows and blurred CSS `text-shadow` on both renderers
- Add conic gradients, gradient spread modes (pad, reflect, repeat), and gradient and pattern brushes on strokes, SVG paint servers, and text

**Web Platform & CSS:**

- Add support for CSS `mask-image`
- Add support for CSS `mix-blend-mode`
- Add GPU acceleration for SVG filters: all 17 primitives (`feGaussianBlur`, `feDropShadow`, `feMerge`, `feComposite`, `feBlend`, `feColorMatrix`, `feComponentTransfer`, `feFlood`, `feOffset`, `feTile`, `feImage`, `feMorphology`, `feConvolveMatrix`, `feDisplacementMap`, `feTurbulence`, `feDiffuseLighting`, `feSpecularLighting`) run on GPU Views, as do SVG-content filters and `backdrop-filter: url()` chains
- Add `linearRGB` color interpolation in SVG filters (`color-interpolation-filters`)
- Add support for SVG `<mask>` references and SVG2 `context-fill` / `context-stroke`
- Add CSS `conic-gradient()`, `repeating-conic-gradient()`, and canvas `createConicGradient()`
- Add support for `background-clip: text`
- Add support for `background-repeat: space`
- Add support for `-webkit-text-stroke` and `CanvasRenderingContext2D.strokeText()`
- Add support for `image-rendering: pixelated` / `crisp-edges` and canvas `imageSmoothingEnabled` / `imageSmoothingQuality` (nearest sampling matches Chromium byte-for-byte on both renderers)
- Unprefix `backdrop-filter` (`-webkit-backdrop-filter` remains an alias)
- Add the `app-region` CSS property, `color-scheme` support with a per-View pin, and `prefers-color-scheme` driven by `Renderer::set_system_color_scheme()`
- Turn on interactive form validation: an invalid control blocks submission, fires `invalid`, and takes focus; `reportValidity()` is available (add `novalidate` for 1.4 behavior)
- Fix the UA stylesheet so `pre` and `code` size at 13 px and the default font size setting takes effect to match mainstream browsers

**Text & Fonts:**

- Enable kerning and ligatures (text is now shaped through HarfBuzz)
- Add WOFF2 web font support
- Complete variable-font support: `font-weight`, `font-stretch`, `font-style: oblique`, `font-optical-sizing`, `font-variation-settings`, and `@font-face` descriptor ranges
- Add color emoji (CBDT bitmap strikes and COLRv0 layers, including COLRv1 fonts with a COLRv0 fallback) rendered in color on both renderers
- Add synthetic bold and synthetic oblique for families without those faces
- Add font profiles (`Config::font_profile`: `Default`, `WindowsLike`, `MacOSLike`, `Light`) with per-knob overrides (`font_gamma`, `font_contrast`, `font_stem_darkening`, `font_embolden`, `font_blend_mode`); AppCore picks the host platform's profile and generic font families automatically
- Replace the per-glyph grid-fitting cascade with one glyph positioning policy at every size, so small text no longer shakes during scroll or animation
- Add the cursive, fantasy, and pictograph generic families and default font sizes to `ViewConfig`
- Open the requested face of a `.ttc` font collection: font loaders pass the face index through `FontFile::Create()`

**HTML5 Canvas:**

- Enable GPU-accelerated HTML5 Canvas (`HTMLCanvasElement`)
- Enable `OffscreenCanvas` API
- Enable `OffscreenCanvas` painting on WebWorker threads
- Add support for HTML5 Canvas filters (`CanvasRenderingContext2D.filter`)
- Add support for `willReadFrequently` hint on `CanvasRenderingContext2D` (allows readback, forces CPU rendering)
- Enable `createImageBitmap(canvas)` in JavaScript
- Share decoded image pixels and GPU textures across `drawImage()` calls instead of copying per draw

**Images & Compressed Textures (Pro edition and above):**

- Add block-compressed textures: `.dds` files (BC1, BC2, BC3, BC7) load in `<img>`, CSS `background-image`, canvas `drawImage()`, and `createImageBitmap()`, uploaded to the GPU as raw blocks with no decode step
- Upload BCn natively in the Direct3D 11, Direct3D 12, Metal, and OpenGL reference drivers, with a software decompression fallback (parallel, 4K BC7 in about 12 ms) for the CPU renderer and drivers without the format
- Fix downscaled image drawing on both renderers

**HTML5 Media (Pro edition and above):**

- Add HTML5 `<video>` and `<audio>` playback (VP9 video, Opus audio, WebM container)
- Add support for transparent video (VP9 alpha channel) in GPU and CPU renderers
- Add HTTP byte-range streaming for media with progressive buffering and preload support
- Add stylized media controls for `<video>` / `<audio>` elements
- Add the `AudioOutput` platform interface with reference implementations for Windows (WASAPI), macOS (CoreAudio), and Linux (PulseAudio)
- Add `Config::media_profile` (`Balanced`, `FavorMemory`, `FavorPerformance`)
- Select video frames against the display's presentation time and use the audio device clock for A/V sync

**JavaScript Bridge:**

- Add `ultralight::js`, a typed header-only C++ API for JavaScript interop: navigation-surviving API registries (`js::API`), typed function/property/constant bindings with automatic marshaling (aggregate structs and enums included), Promise-returning async bindings (coroutine `js::Task<T>` or callback-style `js::Resolver`), native class binding, events, weak references, a worker pool, and zero-copy ArrayBuffer / typed-array interop
- Add the ULJS C API (`<Ultralight/CAPI/CAPI_JS*.h>`), the language-neutral core the C++ layer is built on (usable directly from C or via P/Invoke; future official language bindings build on the same core)
- Add self-describing schema: `js::API::schema()` / `ulJSAPIGetSchema()` emit versioned JSON, and `tools/scripts/gen-typescript.py` generates TypeScript declarations from it
- Add per-registry diagnostics (Off/Warn/Strict, defaulting to `Config::diagnostics`) with did-you-mean suggestions, lossy-conversion warnings, and a new `NativeAPI` console message source, plus the `ul.diagnostics` page-side registry
- Add per-(registry, View) origin policy: app-content-only injection by default, `scheme://host[:port]` allowlist rules, and a per-View filter callback
- Add bindings that survive back/forward cache restore
- Extend the JavaScriptCore C API: `JSClassDefinition` version 1000 with per-class private data and `...Ex` callbacks, `JSObjectMakeFunctionWithData()`, UTF-8 batch property access, typed errors, promise inspection, `JSArrayBufferDetach()`, and external memory hints
- Remove AppCore's JSHelpers (`<AppCore/JSHelpers.h>`); the typed bridge replaces it (see the porting guide)

**DOM API & Data Bindings (all editions):**

- Add `ultralight::dom` (`<Ultralight/DOM.h>`) and the ULDOM C API, a native DOM API with the same names and syntax as the JavaScript DOM: documents, elements, text nodes, fragments, inline and computed styles, `classList`, `dataset`, forms and form-control selection, geometry and scrolling, events (capture, once, passive, delegation, synthetic dispatch), ranges, selections, and the window. Works with JavaScript disabled
- Add `dom::Triggers`, navigation-surviving DOM wiring: selector-addressed delegated listeners and DOM-ready hooks that apply to every page a View loads
- Add `ultralight::dom::data` (`<Ultralight/dom/data/Context.h>`) and the ULDOMData C API, declarative data binding from schema-described C++ models to `ul-*` markup and `{{path}}` templates, with two-way edits, actions, keyed lists, format pipes, and validators. A context belongs to your thread; `Sync()` publishes one atomic snapshot per tick
- Add `ultralight::URL`, `ultralight::JSON`, and `ultralight::Color` value types (with C mirrors), and `ultralight::Vector<T>`, an ABI-safe container for the library boundary
- Add `View::GetDOMDocument()`, `View::AttachDOMTriggers()`, `View::AttachDOMDataContext()`, and DOM-to-JavaScript interop (`dom::ToJS` / `dom::FromJS`)

**Text Editing, IME & Clipboard:**

- Add `View::editor()`, an `Editor` with a curated command set (`Execute()` / `CanExecute()`), text insertion, caret and selection geometry, and text-offset queries for native text-input integrations
- Add `EditorListener` with editable-state events (element kind, mutability, `inputmode`, `enterkeyhint`, bounds) for native IME and on-screen keyboard integration
- Add IME composition (`SetComposition()` with styled segments, `Commit`, `Finish`, `Cancel`), with reference IME handlers for Windows (IMM32) and macOS (NSTextInputClient) in AppCore
- Replace the plain-text `Clipboard` interface with multi-format `ClipboardData` payloads, so one copy carries `text/plain` and `text/html` together; AppCore's clipboards map CF_HTML and MIME-named formats on Windows and `text/plain` / `text/html` on macOS
- Add `ViewConfig::clipboard_read_policy` governing script-initiated paste
- Add `match_native_editing_behavior` (`ViewConfig` and AppCore `Settings`) so selection and caret behavior follow each host OS

**Scrolling & Input:**

- Rewrite smooth scroll animation using physics-based spring-mass system (matches macOS/iOS feel)
- Carry scroll positions at sub-pixel precision end to end, so composited scrolling is smooth at every device scale and `position: fixed` / `position: sticky` content no longer shakes at fractional scales
- Add back/forward mouse button support
- Add cursor coordinates to `ScrollEvent` for dispatching a scroll at a specific point
- Add `View::is_settled()` and `LoadListener::OnPageSettled()`, which fire once a page has stopped changing after load

**Performance & Memory:**

- Switch the WebCore and JavaScriptCore heap to bmalloc/libpas
- Add automatic idle memory reclamation (`Config::idle_gc_enabled`): a heavy page followed by navigation returns to the recycle floor on its own (800 MB to 225 MB within 30 s in the reference scenario)
- Add `Config::memory_profile` (`Balanced`, `LowMemory`, `Performance`) and `Config::max_heap_size`
- Add `Renderer::Recycle(RecycleMode)` (`Lightweight` is cheap enough to call every frame, `Full` suits idle periods) and make the automatic recycler run every `Config::recycle_delay` seconds
- Add the `GCListener` (Pro edition and above): `OnRequestGC()` lets you schedule the engine's collections onto a frame that hides the pause, with `Renderer::CollectNow()`, `gc_status()`, and `CodeDropMode`
- Reduce Windows private commit
- Park contended locks in the kernel instead of spinning
- Speed up style recalculation on custom-property-heavy pages 2-3x
- Reduce CSS and SVG filter chain memory usage
- Stop pinning AppCore's window thread to CPU 0 (whoops!)
- Increase performance in multiple profiled bottlenecks via improved parallelism

**Diagnostics & Profiling:**

- Add `Config::diagnostics`: a developer mode, the JavaScript and DOM diagnostics levels, and a minimum log level
- Add the `Fatal` and `Debug` log levels, and start every engine log message with a category tag (`[render]`, `[net]`, ...)
- Add a memory usage reporting API (`Renderer::GetMemoryUsage()` / `LogMemoryUsage()`) covering fonts, bitmaps, textures, caches, GPU resources by purpose, process working set and commit, and the allocator heaps
- Add Profiler platform API and Render Trace diagnostic system (runtime-toggleable tracing, jank detection) (Profiler: Pro edition and above; Render Trace: Enterprise edition and above)
- Add a built-in Perfetto backend (`CreateProfilerPerfetto()`) and `Settings::enable_profiler` for AppCore apps
- Add `App::GetGPUMemoryStats()` reporting driver-tracked GPU allocations and OS-reported process video memory
- Add compositor debug overlays (`ViewConfig::enable_compositor_debug_info`) and `Config::paint_full_layers`
- Trace every GPU driver call, GC cycle, and recycle pass as its own scope with resource attribution

**Networking & Security:**

- Upgrade LibreSSL from 3.0.2 to 3.9.2 (enables TLS 1.3 and patches many security vulnerabilities)
- Enable Public Suffix List for improved site compatibility and security
- Add `Config::use_system_certificate_store` (Windows) to verify TLS against the OS root store instead of the bundled `cacert.pem`
- Add `Config::ignore_ssl_errors` for development against self-signed certificates
- Increase `CURL_MAX_WRITE_SIZE` for better HTTP/2 throughput
- Report the real host platform in the default User-Agent string (1.4 reported Windows on every OS)

**AppCore (Windows, Layout & Chrome):**

- Replace `Overlay` with a flexible panel layout: `Panel`, `Container` (rows and columns with resizable dividers), and a `Foreground` layer of floating panels with anchors and auto-dismissal, buildable in one `Window::BuildLayout()` expression, with a full C API (`<AppCore/CAPI/CAPI_Layout.h>`)
- Add custom window chrome: `WindowFlags::CustomChrome`, hit-test regions, CSS `app-region`, window buttons, `BeginDragMove()` / `BeginDragResize()`, caption color, and corner styles
- Add native backdrop materials (`Window::SetBackdrop()`: Window, Popup, Overlay; Frosted or Matte; theme pin; Vivid rendition on Windows)
- Add per-pixel transparent windows (`WindowFlags::Transparent`) and `Window::SetBackgroundColor()`
- Add popup windows (`Window::CreatePopup()`) with engine-owned auto dismissal and `WindowFlags::Popup`
- Add window state control (`Maximize`, `Minimize`, `Restore`) and the `OnWindowStateChanged`, `OnActivationChanged`, `OnBackdropChanged`, `OnDismiss`, `OnClear`, and `OnPaint` listener callbacks
- Move window and monitor geometry to logical pixels (`double`), with `device_width()` / `device_height()` for device pixels and `LogicalToDevice()` / `DeviceToLogical()` conversions
- Add multi-monitor enumeration (`App::monitor_count()` / `App::monitor()`) and drive each View's refresh from its monitor's hardware display link, presenting capped frames on the display's frame lattice
- Add `App::RunOnce()`, `PostTask()`, `PostDelayedTask()`, and `SetInterval()` for running AppCore from an existing main loop
- Add idle detection: `AppListener::OnIdle()`, `App::is_idle()`, `App::thread_utilization()`, and the `Settings` idle thresholds
- Add `Window::TakeScreenshot()`, `Window::SetIcon()`, `Settings::app_icon`, `Settings::device_scale_override`, `Settings::headless_paint_fps`, and `Settings::full_speed_loop`
- Add the window-level editable-state stream (`Window::OnEditableStateChange()`) and forward the hovered panel's page cursor automatically
- Present and upload only the dirty region of CPU-rendered windows
- Add `Window::ShowWhenReady()` to show a new window once its pages have finished loading
- Update the Linux x64 port: OpenGL driver, GPU-less CPU windows, PulseAudio, X11 clipboard, and XIM input methods (X11; Wayland desktops through XWayland)
- Update the macOS port: Metal driver at parity with Direct3D 11 (two frames in flight, async presents), CADisplayLink / CVDisplayLink refresh, backdrop materials, popups, custom chrome, IME, CoreAudio, retina-correct text, and a GPU-less `force_cpu_renderer` path
- Publish the reference platform implementations (GPU drivers and contexts, font loaders, file systems, clipboards, audio outputs, shaders) as Zlib-licensed source in the SDK's `platform/` folder and the `ultralight-ux/Platform` repository

**SDK, Build & Packaging:**

- Ship a CMake package: `find_package(Ultralight)`, the `Ultralight::AppCore` / `Ultralight` / `WebCore` / `UltralightCore` targets, and `ultralight_copy_runtime_files()` to stage the runtime files
- Flatten the package layout (`bin/`, `lib/`, `include/`, `resources/`, `inspector/`, `samples/`, `tools/`, `platform/`, `license/`)
- Raise the toolchain floor to C++20 (Visual Studio 2022 or later on Windows, macOS 11.3 or later, GCC 11 or later or a C++20 clang on Linux)
- Build the Linux libraries against glibc 2.31 with a private copy of libc++, so apps can use either libstdc++ or libc++
- Add the generated `<Ultralight/Config.h>` with `UL_HAS(x)` and `UL_EDITION_AT_LEAST(x)` for compile-time edition and feature checks
- Enable Profile-Guided Optimization (PGO) and LTO in Release builds
- Switch to zlib-ng v2.3.2 for faster SIMD-accelerated decompression
- Patch ICU to avoid rare `call_once` deadlock during early init on Windows
- Adopt SemVer versioning scheme

### Major API Changes

**Breaking Changes:**

- The C++ headers now require C++20 (Visual Studio 2022 or later on Windows)
- `Overlay` and the `ulOverlay*` C API are removed; `Panel` replaces it (`Window::AddPanel()` / `AdoptPanel()`, `ulWindowAddPanel()`), and the layout tree arranges panels in rows, columns, and a floating foreground layer
- `<AppCore/JSHelpers.h>` is removed; use `ultralight::js`
- `Window` and `Monitor` geometry is logical pixels as `double`: `Window::width()` / `height()` and `Monitor::width()` / `height()` reported device pixels in 1.4. `screen_width()`, `screen_height()`, `ScreenToPixels()`, and `PixelsToScreen()` are removed
- `WindowFlags` is an `enum class` (`WindowFlags::Titled | WindowFlags::Resizable`)
- `WindowListener::OnKeyEvent()`, `OnMouseEvent()`, and `OnScrollEvent()` take a leading `Window*`; `OnResize()` receives logical `double` dimensions
- `Clipboard::ReadPlainText()` / `WritePlainText()` are replaced by `Read()` / `Write()` over `ClipboardData`; the C `ULClipboard` struct changed shape
- `Surface::Scroll()` is a new required method on custom surfaces (`ULSurfaceDefinition` gains a `scroll` callback, `NULL` is handled)
- `GPUDriver::CreateTexture()` takes a `flags` parameter and `UpdateTexture()` takes a `dirty_rect`; seven shader programs are required (`FilterBasic`, `FilterBlur`, `FilterDropShadow`, `FillPhoton`, `FillPhotonGrid` join `Fill` and `FillPath`); `GPUState` gains blend state, `texture_4_id`, and `uniform_integer[8]`; `CommandType::Flush` and `VertexBufferFormat::_2f_2ui` are new
- Every 1.4 C callback setter gained a trailing `destroy_user_data` parameter (the thirteen `ulViewSet*Callback` functions, `ulAppSetUpdateCallback()`, `ulWindowSetCloseCallback()`, `ulWindowSetResizeCallback()`)
- `LogLevel` gains `Fatal` (now the first value) and `Debug` (the last), so `Error`, `Warning`, and `Info` changed value; the C `ULLogLevel` changed the same way
- `RefPtr`'s boolean conversion is `explicit`
- `KeyEvent` is no longer trivially copyable
- `Config::animation_timer_delay` and `Config::scroll_timer_delay` are removed (use `ViewConfig::max_render_fps`)
- `ulCreateScrollEvent()` takes two extra cursor-coordinate parameters (pass `-1, -1` for 1.4 behavior)

**New Headers:**

- `<Ultralight/JS.h>` and `<Ultralight/js/*.h>`: the JavaScript bridge
- `<Ultralight/DOM.h>` and `<Ultralight/dom/*.h>`: the DOM API
- `<Ultralight/dom/data/Context.h>`: data bindings
- `<Ultralight/Editor.h>`, `<Ultralight/ClipboardData.h>`, `<Ultralight/Color.h>`, `<Ultralight/URL.h>`, `<Ultralight/JSON.h>`, `<Ultralight/Vector.h>`, `<Ultralight/GCListener.h>`, `<Ultralight/HolderTraits.h>`, `<Ultralight/OriginRules.h>`, `<Ultralight/DiagnosticsLevel.h>`
- `<Ultralight/platform/AudioOutput.h>` (Pro edition and above), `<Ultralight/platform/Profiler.h>` (Pro edition and above)
- `<Ultralight/Config.h>` (generated edition and feature macros; distinct from `<Ultralight/platform/Config.h>`)
- `<AppCore/Layout.h>` and `<AppCore/layout/*.h>`: the layout tree
- `<AppCore/GPUMemoryStats.h>`
- C API: `CAPI_JS*.h` (5), `CAPI_DOM*.h` (9, not included by `<Ultralight/CAPI.h>`), `CAPI_Editor.h`, `CAPI_Color.h`, `CAPI_URL.h`, `CAPI_AudioOutput.h`, `CAPI_Profiler.h`, `CAPI_ThreadFactory.h`, `<AppCore/CAPI/CAPI_Layout.h>`

**New Enums:**

- `BlendFactor` (Zero, One, SrcColor, InvSrcColor, SrcAlpha, InvSrcAlpha, DestColor, InvDestColor, DestAlpha, InvDestAlpha, SrcAlphaSaturate)
- `BlendEquation` (Add, Subtract, RevSubtract, Min, Max)
- `GPUTextureFlags` (RenderTarget, Antialiased, Immutable) and `GPUCompressedFormat`
- `RecycleMode` (Lightweight, Full) and `CodeDropMode` (Nothing, Linked, All)
- `MemoryProfile` (Balanced, LowMemory, Performance)
- `DiagnosticsLevel` (Off, Warn, Strict, Auto)
- `MediaProfile` (Balanced, FavorMemory, FavorPerformance) (Pro edition and above)
- `FontProfile` (Default, MacOSLike, WindowsLike, Light) and `FontBlendMode` (Profile, GammaCorrected, SRGB)
- `ColorScheme` (Auto, Light, Dark) and `ClipboardReadPolicy` (Deny, AllowForAppContent, Allow)
- `EditorCommand`, `EditableType`, `InputMode`, `EnterKeyHint`
- AppCore: `WindowFlags` (now scoped, with `Transparent`, `Popup`, `CustomChrome`), `WindowState`, `WindowButtons`, `BackdropMaterial`, `BackdropVariant`, `BackdropTheme`, `BackdropRendition`, `BackdropState`, `CornerStyle`, `HitRegionRole`, `ResizeEdge`, `Dismiss`, `FocusPolicy`, `AnchorCorner`, `AnchorAlign`, `AnchorFit`

**New Platform Interfaces:**

- `AudioOutput` (set via `Platform::set_audio_output()`): embedder-implementable audio sink for HTML5 media (Pro edition and above)
- `Profiler` (set via `Platform::set_profiler()`): embedder-implementable performance profiler (Pro edition and above)
- `ThreadFactory` is now available from C (`ulPlatformSetThreadFactory()`)

**New API Functions:**

- `Renderer::RefreshDisplay(display_id, target_timestamp)`, `Renderer::SetDisplayRefreshRate()`, `Renderer::SetDisplayUsesCustomClock()`
- `Renderer::PostTask()` / `PostDelayedTask()` (from any thread), `Renderer::Recycle(RecycleMode)`, `Renderer::GetMemoryUsage()`
- `Renderer::set_system_color_scheme()` / `system_color_scheme()`
- `Renderer::set_gc_listener()`, `gc_status()`, `CollectNow()`, `Recycle(RecycleMode, CodeDropMode)` (Pro edition and above)
- `Renderer::BeginRenderTrace()` / `EndRenderTrace()` / `is_render_trace_active()` (Enterprise edition and above)
- `View::set_visible()` / `is_visible()`, `View::set_max_render_fps()` / `max_render_fps()`, `View::set_preferred_color_scheme()`, `View::is_settled()`, `View::Resize(width, height, device_scale)`
- `View::editor()`, `View::set_editor_listener()`, `View::GetJSContext()`, `View::AttachJSAPI()` / `DetachJSAPI()`, `View::GetDOMDocument()`, `View::AttachDOMTriggers()`, `View::AttachDOMDataContext()`
- `View::set_compositor_debug_info_enabled()` / `compositor_debug_info_enabled()`
- `LoadListener::OnPageSettled()`, `EditorListener` (`OnChangeEditableState()`, `OnUpdateComposition()`, `OnDiscardComposition()`)
- `Surface::Scroll()`, `Surface::ShiftPixels()`, `Surface::LockPixelsSafe()`, `Surface::dirty_rect_count()` / `dirty_rect()`
- `GPUDriver::GetDeviceCaps()`
- `FontFile::Create(path_or_buffer, face_index)` and `FontFile::face_index()`
- `Bitmap::is_compressed()`, `Bitmap::MaxDimension`, `GetBitmapFormatInfo()`, `IsCompressedFormat()`
- `WeakPtr<T>`, `WeakRefCounted`, `MakeRefPtr<T>()`, `Rect::FromXYWH()`
- `ul_malloc()` / `ul_free()` and the aligned and realloc variants (the library allocator, exported)
- `CreateProfilerPerfetto()` / `DestroyProfilerPerfetto()` (Pro edition and above)
- `App::RunOnce()`, `App::PostTask()` / `PostDelayedTask()` / `SetInterval()`, `App::is_idle()` / `thread_utilization()`, `App::monitor_count()` / `monitor()`, `App::GetGPUMemoryStats()`, `App::is_profiler_active()` / `profiler_trace_path()`, `AppListener::OnIdle()`
- `Window::CreatePopup()`, `Window::ShowWhenReady()`, `Window::AddPanel()` / `AdoptPanel()` / `BuildLayout()` / `layout()` / `foreground()` / `FindPanel()` / `focused_panel()` / `ClearFocus()`, `Window::SetBackdrop()`, `Window::SetBackgroundColor()`, `Window::SetCaptionColor()`, `Window::SetCornerStyle()`, `Window::SetHitTestRegions()`, `Window::SetWindowButtons()`, `Window::BeginDragMove()` / `BeginDragResize()`, `Window::Maximize()` / `Minimize()` / `Restore()`, `Window::SetIcon()`, `Window::TakeScreenshot()`, `Window::Invalidate()`, `Window::device_width()` / `device_height()`, `Window::LogicalToDevice()` / `DeviceToLogical()`, `Window::OnFocusChange()` / `OnEditableStateChange()`
- `Monitor::device_width()` / `device_height()`
- JavaScriptCore: `JSObjectMakeFunctionWithData()`, `JSObjectGetPropertiesUTF8()` / `SetPropertiesUTF8()` / `MakeWithPropertiesUTF8()` / `GetOwnEntriesUTF8()` / `DefinePropertyUTF8()` / `DefineAccessorUTF8()`, `JSObjectFreeze()`, `JSObjectMakeTypedError()`, `JSValueGetErrorDetails()`, `JSValueIsFunctionObject()`, `JSPromiseGetStatus()` / `GetResult()` / `MarkAsHandled()`, `JSArrayBufferDetach()` / `IsDetached()`, `JSObjectSetExternalMemoryHint()`, `JSClassGetPrivate()` / `SetPrivate()`

**Changes to Existing API:**

- `View::LockJSContext()`, `View::JavaScriptVM()`, and `View::EvaluateScript()` now accept an optional `frame` parameter to target a specific frame
- `GPUState` struct has new fields: `blend_src_factor`, `blend_dst_factor`, `blend_equation`, `texture_4_id`, `uniform_integer[8]`
- `ShaderType` has five new values, `VertexBufferFormat` has `_2f_2ui` (with `Vertex_2f_2ui`), and `CommandType` has `Flush`
- `BitmapFormat` enum has eight new values: `RG8_UNORM`, `BC1_UNORM`, `BC2_UNORM`, `BC3_UNORM`, `BC7_UNORM`, `RGBA16F`, `RGBA16UI`, `RGBA32F`
- `MouseEvent::Button` has `kButton_Back` and `kButton_Forward`; `ScrollEvent` has `x` and `y` (default `-1`)
- `MessageSource` has `kMessageSource_NativeAPI`
- `Bitmap` derives from `WeakRefCounted`
- `ViewConfig` struct has new fields: `background_color`, `preferred_color_scheme`, `enable_compositor_debug_info`, `enable_canvas_filters` (default true), `match_native_editing_behavior`, `clipboard_read_policy`, `enable_hidden_timer_throttling`, `max_render_fps`, `allow_universal_access_from_file_urls` (default true), `javascript_can_open_windows_automatically`, `font_family_cursive`, `font_family_fantasy`, `font_family_pictograph`, `font_size_default`, `font_size_fixed`
- `ViewConfig::enable_compositor` default changed from false to true (compositor enabled by default)
- `ViewConfig::user_agent` defaults to `ULTRALIGHT_USER_AGENT`, which reports the host platform
- `Config` struct has new fields: `font_profile`, `font_blend_mode`, `font_contrast`, `font_stem_darkening`, `font_embolden`, `enable_photon`, `enable_photon_text`, `photon_text_min_px`, `page_settle_delay`, `page_settle_cycles`, `page_settle_tick`, `idle_gc_enabled`, `idle_gc_idle_time`, `memory_profile`, `max_heap_size`, `ignore_ssl_errors`, `use_system_certificate_store`, `paint_full_layers`, `resource_sample_interval`, `diagnostics`, `media_profile` (Pro edition and above)
- `Config::memory_cache_size` default changed from 64 MiB to 256 MiB
- `Config::recycle_delay` default changed from 4.0 to 0.5 seconds and is now the automatic recycle interval; 0 disables the automatic recycler
- `Config::font_gamma` default changed from 1.8 to 0 (inherit from `font_profile`); `Config::min_large_heap_size` default changed from 32 MiB to 0 (derive from `memory_profile`)
- `Settings` struct has new fields: `device_scale_override`, `auto_font_profile`, `auto_font_families`, `match_native_editing_behavior`, `idle_threshold`, `sustained_idle_time`, `idle_utilization_threshold`, `enable_profiler`, `headless_paint_fps`, `full_speed_loop`, `sync_animations_to_present`, `app_icon`
- Engine log messages start with a category tag, and `Debug` messages are not sent to the `Logger` unless `Config::diagnostics.min_log_level` is lowered
- `Surface::set_dirty_bounds()` adds to the dirty area instead of replacing it
- `FontFile` gains a pure virtual `face_index()` (only code that subclasses `FontFile` is affected)
- `JSValueGetType()` returns the new `kJSTypeBigInt` for a BigInt (it returned `kJSTypeObject`)
- `Window::MoveTo()`, `x()`, and `y()` use global screen coordinates spanning all monitors (1.4 was relative to the window's monitor)
- `<AppCore/AppCore.h>` no longer includes `<JavaScriptCore/JavaScript.h>` transitively
- `JSClassDefinition` gained `privateData` and the `...Ex` callback union (version-0 definitions work unchanged; recompile)
- `UL_COMPILER_GCC_LIKE` is a presence macro (`#if defined(...)`)
- C API at full parity with the C++ API: every `Config` field has a `ulConfigSet*` setter, and about 590 new functions cover the DOM, JavaScript, Editor, Layout, URL, Color, and window surfaces

**Deprecated:**

- `Renderer::RenderOnly()` and `ulRenderOnly()` (use `Render()`, which skips hidden Views, and hide a View with `View::set_visible(false)`)

**Removed:**

- `Overlay` (`<AppCore/Overlay.h>`) and the `ULOverlay` C API, replaced by `Panel` (`<AppCore/layout/Panel.h>`, `ULPanel`)
- `<AppCore/JSHelpers.h>` and everything it declared
- `Settings::load_shaders_from_file_system` and `ulSettingsSetLoadShadersFromFileSystem` (the setting had no effect; shaders are always loaded from memory)
- `Config::animation_timer_delay`, `Config::scroll_timer_delay`, `ulConfigSetAnimationTimerDelay()`, `ulConfigSetScrollTimerDelay()`
- `Clipboard::ReadPlainText()`, `Clipboard::WritePlainText()`
- `Window::screen_width()`, `screen_height()`, `ScreenToPixels()`, `PixelsToScreen()` and their C mirrors

### Major Bugfixes

**GPU Renderer:**

- Fix issue with premultiplied alpha being applied twice when drawing glyphs in GPU renderer
- Fix issue with transforms not being applied to glyphs when drawing with GPU renderer
- Fix issue where inverse clipping masks were ignored in GPU renderer
- Fix issue with clipping, box shadows, drop shadows, and blurs in GPU renderer
- Fix clipping issue in `CanvasGPU` (scissor rect wasn't being offset in created layers)
- Fix Image synchronization issue in GPU renderer
- Fix GPU stroke rendering for collinear path segments
- Fix nested clips on the GPU canvas blanking the clipped content
- Fix `globalCompositeOperation` being ignored inside `ctx.clip()`, and destination-out / source-atop / xor / source-out falling through as source-over on direct GPU canvas draws
- Fix `clearRect()` ignoring an active path clip on the GPU canvas
- Fix vector content inside opacity, mask, clip-path, and blend groups rendering without edge antialiasing
- Fix pooled render-target reuse corrupting transparency layers on some driver stacks (block dropouts on NVIDIA D3D11)
- Fix a draw sampling the texture that backs its own render target, which D3D11 and D3D12 silently voided
- Fix an access violation in the D3D12 driver on blend modes with a `*_COLOR` factor (such as Screen)
- Fix CSS repeating gradients clamping at 11 stops on the GPU
- Fix GPU radial gradients using an approximation instead of the exact two-point conical solve
- Fix dashed strokes at small user units (lost dots, dash-phase drift, fused subpaths) and seams at dash boundaries
- Fix an open path starting at the origin losing its first point, which cropped the leading half of rules and progress tracks
- Fix canvas-backed patterns rendering blank for dense fill regions
- Fix a hard crash when a box shadow spanning an extreme area exceeded `Bitmap::MaxDimension`
- Bound GPU path tessellation so pathological paths degrade instead of filling the geometry buffer
- Replace the modal message boxes the D3D11 and D3D12 drivers showed on GPU resource failures with logger warnings

**CPU Renderer:**

- Fix issue with `DrawCanvas` not applying alpha when drawing with CPU renderer
- Fix canvas `globalAlpha` and SVG `stroke-opacity` painting fully opaque on the CPU renderer
- Fix canvas full-canvas composite operations (`source-in`, `source-out`, `destination-in`, `destination-atop`) rendering blank on the CPU canvas
- Fix a crash and diagonal shear in CPU SVG reference filters when the filtered region width was not a multiple of 4 pixels
- Fix a transparent CPU View or popup hanging on first paint
- Fix issue with CPU renderer not using AVX2 during runtime when available

**Canvas:**

- Fix issue drawing CPU-backed ImageBuffers to GPU-backed GraphicsContext (texture corruption)
- Fix `CanvasRenderingContext2D.filter` on detached canvases
- Fix crash in `CanvasGPU.cc` when drawing GPU canvases to other GPU canvases
- Fix crash from unmatched save/restore in GPU canvas replay
- Fix canvas-source copies on the GPU backend (point-in-time snapshots, `OffscreenCanvas` placeholder commits, transferring contextless canvases)

**Compositor & TextureMapper:**

- Fix GPU `BitmapTexturePool` reusing textures while deferred commands still reference them
- Fix issue with texture mapper updating tile with fractional device scale
- Fix issue with `TextureMapper` layers being clipped to document bounds
- Fix stale tiles being retained across a tile-layout change, which composited content twice and double-blended transparent Views
- Fix `clip-path` rendering blank on composited layers and on SVG roots
- Fix potential overflow in `TextureMapperTiledBackingStore::createOrDestroyTilesIfNeeded`
- Fix issue where logical frame rect wasn't properly rounded up with fractional device scale
- Fix black flash in compositor (force clear to white when View is not transparent)
- Fix flicker during `View::Resize`
- Fix inline text not repainting when block dimensions are unchanged
- Fix re-entrant layer flush requests being dropped, which caused occasional partial repaints
- Fix antialiasing being silently stripped from content tiles when the texture pool returned a single-sample intermediate
- Fix the backdrop-filter snapshot including content painted above the element
- Fix overlay scrollbars painting over elements stacked above their scroller

**CSS, Layout & Text:**

- Fix drawing issue with `background-clip: text`
- Fix `background-clip: text` clipping under opacity
- Fix CSS transitions losing to `!important` declarations (transitions now take precedence per the CSS cascade spec)
- Fix dashed and dotted straight borders painting solid
- Fix thin rounded borders dropping a side, outlines on pill-shaped boxes painting square corners, and collapsed table borders disappearing at fractional device scales
- Fix SVG gradient rendering (apply gradient-space transform) and SVG `spreadMethod` being dropped
- Fix dashed SVG circles and ellipses (including `pathLength` progress rings) rendering solid
- Fix SVG markers placed at subdivision vertices instead of authored vertices, and markers dropped from gradient-stroked paths
- Fix SVG `clipPath` shapes in `objectBoundingBox` units clipping with visible facets
- Fix bounding-box reporting for individual SVG `<tspan>` elements
- Fix SVG text fill covering a neighboring glyph's stroke
- Fix gradient and pattern brushes on SVG text stroke and canvas `strokeText()` rendering black
- Fix undefined SVG filter inputs failing the whole filter, filter output sized to the content box instead of the filter region, and `feConvolveMatrix` bias being ignored on the GPU
- Fix CSS basic filters being applied in premultiplied space (color correctness)
- Fix CSS non-separable blend modes (HSL, soft-light) to match the W3C spec
- Fix stroke state (dash, cap, join, miter) leaking between elements
- Fix sizing of absolutely-positioned replaced elements from `inset`
- Fix variable-font advance widths
- Fix macOS system font families always opening face 0 (all weights rendered the first face with synthetic bold and oblique) and Apple Color Emoji sitting about 1/8 em too high
- Fix COLRv1 color fonts rendering as monochrome outlines
- Fix a family inside a `.ttc` font collection rendering the collection's first face
- Fix `system-ui` and `BlinkMacSystemFont` falling through to the fallback font on macOS
- Fix the engine font cache serving the wrong face on a hash collision
- Fix crash with `color-mix()` in color property
- Fix hang in CSS gamut mapping with NaN inputs
- Fix progressive WebP decode corruption (large WebP images intermittently rendering blank or with doubled semi-transparent regions)

**Stability & Crashes:**

- Fix crash in path triangulation
- Fix use-after-free in the texture atlas during recycling
- Fix crash during resource loads
- Fix JS engine crash after idle footprint shrink in pages using JS modules
- Fix font thread-race crashes under WebWorker / OffscreenCanvas
- Fix a stack-overflow crash when a thread's first park was contended (customer map-search crash)
- Fix an intermittent hang at process exit (roughly one in 15-30 launches) in the allocator's thread-local cache teardown
- Fix a hang under heavy JavaScript JIT tier-up where a worker thread exiting mid-registration orphaned a global lock
- Fix the renderer freezing when a page constructed `Intl.DateTimeFormat` for a locale absent from the trimmed ICU data
- Fix a crash in the matched-declarations cache sweep when a style resolver was destroyed mid-iteration (reproducible on Reddit)
- Fix `ReportingObserver` keeping whole documents alive after navigation (about 24 detached documents leaked per StackOverflow visit)
- Fix a crash after destroying and re-creating a Renderer (ICU data ownership)
- Fix a crash in `View::LockJSContext()` and `View::JavaScriptVM()` on Views with JavaScript disabled (both now return null)
- Fix a View's Surface being destroyed through its destructor instead of `SurfaceFactory::DestroySurface()`
- Fix a teardown race where a worker thread could still be releasing engine objects while the painter was destroyed
- Fix `Config::min_large_heap_size` and `min_small_heap_size` never reaching JavaScriptCore

**Media:**

- Fix seeks that never finalized when the target was past the last video frame
- Fix a seek that resumed into the paused state never showing the post-seek frame
- Fix audio remaining audible ahead of the picture after a scrub, and a false `ended` after a seek
- Fix `canPlayType()` advertising Vorbis, for which no decoder exists

**Input & Scrolling:**

- Fix numerical instability in smooth scroll at low framerates
- Fix scroll wheel events not working properly with Retina DPI scaling on macOS
- Fix composited scroll advancing in groups of device pixels at scales other than 1.0
- Fix `position: fixed` and `position: sticky` content drifting or shimmering during fractional-scale scroll
- Fix issue where hover state wouldn't be updated when mouse leaves a View
- Fix `GetKeyFromVirtualKeyCode` to map space, escape, numpad, and special keys per W3C spec
- Fix `ClearFocus()` leaving a pending focus grant armed
- Fix right-click not dispatching `contextmenu` on the page
- Fix Ctrl- and Shift-click when key events are built from Windows messages
- Fix the legacy `wheelDelta` reading about 11520 per notch instead of 120
- Fix `gamepad.id` being empty (the gamepad name was reported in `gamepad.mapping`)

**View, Session & Downloads:**

- Fix `window.close()` never reaching `ViewListener::OnRequestClose()`, `View::CancelDownload()` not stopping the transfer, and `OnUpdateHistory()` not firing
- Fix every navigation download reporting id 0
- Fix in-memory Sessions writing `localStorage` to disk and sharing it with any same-named Session

**C API:**

- Fix `ulCreateRenderer()` returning a non-NULL handle when the renderer failed to create
- Fix a second `ULView` handle for the same View turning off the callbacks set through the first
- Fix `ulViewSetCreateInspectorViewCallback()` never firing

**Platform & Build:**

- Fix issue on Windows where idle run loop wouldn't wake on external `SendMessage()` call
- Fix a timer destroying itself from its own callback having its `user_data` freed while the callback ran
- Fix `App::Create()` installing the built-in profiler over one the embedder had set
- Fix the reference `FileLogger` writing from several threads without a lock
- Fix POSIX absolute paths failing to load through `file:` URLs
- Fix `Lock` having a layout that varied with the consumer's `NDEBUG` setting
- Fix SDK build presets defaulting to Debug under the Visual Studio multi-config generator
- Fix `StringSTL`'s `Convert()` failing to compile for string literals
