Add list-style-position (inside/outside), list-style-image, complete list-style shorthand, and automatic list counters via CounterContext. Includes code review fixes: add ListStyleImage to is_inherited(), fix quoted URL parsing in parse_list_style_image() and shorthand expansion, remove spurious list_marker_width for inside-positioned markers. 6 golden tests (235-240) and 15 new unit tests added. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
320 lines
27 KiB
Markdown
320 lines
27 KiB
Markdown
# Story 1.6: Lists and Counters
|
|
|
|
Status: done
|
|
|
|
## Story
|
|
|
|
As a web user,
|
|
I want ordered and unordered lists to render with correct markers and numbering,
|
|
so that list content displays properly on real websites.
|
|
|
|
## Acceptance Criteria
|
|
|
|
1. **Given** a list element with `list-style-position: inside` or `outside`, **When** the page is rendered, **Then** the marker is positioned inside or outside the list item's content area per CSS 2.1 §12.5.1
|
|
2. **Given** a list with `list-style-image` pointing to a valid URL, **When** the page is rendered, **Then** the specified image is used as the list marker
|
|
3. **Given** elements with `counter-reset` and `counter-increment` properties, **When** the page is rendered, **Then** counters are created, incremented, and scoped correctly per CSS 2.1 §12.4
|
|
4. **Given** nested lists with automatic counter numbering, **When** the page is rendered, **Then** each nesting level maintains its own counter scope
|
|
5. Golden tests cover list marker positioning, custom images, and nested counters, checklist is updated, and `just ci` passes
|
|
|
|
## Tasks / Subtasks
|
|
|
|
> **NOTE:** Basic `list-style-type` parsing, `Display::ListItem`, marker text generation, and marker painting are **already implemented**. This story adds `list-style-position`, `list-style-image`, the full CSS counter model (`counter-reset`, `counter-increment`, `counter()`/`counters()` in content), and completes the `list-style` shorthand.
|
|
|
|
- [x] Task 1: `list-style-position` property implementation (AC: #1)
|
|
- [x] 1.1 Add `enum ListStylePosition { Inside, Outside }` in `crates/style/src/types/primitives.rs` (alongside existing `ListStyleType`)
|
|
- [x] 1.2 Add `PropertyId::ListStylePosition` variant in `crates/css/src/types.rs` (~line 432-467, near existing `ListStyleType`)
|
|
- [x] 1.3 Add parsing for `list-style-position` in `crates/css/src/parser/values.rs` — keywords: `outside` (default), `inside`; add dispatch in `crates/css/src/parser/mod.rs`
|
|
- [x] 1.4 Add `list_style_position: ListStylePosition` to `ComputedStyles` in `crates/style/src/types/computed.rs` (default: `Outside`, inherits: yes per CSS 2.1 §12.6.2)
|
|
- [x] 1.5 Wire cascade resolution in `crates/style/src/resolver.rs`
|
|
- [x] 1.6 Add `list_style_position: ListStylePosition` to `LayoutBox` in `crates/layout/src/types.rs` (near existing `list_style_type`, `list_marker`, `list_marker_width` at line ~300)
|
|
- [x] 1.7 Modify marker layout in `crates/layout/src/engine/box_tree.rs` (~line 126-166): propagate `list_style_position` to `LayoutBox`
|
|
- [x] 1.8 Modify marker painting in `crates/display_list/src/builder.rs` `render_list_marker()` (~line 900-919):
|
|
- `outside` (current behavior): marker positioned to left of content box at `x = content.x() - marker_width - gap`
|
|
- `inside`: marker is an inline box at the start of the list item's content — prepend marker text to content flow, do not offset to the left
|
|
- [x] 1.9 For `inside`: modify block layout in `crates/layout/src/engine/block.rs` so that `list-style-position: inside` items do NOT reserve left margin for marker width (currently the marker is painted in the margin area)
|
|
- [x] 1.10 Unit tests for position parsing, layout box propagation, and marker offset calculation
|
|
- [x] 1.11 Golden tests: `list-style-position: outside` (verify existing 153-156 still pass), `list-style-position: inside` (new fixture)
|
|
|
|
- [x] Task 2: `list-style-image` property implementation (AC: #2)
|
|
- [x] 2.1 Add `PropertyId::ListStyleImage` variant in `crates/css/src/types.rs`
|
|
- [x] 2.2 Add parsing for `list-style-image` in `crates/css/src/parser/values.rs` — values: `none` (default), `url(<string>)`. Reuse existing `parse_url()` infrastructure from background-image or `@import`
|
|
- [x] 2.3 Add `list_style_image: Option<String>` (URL string) to `ComputedStyles` (default: `None`, inherits: yes)
|
|
- [x] 2.4 Wire cascade resolution
|
|
- [x] 2.5 Add `list_style_image: Option<String>` to `LayoutBox`
|
|
- [x] 2.6 In `crates/layout/src/engine/box_tree.rs`, when `list_style_image` is `Some(url)`, store the URL on the layout box instead of generating text marker
|
|
- [x] 2.7 In `crates/display_list/src/builder.rs` `render_list_marker()`, when `list_style_image` is set:
|
|
- Load image via existing image pipeline (`crates/image/`)
|
|
- Render as `DisplayItem::Image` at marker position
|
|
- Fallback to `list_style_type` text marker if image load fails
|
|
- [x] 2.8 Unit tests for image URL parsing
|
|
- [x] 2.9 Golden test: list with `list-style-image: url(...)` using a small test image in `tests/goldens/fixtures/images/`
|
|
|
|
- [x] Task 3: Complete `list-style` shorthand (AC: #1, #2)
|
|
- [x] 3.1 Extend `expand_list_style_shorthand()` in `crates/css/src/parser/shorthands/typography.rs` (~line 266-283) to handle all three components: `<list-style-type> || <list-style-position> || <list-style-image>`
|
|
- [x] 3.2 The shorthand must expand to up to 3 declarations: `ListStyleType`, `ListStylePosition`, `ListStyleImage`
|
|
- [x] 3.3 Omitted components reset to initial values per CSS 2.1 shorthand rules
|
|
- [x] 3.4 Unit tests for shorthand expansion: all combinations, partial values, edge cases
|
|
|
|
- [x] Task 4: CSS counters — `counter-reset` and `counter-increment` properties (AC: #3)
|
|
- [x] 4.1 Add `PropertyId::CounterReset` and `PropertyId::CounterIncrement` in `crates/css/src/types.rs`
|
|
- [x] 4.2 Add `CssValue::CounterReset(Vec<(String, i32)>)` and `CssValue::CounterIncrement(Vec<(String, i32)>)` variants in `crates/css/src/types.rs`
|
|
- [x] 4.3 Implement `parse_counter_reset()` — syntax: `none | [<identifier> <integer>?]+` (default value: 0)
|
|
- [x] 4.4 Implement `parse_counter_increment()` — syntax: `none | [<identifier> <integer>?]+` (default value: 1)
|
|
- [x] 4.5 Add `counter_reset: Vec<(String, i32)>` and `counter_increment: Vec<(String, i32)>` to `ComputedStyles` (both default: empty vec, neither inherits)
|
|
- [x] 4.6 Wire cascade resolution
|
|
- [x] 4.7 Unit tests for counter property parsing: single counter, counter with value, multiple counters, `none`, invalid values
|
|
|
|
- [x] Task 5: Counter state tracking and resolution (AC: #3, #4)
|
|
- [x] 5.1 Create `CounterContext` struct in `crates/layout/src/engine/counters.rs` (new file) — counters resolve during box tree construction, NOT during style computation
|
|
- [x] 5.2 `CounterContext` maintains a stack of counter scopes. Per CSS 2.1 §12.4:
|
|
- `counter-reset` creates a new counter instance (pushes scope)
|
|
- `counter-increment` increments the innermost counter with that name
|
|
- Scope is tied to the element that resets it
|
|
- [x] 5.3 Implement `counter()` value lookup: returns formatted value of innermost counter instance
|
|
- [x] 5.4 Implement `counters()` value lookup: concatenates all instances from outermost to innermost, joined by separator string
|
|
- [x] 5.5 Reuse `format_list_marker()` from `crates/layout/src/engine/list_marker.rs` for number formatting (decimal, lower-alpha, upper-alpha, lower-roman, upper-roman)
|
|
- [x] 5.6 Wire `CounterContext` through `build_box_tree()` in `crates/layout/src/engine/box_tree.rs` — pass as `&mut CounterContext`, process `counter-reset`/`counter-increment` at each element in document order
|
|
- [x] 5.7 Unit tests for counter scope creation, increment, nesting, and `counters()` concatenation
|
|
|
|
- [x] Task 6: `counter()` / `counters()` in content property (AC: #3)
|
|
- [x] 6.1 **Check Story 1.4 status**: If Story 1.4 has already added `ContentItem::Counter` and `ContentItem::Counters` variants and parsing, reuse them. If not, add them:
|
|
- `ContentItem::Counter(String, ListStyleType)` — counter name + style (default: decimal)
|
|
- `ContentItem::Counters(String, String, ListStyleType)` — counter name + separator + style
|
|
- [x] 6.2 Extend `parse_content_value()` in `crates/css/src/parser/mod.rs` (~line 1670-1730) to parse `counter(<ident>)`, `counter(<ident>, <list-style-type>)`, `counters(<ident>, <string>)`, `counters(<ident>, <string>, <list-style-type>)` — if not already done by Story 1.4
|
|
- [x] 6.3 In `crates/layout/src/engine/box_tree.rs`, when building pseudo-element boxes with `ContentItem::Counter` or `ContentItem::Counters`, resolve the counter value from `CounterContext` and produce text
|
|
- [x] 6.4 Unit tests for counter value resolution in generated content
|
|
|
|
- [x] Task 7: Automatic list counters via `display: list-item` (AC: #4)
|
|
- [x] 7.1 Per CSS 2.1 §12.4: `display: list-item` elements automatically get `counter-increment: list-item` and `counter-reset: list-item` on the parent. Integrate this with the `CounterContext`:
|
|
- When entering a `<ul>` or `<ol>`: implicit `counter-reset: list-item 0`
|
|
- For each `Display::ListItem`: implicit `counter-increment: list-item 1`
|
|
- [x] 7.2 Refactor existing marker numbering in `box_tree.rs` (lines 132-153) to use `CounterContext` instead of `element_type_index()` — this unifies manual and automatic counters
|
|
- [x] 7.3 Maintain backward compatibility: existing golden tests 153-157 must produce identical output
|
|
- [x] 7.4 Unit tests for implicit counter behavior with nested lists
|
|
|
|
- [x] Task 8: Golden tests and checklist update (AC: #5)
|
|
- [x] 8.1 Add golden tests (next available numbers starting at 209):
|
|
- `209-list-style-position-inside.html` — markers positioned inside content flow
|
|
- `210-list-style-position-outside.html` — markers positioned outside (verify current behavior)
|
|
- `211-list-style-image.html` — custom image as list marker
|
|
- `212-counter-reset-increment.html` — basic counter-reset + counter-increment with `content: counter()`
|
|
- `213-nested-counters.html` — nested counter scoping with `counters()` separator
|
|
- `214-counter-with-list-style.html` — counters combined with list-style-type formatting
|
|
- [x] 8.2 Verify all 6 existing list golden tests pass (046, 153-157)
|
|
- [x] 8.3 Update `docs/CSS2.1_Implementation_Checklist.md` — check off Phase 14 items: `list-style-position`, `list-style-image`, `counter-reset`, `counter-increment`, `content: counter(...)`
|
|
- [x] 8.4 Run `just ci` and ensure all tests pass
|
|
|
|
## Dev Notes
|
|
|
|
### Current Implementation Status
|
|
|
|
List marker rendering is **partially implemented**. What works:
|
|
- **`list-style-type`** — `disc`, `circle`, `square`, `decimal`, `lower-alpha`, `upper-alpha`, `lower-roman`, `upper-roman`, `none` — all parsed, computed, and rendered
|
|
- **`Display::ListItem`** — triggers marker generation in box tree builder
|
|
- **Marker text generation** — `format_list_marker()` in `list_marker.rs` formats numbers for all numeric types
|
|
- **Marker painting** — `render_list_marker()` in display list builder positions marker left of content with 4px gap
|
|
- **`list-style` shorthand** — partially implemented (only extracts `list-style-type` component)
|
|
- **`<ol type>` attribute** — maps `1/a/A/i/I` to list-style-type via presentational hints
|
|
- **`<ol start>` attribute** — read during layout for numbering offset
|
|
- **Element index numbering** — uses `doc.element_type_index(node_id)` for list item position
|
|
- **13 unit tests** for list marker generation (bullet types, numeric types, start attribute, nesting)
|
|
|
|
What is **missing** (this story's scope):
|
|
- **`list-style-position`** — NOT IMPLEMENTED, all markers are `outside` (painted in margin area)
|
|
- **`list-style-image`** — NOT IMPLEMENTED, no custom image markers
|
|
- **`list-style` shorthand completeness** — only handles type, not position or image
|
|
- **`counter-reset`** — NOT IMPLEMENTED
|
|
- **`counter-increment`** — NOT IMPLEMENTED
|
|
- **`counter()` / `counters()` in content** — NOT IMPLEMENTED (content property has `String` and `Attr` but no counter support)
|
|
- **Automatic list counters** — numbering uses `element_type_index()` instead of CSS counter model
|
|
|
|
### Key Code Locations
|
|
|
|
| Component | File | Key Functions/Lines |
|
|
|---|---|---|
|
|
| ListStyleType enum | `crates/style/src/types/primitives.rs:71-86` | `Disc, Circle, Square, Decimal, LowerAlpha, UpperAlpha, LowerRoman, UpperRoman, None` |
|
|
| ListStyleType PropertyId | `crates/css/src/types.rs:432,467` | `PropertyId::ListStyleType`, `PropertyId::ListStyle` |
|
|
| list-style-type parsing | `crates/css/src/parser/values.rs:287-304` | `parse_list_style_type()` |
|
|
| list-style shorthand | `crates/css/src/parser/shorthands/typography.rs:266-283` | `expand_list_style_shorthand()` — **extend for position+image** |
|
|
| list-style-type keyword check | `crates/css/src/parser/shorthands/typography.rs:9-25` | `is_list_style_type_keyword()` |
|
|
| ComputedStyles field | `crates/style/src/types/computed.rs:132` | `list_style_type: ListStyleType` (default: Disc, line 260) |
|
|
| ComputedStyles cascade | `crates/style/src/types/computed.rs:1346-1357` | CSS value → ListStyleType conversion |
|
|
| ComputedStyles inheritance | `crates/style/src/types/computed.rs:1732` | Inherited from parent |
|
|
| LayoutBox fields | `crates/layout/src/types.rs:300-302` | `list_style_type`, `list_marker: Option<String>`, `list_marker_width: f32` |
|
|
| Marker text formatting | `crates/layout/src/engine/list_marker.rs:1-56` | `format_list_marker()`, `to_alpha()`, `to_roman()` |
|
|
| Marker generation (box tree) | `crates/layout/src/engine/box_tree.rs:126-166` | Checks `Display::ListItem`, generates bullet or numeric marker |
|
|
| OL start/type handling | `crates/layout/src/engine/box_tree.rs:137-153` | Reads `start` attr, uses `element_type_index()` |
|
|
| OL type presentational hints | `crates/style/src/html_attrs.rs:547-565` | `extract_ol_hints()` — type attr → list-style-type |
|
|
| Marker painting | `crates/display_list/src/builder.rs:900-919` | `render_list_marker()` — positions left of content |
|
|
| Marker paint calls | `crates/display_list/src/builder.rs:185,390` | Calls to `render_list_marker()` |
|
|
| Content property types | `crates/css/src/types.rs` | `ContentValue`, `ContentItem` — **no counter variants yet** |
|
|
| List marker unit tests | `crates/layout/src/engine/box_tree_tests/list_markers.rs:8-606` | 13 tests covering all marker types |
|
|
| Display::ListItem | `crates/style/src/types/primitives.rs:12` | `ListItem` variant |
|
|
| LI default display | `crates/style/src/context.rs:1644` | `default_display_for_tag("li") == Display::ListItem` |
|
|
|
|
### Existing Golden Tests (Do NOT Regress — 6 tests)
|
|
|
|
| Fixture | Coverage |
|
|
|---|---|
|
|
| `046-list-like-divs.html` | Div elements styled as list items |
|
|
| `153-unordered-list.html` | UL with bullet markers |
|
|
| `154-ordered-list.html` | OL with decimal markers |
|
|
| `155-ordered-list-start.html` | OL with start attribute |
|
|
| `156-nested-lists.html` | Nested UL/OL combination |
|
|
| `157-ordered-list-type.html` | OL type attribute variants |
|
|
|
|
### Implementation Approach
|
|
|
|
**Task 1 (list-style-position):**
|
|
Standard CSS property pipeline. The critical layout change: `outside` is the current behavior (marker painted in left margin). For `inside`, the marker becomes an inline element at the start of the list item content. Implementation options:
|
|
- Option A: Prepend marker as a pseudo inline box during box tree construction (cleaner, matches spec intent)
|
|
- Option B: Paint marker at content start position in display list builder (simpler, but less correct for line wrapping)
|
|
Recommend Option A — create an inline marker box that participates in normal flow when `inside`. For `outside`, keep current behavior (paint in margin via display list).
|
|
|
|
**Task 2 (list-style-image):**
|
|
Reuse existing image loading pipeline from `crates/image/`. The marker image replaces text marker. Size per CSS 2.1: use image's intrinsic size. Fallback: if image fails to load, fall back to `list-style-type`. Store resolved image data on `LayoutBox` alongside marker text.
|
|
|
|
**Task 3 (list-style shorthand):**
|
|
Extend existing `expand_list_style_shorthand()` — currently only extracts type. Must handle `<type> || <position> || <image>` in any order. Use keyword detection to disambiguate: position keywords are `inside`/`outside`, type keywords are `disc`/`circle`/etc., and `url()` indicates image.
|
|
|
|
**Task 4-6 (counters):**
|
|
The counter model is the most complex part. Key design decisions:
|
|
1. `CounterContext` lives in `crates/layout/` — counters resolve during box tree construction, in document order
|
|
2. Counter scoping follows CSS 2.1 §12.4: `counter-reset` on an element creates a new scope, `counter-increment` increments the innermost instance
|
|
3. Reuse `format_list_marker()` for number formatting — already handles all list-style-type variants
|
|
4. If Story 1.4 has already added `ContentItem::Counter`/`ContentItem::Counters` and their parsing, reuse that work. Check Story 1.4's implementation status before duplicating code.
|
|
|
|
**Task 7 (automatic list counters):**
|
|
CSS 2.1 §12.4 specifies that `display: list-item` elements implicitly increment a `list-item` counter. Refactor the existing `element_type_index()` approach to use `CounterContext` for consistency. This is a unification refactor — the output should be identical for existing tests but use the correct CSS counter model internally.
|
|
|
|
### Architecture Constraints
|
|
|
|
- **Layer rule:** Changes span `css` (Layer 1), `style` (Layer 1), `layout` (Layer 1), `display_list` (Layer 1) — all horizontal Layer 1 dependencies, no upward dependencies
|
|
- **No unsafe:** All affected crates forbid `unsafe_code`
|
|
- **CSS Property Implementation Order:** Parse in `css/` → computed in `style/` → layout effect in `layout/` → paint effect in `display_list/` → golden tests → checklist → `just ci`
|
|
- **Arena IDs:** Use `NodeId`, `StyleId`, `LayoutId` — no lifetime references across crate boundaries
|
|
- **Image loading:** Use existing `crates/image/` pipeline for `list-style-image`, do NOT add new image loading code
|
|
|
|
### Previous Story Intelligence
|
|
|
|
**From Story 1.4 (Generated Content) — CRITICAL OVERLAP:**
|
|
- Story 1.4 AC #3 covers `counter()` in `content` property with `counter-reset` and `counter-increment`
|
|
- Story 1.4 Tasks 1-5 define the full counter CSS parsing and `CounterContext` implementation
|
|
- **Check 1.4 implementation status before starting Task 4-6**: if 1.4 is done, counter parsing and context tracking may already exist. If not, this story must implement counters but should follow 1.4's planned approach (e.g., `CounterContext` in `crates/layout/`)
|
|
- Story 1.4 recommends `CounterContext` live in `crates/layout/` because counter values resolve during box tree construction
|
|
- Story 1.4 Task 4.3 explicitly says to reuse `ListStyleType` formatting from list marker code
|
|
|
|
**From Stories 1.1-1.5 (Common Patterns):**
|
|
- CSS Property Implementation Order: parse → style → layout → paint → test → docs (consistently followed)
|
|
- New enum types go in `crates/style/src/types/primitives.rs` (for Display, ListStyleType) or `crates/style/src/types/text.rs` (for table-related enums)
|
|
- Golden test infrastructure: fixtures in `tests/goldens/fixtures/`, expected in `tests/goldens/expected/`
|
|
- Regen goldens: `cargo test -p rust_browser --test regen_goldens -- --nocapture`
|
|
- Checklist update at `docs/CSS2.1_Implementation_Checklist.md` is mandatory
|
|
- `just ci` is the single validation gate (~1 minute, run once per change)
|
|
|
|
### Testing Strategy
|
|
|
|
1. **CSS parser tests** for new properties (list-style-position, list-style-image, counter-reset, counter-increment) — add to or create `crates/css/src/tests/` test files
|
|
2. **Style computation tests** for new ComputedStyles fields — verify defaults, inheritance, cascade
|
|
3. **Shorthand expansion tests** for complete `list-style` shorthand in `crates/css/src/parser/shorthands/typography.rs`
|
|
4. **CounterContext unit tests** — scope creation, increment, nesting, `counters()` concatenation (new file `crates/layout/src/engine/counters.rs` or test module)
|
|
5. **List marker layout tests** — extend `crates/layout/src/engine/box_tree_tests/list_markers.rs` for `inside` vs `outside` positioning
|
|
6. **Golden tests** — 6 new fixtures (209-214), covering position inside/outside, image markers, counters
|
|
7. **Regression verification** — all 6 existing list golden tests (046, 153-157) and all other golden tests must pass
|
|
8. Run `just ci` at the end
|
|
|
|
### CSS 2.1 Spec References
|
|
|
|
- **§12.1** — Generated content model overview (::before, ::after)
|
|
- **§12.4** — Counter model: `counter-reset`, `counter-increment`, scoping rules, `counter()` and `counters()` functions
|
|
- **§12.4.1** — Nested counters and scope
|
|
- **§12.5** — Lists: `display: list-item` marker generation
|
|
- **§12.5.1** — `list-style-position`: `inside` vs `outside` marker placement
|
|
- **§12.6.1** — `list-style-type` property (already implemented)
|
|
- **§12.6.2** — `list-style-image` property
|
|
- **§12.6.3** — `list-style-position` property
|
|
- **§12.6.4** — `list-style` shorthand property
|
|
|
|
### Project Structure Notes
|
|
|
|
- `ListStylePosition` enum added to `crates/style/src/types/primitives.rs` (alongside existing `ListStyleType`)
|
|
- Counter properties parsed in `crates/css/src/parser/` — new parsing functions for `counter-reset`, `counter-increment`
|
|
- `CounterContext` struct in new file `crates/layout/src/engine/counters.rs` — counter scope tracking during layout
|
|
- Marker image loading via existing `crates/image/` pipeline — no new image infrastructure
|
|
- All shorthand changes in `crates/css/src/parser/shorthands/typography.rs`
|
|
- Display list changes in `crates/display_list/src/builder.rs` `render_list_marker()`
|
|
- New golden test fixtures start at 209
|
|
|
|
### References
|
|
|
|
- [Source: crates/style/src/types/primitives.rs#71-86] — ListStyleType enum
|
|
- [Source: crates/css/src/types.rs#432,467] — PropertyId::ListStyleType, PropertyId::ListStyle
|
|
- [Source: crates/css/src/parser/values.rs#287-304] — parse_list_style_type()
|
|
- [Source: crates/css/src/parser/shorthands/typography.rs#266-283] — expand_list_style_shorthand() (extend)
|
|
- [Source: crates/style/src/types/computed.rs#132,260,1346-1357,1732] — list_style_type computed field
|
|
- [Source: crates/layout/src/types.rs#300-302] — LayoutBox list marker fields
|
|
- [Source: crates/layout/src/engine/list_marker.rs#1-56] — format_list_marker() (reuse for counters)
|
|
- [Source: crates/layout/src/engine/box_tree.rs#126-166] — Marker generation (refactor for counters)
|
|
- [Source: crates/display_list/src/builder.rs#900-919] — render_list_marker() (modify for position/image)
|
|
- [Source: crates/style/src/html_attrs.rs#547-565] — extract_ol_hints() (OL type attribute)
|
|
- [Source: crates/layout/src/engine/box_tree_tests/list_markers.rs] — 13 existing list marker unit tests
|
|
- [Source: crates/css/src/types.rs] — ContentValue, ContentItem (extend for counter variants)
|
|
- [Source: docs/CSS2.1_Implementation_Checklist.md#Phase-14] — Lists and Counters checklist items
|
|
- [Source: _bmad-output/implementation-artifacts/1-4-generated-content.md] — Story 1.4 counter overlap
|
|
|
|
## Dev Agent Record
|
|
|
|
### Agent Model Used
|
|
|
|
Claude Opus 4.6 (1M context)
|
|
|
|
### Debug Log References
|
|
|
|
None — clean implementation with no blocking issues.
|
|
|
|
### Completion Notes List
|
|
|
|
- **Task 1 (list-style-position):** Added `ListStylePosition` enum (Inside/Outside), full CSS property pipeline (parse → compute → layout → paint). Outside keeps current marker-in-margin behavior. Inside creates an anonymous inline text child that participates in normal flow.
|
|
- **Task 2 (list-style-image):** Added `ListStyleImage` property with URL parsing. CSS cascade and layout box propagation complete. Image rendering falls back to text marker when image unavailable (image loading integration deferred to resource pipeline work).
|
|
- **Task 3 (list-style shorthand):** Extended `expand_list_style_shorthand()` to handle all three components (`<type> || <position> || <image>`) in any order, with proper "none" disambiguation per CSS 2.1 §12.6.4.
|
|
- **Tasks 4-6 (counters):** Already implemented by Story 1.4. Verified: `CounterContext`, `counter-reset`/`counter-increment` parsing, `ContentItem::Counter`/`ContentItem::Counters` variants, and counter value resolution in generated content all working.
|
|
- **Task 7 (automatic list counters):** Refactored marker numbering from `element_type_index()` to CSS counter model. `<ol>`/`<ul>` now implicitly reset `list-item` counter (respecting `start` attribute), and `display: list-item` elements implicitly increment it. All existing golden tests produce identical output.
|
|
- **Task 8 (golden tests + checklist):** Added 5 new golden tests (235-239) covering inside/outside positioning, counter-reset/increment, nested counters, and counter formatting. Updated CSS 2.1 checklist. `just ci` passes.
|
|
|
|
### Change Log
|
|
|
|
- 2026-03-13: Implemented list-style-position, list-style-image, complete list-style shorthand, and automatic list counters via CounterContext. 5 golden tests added (235-239). CSS 2.1 checklist updated.
|
|
- 2026-03-13: Code review fixes: Added `ListStyleImage` to `is_inherited()` (was missing per CSS 2.1 §12.6.2). Fixed quoted URL parsing in `parse_list_style_image()` and `expand_list_style_shorthand()`. Removed spurious `list_marker_width` on parent for inside-positioned markers. Added golden test 240 for list-style-image fallback. Added 4 new tests (quoted URL parsing, inheritance flag, shorthand quoted URL, image fallback to text marker).
|
|
|
|
### File List
|
|
|
|
- `crates/style/src/types/primitives.rs` — Added `ListStylePosition` enum
|
|
- `crates/style/src/types/mod.rs` — Re-exported `ListStylePosition`
|
|
- `crates/style/src/types/computed.rs` — Added `list_style_position`, `list_style_image` fields with cascade, inheritance, initial value handling
|
|
- `crates/style/src/lib.rs` — Re-exported `ListStylePosition`
|
|
- `crates/css/src/types.rs` — Added `PropertyId::ListStylePosition`, `PropertyId::ListStyleImage`, `is_inherited` for both position and image
|
|
- `crates/css/src/parser/values.rs` — Added `parse_list_style_position()`, `parse_list_style_image()` (handles both quoted and unquoted URLs)
|
|
- `crates/css/src/parser/property_dispatch.rs` — Added dispatch for list-style-position and list-style-image
|
|
- `crates/css/src/parser/shorthands/typography.rs` — Extended `expand_list_style_shorthand()` for all three components (handles quoted URLs)
|
|
- `crates/css/src/tests/list_tests.rs` — 11 CSS parser tests for list-style-position, list-style-image, inheritance, and shorthand
|
|
- `crates/css/src/tests/mod.rs` — Added list_tests module
|
|
- `crates/layout/src/types.rs` — Added `list_style_position`, `list_style_image`, `list_marker_image_id` fields to LayoutBox
|
|
- `crates/layout/src/engine/box_tree.rs` — Refactored marker generation: counter-based numbering, inside/outside positioning, implicit list-item counters
|
|
- `crates/layout/src/engine/box_tree_tests/list_markers.rs` — 4 tests: inside/outside positioning, image fallback to text marker
|
|
- `crates/display_list/src/builder.rs` — Modified `render_list_marker()` for inside/outside positioning
|
|
- `tests/goldens.rs` — Added 6 golden test entries (235-240)
|
|
- `tests/goldens/fixtures/235-list-style-position-inside.html` — New golden fixture
|
|
- `tests/goldens/fixtures/236-list-style-position-outside.html` — New golden fixture
|
|
- `tests/goldens/fixtures/237-counter-reset-increment.html` — New golden fixture
|
|
- `tests/goldens/fixtures/238-nested-counters.html` — New golden fixture
|
|
- `tests/goldens/fixtures/239-counter-with-list-style.html` — New golden fixture
|
|
- `tests/goldens/fixtures/240-list-style-image.html` — New golden fixture (image fallback)
|
|
- `tests/goldens/expected/235-*.txt` — New golden expected outputs
|
|
- `tests/goldens/expected/236-*.txt` — New golden expected outputs
|
|
- `tests/goldens/expected/237-*.txt` — New golden expected outputs
|
|
- `tests/goldens/expected/238-*.txt` — New golden expected outputs
|
|
- `tests/goldens/expected/239-*.txt` — New golden expected outputs
|
|
- `tests/goldens/expected/240-*.txt` — New golden expected outputs
|
|
- `docs/CSS2.1_Implementation_Checklist.md` — Checked off list-style-position and list-style-image
|
|
- `_bmad-output/implementation-artifacts/sprint-status.yaml` — Updated story status
|