Skip to content

Commit dccdbda

Browse files
author
Ivan
committed
fix(micron): preserve multiple spaces under ForceMonospace
ASCII art such as the shadow block logo collapsed consecutive spaces because appendSplitAtSpaces left plain/space-only segments as bare text. Parent line divs have white-space: normal, so the browser collapsed the space runs and broke the layout. Wrap every split segment: - plain printable ASCII segments in <span class="Mu-mnt-group"> - non-ASCII or HTML-special segments in <span class="Mu-mws"> This keeps all whitespace in a white-space: pre-wrap span and restores the rendering that worked in v1.0.7 while keeping the Latin fast path. Add .Mu-mnt-group to playground CSS and a regression test for multiple consecutive spaces. Refs: v1.1.5
1 parent f3ffb6b commit dccdbda

4 files changed

Lines changed: 45 additions & 18 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
Dates use YYYY-MM-DD.
44

5+
## [1.1.5] - 2026-09-07
6+
7+
### Fixed
8+
9+
- `ForceMonospace` now preserves multiple consecutive ASCII spaces in Micron markup. Previously, only the first space in a run survived, collapsing ASCII-art whitespace and breaking block-character shading in pages like `shadow`.
10+
- Plain printable ASCII segments are wrapped in `<span class="Mu-mnt-group">` so the host CSS `white-space: pre-wrap` keeps spacing intact; non-ASCII or HTML-special segments remain in `<span class="Mu-mws">`.
11+
- Added `.Mu-mnt-group` to the playground CSS so the standalone renderer also keeps whitespace.
12+
513
## [1.1.4] - 2026-09-07
614

715
### Added

micron/extra_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,17 @@ func TestCincinnatusAsciiColor(t *testing.T) {
108108
t.Fatalf("expected Mu-mnt cell for block-drawing char: %s", out)
109109
}
110110
}
111+
112+
func TestForceMonospacePreservesMultipleSpaces(t *testing.T) {
113+
p := Parser{DarkTheme: true, ForceMonospace: true}
114+
out := p.ConvertMicronToHTML("█ █")
115+
if stripTags(out) != "█ █" {
116+
t.Fatalf("multiple spaces must be preserved in output text, got %q", stripTags(out))
117+
}
118+
if strings.Count(out, `class="Mu-mnt-group"`) != 2 {
119+
t.Fatalf("expected two space-only Mu-mnt-group spans, got: %s", out)
120+
}
121+
if strings.Count(out, `class="Mu-mnt">█`) != 2 {
122+
t.Fatalf("expected two block Mu-mnt cells, got: %s", out)
123+
}
124+
}

micron/split.go

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@ func splitAfterSpaceSegments(s string) []string {
3030
}
3131

3232
// appendSplitAtSpaces mirrors micron-parser-js splitAtSpaces / wrapWord.
33-
// Plain printable ASCII words (no & < >) are emitted unchanged. Words that
34-
// need escaping or contain non-ASCII are wrapped in Mu-mws, with Mu-mnt cells
35-
// only around complex grapheme clusters and HTML-special bytes.
33+
// Every space-delimited segment is wrapped in a whitespace-preserving span so
34+
// multiple consecutive spaces are not collapsed. Plain printable ASCII words
35+
// (no & < >) use Mu-mnt-group; words that need escaping or contain non-ASCII
36+
// use Mu-mws with Mu-mnt cells around complex grapheme clusters and HTML-special
37+
// bytes.
3638
func (p *Parser) appendSplitAtSpaces(b *strings.Builder, line string) {
3739
if line == "" {
38-
b.WriteString(`<span class="Mu-mws"></span>`)
40+
b.WriteString(`<span class="Mu-mnt-group"></span>`)
3941
return
4042
}
4143
start := 0
@@ -45,7 +47,16 @@ func (p *Parser) appendSplitAtSpaces(b *strings.Builder, line string) {
4547
if rel >= 0 {
4648
end = start + rel + 1
4749
}
48-
p.appendWrapWord(b, line[start:end])
50+
word := line[start:end]
51+
if wordNeedsMonoWrap(word) {
52+
b.WriteString(`<span class="Mu-mws">`)
53+
p.appendForceMonospace(b, word)
54+
b.WriteString(`</span>`)
55+
} else {
56+
b.WriteString(`<span class="Mu-mnt-group">`)
57+
b.WriteString(word)
58+
b.WriteString(`</span>`)
59+
}
4960
start = end
5061
}
5162
}
@@ -60,19 +71,6 @@ func wordNeedsMonoWrap(word string) bool {
6071
return false
6172
}
6273

63-
func (p *Parser) appendWrapWord(b *strings.Builder, word string) {
64-
if word == "" {
65-
return
66-
}
67-
if !wordNeedsMonoWrap(word) {
68-
b.WriteString(word)
69-
return
70-
}
71-
b.WriteString(`<span class="Mu-mws">`)
72-
p.appendForceMonospace(b, word)
73-
b.WriteString(`</span>`)
74-
}
75-
7674
// isComplexScriptBase reports scripts that must stay as continuous text runs
7775
// under ForceMonospace. Wrapping each rune in display:inline-block Mu-mnt spans
7876
// breaks Arabic/Persian joining, Hebrew/RTL order, CJK spacing, and Indic/Thai

web/css/playground.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,13 @@ body.preview-only .layout #preview-pane {
741741
white-space: pre-wrap;
742742
}
743743

744+
.Mu-mnt-group {
745+
display: inline;
746+
white-space: pre-wrap;
747+
text-decoration: inherit;
748+
vertical-align: baseline;
749+
}
750+
744751
.Mu-partial {
745752
display: block;
746753
min-height: 1.2em;

0 commit comments

Comments
 (0)