Skip to content

Commit 3e20ade

Browse files
sametbasbugclaude
andcommitted
Put the panel's own stylesheet back on the scale
The scale lock only ever read src/styles/. The dashboard keeps its CSS in an inline <style> on the page, so none of it was checked: 110 raw values there, 40 on the platform page I added this week, 17 on the avatar upload page. Among them were four separate small-text sizes — 0.76, 0.78, 0.80, 0.82rem — all reaching for the same rung, half a pixel apart. Nobody chose that; each was typed in the moment and they piled up. Two holes, not one. The scan also anchored each axis to the start of a line, which works when a file keeps one declaration per line but misses everything in a rule written on a single line. It splits on declarations now, so both files that were never read and rules that were read but never matched are covered. Scope follows the pattern rather than the folder: a new page may still keep its styles inline, it just can no longer sit outside the lock. Every conversion snapped under 1.6px except two I set by hand: 700 sat exactly between two weights and the automatic pick rounded it down, and 7rem has no rung above 6rem. Two exemptions had to lose a trailing semicolon to keep matching, since declarations no longer carry one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent e0cc8a9 commit 3e20ade

4 files changed

Lines changed: 196 additions & 143 deletions

File tree

‎scripts/orbit-style-scale-tests.ts‎

Lines changed: 72 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,63 @@ import { readFileSync, readdirSync } from 'node:fs';
1515
import { test } from 'node:test';
1616

1717
const STYLE_DIR = new URL('../src/styles/', import.meta.url);
18+
const SRC_DIR = new URL('../src/', import.meta.url);
1819

1920
/** tokens.css skalanın tanımlandığı yer; ham değer orada olmalı. */
20-
const SCANNED = readdirSync(STYLE_DIR)
21+
const CSS_FILES = readdirSync(STYLE_DIR)
2122
.filter((name) => name.endsWith('.css') && name !== 'tokens.css')
22-
.sort();
23+
.sort()
24+
.map((name) => ({ label: `styles/${name}`, css: readFileSync(new URL(name, STYLE_DIR), 'utf8') }));
2325

2426
/**
25-
* Skalanın dışında kalmasına karar verilen satırlar. Her biri kasıtlı:
27+
* `.astro` dosyalarının içindeki `<style>` blokları.
28+
*
29+
* Kilit bir dönem yalnız `src/styles/` klasörünü tarıyordu ve panel stilini
30+
* sayfanın kendi içinde tuttuğu için hiç görmüyordu: dashboard'da 110,
31+
* platform sayfasında 40, avatar yükleme sayfasında 17 ham değer birikmişti.
32+
* Aralarında dört ayrı "küçük yazı" vardı — 0.76, 0.78, 0.80, 0.82 — hepsi
33+
* aynı kademeyi istiyordu ve aralarındaki fark yarım pikseldi.
34+
*
35+
* Kapsamı klasöre değil kalıba bağlamak önemliydi: yeni bir sayfa stilini
36+
* yine kendi içinde tutabilir, ama artık kilidin dışında kalamaz.
37+
*/
38+
function astroFiles(dir: URL): { label: string; css: string }[] {
39+
const found: { label: string; css: string }[] = [];
40+
for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
41+
if (entry.isDirectory()) {
42+
found.push(...astroFiles(new URL(`${entry.name}/`, dir)));
43+
continue;
44+
}
45+
if (!entry.name.endsWith('.astro')) continue;
46+
const source = readFileSync(new URL(entry.name, dir), 'utf8');
47+
const blocks = [...source.matchAll(/<style[^>]*>([\s\S]*?)<\/style>/gu)].map((match) => match[1]!);
48+
if (blocks.length === 0) continue;
49+
const relative = new URL('.', dir).href.slice(SRC_DIR.href.length);
50+
found.push({ label: `${relative}${entry.name}`, css: blocks.join('\n') });
51+
}
52+
return found;
53+
}
54+
55+
const SCANNED = [...CSS_FILES, ...astroFiles(SRC_DIR)];
56+
57+
/**
58+
* Skalanın dışında kalmasına karar verilen bildirimler. Her biri kasıtlı:
2659
* viewport ile sürekli interpolasyon yapanlar kademe seçemez, hizalama ve
2760
* grafik ölçüleri ise ritimden değil kendi geometrisinden gelir.
61+
*
62+
* Kalıplar bildirimin SONUNA çapalı, satırın değil: tarama artık `;` ve `{}`
63+
* üzerinden bölüyor, yani noktalı virgül kalıba dahil edilirse istisna hiç
64+
* eşleşmez.
2865
*/
2966
const EXEMPT: readonly RegExp[] = [
3067
/clamp\(/u, // akışkan hero başlıkları ve kabuk boşlukları
3168
/env\(safe-area-inset/u, // cihaz çentiği, tasarım kararı değil
3269
/font-size:\s*(?:10|16)rem/u, // dev monogram filigranı: tip değil grafik
3370
/letter-spacing:\s*-0\.1em/u, // aynı filigranın sıkışması
34-
/line-height:\s*0;/u, // ikon kutusunu çökerten yerleşim sıfırlaması
71+
/line-height:\s*0$/u, // ikon kutusunu çökerten yerleşim sıfırlaması
3572
/gap:\s*0\.02rem/u, // 0.32px: boşluk değil, kıl payı itme
3673
/margin:\s*-1px !important/u, // ekran okuyucu gizleme kalıbı, yerleşim değil
37-
/padding:\s*2px;/u, // avatar halkası: kalınlığı kenarlığa bağlı, ritme değil
74+
/padding:\s*2px$/u, // avatar halkası: kalınlığı kenarlığa bağlı, ritme değil
3875
];
3976

4077
type Axis = { readonly property: RegExp; readonly token: string };
@@ -63,31 +100,47 @@ function hasRawLength(line: string): boolean {
63100
return carriesLength(line.replaceAll(/var\(--[\w-]+\)/gu, ''));
64101
}
65102

66-
for (const file of SCANNED) {
67-
test(`${file} tip ve boşluk değerlerini skaladan alıyor`, () => {
68-
const source = readFileSync(new URL(file, STYLE_DIR), 'utf8');
103+
/**
104+
* Bildirim bildirim tara, satır satır değil.
105+
*
106+
* Eski tarama satırı bir bütün olarak alıyor ve ekseni `^\s*font-size:`
107+
* gibi satır başına çapalı bir kalıpla arıyordu. `src/styles/` altındaki
108+
* dosyalar satırda tek bildirim tuttuğu için bu yürüyordu; ama tek satıra
109+
* sığdırılmış bir kuralda — `.foo { font-size: .78rem; margin: .5rem; }` —
110+
* hiçbir eksen eşleşmiyor ve kural sessizce kilidin altından geçiyordu.
111+
*/
112+
function declarations(css: string): string[] {
113+
return css
114+
.replaceAll(/\/\*[\s\S]*?\*\//gu, '')
115+
.split(/[;{}]/u)
116+
.map((part) => part.trim())
117+
.filter((part) => part.includes(':'));
118+
}
119+
120+
for (const { label, css } of SCANNED) {
121+
test(`${label} tip ve boşluk değerlerini skaladan alıyor`, () => {
69122
const offenders: string[] = [];
70123

71-
for (const raw of source.split('\n')) {
72-
const line = raw.trim();
73-
if (!carriesLength(line)) continue;
74-
if (EXEMPT.some((pattern) => pattern.test(line))) continue;
75-
const axis = AXES.find(({ property }) => property.test(raw));
124+
for (const declaration of declarations(css)) {
125+
if (!carriesLength(declaration)) continue;
126+
if (EXEMPT.some((pattern) => pattern.test(declaration))) continue;
127+
const axis = AXES.find(({ property }) => property.test(declaration));
76128
if (!axis) continue;
77-
if (!line.includes(`var(${axis.token}`) || hasRawLength(line)) offenders.push(line);
129+
if (!declaration.includes(`var(${axis.token}`) || hasRawLength(declaration)) {
130+
offenders.push(declaration);
131+
}
78132
}
79133

80-
assert.deepEqual(offenders, [], `${file} içinde skala dışı değer:\n ${offenders.join('\n ')}`);
134+
assert.deepEqual(offenders, [], `${label} içinde skala dışı değer:\n ${offenders.join('\n ')}`);
81135
});
82136
}
83137

84138
test('skalanın kendisi tokens.css dışında tanımlanmıyor', () => {
85-
for (const file of SCANNED) {
86-
const source = readFileSync(new URL(file, STYLE_DIR), 'utf8');
139+
for (const { label, css } of SCANNED) {
87140
assert.doesNotMatch(
88-
source,
141+
css,
89142
/^\s*--(?:text|weight|tracking|leading|space)-[\w-]+:/mu,
90-
`${file} skalayı yeniden tanımlıyor; kademe eklenecekse yeri tokens.css.`,
143+
`${label} skalayı yeniden tanımlıyor; kademe eklenecekse yeri tokens.css.`,
91144
);
92145
}
93146
});

0 commit comments

Comments
 (0)