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_tokenizer → css_tokenizer → style_resolver → layout_box → text_layout → render_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).
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:
- Tag selector (first byte is a letter):
strcmp(selector, node.tag_name)→ 1 if match (e.g.,h1matches<h1>). - Class selector (first byte is
.):strcmp(selector+1, node.class_name)→ 1 if match (e.g.,.leadmatches<p class="lead">). - ID selector (first byte is
#):strcmp(selector+1, node.id)→ 1 if match (e.g.,#logomatches<img id="logo">).
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):
| Selector | Property | Value |
|---|---|---|
h1 | color | 16711680 (0xFF0000 = red) |
p | display | 1 (block) |
.lead | font-size | 16 |
#logo | width | 100 |
img | display | 2 (inline) |
StyleMap after resolve():
| Node | color | font_size | width | display |
|---|---|---|---|---|
h1 | 16711680 | 0 | 0 | 0 |
p.lead | 0 | 16 | 0 | 1 |
img#logo | 0 | 0 | 100 | 2 |
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).