← Blog
0.7.0 · APPS · BROWSER · CSS

Style Resolver — CSS Selector Matching → StyleMap (sigil-apps 6df6641)

June 22, 2026 · sigil-apps 6df6641 · Sigil-Docs
browser css style resolver layout 0.7.0

apps/web/style_resolver.sg (sigil-apps 6df6641) is the CSS selector resolver for the sigilOS browser — the pipeline step between tokenization and layout. It reads the html_tokenizer + css_tokenizer output streams, matches CSS rules (tag/class/id) against DOM nodes, and writes per-node resolved style properties to a StyleMap at 0x700000. Pipeline: html_tokenizercss_tokenizerstyle_resolverlayout_boxtext_layoutrender_page. 12240B arm-el0, cc0 PASS.


StyleMap at 0x700000 — 48B per node

Each DOM node gets a 48-byte StyleMap entry. Layout: color:i32 (byte 0), font_size:i32 (byte 4), width:i32 (byte 8), height:i32 (byte 12), display:i32 (byte 16), margin:i32 (byte 20). 6 properties × 4B = 24B useful, 24B reserved for future properties (padding, border, background, etc.). stylemap_base() = 0x700000. stylemap_entry(node_idx) = stylemap_base() + node_idx × 48. Properties not matched by any rule remain 0 (inherit or initial value — passed through by layout_box).

color:i32
byte 0 — packed ARGB or 0xRRGGBB integer. 0 = unset (inherit).
font_size:i32
byte 4 — font size in pixels. 0 = unset (layout uses default).
width:i32
byte 8 — explicit width in pixels. 0 = unset (auto).
height:i32
byte 12 — explicit height in pixels. 0 = unset (auto).
display:i32
byte 16 — 1 = block, 2 = inline, 0 = unset.
margin:i32
byte 20 — uniform margin in pixels. 0 = unset. Future: split to margin-top/right/bottom/left at bytes 20–32.

resolve(dom_nodes, node_count, css_rules, rule_count)

The main resolver entry point. Walks node_count DOM nodes. For each node: walks rule_count CSS rules. For each rule: calls sel_match(rule_selector, node) — returns 1 if the selector matches, 0 if not. On match: writes the rule's property+value to the node's StyleMap entry via poke32. Later rules in source order overwrite earlier ones (cascade — last-wins per-property, no specificity scoring yet). When all rules are walked, the node's StyleMap entry holds the resolved values for all matched properties.

sel_match(selector_token, node) — tag/class/id dispatch

Three match paths, determined by the first byte of the selector string:

Universal * selector always returns 1. The selector string is looked up in the css_tokenizer output token at the given offset in src_buf @0x400000.


Pipeline test — 5-rule DOM

Synthetic DOM: h1 (tag=1, class=none, id=none), p.lead (tag=2, class="lead", id=none), img#logo (tag=3, class=none, id="logo").

CSS rules (from css_tokenizer output):

SelectorPropertyValue
h1color16711680 (0xFF0000 = red)
pdisplay1 (block)
.leadfont-size16
#logowidth100
imgdisplay2 (inline)

StyleMap after resolve():

Nodecolorfont_sizewidthdisplay
h116711680000
p.lead01601
img#logo001002

p matched by tag selector → display=1; .lead matched by class selector → font_size=16. Both apply to p.lead — last-wins cascade gives display=1 and font_size=16 simultaneously (different properties, no conflict). img#logo matched by img tag (display=2) and #logo id (width=100).


Pipeline position

The style_resolver is the bridge between the tokenizer layer and the geometry layer:

html_tokenizer (de47df3) → tok_buf @0x500000  (HTML token stream)
css_tokenizer  (2ae5bda) → tok_buf @0x500000  (CSS token stream, same layout)
style_resolver (6df6641) → StyleMap @0x700000 (per-node resolved properties)
layout_box     (f33524f) → reads StyleMap width/height/margin for geometry
text_layout    (6158863) → reads font_size from StyleMap
render_page    (e173c87) → reads color + display from StyleMap

The only remaining gap before a fully CSS-driven render_page: layout_box geometry pass needs to read StyleMap width/height/margin (currently uses hardcoded geometry). That integration is the next sprint (T7).