Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-12
176 changes: 176 additions & 0 deletions openspec/changes/archive/2026-07-12-auto-cycle-list-styles/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
## Context

Rich text widget renders ordered (`<ol>`) and unordered (`<ul>`) lists using Tiptap's StarterKit extensions. Current SCSS (`RichText.scss` lines 158-162) only sets basic padding and margin:

```scss
ul,
ol {
padding-left: 1.5em;
margin: 0.5em 0;
}
```

All nesting levels use browser defaults:

- Ordered lists: `list-style-type: decimal` (1, 2, 3) at all levels
- Unordered lists: `list-style-type: disc` (●) at all levels

Standard word processors (Word, Google Docs, Notion) auto-cycle list styles by depth for visual hierarchy. Users coming from these tools expect similar behavior.

Recent change `fix-list-tab-indent` added Tab key support for nesting lists, making this enhancement more valuable.

## Goals / Non-Goals

**Goals:**

- Auto-cycle ordered list styles: decimal → lower-alpha → lower-roman (repeat)
- Auto-cycle unordered list styles: disc → circle → square (repeat)
- CSS-only implementation (zero JavaScript)
- Preserve inline style overrides (user-set `style="list-style-type: ..."`)
- Support 6+ nesting levels

**Non-Goals:**

- User controls for changing list style (Phase 2 scope)
- Toolbar UI changes
- Extending Tiptap list extensions
- Data model changes (no new attributes)
- RTL-specific list markers

## Decisions

### Decision 1: Pure CSS nested selectors

**Choice**: Use CSS descendant combinators (`ol ol { ... }`) to target nested lists.

**Rationale**:

- Zero JavaScript overhead
- Standard CSS, widely supported
- Works immediately with existing HTML structure
- No Tiptap extension modifications needed
- Inline styles naturally override via CSS specificity

**Alternatives considered**:

- Data attributes (`data-list-level="2"`) → Requires Tiptap extension to inject attributes, adds complexity
- JavaScript depth calculation → Performance overhead, unnecessary for static styling
- CSS `:nth-child()` or counters → Incorrect semantic (targets item position, not nesting depth)

### Decision 2: Cycle sequence matches Word

**Choice**:

- Ordered: decimal (1, 2, 3) → lower-alpha (a, b, c) → lower-roman (i, ii, iii)
- Unordered: disc (●) → circle (○) → square (■)

**Rationale**:

- Industry standard (Microsoft Word, Google Docs use same sequence)
- User familiarity reduces cognitive load
- Lower-case styles preferred for nested content (less visual weight than uppercase)

**Alternatives considered**:

- Upper-case roman/alpha at level 1 → Too visually heavy for running text
- Custom sequence → No benefit, breaks user expectations

### Decision 3: Define 6 nesting levels explicitly

**Choice**: Write CSS rules for 6 levels of nesting (2 full cycles).

**Rationale**:

- Covers 95%+ of real-world use cases
- Cost is ~30 lines of CSS (cheap)
- Deeper nesting (7+) falls back to browser defaults (acceptable edge case)

**Alternatives considered**:

- 3 levels only → Breaks at 4th level (incomplete cycle)
- 9 levels → Unnecessary (deeply nested lists are rare, unreadable)
- Infinite via preprocessor loops → Adds build complexity for marginal gain

### Decision 4: Preserve inline style precedence

**Choice**: CSS specificity naturally prioritizes inline styles. No `!important` needed for base rules.

**Rationale**:

- Users who manually set `style="list-style-type: upper-roman;"` keep their choice
- Supports future Phase 2 (user overrides via toolbar)
- Standard CSS behavior, no surprises

**CSS specificity**:

- Inline style: 1000 points (highest)
- `ol ol ol`: 3 points
- Inline wins automatically

## Risks / Trade-offs

**[Risk]** Users with existing nested lists see visual change after upgrade → **Mitigation**: Document in CHANGELOG as enhancement. Not breaking (only affects appearance, not functionality).

**[Risk]** Deeply nested lists (7+ levels) don't cycle correctly → **Mitigation**: Define extra levels if needed (cheap). Fallback to browser default (decimal/disc) is acceptable.

**[Risk]** User expectations from non-Word apps → **Mitigation**: Word/Google Docs are dominant, setting de facto standard. Other tools vary, but decimal→alpha→roman is most common.

**[Trade-off]** No per-list customization in Phase 1 → Accepted. Phase 2 adds toolbar controls for manual override.

**[Trade-off]** CSS file size increases ~40 lines → Negligible (gzipped impact <0.5KB).

## Implementation

**File modified**: `packages/pluggableWidgets/rich-text-web/src/ui/RichText.scss`

**Change location**: Lines 158-162 (existing `ul, ol` rules)

**Strategy**: Replace simple `ul, ol` selector with nested rules:

```scss
// Ordered list auto-cycling (6 levels = 2 full cycles)
ol {
list-style-type: decimal;
ol {
list-style-type: lower-alpha;
ol {
list-style-type: lower-roman;
ol {
list-style-type: decimal; // Cycle repeats
ol {
list-style-type: lower-alpha;
ol {
list-style-type: lower-roman;
}
}
}
}
}
}

// Unordered list auto-cycling (6 levels = 2 full cycles)
ul {
list-style-type: disc;
ul {
list-style-type: circle;
ul {
list-style-type: square;
ul {
list-style-type: disc; // Cycle repeats
ul {
list-style-type: circle;
ul {
list-style-type: square;
}
}
}
}
}
}
```

Keep existing padding/margin rules intact.

## Open Questions

None. Design is complete and straightforward.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## Why

Nested lists in rich text widget currently use same numbering style at all levels (1, 2, 3 everywhere). Standard word processors (Word, Google Docs) automatically cycle through different styles for visual hierarchy (decimal → lower-alpha → lower-roman). This improves readability of complex nested lists.

## What Changes

- Ordered lists (`<ol>`) will auto-cycle numbering styles based on nesting depth:
- Level 1: decimal (1, 2, 3)
- Level 2: lower-alpha (a, b, c)
- Level 3: lower-roman (i, ii, iii)
- Level 4+: cycle repeats (decimal → lower-alpha → lower-roman)
- Unordered lists (`<ul>`) will auto-cycle bullet styles based on nesting depth:
- Level 1: disc (●)
- Level 2: circle (○)
- Level 3: square (■)
- Level 4+: cycle repeats (disc → circle → square)
- CSS-only implementation (no JavaScript changes)
- No toolbar changes or user override controls (Phase 1 scope)

## Capabilities

### New Capabilities

- `list-style-auto-cycle`: Nested ordered and unordered lists automatically cycle through numbering/bullet styles based on depth

### Modified Capabilities

<!-- No existing spec requirements changing - this is pure visual enhancement -->

## Impact

**Files affected**:

- `packages/pluggableWidgets/rich-text-web/src/ui/RichText.scss` — Add nested selector rules for `ol` and `ul`

**User-facing changes**:

- Nested lists will display different numbering/bullet styles automatically
- Matches standard word processor behavior
- Existing lists with manual `style="list-style-type: ..."` inline styles unchanged (inline styles override CSS)
- No breaking changes — pure visual enhancement

**Testing scope**:

- Visual verification of 6 nesting levels for ordered lists
- Visual verification of 4 nesting levels for unordered lists
- Mixed list types (ordered inside unordered, vice versa)
- Task lists (`ul[data-type="taskList"]`) should remain unaffected
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
## ADDED Requirements

### Requirement: Ordered lists cycle through numbering styles by nesting depth

Ordered lists (`<ol>`) SHALL automatically cycle through numbering styles based on nesting level using CSS descendant selectors.

#### Scenario: Level 1 ordered list uses decimal

- **WHEN** an ordered list is at the top level (not nested)
- **THEN** list items display with decimal numbering (1, 2, 3)

#### Scenario: Level 2 ordered list uses lower-alpha

- **WHEN** an ordered list is nested one level deep inside another ordered list
- **THEN** list items display with lower-alpha numbering (a, b, c)

#### Scenario: Level 3 ordered list uses lower-roman

- **WHEN** an ordered list is nested two levels deep
- **THEN** list items display with lower-roman numbering (i, ii, iii)

#### Scenario: Level 4 ordered list cycles back to decimal

- **WHEN** an ordered list is nested three levels deep
- **THEN** list items display with decimal numbering (1, 2, 3)

#### Scenario: Level 5 ordered list cycles to lower-alpha

- **WHEN** an ordered list is nested four levels deep
- **THEN** list items display with lower-alpha numbering (a, b, c)

#### Scenario: Level 6 ordered list cycles to lower-roman

- **WHEN** an ordered list is nested five levels deep
- **THEN** list items display with lower-roman numbering (i, ii, iii)

### Requirement: Unordered lists cycle through bullet styles by nesting depth

Unordered lists (`<ul>`) SHALL automatically cycle through bullet styles based on nesting level using CSS descendant selectors.

#### Scenario: Level 1 unordered list uses disc

- **WHEN** an unordered list is at the top level (not nested)
- **THEN** list items display with disc bullets (●)

#### Scenario: Level 2 unordered list uses circle

- **WHEN** an unordered list is nested one level deep inside another unordered list
- **THEN** list items display with circle bullets (○)

#### Scenario: Level 3 unordered list uses square

- **WHEN** an unordered list is nested two levels deep
- **THEN** list items display with square bullets (■)

#### Scenario: Level 4 unordered list cycles back to disc

- **WHEN** an unordered list is nested three levels deep
- **THEN** list items display with disc bullets (●)

### Requirement: Inline styles override CSS defaults

When a list element has an inline `style="list-style-type: ..."` attribute, that style SHALL take precedence over CSS auto-cycle rules.

#### Scenario: Manually styled list preserves inline style

- **WHEN** an ordered list has `style="list-style-type: upper-roman;"`
- **THEN** list displays with upper-roman (I, II, III) regardless of nesting depth

#### Scenario: Nested list without inline style uses auto-cycle

- **WHEN** parent list has inline style but nested child list has no inline style
- **THEN** child list uses CSS auto-cycle rules based on its depth

### Requirement: Task lists remain unstyled

Task lists with `data-type="taskList"` SHALL remain unstyled with `list-style: none`, unaffected by auto-cycle rules.

#### Scenario: Task list ignores auto-cycle

- **WHEN** a list has `data-type="taskList"` attribute
- **THEN** list displays with no bullet/number markers (checkboxes only)

### Requirement: Mixed list types cycle independently

When ordered and unordered lists are mixed (e.g., `<ul>` inside `<ol>`), each list type SHALL follow its own cycle sequence.

#### Scenario: Ordered list inside unordered list starts at decimal

- **WHEN** an `<ol>` is nested inside a `<ul>`
- **THEN** ordered list uses decimal (1, 2, 3) regardless of parent's depth

#### Scenario: Unordered list inside ordered list starts at disc

- **WHEN** a `<ul>` is nested inside an `<ol>`
- **THEN** unordered list uses disc (●) regardless of parent's depth
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## 1. Update SCSS for list style cycling

- [x] 1.1 Add nested selector rules for ordered lists (ol) with 6 levels: decimal → lower-alpha → lower-roman → decimal → lower-alpha → lower-roman
- [x] 1.2 Add nested selector rules for unordered lists (ul) with 6 levels: disc → circle → square → disc → circle → square
- [x] 1.3 Preserve existing padding-left and margin rules for ul/ol

## 2. Manual testing

- [x] 2.1 Test ordered list nesting: verify 6 levels show correct cycle (1 → a → i → 1 → a → i)
- [x] 2.2 Test unordered list nesting: verify 4 levels show correct cycle (● → ○ → ■ → ●)
- [x] 2.3 Test mixed list types: ordered inside unordered starts at decimal, unordered inside ordered starts at disc
- [x] 2.4 Test inline style override: list with style="list-style-type: upper-roman;" displays upper-roman at any depth
- [x] 2.5 Test task lists remain unaffected: ul[data-type="taskList"] shows no bullets/numbers

## 3. Documentation

- [x] 3.1 Add entry to CHANGELOG.md describing auto-cycle enhancement
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-10
Loading
Loading