| author | |
| committer | |
| log | 2c4158f87a1b6a586d0aa5e0338f122cb7887c4f |
| tree | 6bbc0f72e82354fe297815b1ea433f537864461c |
| parent | 0ede2ed0de8214c0e9168dcf1936e68e5c730e10 |
| signature | Signed by PGP key B5690EEEBB952194 |
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", () => { |
| 24 | 24 | it("accounts for a non-default start attribute", () => { |
| 25 | 25 | // start=95 + 9 items => last marker is "103", three digits. |
| 26 | 26 | expect(orderedListGutterStyle(9, 95)).toEqual({ "--list-gutter": "4ch" }); |
| 27 | expect(orderedListGutterStyle(5, "999995")).toEqual({ "--list-gutter": "7ch" }); | |
| 27 | 28 | }); |
| 28 | 29 | |
| 29 | 30 | it("scales further for four-digit markers", () => { |
| 30 | 31 | expect(orderedListGutterStyle(1000, undefined)).toEqual({ "--list-gutter": "5ch" }); |
| 31 | 32 | }); |
| 32 | 33 | |
| 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 | ||
| 33 | 40 | it("treats a missing/zero item count as a single item", () => { |
| 34 | 41 | expect(orderedListGutterStyle(0, undefined)).toBeUndefined(); |
| 42 | expect(orderedListGutterStyle(0, 100)).toEqual({ "--list-gutter": "4ch" }); | |
| 35 | 43 | }); |
| 36 | 44 | }); |
apps/web/src/components/ChatMarkdown.tsx+13-11| ... | ... | @@ -159,22 +159,24 @@ function findTaskListMarkerOffset(markdown: string, listItemStart: number): numb |
| 159 | 159 | } |
| 160 | 160 | |
| 161 | 161 | /** |
| 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. | |
| 168 | 169 | */ |
| 169 | 170 | export function orderedListGutterStyle( |
| 170 | 171 | itemCount: number, |
| 171 | start: number | undefined, | |
| 172 | start: unknown, | |
| 172 | 173 | ): { "--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; | |
| 174 | 176 | 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` }; | |
| 178 | 180 | } |
| 179 | 181 | |
| 180 | 182 | const CHAT_MARKDOWN_SANITIZE_SCHEMA = { |
apps/web/src/index.css+5-2| ... | ... | @@ -2040,7 +2040,7 @@ code { |
| 2040 | 2040 | |
| 2041 | 2041 | /* --list-gutter defaults to the same 1.25rem as .chat-markdown ul, but |
| 2042 | 2042 | 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: | |
| 2044 | 2044 | outside painting the marker past the padding box. Reset it here too so a |
| 2045 | 2045 | nested ol without its own widened marker doesn't inherit the outer one. */ |
| 2046 | 2046 | .chat-markdown ol { |
| ... | ... | @@ -2049,6 +2049,10 @@ code { |
| 2049 | 2049 | list-style-type: decimal; |
| 2050 | 2050 | } |
| 2051 | 2051 | |
| 2052 | .chat-markdown ol > li::marker { | |
| 2053 | font-variant-numeric: tabular-nums; | |
| 2054 | } | |
| 2055 | ||
| 2052 | 2056 | .chat-markdown ul ul { |
| 2053 | 2057 | list-style-type: circle; |
| 2054 | 2058 | } |
| ... | ... | @@ -2114,7 +2118,6 @@ code { |
| 2114 | 2118 | |
| 2115 | 2119 | .chat-markdown section[data-footnotes] ol { |
| 2116 | 2120 | margin: 0; |
| 2117 | padding-left: 1.25rem; | |
| 2118 | 2121 | } |
| 2119 | 2122 | |
| 2120 | 2123 | .chat-markdown section[data-footnotes] li + li { |