| 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,13 +24,21 @@ describe("orderedListGutterStyle", () => { |
| 24 | it("accounts for a non-default start attribute", () => { | 24 | it("accounts for a non-default start attribute", () => { |
| 25 | // start=95 + 9 items => last marker is "103", three digits. | 25 | // start=95 + 9 items => last marker is "103", three digits. |
| 26 | expect(orderedListGutterStyle(9, 95)).toEqual({ "--list-gutter": "4ch" }); | 26 | expect(orderedListGutterStyle(9, 95)).toEqual({ "--list-gutter": "4ch" }); |
| 27 | expect(orderedListGutterStyle(5, "999995")).toEqual({ "--list-gutter": "7ch" }); | ||
| 27 | }); | 28 | }); |
| 28 | 29 | ||
| 29 | it("scales further for four-digit markers", () => { | 30 | it("scales further for four-digit markers", () => { |
| 30 | expect(orderedListGutterStyle(1000, undefined)).toEqual({ "--list-gutter": "5ch" }); | 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 | it("treats a missing/zero item count as a single item", () => { | 40 | it("treats a missing/zero item count as a single item", () => { |
| 34 | expect(orderedListGutterStyle(0, undefined)).toBeUndefined(); | 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,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 | 162 | * The default `1.25rem` marker gutter (`.chat-markdown ol`) fits markers up to |
| 163 | * decimal markers. Once a list's last item reaches three digits (item 100+), | 163 | * two characters wide. Once a marker reaches three characters (item 100+), |
| 164 | * `list-style-position: outside` paints the marker wider than that gutter and | 164 | * `list-style-position: outside` paints it wider than that gutter and clips |
| 165 | * the leading digit gets clipped by the item's own overflow. Rather than | 165 | * the leading character against the item's own overflow. Rather than widening |
| 166 | * widening the gutter for every list, only lists whose last marker is 3+ | 166 | * the gutter for every list, only lists whose widest marker is 3+ characters |
| 167 | * digits get a wider `--list-gutter`, sized to that marker's digit count. | 167 | * get a wider `--list-gutter`. The width includes a negative marker's minus |
| 168 | * sign. | ||
| 168 | */ | 169 | */ |
| 169 | export function orderedListGutterStyle( | 170 | export function orderedListGutterStyle( |
| 170 | itemCount: number, | 171 | itemCount: number, |
| 171 | start: number | undefined, | 172 | start: unknown, |
| 172 | ): { "--list-gutter": string } | undefined { | 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 | const lastNumber = firstNumber + Math.max(itemCount - 1, 0); | 176 | const lastNumber = firstNumber + Math.max(itemCount - 1, 0); |
| 175 | const digits = String(Math.abs(lastNumber)).length; | 177 | const markerWidth = Math.max(String(firstNumber).length, String(lastNumber).length); |
| 176 | if (digits <= 2) return undefined; | 178 | if (markerWidth <= 2) return undefined; |
| 177 | return { "--list-gutter": `${digits + 1}ch` }; | 179 | return { "--list-gutter": `${markerWidth + 1}ch` }; |
| 178 | } | 180 | } |
| 179 | 181 | ||
| 180 | const CHAT_MARKDOWN_SANITIZE_SCHEMA = { | 182 | const CHAT_MARKDOWN_SANITIZE_SCHEMA = { |
apps/web/src/index.css+5-2| ... | @@ -2040,7 +2040,7 @@ code { | ... | @@ -2040,7 +2040,7 @@ code { |
| 2040 | 2040 | ||
| 2041 | /* --list-gutter defaults to the same 1.25rem as .chat-markdown ul, but | 2041 | /* --list-gutter defaults to the same 1.25rem as .chat-markdown ul, but |
| 2042 | ChatMarkdown's `ol` renderer widens it (via inline style) for lists whose | 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 | outside painting the marker past the padding box. Reset it here too so a | 2044 | outside painting the marker past the padding box. Reset it here too so a |
| 2045 | nested ol without its own widened marker doesn't inherit the outer one. */ | 2045 | nested ol without its own widened marker doesn't inherit the outer one. */ |
| 2046 | .chat-markdown ol { | 2046 | .chat-markdown ol { |
| ... | @@ -2049,6 +2049,10 @@ code { | ... | @@ -2049,6 +2049,10 @@ code { |
| 2049 | list-style-type: decimal; | 2049 | list-style-type: decimal; |
| 2050 | } | 2050 | } |
| 2051 | 2051 | ||
| 2052 | .chat-markdown ol > li::marker { | ||
| 2053 | font-variant-numeric: tabular-nums; | ||
| 2054 | } | ||
| 2055 | |||
| 2052 | .chat-markdown ul ul { | 2056 | .chat-markdown ul ul { |
| 2053 | list-style-type: circle; | 2057 | list-style-type: circle; |
| 2054 | } | 2058 | } |
| ... | @@ -2114,7 +2118,6 @@ code { | ... | @@ -2114,7 +2118,6 @@ code { |
| 2114 | 2118 | ||
| 2115 | .chat-markdown section[data-footnotes] ol { | 2119 | .chat-markdown section[data-footnotes] ol { |
| 2116 | margin: 0; | 2120 | margin: 0; |
| 2117 | padding-left: 1.25rem; | ||
| 2118 | } | 2121 | } |
| 2119 | 2122 | ||
| 2120 | .chat-markdown section[data-footnotes] li + li { | 2123 | .chat-markdown section[data-footnotes] li + li { |