authorgravatar for 63693423+abcdmku@users.noreply.github.comabcdmku <63693423+abcdmku@users.noreply.github.com> 2026-08-22 09:30:58-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-22 10:30:58-04:00
log2c4158f87a1b6a586d0aa5e0338f122cb7887c4f
tree6bbc0f72e82354fe297815b1ea433f537864461c
parent0ede2ed0de8214c0e9168dcf1936e68e5c730e10
signature Signed by PGP key B5690EEEBB952194

fix(web): handle wide ordered-list marker edge cases (#7856)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

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

apps/web/src/components/ChatMarkdown.test.tsx+8
......@@ -24,13 +24,21 @@ describe("orderedListGutterStyle", () => {
2424 it("accounts for a non-default start attribute", () => {
2525 // start=95 + 9 items => last marker is "103", three digits.
2626 expect(orderedListGutterStyle(9, 95)).toEqual({ "--list-gutter": "4ch" });
27 expect(orderedListGutterStyle(5, "999995")).toEqual({ "--list-gutter": "7ch" });
2728 });
2829
2930 it("scales further for four-digit markers", () => {
3031 expect(orderedListGutterStyle(1000, undefined)).toEqual({ "--list-gutter": "5ch" });
3132 });
3233
34 it("uses the widest marker and includes a negative start's minus sign", () => {
35 expect(orderedListGutterStyle(1001, -1000)).toEqual({ "--list-gutter": "6ch" });
36 expect(orderedListGutterStyle(3, -15)).toEqual({ "--list-gutter": "4ch" });
37 expect(orderedListGutterStyle(3, -5)).toBeUndefined();
38 });
39
3340 it("treats a missing/zero item count as a single item", () => {
3441 expect(orderedListGutterStyle(0, undefined)).toBeUndefined();
42 expect(orderedListGutterStyle(0, 100)).toEqual({ "--list-gutter": "4ch" });
3543 });
3644});
apps/web/src/components/ChatMarkdown.tsx+13-11
......@@ -159,22 +159,24 @@ function findTaskListMarkerOffset(markdown: string, listItemStart: number): numb
159159}
160160
161161/**
162 * The default `1.25rem` marker gutter (`.chat-markdown ol`) fits two-digit
163 * decimal markers. Once a list's last item reaches three digits (item 100+),
164 * `list-style-position: outside` paints the marker wider than that gutter and
165 * the leading digit gets clipped by the item's own overflow. Rather than
166 * widening the gutter for every list, only lists whose last marker is 3+
167 * digits get a wider `--list-gutter`, sized to that marker's digit count.
162 * The default `1.25rem` marker gutter (`.chat-markdown ol`) fits markers up to
163 * two characters wide. Once a marker reaches three characters (item 100+),
164 * `list-style-position: outside` paints it wider than that gutter and clips
165 * the leading character against the item's own overflow. Rather than widening
166 * the gutter for every list, only lists whose widest marker is 3+ characters
167 * get a wider `--list-gutter`. The width includes a negative marker's minus
168 * sign.
168169 */
169170export function orderedListGutterStyle(
170171 itemCount: number,
171 start: number | undefined,
172 start: unknown,
172173): { "--list-gutter": string } | undefined {
173 const firstNumber = typeof start === "number" && Number.isFinite(start) ? start : 1;
174 const parsedStart = Number.parseInt(String(start ?? 1), 10);
175 const firstNumber = Number.isNaN(parsedStart) ? 1 : parsedStart;
174176 const lastNumber = firstNumber + Math.max(itemCount - 1, 0);
175 const digits = String(Math.abs(lastNumber)).length;
176 if (digits <= 2) return undefined;
177 return { "--list-gutter": `${digits + 1}ch` };
177 const markerWidth = Math.max(String(firstNumber).length, String(lastNumber).length);
178 if (markerWidth <= 2) return undefined;
179 return { "--list-gutter": `${markerWidth + 1}ch` };
178180}
179181
180182const CHAT_MARKDOWN_SANITIZE_SCHEMA = {
apps/web/src/index.css+5-2
......@@ -2040,7 +2040,7 @@ code {
20402040
20412041/* --list-gutter defaults to the same 1.25rem as .chat-markdown ul, but
20422042 ChatMarkdown's `ol` renderer widens it (via inline style) for lists whose
2043 last marker is 3+ digits, so item 100+ isn't clipped by list-style-position:
2043 widest marker is 3+ characters, so item 100+ isn't clipped by list-style-position:
20442044 outside painting the marker past the padding box. Reset it here too so a
20452045 nested ol without its own widened marker doesn't inherit the outer one. */
20462046.chat-markdown ol {
......@@ -2049,6 +2049,10 @@ code {
20492049 list-style-type: decimal;
20502050}
20512051
2052.chat-markdown ol > li::marker {
2053 font-variant-numeric: tabular-nums;
2054}
2055
20522056.chat-markdown ul ul {
20532057 list-style-type: circle;
20542058}
......@@ -2114,7 +2118,6 @@ code {
21142118
21152119.chat-markdown section[data-footnotes] ol {
21162120 margin: 0;
2117 padding-left: 1.25rem;
21182121}
21192122
21202123.chat-markdown section[data-footnotes] li + li {