Skip to content

Latest commit

 

History

History
172 lines (147 loc) · 14.9 KB

CONFORMANCE.md

File metadata and controls

172 lines (147 loc) · 14.9 KB

Conformance with widget-sandbox v0.6.0

widget-sandbox froze widget profile 1 in v0.1.0, and every release since ships a conformance set that every client runs on its renderer (packages/conformance/CLIENTS.md there; what each release changed is in its docs/changes.md). This page tracks TwidgetCore against it: what the contract asks of the renderer, and what the set reports, item by item.

Measured with tools/conformance (Windows, the Direct2D painter on WARP, 390 CSS px, DPR 2) and the set's tools/report.mjs. The verdicts that come from the widgets' own checks (layout, computed style, events) hold for TwidgetCore on every platform; the pixel stages judge the Direct2D painter only, and each platform's painter needs its own run.

Where it stands

Date Commit In profile: pass / partly / fail / untested Out of profile: neutralised / not yet / fail
2026-10-18 f649455 (contract) 90 / 49 / 24 / 10 69 / 3 / 8
2026-10-18 3420fc5 (computed style) 111 / 38 / 14 / 10 73 / 2 / 5
2026-10-18 099f3cb (inline boxes) 120 / 32 / 11 / 10 73 / 2 / 5
2026-10-18 image load and error (E1) 121 / 32 / 10 / 10 73 / 2 / 5
2026-10-19 the small fixes (Q2, E5, E6, L5, L10, L12, O1–O3, sans-serif) 129 / 26 / 8 / 10 79 / 1 / 0
2026-10-19 events and animations (E2–E4, A1, A2, L7) 135 / 21 / 7 / 10 79 / 1 / 0
2026-10-19 layout and style in the fork (L1–L13, O4) 148 / 9 / 6 / 10 80 / 0 / 0
2026-10-19 SVG queries and painting (Q3, P1–P9) 158 / 5 / 0 / 10 80 / 0 / 0
2026-10-20 the Swift renderer's text, border, canvas and image fixes 159 / 4 / 0 / 10 80 / 0 / 0
2026-10-20 widget-sandbox main 7654e69 (live collections, Web Animations state before a query); Intl on the system's ICU (IcuIntl) 162 / 1 / 0 / 10 80 / 0 / 0
2026-10-21 v0.1.1 (selection moves into the profile): the profile's UA layer, text tokens, user-select, relative offsets on inline elements 163 / 1 / 0 / 10 79 / 0 / 0
2026-10-23 v0.1.2 (dblclick and contextmenu move into the profile): 163 / 2 / 2 / 9 before; the gesture policy in the renderer, the runner's presses through a bubble 166 / 1 / 0 / 9 78 / 0 / 0
2026-10-24 v0.1.3 (vertical drags are the chat's, setResizeAnchor, transforms in % and calc()): 165 / 3 / 0 / 10 before; calc() of % and px inside a transform 167 / 1 / 0 / 10 78 / 0 / 0
2026-10-26 v0.2.1 (the bot's state, requests, the message context; nothing new for the renderer): 170 / 4 / 0 / 14 before; the runner creates the host as the test's kind of message and answers as the test bot 173 / 1 / 0 / 14 78 / 0 / 0
2026-10-26 v0.2.2 (haptics need a touch: host glue only) 173 / 1 / 0 / 14 78 / 0 / 0
2026-10-27 v0.2.3 (the shim converts canvas arguments and loads images a script creates; pictures follow profile 1) 173 / 1 / 0 / 14 78 / 0 / 0
2026-11-02 v0.6.0 (the renderer counts heavy boxes, a long flush comes in parts, 4000 canvas commands): 175 / 6 / 1 / 16 at v0.4.0; the paint count, the stopped paint, and the runner waits for the stop of a stops test 179 / 6 / 0 / 16 77 / 0 / 0
2026-11-02 db9afca (grid tracks, aspect-ratio after max-width, transform-box: fill-box, the drawn canvas's size) 184 / 1 / 0 / 16 77 / 0 / 0

What is left partly passing is not TwidgetCore's to fix: see the last section.

The 16 untested features are the host's (the CPU, memory, main-thread and animation limits, the size of the widget's HTML, restart, storage, message and state edits, forwarded copies, previews, the network marker, size hints, the resize anchor, the client's time over the corner): the automatic set does not reach them, and each client checks them in its own host. The gesture claim is tested since v0.1.2: the runner's presses go through a bubble that follows the shared policy, as CLIENTS.md asks. Since v0.2.1 a test with a message runs as that kind of message (the server's flags, the bot's state and a viewer where requests are allowed), and the runner answers its requests from the manifest's bot table, as the test bot would.

For comparison, widget-sandbox's litehtml prototype (web build, upstream litehtml; frozen since 2026-10-30) was at 66 / 41 / 52 in widget-sandbox's own results.

Swift renderer on macOS

ConformanceRunnerTests.testAutomaticSet runs the same automatic widgets through Swift's WidgetHost and LiteHTMLWidgetRenderer, with native mouse input and renderer snapshots. The runner configures each test's first message spec with its flags, chat state, shared state and viewer, and answers requests from the manifest's bot table. This lets the context widgets exercise originals, copies and previews with their actual permissions. Tests without a message use the private-chat context, as CLIENTS.md requires. Unknown message kinds stop the run.

The last recorded run, on 28 October 2026 against widget-sandbox v0.2.3: all 53 automatic runs complete without runtime errors, with 173 in-profile features passing and one partial (tw.theme-params: inside themeChanged the widget sees the new palette with the old CSS background, a cause still open).

From this repository:

TW_CONFORMANCE=1 TW_CONFORMANCE_OUT=/tmp/twidget-conformance \
  swift test --scratch-path /tmp/twidget-tests --disable-sandbox \
  --enable-xctest --disable-swift-testing \
  -Xlinker -framework -Xlinker QuartzCore \
  --filter ConformanceRunnerTests.testAutomaticSet
node ../widget-sandbox/packages/conformance/tools/report.mjs \
  --out /tmp/twidget-conformance/report --fail-on in \
  /tmp/twidget-conformance/macos-twidget-litehtml.run.json

Always judge the run file: XCTest records the widget checks without turning their failures into XCTest assertions. TW_CONFORMANCE_ONLY accepts comma-separated manifest IDs for a focused run; leave it unset for the whole set. TW_CONFORMANCE_FEATURES selects the host's feature set, with webAnimations as the default. The test needs a logged-in macOS graphical session. Telegram's message-list integration and live bot delivery need their own checks.

Done

What the contract asks Where
Real input marked trusted: true, or the hosts count no user gesture Renderer::event_message
Only profile 1's events: no keys, focus, input, touch, wheel or scroll PROFILE_EVENTS, listened
The gesture policy (v0.1.2): dblclick after every double tap's second click; contextmenu for a hold or a secondary-button press on touch-action: none, and no click after it; arbitrate plays the bubble Renderer::touch, settle_gesture, bubble_takes (widget-sandbox's GestureArbiter)
The theme message: --tg-theme-*, --tg-color-scheme, --tg-button-* and --tg-text-* on the root, important, values checked, the palette and text tokens always complete css::theme_variables
The profile's UA layer (tables.uaStylesheet) in the master CSS, the theme's values substituted and :where() unwrapped Renderer::apply_theme, tw_ua_stylesheet.h (CMake)
revert goes past that layer to litehtml's own sheet, as it goes past author layers to Chromium's the fork's css_selector::m_layered
user-select and -webkit-user-select computed (none, text, auto) the fork's css_properties::get_user_select
Text selection (docs/sdk-v1.md §5.10): a mouse drag that starts on user-select: text, unless the widget prevents the press, selects it and claims the drag; the host copies selected_text() and calls clear_selection() on a press of its own. Touch selection starts from the client's message menu and is the host's: selectable_text_json() lists the runs, each with the transform it shows under Renderer::touch, text_runs, paint_selection, selectable_text_json
position: relative on an inline element moves its text and its line boxes the fork's line_box::finish
gesture claims with x and hold (never y since v0.1.3: vertical drags are the chat's), from the target and its ancestors up to <html> Renderer::gesture_claim
The shared height bounds, 50 to 600 CSS px MIN_HEIGHT, max_height_
getComputedStyle answered for every property the fork computes, as Chromium writes it computed_style.cpp
An inline element's rect is its line boxes, with a rect per line inline_boxes_of
<img> sends load with its natural size, or error Renderer::load_images
scrollWidth and scrollHeight reach the overflowing content content_extent
pointer-events: none passes a hit to what is below Container::hittable
Pointer and mouse events carry movementX and movementY Renderer::emit_event, dispatch
initial, unset and revert the fork's css_wide_keyword, html_tag::ua_property
Every CSS colour name, rebeccapurple included the fork's web_color.cpp
A refused image is widget-sandbox's 48 px placeholder Container::draw_placeholder
A canvas gradient with no length (the validator's conic gradient or pattern) paints nothing Canvas::paint_of
On Windows, sans-serif and Helvetica are Arial, as in browsers D2DBackend::resolve_family
getComputedStyle reads running, paused and seeked animations Animator::peek in the cs query
animationstart, animationend and transitionend go out before the widget's listen ALWAYS_FORWARD
ResizeObserver: resize on observe and on every change Renderer::report_resizes
Tapping a <summary> opens and closes its <details> and sends toggle; a closed one shows only its summary, with a disclosure marker Renderer::toggle_details, UA_EXTRA, the fork's list_style_type_disclosure_*
calc(), min(), max(), clamp() mixing percentages with lengths the fork's css_units_calc, calc_evaluate
width: fit-content, min-content, max-content on blocks the fork's render_block
Empty auto table cells share the spare width the fork's table_grid::calc_table_width
word-break (break-all, break-word) the fork's css_properties::get_wrapping
open-quote / close-quote from quotes, and <q> marks the fork's el_before_after_base::add_quote, UA_EXTRA
direction and dir (auto too): right to left lines, alignment, blocks, and a simplified bidi that keeps embedded runs in their order the fork's line_box::reorder_bidi, render_block_context
:lang() on the nearest lang, :empty the fork's html_tag::select_pseudoclass
The translate, rotate and scale properties the fork's css_properties::compute_transform
letter-spacing and word-spacing, a no-break space widened inside its word too Container (spacing_units, spacing_words), the fork's font_description
font-variant-numeric and font-feature-settings as OpenType features the fork's font_description::features; Backend::create_font
SVG: getComputedTextLength, text getBBox, a shape's or text's rect through the view box, <use> of a <symbol>, <tspan> with its own fill and shifts the fork's svg_ctm, svg_text_runs, el_svg painter; svg_box_of
Dashed and dotted borders and outlines, mitred corners between sides that differ; dashes and dots spaced as Chromium does, round the centre line of a rounded border Container::border_dash, draw_sides, dashed_ring
Computed fill and stroke inherited (through <use> too), currentColor resolved the fork's svg_paint_color
Canvas textBaseline on the em box (the font's OS/2 typographic metrics), as Chromium places it FontMetrics::em_ascent, Canvas::text
A PNG cut short or corrupt keeps the rows before the damage, the rest transparent png_intact_rows (src/png.cpp), in each backend's image loading
background-repeat Container::draw_image
object-fit, object-position the fork's el_image::fitted
Gradient stop positions fixed up as CSS does (an explicit 0 is a position) the fork's prepare_color_points
text-overflow: ellipsis the fork's render_item_inline_context::apply_ellipsis
filter and backdrop-filter: blur, brightness, contrast, grayscale, hue-rotate, invert, opacity, saturate, sepia, drop-shadow FilterPush, Backdrop in the display list; D2DBackend::filtered
A canvas drawn from another canvas that the same batch draws later Renderer::apply_ops
The paint limit (v0.6.0): heavy boxes counted before every frame from the computed style and running animations, limit once past 16 (2 with backdrop-filter), then a paint without blur, filters, backdrop or transparency Renderer::count_paint, stop_paint, src/paint_policy.*
A long flush in parts of 1000 ops builds once, at the frame PARTIAL_BATCH_OPS

The visual set

twidget-conformance --visual runs the visual widgets (the SDK group aside, which needs a client's host) at each of their widths, drawn whole as the Swift runner draws them (2400 px), and writes the stage crops for tools/conformance/visual-report.mjs, which judges them with the set's diff.mjs. On 2026-10-20: 123 of 171 crops pass. What fails is not a rendering difference to fix:

  • text stages (03 align, families, weights, scripts, ellipsis, text-shadow; 08 and 09 text; 13 fonts and forms): the references were drawn with other fonts (system-ui is not Segoe UI there, and the strokes are heavier); the layout matches;
  • motion diagrams (06 slide, spin-pulse; 07 bounce; 09 smil): a template shows the animation's path, the crop is a frame of it;
  • 08 paint: createPattern is out of profile 1 and draws nothing, the reference shows Chromium's;
  • 04 table: 5 % of ink, at the threshold, in antialiasing.

v0.1.1 adds widgets judged whole: 14 (no CSS at all), 15 (a body background without a margin reset) and 16 (selection). The runner paints the default palette's bg_color under the transparent renderer, as a client draws its bubble. On 2026-10-21 they differ from the references by where their lines wrap, the fonts again: the body background of 15 fills the bubble edge to edge, and 14 lays out as the reference does. 16 is a tester's: a mouse selects its opted-in paragraph in a Windows host, touch does not yet.

Each platform's painter

The pixel stages above judge the Direct2D painter. The filter commands on the others, which this machine type-checks but does not run:

Command CoreGraphics Android Web Qt
FilterPush / FilterPop (filter) a bitmap context through filter_pixels a bitmap through filter_pixels (JNI) the canvas's own filter a layer through filter_pixels
Backdrop (backdrop-filter) the bitmap's pixels through filter_pixels not drawn: a Canvas gives no pixels to read a copy of the canvas through filter the layer's pixels through filter_pixels

Dashed and dotted borders are Path strokes with dashes, which every painter draws for SVG already.

Not TwidgetCore's

Item Where
The emoji stage (css.text.emoji) the reference is Apple Color Emoji; Windows draws Segoe UI Emoji