Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions packages/app/e2e/icon-viewbox-fit.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* icon-viewbox-fit.spec.ts @smoke
*
* Static compliance check: every glyph in packages/ui/src/components/icon.tsx
* must render inside the `0 0 20 20` viewBox. The chrome icon registry stores
* each glyph as an inner `<g transform=...>` that re-positions a traced path
* into the shared 20x20 canvas; a mis-fit transform leaves part of the glyph
* outside the viewport, and the svg's UA `overflow: hidden` then clips it on
* screen.
*
* Originally added with the `read-file` refit — that glyph extended to y≈22.1
* (clipped at the bottom). This test renders each icon, calls the browser's
* native `getBBox`, and asserts the bounding box stays within `[0, 20]` so a
* future port that overshoots a side is caught before it ships.
*
* No app server / opencode backend required: the spec only uses
* `page.setContent` to evaluate SVG geometry in chromium.
*/
import { readFileSync } from "node:fs"
import { dirname, resolve } from "node:path"
import { fileURLToPath } from "node:url"
import { test, expect } from "@playwright/test"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

const here = dirname(fileURLToPath(import.meta.url))
const iconRegistry = resolve(here, "../../ui/src/components/icon.tsx")

function loadIcons(): Record<string, string> {
const src = readFileSync(iconRegistry, "utf8")
const startIdx = src.indexOf("export const icons = {")
if (startIdx < 0) throw new Error("icon registry export not found")
const body = src.slice(startIdx)
const out: Record<string, string> = {}
const re = /"([\w-]+)":\s*`([\s\S]*?)`,/g
let m: RegExpExecArray | null
while ((m = re.exec(body))) out[m[1]] = m[2]
return out
}
Comment thread
Astro-Han marked this conversation as resolved.
Outdated

// 1-unit margin is the design contract noted at the top of icon.tsx; we lock
// the harder 0-20 bound so the test fails only on real clipping, not on
// glyphs that hug the keyshape edge.
const VIEWBOX_LOW = 0
const VIEWBOX_HIGH = 20
const EPSILON = 0.05

test("@smoke every chrome icon fits inside the 0..20 viewBox", async ({ page }) => {
const icons = loadIcons()
const names = Object.keys(icons)
expect(names.length, "icon registry should expose at least one glyph").toBeGreaterThan(0)

await page.setContent('<!doctype html><html><body><div id="stage"></div></body></html>')

const measurements = await page.evaluate((iconMap) => {
const ns = "http://www.w3.org/2000/svg"
const stage = document.getElementById("stage")!
const results: Array<{ name: string; x: number; y: number; w: number; h: number } | { name: string; error: string }> = []
for (const [name, inner] of Object.entries(iconMap)) {
const svg = document.createElementNS(ns, "svg")
svg.setAttribute("viewBox", "0 0 20 20")
svg.setAttribute("width", "200")
svg.setAttribute("height", "200")
svg.innerHTML = inner
stage.appendChild(svg)
try {
const bbox = svg.getBBox()
results.push({ name, x: bbox.x, y: bbox.y, w: bbox.width, h: bbox.height })
} catch (e) {
results.push({ name, error: String(e) })
}
stage.removeChild(svg)
}
return results
}, icons)

const overflow = measurements
.map((entry) => {
if ("error" in entry) {
return `${entry.name}: getBBox threw ${entry.error}`
}
const x1 = entry.x + entry.w
const y1 = entry.y + entry.h
const sides: string[] = []
if (entry.x < VIEWBOX_LOW - EPSILON) sides.push(`left ${entry.x.toFixed(2)}`)
if (entry.y < VIEWBOX_LOW - EPSILON) sides.push(`top ${entry.y.toFixed(2)}`)
if (x1 > VIEWBOX_HIGH + EPSILON) sides.push(`right ${x1.toFixed(2)}`)
if (y1 > VIEWBOX_HIGH + EPSILON) sides.push(`bottom ${y1.toFixed(2)}`)
if (!sides.length) return null
return `${entry.name}: ${sides.join(", ")} (bbox x:[${entry.x.toFixed(2)},${x1.toFixed(2)}] y:[${entry.y.toFixed(2)},${y1.toFixed(2)}])`
})
.filter((line): line is string => line !== null)

expect(overflow, `icons overflowing the 0..20 viewBox:\n${overflow.join("\n")}`).toEqual([])
})
2 changes: 1 addition & 1 deletion packages/ui/src/components/icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const icons = {
"plus": `<g transform="translate(1.000 1.052) scale(0.104046)"><path d="M0,0 L7,1 L9,5 L9,80 L84,80 L89,84 L88,91 L85,93 L9,93 L9,168 L6,172 L-1,172 L-4,169 L-4,93 L-81,93 L-84,90 L-84,84 L-81,81 L-79,80 L-4,80 L-4,4 Z " transform="translate(84.000 0.000)" fill="currentColor"/></g>`,
"plus-small": `<g transform="translate(5.266 5.370) scale(0.104046)"><path d="M0,0 L6,1 L8,3 L9,7 L9,38 L43,38 L47,41 L48,46 L46,50 L44,51 L9,51 L9,84 L7,88 L5,89 L-1,89 L-4,86 L-5,52 L-39,51 L-42,49 L-43,43 L-38,38 L-4,38 L-5,34 L-4,3 Z " transform="translate(43.000 0.000)" fill="currentColor"/></g>`,
"prompt": `<g transform="translate(-1.1486 -10.1274) scale(0.0282)"><path d="M 186.500 435.495 C 172.081 437.880, 161.975 441.924, 149.500 450.304 C 136.187 459.246, 122.543 476.311, 117.419 490.426 C 111.710 506.155, 112 494.201, 112 713.870 C 112 931.383, 111.806 922.843, 117.055 936.879 C 121.822 949.628, 126.168 956.199, 136.985 967.009 C 148.890 978.907, 157.090 984.129, 171.534 989.012 C 176.460 990.677, 184.543 992.504, 189.495 993.072 C 195.490 993.759, 266.528 994.002, 402 993.797 L 605.500 993.489 613.500 991.251 C 628.585 987.030, 639.442 981.124, 651.259 970.711 C 663.550 959.881, 673.153 943.995, 677.683 927 C 678.947 922.259, 679.165 893.010, 679.263 715 L 679.377 508.500 677.159 500.500 C 675.940 496.100, 674.437 491.150, 673.820 489.500 C 671.026 482.031, 662.883 469.949, 655.179 461.844 C 643.156 449.195, 630.796 441.768, 614.089 437.155 L 604.500 434.508 399.500 434.340 C 233.557 434.204, 192.976 434.424, 186.500 435.495 M 187.369 471.037 C 180.714 472.355, 169.654 478.500, 163.226 484.451 C 156.951 490.261, 151.198 499.762, 148.966 508 C 147.494 513.435, 147.459 600.859, 148.928 602.328 C 149.481 602.881, 154.916 600.563, 162.672 596.467 C 201.213 576.108, 203.628 575, 209.459 575 C 215.390 575, 218.008 576.196, 251.890 594.383 C 259.805 598.631, 266.892 601.872, 267.640 601.585 C 268.759 601.155, 269.019 592.492, 269.108 552.781 C 269.253 487.732, 269.010 472.738, 267.784 471.250 C 266.991 470.287, 258.123 470.025, 229.128 470.110 C 208.432 470.170, 189.641 470.587, 187.369 471.037 M 305.062 471.425 C 304.172 472.498, 303.900 492.635, 303.964 552.844 C 304.040 624.666, 303.882 633.256, 302.414 636.923 C 299.392 644.476, 291.148 648.864, 282.637 647.450 C 278.645 646.787, 252.432 633.773, 225.507 619.087 C 218.361 615.189, 211.490 612, 210.239 612 C 206.795 612, 151.598 640.370, 149.035 643.457 C 148.308 644.334, 147.986 686.664, 147.998 780.114 C 148.016 925.504, 147.824 919.809, 153.056 930.616 C 156.070 936.841, 166.015 947.294, 172.349 950.894 C 175.182 952.504, 180.387 954.761, 183.915 955.911 L 190.330 958 395.915 957.989 C 623.110 957.977, 604.488 958.531, 618.087 951.385 C 629.300 945.493, 639.342 932.904, 642.605 920.650 C 643.745 916.369, 643.978 881.487, 643.988 713.915 C 644 512.409, 643.999 512.328, 641.919 505.915 C 639.080 497.167, 635.887 491.955, 629.182 485.123 C 622.730 478.550, 611.749 472.600, 603.114 470.999 C 599.684 470.363, 544.767 470, 451.986 470 C 327.057 470, 306.076 470.204, 305.062 471.425 M 237.966 717.750 C 233.743 719.904, 232.084 721.476, 230.315 725 C 229.073 727.475, 228.044 730.755, 228.028 732.290 C 227.994 735.696, 230.975 741.726, 234.166 744.702 C 240.156 750.289, 232.126 750.002, 381.718 749.978 C 513.535 749.957, 519.673 749.876, 523.500 748.110 C 537.175 741.798, 537.822 724.077, 524.618 717.497 L 519.607 715 381.483 715 L 243.359 715 237.966 717.750 M 244.500 819.936 C 238.921 820.893, 234.561 823.405, 231.829 827.235 C 229.957 829.859, 229.500 831.792, 229.500 837.078 C 229.500 842.680, 229.900 844.180, 232.194 847.187 C 233.675 849.129, 236.600 851.569, 238.694 852.609 C 242.343 854.422, 246.878 854.512, 348.500 854.796 C 424.350 855.007, 455.780 854.772, 459 853.970 C 467.870 851.761, 473 845.540, 473 836.992 C 473 832.547, 470.384 826.559, 467.173 823.655 C 462.042 819.015, 463.779 819.083, 353 819.169 C 295.525 819.214, 246.700 819.559, 244.500 819.936" fill="currentColor"/></g>`,
"read-file": `<g transform="translate(-5.7316 36.8284) scale(0.011675 -0.011675)"><path fill-rule="evenodd" d="M905 2891 c-22 -10 -54 -34 -72 -52 -65 -69 -63 -42 -63 -759 0 -711 -2 -691 58 -753 15 -16 44 -38 65 -48 36 -19 60 -19 541 -17 l504 3 44 30 c23 17 54 51 68 75 l25 45 3 495 c2 272 0 510 -3 528 -4 23 -25 52 -68 94 -34 34 -135 133 -224 220 l-162 158 -338 0 c-308 0 -342 -2 -378 -19z m543 -71 l102 0 0 -122 c0 -178 20 -231 103 -279 38 -21 57 -24 190 -27 l147 -4 0 -477 0 -476 -25 -37 c-17 -25 -38 -41 -63 -48 -25 -8 -187 -10 -504 -8 l-467 3 -31 30 c-16 16 -35 46 -40 67 -7 24 -10 262 -8 661 3 686 0 652 65 694 30 20 192 33 343 27 47 -2 131 -3 188 -4z m342 -195 l145 -145 -114 0 -113 0 -34 34 -34 34 0 111 c0 61 1 111 3 111 1 0 67 -65 147 -145z M1072 2238 c-16 -16 -15 -43 2 -57 9 -8 75 -11 216 -9 181 3 204 5 214 21 8 12 8 22 0 35 -10 15 -34 17 -215 20 -151 2 -208 -1 -217 -10z M1151 1980 c-81 -5 -107 -23 -85 -57 10 -17 40 -18 344 -21 237 -2 337 1 346 9 17 14 18 43 1 57 -13 11 -479 21 -606 12z M1070 1705 c-7 -8 -10 -25 -6 -39 5 -22 12 -25 73 -28 38 -1 176 -1 308 2 l240 5 3 23 c8 51 2 52 -313 52 -247 0 -294 -2 -305 -15z" fill="currentColor"/></g>`,
"read-file": `<g transform="translate(-5.7501 33.0610) scale(0.011057 -0.011057)"><path fill-rule="evenodd" d="M905 2891 c-22 -10 -54 -34 -72 -52 -65 -69 -63 -42 -63 -759 0 -711 -2 -691 58 -753 15 -16 44 -38 65 -48 36 -19 60 -19 541 -17 l504 3 44 30 c23 17 54 51 68 75 l25 45 3 495 c2 272 0 510 -3 528 -4 23 -25 52 -68 94 -34 34 -135 133 -224 220 l-162 158 -338 0 c-308 0 -342 -2 -378 -19z m543 -71 l102 0 0 -122 c0 -178 20 -231 103 -279 38 -21 57 -24 190 -27 l147 -4 0 -477 0 -476 -25 -37 c-17 -25 -38 -41 -63 -48 -25 -8 -187 -10 -504 -8 l-467 3 -31 30 c-16 16 -35 46 -40 67 -7 24 -10 262 -8 661 3 686 0 652 65 694 30 20 192 33 343 27 47 -2 131 -3 188 -4z m342 -195 l145 -145 -114 0 -113 0 -34 34 -34 34 0 111 c0 61 1 111 3 111 1 0 67 -65 147 -145z M1072 2238 c-16 -16 -15 -43 2 -57 9 -8 75 -11 216 -9 181 3 204 5 214 21 8 12 8 22 0 35 -10 15 -34 17 -215 20 -151 2 -208 -1 -217 -10z M1151 1980 c-81 -5 -107 -23 -85 -57 10 -17 40 -18 344 -21 237 -2 337 1 346 9 17 14 18 43 1 57 -13 11 -479 21 -606 12z M1070 1705 c-7 -8 -10 -25 -6 -39 5 -22 12 -25 73 -28 38 -1 176 -1 308 2 l240 5 3 23 c8 51 2 52 -313 52 -247 0 -294 -2 -305 -15z" fill="currentColor"/></g>`,
"providers": `<g transform="translate(1.236 1.000) scale(0.094241)"><path d="M0,0 L10,0 L20,4 L47,19 L69,31 L87,41 L95,47 L97,50 L97,57 L94,62 L85,68 L73,75 L90,85 L96,90 L97,99 L94,104 L80,112 L73,116 L78,120 L92,128 L96,132 L98,136 L97,141 L94,146 L85,152 L61,165 L39,177 L17,189 L12,191 L-2,191 L-21,182 L-37,173 L-60,160 L-72,154 L-84,147 L-88,141 L-87,134 L-85,130 L-64,118 L-62,118 L-62,116 L-67,114 L-83,105 L-87,100 L-88,94 L-85,88 L-66,77 L-62,76 L-62,74 L-68,72 L-84,63 L-88,56 L-87,50 L-84,45 L-65,35 L-44,23 L-23,11 L-12,5 L-4,1 Z M1,9 L-16,18 L-34,28 L-57,41 L-78,53 L-77,56 L-65,63 L-54,69 L-37,78 L-18,89 L-8,94 L-1,98 L10,98 L21,92 L38,83 L53,74 L69,66 L87,55 L87,52 L63,39 L40,26 L14,12 L8,9 Z M-53,81 L-69,90 L-78,95 L-73,100 L-53,111 L-43,117 L-29,124 L-13,133 L-2,139 L0,140 L9,140 L21,134 L39,124 L57,114 L76,103 L87,97 L87,94 L63,81 L57,82 L34,95 L16,105 L12,106 L-3,106 L-19,98 L-41,86 L-50,81 Z M60,122 L46,130 L32,138 L20,144 L15,147 L11,148 L-2,148 L-11,144 L-39,129 L-50,123 L-55,124 L-76,136 L-78,139 L-64,147 L-37,162 L-23,170 L-9,177 L-1,182 L10,182 L36,168 L59,155 L78,145 L87,140 L86,136 L65,124 Z " transform="translate(88.000 0.000)" fill="currentColor"/></g>`,
"remote-control": `<g transform="translate(-1.4463 -18.3762) scale(0.0308)"><path d="M 278 662.047 C 248.945 665.244, 218.581 677.902, 196.502 696.024 C 172.616 715.628, 153.574 744.139, 145.091 773 C 144.363 775.475, 143.412 778.625, 142.976 780 C 142.540 781.375, 141.656 785.425, 141.012 789 C 140.367 792.575, 139.426 797.660, 138.920 800.299 C 138.394 803.044, 138 853.693, 138 918.627 C 138 1027.444, 138.083 1032.640, 139.993 1043.828 C 142.262 1057.122, 142.873 1059.498, 147.753 1074 C 154.091 1092.837, 162.713 1108.227, 176.136 1124.662 C 191.814 1143.860, 218.350 1162.555, 241 1170.360 C 255.274 1175.279, 268.333 1178.087, 281.733 1179.119 C 300.746 1180.582, 466.985 1179.724, 473 1178.131 C 475.475 1177.476, 479.750 1176.453, 482.500 1175.858 C 489.200 1174.408, 506.140 1168.751, 513.219 1165.599 C 535.346 1155.747, 560.882 1133.774, 575.833 1111.721 C 592.294 1087.439, 601.122 1060.765, 603.872 1027 C 604.975 1013.452, 604.711 818.700, 603.573 806.500 C 599.698 764.950, 583.780 731.342, 554.720 703.353 C 537.436 686.705, 516.885 674.550, 495.060 668.066 C 472.539 661.375, 476.731 661.611, 376.500 661.407 C 326.450 661.305, 282.125 661.593, 278 662.047 M 280.609 693.036 C 245.091 697.295, 213.520 715.425, 194.031 742.755 C 182.603 758.781, 173.845 779.588, 170.566 798.500 C 168.293 811.616, 168.242 1028.181, 170.509 1040.500 C 175.898 1069.776, 186.278 1090.755, 205.134 1110.478 C 221.090 1127.169, 233.086 1134.772, 256.629 1143.115 C 272.114 1148.603, 269.937 1148.500, 371 1148.500 C 468.286 1148.500, 464.751 1148.629, 481.500 1144.456 C 495.663 1140.927, 512.310 1132.584, 524.063 1123.126 C 552.240 1100.450, 567.432 1072.558, 572.983 1033.308 C 573.621 1028.799, 573.997 987.023, 573.992 921.308 C 573.983 814.326, 573.813 808.589, 570.102 790.500 C 566.735 774.092, 557.505 753.402, 547.810 740.534 C 530.148 717.092, 504.206 700.773, 474.305 694.297 C 467.345 692.789, 456.809 692.585, 377 692.408 C 327.775 692.299, 284.399 692.581, 280.609 693.036 M 334.922 728.274 C 324.798 735.096, 324.535 746.158, 334.325 753.408 C 337.053 755.429, 338.245 755.500, 369.325 755.500 C 399.702 755.500, 401.690 755.388, 404.895 753.500 C 415.245 747.404, 415.577 734.353, 405.532 728.466 L 401.324 726 369.810 726 C 338.687 726, 338.254 726.028, 334.922 728.274 M 363.368 807.590 C 361.646 808.440, 353.034 816.422, 344.230 825.328 C 326.734 843.028, 324.404 846.732, 326.930 852.831 C 330.028 860.311, 339.078 864.120, 345.584 860.682 C 347.188 859.835, 352.992 854.610, 358.481 849.071 C 364.183 843.318, 369.280 839, 370.370 839 C 371.461 839, 376.482 843.261, 382.101 848.955 C 393.452 860.457, 396.616 862.449, 402.326 861.683 C 407.420 861, 411 858.378, 413.379 853.588 C 416.771 846.759, 415.043 843.810, 397.142 825.880 C 388.539 817.263, 379.925 809.266, 378 808.110 C 373.854 805.620, 367.799 805.405, 363.368 807.590 M 263 905.032 C 258.506 907.781, 230.317 935.644, 228.432 939.200 C 226.324 943.176, 226.668 948.957, 229.255 953.058 C 232.232 957.776, 260.622 985.239, 264.700 987.347 C 268.646 989.385, 271.261 989.433, 274.931 987.536 C 279.840 984.997, 283.001 980.347, 282.993 975.673 C 282.985 970.370, 281.671 968.525, 269.750 957.073 C 264.387 951.921, 260 946.975, 260 946.081 C 260 945.187, 263.874 940.641, 268.609 935.978 C 282.854 921.950, 285.029 917.800, 281.595 911.200 C 277.689 903.692, 269.584 901.004, 263 905.032 M 465.270 904.483 C 459.082 907.172, 456.204 915.706, 459.429 921.800 C 460.215 923.285, 465.390 928.992, 470.929 934.481 C 476.468 939.971, 481 945.225, 481 946.158 C 481 947.091, 476.298 952.226, 470.551 957.570 C 458.622 968.663, 456.289 972.646, 457.999 978.997 C 459.120 983.161, 463.581 987.606, 467.668 988.631 C 473.303 990.046, 477.515 987.390, 493.560 972.300 C 502.107 964.263, 510.202 955.806, 511.549 953.506 C 514.315 948.786, 514.716 943.273, 512.590 939.200 C 510.650 935.484, 478.822 905.263, 475.532 904.012 C 471.979 902.661, 469.161 902.791, 465.270 904.483 M 396 1034.595 C 394.075 1035.429, 387.721 1040.820, 381.880 1046.574 C 373.655 1054.679, 370.837 1056.890, 369.380 1056.384 C 368.346 1056.024, 363 1051.340, 357.500 1045.974 C 346.236 1034.986, 344.730 1034, 339.201 1034 C 330.281 1034, 324.160 1044.666, 328.412 1052.800 C 329.188 1054.285, 337.416 1062.999, 346.695 1072.164 C 370.917 1096.088, 370.106 1096.089, 394.329 1072.139 C 403.584 1062.988, 411.797 1054.285, 412.579 1052.800 C 416.008 1046.285, 412.913 1038.225, 405.729 1034.964 C 400.675 1032.669, 400.462 1032.661, 396 1034.595" fill="currentColor"/></g>`,
"reset": `<g transform="translate(1.346 1.000) scale(0.098901)"><path d="M0,0 L16,1 L32,5 L48,13 L60,22 L70,32 L76,40 L77,13 L80,10 L85,11 L87,17 L87,56 L85,60 L83,61 L43,61 L39,57 L41,52 L42,51 L71,51 L61,38 L50,27 L36,18 L22,13 L12,11 L-9,11 L-24,15 L-36,20 L-49,29 L-59,39 L-68,53 L-73,65 L-76,75 L-77,82 L-77,101 L-73,118 L-67,132 L-60,142 L-51,152 L-39,161 L-24,168 L-12,171 L-5,172 L11,172 L26,169 L39,164 L49,158 L59,150 L69,138 L77,122 L80,117 L84,116 L87,118 L87,125 L79,142 L71,152 L60,163 L46,172 L35,177 L21,181 L15,182 L-9,182 L-26,178 L-42,171 L-55,162 L-68,149 L-77,135 L-84,119 L-87,106 L-88,97 L-88,86 L-86,72 L-81,56 L-74,42 L-66,32 L-59,24 L-46,14 L-30,6 L-17,2 Z " transform="translate(88.000 0.000)" fill="currentColor"/></g>`,
Expand Down
Loading