authorgravatar for gr261212@gmail.comG-R3 <gr261212@gmail.com> 2026-09-02 05:15:02-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-02 09:15:02+00:00
log70cd258d8aac43ea57494527b00bf36de3efa6c0
treed8430759077384a429c6a53fafa5e82ed7d4b055
parentf14f41b894448298a86865c9114e6700245356e7
signature Signed by PGP key B5690EEEBB952194

fix(web): prevent two-digit list markers from being clipped (#9101)

Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com>

3 files changed, 13 insertions(+), 16 deletions(-)

apps/web/src/components/ChatMarkdown.test.tsx+5-5
...@@ -360,13 +360,13 @@ describe("orderedListGutterStyle", () => {...@@ -360,13 +360,13 @@ describe("orderedListGutterStyle", () => {
360 expect(orderedListGutterStyle(9, undefined)).toBeUndefined();360 expect(orderedListGutterStyle(9, undefined)).toBeUndefined();
361 });361 });
362362
363 it("leaves the default gutter alone for two-digit lists", () => {363 it("widens the gutter for two-digit lists", () => {
364 expect(orderedListGutterStyle(99, undefined)).toBeUndefined();364 expect(orderedListGutterStyle(99, undefined)).toEqual({ "--list-gutter": "3ch" });
365 });365 });
366366
367 it("leaves the default gutter alone for a two-digit list that starts above 1", () => {367 it("widens the gutter for a two-digit list that starts above 1", () => {
368 // start=50 + 49 items => last marker is "98", still two digits.368 // start=50 + 49 items => last marker is "98", still two digits.
369 expect(orderedListGutterStyle(49, 50)).toBeUndefined();369 expect(orderedListGutterStyle(49, 50)).toEqual({ "--list-gutter": "3ch" });
370 });370 });
371371
372 it("widens the gutter once the last marker reaches three digits", () => {372 it("widens the gutter once the last marker reaches three digits", () => {
...@@ -387,7 +387,7 @@ describe("orderedListGutterStyle", () => {...@@ -387,7 +387,7 @@ describe("orderedListGutterStyle", () => {
387 it("uses the widest marker and includes a negative start's minus sign", () => {387 it("uses the widest marker and includes a negative start's minus sign", () => {
388 expect(orderedListGutterStyle(1001, -1000)).toEqual({ "--list-gutter": "6ch" });388 expect(orderedListGutterStyle(1001, -1000)).toEqual({ "--list-gutter": "6ch" });
389 expect(orderedListGutterStyle(3, -15)).toEqual({ "--list-gutter": "4ch" });389 expect(orderedListGutterStyle(3, -15)).toEqual({ "--list-gutter": "4ch" });
390 expect(orderedListGutterStyle(3, -5)).toBeUndefined();390 expect(orderedListGutterStyle(3, -5)).toEqual({ "--list-gutter": "3ch" });
391 });391 });
392392
393 it("treats a missing/zero item count as a single item", () => {393 it("treats a missing/zero item count as a single item", () => {
apps/web/src/components/ChatMarkdown.tsx+5-8
...@@ -328,13 +328,10 @@ function findTaskListMarkerOffset(markdown: string, listItemStart: number): numb...@@ -328,13 +328,10 @@ function findTaskListMarkerOffset(markdown: string, listItemStart: number): numb
328}328}
329329
330/**330/**
331 * The default `1.25rem` marker gutter (`.chat-markdown ol`) fits markers up to331 * The default `1.25rem` marker gutter (`.chat-markdown ol`) fits one-character
332 * two characters wide. Once a marker reaches three characters (item 100+),332 * markers. Wider markers can extend past it and get clipped by a collapsed
333 * `list-style-position: outside` paints it wider than that gutter and clips333 * message's overflow. Widen the gutter to fit the widest marker, including a
334 * the leading character against the item's own overflow. Rather than widening334 * negative marker's minus sign.
335 * the gutter for every list, only lists whose widest marker is 3+ characters
336 * get a wider `--list-gutter`. The width includes a negative marker's minus
337 * sign.
338 */335 */
339export function orderedListGutterStyle(336export function orderedListGutterStyle(
340 itemCount: number,337 itemCount: number,
...@@ -344,7 +341,7 @@ export function orderedListGutterStyle(...@@ -344,7 +341,7 @@ export function orderedListGutterStyle(
344 const firstNumber = Number.isNaN(parsedStart) ? 1 : parsedStart;341 const firstNumber = Number.isNaN(parsedStart) ? 1 : parsedStart;
345 const lastNumber = firstNumber + Math.max(itemCount - 1, 0);342 const lastNumber = firstNumber + Math.max(itemCount - 1, 0);
346 const markerWidth = Math.max(String(firstNumber).length, String(lastNumber).length);343 const markerWidth = Math.max(String(firstNumber).length, String(lastNumber).length);
347 if (markerWidth <= 2) return undefined;344 if (markerWidth <= 1) return undefined;
348 return { "--list-gutter": `${markerWidth + 1}ch` };345 return { "--list-gutter": `${markerWidth + 1}ch` };
349}346}
350347
apps/web/src/index.css+3-3
...@@ -1643,8 +1643,8 @@ code {...@@ -1643,8 +1643,8 @@ code {
1643}1643}
16441644
1645.chat-markdown ul {1645.chat-markdown ul {
1646 /* Reset for nested uls under a widened ol — --list-gutter is an inherited1646 /* Reset for nested uls under a widened ol. --list-gutter is an inherited
1647 custom property, so without this a task-list under a 3+ digit ordered1647 custom property, so without this a task-list under a multi-digit ordered
1648 list would inherit the outer gutter instead of its own default. */1648 list would inherit the outer gutter instead of its own default. */
1649 --list-gutter: 1.25rem;1649 --list-gutter: 1.25rem;
1650 padding-left: 1.25rem;1650 padding-left: 1.25rem;
...@@ -1653,7 +1653,7 @@ code {...@@ -1653,7 +1653,7 @@ code {
16531653
1654/* --list-gutter defaults to the same 1.25rem as .chat-markdown ul, but1654/* --list-gutter defaults to the same 1.25rem as .chat-markdown ul, but
1655 ChatMarkdown's `ol` renderer widens it (via inline style) for lists whose1655 ChatMarkdown's `ol` renderer widens it (via inline style) for lists whose
1656 widest marker is 3+ characters, so item 100+ isn't clipped by list-style-position:1656 widest marker has multiple characters so it isn't clipped by list-style-position:
1657 outside painting the marker past the padding box. Reset it here too so a1657 outside painting the marker past the padding box. Reset it here too so a
1658 nested ol without its own widened marker doesn't inherit the outer one. */1658 nested ol without its own widened marker doesn't inherit the outer one. */
1659.chat-markdown ol {1659.chat-markdown ol {