From 388aa6421d4a3ac20bf00a54ed0e08139b098dff Mon Sep 17 00:00:00 2001 From: ahliweb Date: Sat, 5 Sep 2026 19:50:28 +0700 Subject: [PATCH 1/3] fix(css): lantai dukungan 360px dapat angka jujur dan gerbang pemeriksa MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `.grid-cards` menulis `minmax(320px, 1fr)`, dan pada lantai dukungan 360px `.container` menyisakan ruang bersih PERSIS 320px — track selebar 320px di ruang 320px pas TANPA sisa sama sekali. box-sizing: border-box menyelamatkannya hari ini, tapi pembulatan sub-piksel atau border yang kelak ditambahkan ke .card akan mendorong gulir mendatar persis di lebar yang dijanjikan repo ini untuk didukung, tanpa satu gerbang pun memerah. - src/styles/global.css: .grid-cards kini minmax(min(320px, 100%), 1fr) — identik di setiap lebar >=320px, tidak lagi bisa dipaksa overflow. - AGENTS.md, AGENTS.id.md, ui-ux-design-system.md(.id.md): "kartu 328px, skala 0.41" adalah aritmetika untuk padding 1rem yang sudah lama tidak sama dengan padding 1.25rem .container yang sungguh berlaku. Diperbaiki ke 320px/skala 0.40; kesimpulan (di bawah 9px, tak terbaca) tidak berubah. - tests/lebar-360.test.mjs (baru): membaca padding .container dan lantai 360px dari CSS-nya sendiri, menolak minmax/width/min-width tetap lain yang mencapai lebar bersih tanpa jalan keluar, dan menjaga angka kartu/skala di AGENTS.md tetap sinkron dengan aritmetika CSS. Gerbang statik atas teks CSS — batasnya dinyatakan di docblock-nya sendiri. - AGENTS.md/.id.md dan SKILL gerbang menyebut gerbang baru ini; hitungan bun test di keenam dokumen naik dari 39 ke 40 (tests/documented-counts). Co-Authored-By: Claude Opus 5 (1M context) --- .../2026-09-05-lantai-360px-dapat-gerbang.md | 42 +++ .../skills/awcms-astro-gerbang/SKILL.id.md | 4 +- .claude/skills/awcms-astro-gerbang/SKILL.md | 2 +- AGENTS.id.md | 26 +- AGENTS.md | 22 +- README.id.md | 4 +- README.md | 2 +- docs/awcms-astro/checklist-repo-baru.id.md | 4 +- docs/awcms-astro/checklist-repo-baru.md | 2 +- docs/awcms-astro/ui-ux-design-system.id.md | 6 +- docs/awcms-astro/ui-ux-design-system.md | 4 +- src/styles/global.css | 12 +- tests/lebar-360.test.mjs | 277 ++++++++++++++++++ 13 files changed, 385 insertions(+), 22 deletions(-) create mode 100644 .changesets/2026-09-05-lantai-360px-dapat-gerbang.md create mode 100644 tests/lebar-360.test.mjs diff --git a/.changesets/2026-09-05-lantai-360px-dapat-gerbang.md b/.changesets/2026-09-05-lantai-360px-dapat-gerbang.md new file mode 100644 index 0000000..f3b35e7 --- /dev/null +++ b/.changesets/2026-09-05-lantai-360px-dapat-gerbang.md @@ -0,0 +1,42 @@ +--- +bump: patch +tipe: perbaikan +dampak: internal +--- + +# Lantai dukungan 360px akhirnya punya angka yang jujur dan gerbang yang menjaganya + +`.grid-cards` menulis `grid-template-columns: repeat(auto-fill, minmax(320px, 1fr))`. +Di lantai dukungan 360px yang didokumentasikan repo ini, `.container` (padding +`0 1.25rem` — 20px tiap sisi) menyisakan ruang bersih PERSIS 320px — sebuah track +selebar 320px di ruang 320px pas TANPA sisa sama sekali. `box-sizing: border-box` +menyelamatkannya hari ini, tapi pembulatan sub-piksel atau border/outline/shadow +yang kelak ditambahkan ke `.card` akan mendorong gulir mendatar persis di lebar +yang dijanjikan repo ini untuk didukung, dan tidak ada satu gerbang pun yang akan +memerah karenanya. + +- `src/styles/global.css`: `.grid-cards` kini memakai + `minmax(min(320px, 100%), 1fr)` — identik hasilnya di setiap lebar di atas + 320px, tapi tidak bisa lagi dipaksa overflow oleh track-nya sendiri. +- `AGENTS.md`, `AGENTS.id.md`, `docs/awcms-astro/ui-ux-design-system.md`, dan + `.id.md`-nya menulis "kartu 328px … skala 0.41" — aritmetika untuk padding + 1rem yang sudah lama tidak sama dengan padding 1.25rem yang sungguh berlaku + di `.container`. Angkanya kini 320px dan skala 0.40 (kesimpulannya tidak + berubah: 22px × 0,40 = 8,8px, tetap di bawah 9px dan praktis tidak terbaca). +- Aturan lantai 360px sebelumnya tidak punya satu pun pemeriksa. Kini ada: + `tests/lebar-360.test.mjs` membaca padding `.container` dan lantai 360px, + menurunkan lebar bersihnya dari CSS itu sendiri (bukan menulis ulang "320" + sebagai angka tetap), dan menolak `minmax(px|rem, …)` atau + `width`/`min-width` tetap lain di `src/styles/global.css` yang mencapai atau + melebihi lebar itu tanpa jalan keluar `min(…, 100%)`, media query khusus + layar lebar, atau `overflow-x` pada dirinya sendiri — juga menjaga angka + kartu/skala di `AGENTS.md` tetap sinkron dengan aritmetika CSS yang + sebenarnya. Gerbang ini statik atas teks CSS: ia tidak bisa membuktikan + keamanan render sungguhan (pembulatan sub-piksel, metrik font nyata, + scrollbar sungguhan) — itu butuh pemeriksaan headless-browser + `document.documentElement.scrollWidth <= 360` atas halaman yang sudah + dibangun, terhadap `awcms` backend yang hidup. +- `AGENTS.md`/`.id.md` §Interface, item Definition of Done tentang 360px, dan + `.claude/skills/awcms-astro-gerbang/SKILL.md`/`.id.md` kini menyebut gerbang + baru ini; hitungan `bun test` di keenam dokumen yang dijaga + `tests/documented-counts.test.mjs` naik dari 39 ke 40. diff --git a/.claude/skills/awcms-astro-gerbang/SKILL.id.md b/.claude/skills/awcms-astro-gerbang/SKILL.id.md index 784136f..0f55b98 100644 --- a/.claude/skills/awcms-astro-gerbang/SKILL.id.md +++ b/.claude/skills/awcms-astro-gerbang/SKILL.id.md @@ -5,7 +5,7 @@ description: Sembilan gerbang awcms-astro (check, test, audit:konten, audit:doku 🇮🇩 Bahasa Indonesia · 🇬🇧 [English (source)](SKILL.md) - + # awcms-astro — gerbang @@ -29,7 +29,7 @@ bun run audit:rilis # backlog changeset menunggu — tanpa build, tanpa j | Gerbang | Kelas cacat | | --- | --- | | `check` | Tipe, props, impor putus, lockfile milik proyek lain | -| `bun test` | 39 berkas. Paritas katalog PO; kontrak `awcms` (traversal, media, kartu) dan **permukaan yang dipanggil build**; **PERAN repo** (`tests/peran-situs.test.mjs`, ADR-0034); **TANPA backend** (`tests/tanpa-backend.test.mjs`, ADR-0038 — dependency kelas backend, jalur tulis, artefak persistensi); **TANPA runtime Node.js** (`tests/runtime-bun.test.mjs`, ADR-0050 — scripts package.json, CI, Dockerfile, shebang, dengan bawaan `node:*` dan keputusan mempertahankan `@astrojs/node`/`compression` dinyatakan sengaja); **kosakata `news`** (`tests/kosakata-news.test.mjs`, ADR-0036); feed Atom (`tests/feed.test.mjs`, ADR-0035); dua tanggal terpisah (`tests/tanggal.test.mjs`, ADR-0033); header + cache penyaji; CSP atas keluaran; **versi toolchain** termasuk pin TypeScript (ADR-0037); SBOM; CWV lab; analisis statik; schema; seni lokal; renderer blok; rilis terkondisi; logika pemeriksa terjemahan (`tests/docs-i18n-checks.test.mjs`, ADR-0039); plus 6 meta-tes yang menjalankan ulang enam dari tujuh skrip audit atas repo ini DAN atas pohon fixture (`audit:translation` yang ketujuh; logikanya digerbangi `tests/docs-i18n-checks.test.mjs`). **Kedua hitungan di baris ini diperiksa terhadap repo oleh `tests/documented-counts.test.mjs`.** Keduanya berbunyi 21 dan tiga sampai 28 Agustus 2026, hanyut satu berkas demi satu berkas seiring gerbang bertambah — dan itulah sebabnya keduanya digerbangi, bukan sekadar dikoreksi | +| `bun test` | 40 berkas. Paritas katalog PO; kontrak `awcms` (traversal, media, kartu) dan **permukaan yang dipanggil build**; **PERAN repo** (`tests/peran-situs.test.mjs`, ADR-0034); **TANPA backend** (`tests/tanpa-backend.test.mjs`, ADR-0038 — dependency kelas backend, jalur tulis, artefak persistensi); **TANPA runtime Node.js** (`tests/runtime-bun.test.mjs`, ADR-0050 — scripts package.json, CI, Dockerfile, shebang, dengan bawaan `node:*` dan keputusan mempertahankan `@astrojs/node`/`compression` dinyatakan sengaja); **kosakata `news`** (`tests/kosakata-news.test.mjs`, ADR-0036); feed Atom (`tests/feed.test.mjs`, ADR-0035); dua tanggal terpisah (`tests/tanggal.test.mjs`, ADR-0033); header + cache penyaji; CSP atas keluaran; **versi toolchain** termasuk pin TypeScript (ADR-0037); SBOM; CWV lab; analisis statik; schema; seni lokal; renderer blok; rilis terkondisi; logika pemeriksa terjemahan (`tests/docs-i18n-checks.test.mjs`, ADR-0039); **lantai lebar 360px** (`tests/lebar-360.test.mjs` — padding `.container` sendiri menurunkan lebar bersih pada lantai itu, dan tidak ada `minmax(…)` angka tetap atau `width`/`min-width` tetap di `src/styles/global.css` yang boleh mencapainya tanpa jalan keluar `min(…, 100%)`, media query khusus layar lebar, atau `overflow-x` pada dirinya sendiri; lahir dari `.grid-cards` yang duduk persis di lebar itu dengan sisa nol — gerbang statik atas teks CSS, jadi tidak bisa membuktikan keamanan render sungguhan seperti pemeriksaan `scrollWidth` headless-browser atas halaman yang sudah dibangun); plus 6 meta-tes yang menjalankan ulang enam dari tujuh skrip audit atas repo ini DAN atas pohon fixture (`audit:translation` yang ketujuh; logikanya digerbangi `tests/docs-i18n-checks.test.mjs`). **Kedua hitungan di baris ini diperiksa terhadap repo oleh `tests/documented-counts.test.mjs`.** Keduanya berbunyi 21 dan tiga sampai 28 Agustus 2026, hanyut satu berkas demi satu berkas seiring gerbang bertambah — dan itulah sebabnya keduanya digerbangi, bukan sekadar dikoreksi | | `audit:konten` | Rasio gambar terhadap `--ratio-visual`, format dibaca dari ISI berkas, judul/canonical/hreflang, aset yang dijanjikan metadata, tautan mati, sitemap, **setiap `.xml` di keluaran** (feed Atom sah, atau pelanggaran), nama key bocor ke layar | | `audit:dokumen` | Tautan markdown mati, indeks ADR lengkap dua arah, status ADR setuju dengan berkasnya, daftar permukaan kilau, jalur berkas yang disebut dokumen, **kutipan `ADR-NNNN` yang resolve ke berkasnya** | | `audit:translation` | Cermin basi (sebuah `.id.md` yang hash tercatatnya tidak lagi cocok dengan sumber Inggrisnya), cermin yatim yang sumbernya hilang, dokumen tanpa cermin yang tidak ada di buku besar yang hanya boleh menyusut, dan entri buku besar yang cerminnya kini ada (ADR-0039) | diff --git a/.claude/skills/awcms-astro-gerbang/SKILL.md b/.claude/skills/awcms-astro-gerbang/SKILL.md index 57465e0..9a02016 100644 --- a/.claude/skills/awcms-astro-gerbang/SKILL.md +++ b/.claude/skills/awcms-astro-gerbang/SKILL.md @@ -27,7 +27,7 @@ bun run audit:rilis # the waiting release backlog — no build, no network | Gate | Class of defect | | --- | --- | | `check` | Types, props, broken imports, a lockfile belonging to another project | -| `bun test` | 39 files. PO catalogue parity; the `awcms` contract (traversal, media, cards) and **the surfaces the build calls**; the repo's ROLE (`tests/peran-situs.test.mjs`, ADR-0034); **NO backend** (`tests/tanpa-backend.test.mjs`, ADR-0038 — backend-class dependencies, write paths, persistence artefacts); **NO Node.js runtime** (`tests/runtime-bun.test.mjs`, ADR-0050 — package.json scripts, CI, Dockerfile, shebangs, with `node:*` built-ins and the `@astrojs/node`/`compression` keeps named as deliberate); the **`news` vocabulary** (`tests/kosakata-news.test.mjs`, ADR-0036); the Atom feed (`tests/feed.test.mjs`, ADR-0035); two separate dates (`tests/tanggal.test.mjs`, ADR-0033); the server's headers + cache; CSP over the output; **toolchain versions** including the TypeScript pin (ADR-0037); SBOM; lab CWV; static analysis; schema; local art; the block renderer; the conditional release; the translation-check logic (`tests/docs-i18n-checks.test.mjs`, ADR-0039); plus 6 meta-tests that re-run six of the seven audit scripts over this repo AND over a fixture tree (`audit:translation` is the seventh; its logic is gated by `tests/docs-i18n-checks.test.mjs` instead). **The two counts in this row are checked against the repo by `tests/documented-counts.test.mjs`.** They read 21 and three until 28 August 2026, drifting one file at a time as gates were added — which is why they are gated rather than merely corrected | +| `bun test` | 40 files. PO catalogue parity; the `awcms` contract (traversal, media, cards) and **the surfaces the build calls**; the repo's ROLE (`tests/peran-situs.test.mjs`, ADR-0034); **NO backend** (`tests/tanpa-backend.test.mjs`, ADR-0038 — backend-class dependencies, write paths, persistence artefacts); **NO Node.js runtime** (`tests/runtime-bun.test.mjs`, ADR-0050 — package.json scripts, CI, Dockerfile, shebangs, with `node:*` built-ins and the `@astrojs/node`/`compression` keeps named as deliberate); the **`news` vocabulary** (`tests/kosakata-news.test.mjs`, ADR-0036); the Atom feed (`tests/feed.test.mjs`, ADR-0035); two separate dates (`tests/tanggal.test.mjs`, ADR-0033); the server's headers + cache; CSP over the output; **toolchain versions** including the TypeScript pin (ADR-0037); SBOM; lab CWV; static analysis; schema; local art; the block renderer; the conditional release; the translation-check logic (`tests/docs-i18n-checks.test.mjs`, ADR-0039); **the 360px layout floor** (`tests/lebar-360.test.mjs` — `.container`'s own padding derives the inner width at that floor, and no fixed-px `minmax(…)` or fixed `width`/`min-width` in `src/styles/global.css` may reach it without a `min(…, 100%)` escape, a desktop-only media query, or its own `overflow-x`; born from `.grid-cards` sitting at that exact width with zero margin — a static gate over CSS text, so it cannot prove real rendering safety the way a headless-browser `scrollWidth` check on a built page would); plus 6 meta-tests that re-run six of the seven audit scripts over this repo AND over a fixture tree (`audit:translation` is the seventh; its logic is gated by `tests/docs-i18n-checks.test.mjs` instead). **The two counts in this row are checked against the repo by `tests/documented-counts.test.mjs`.** They read 21 and three until 28 August 2026, drifting one file at a time as gates were added — which is why they are gated rather than merely corrected | | `audit:konten` | Image ratios against `--ratio-visual`, format read from the file CONTENTS, title/canonical/hreflang, assets promised by metadata, dead links, the sitemap, **every `.xml` in the output** (a valid Atom feed, or a violation), key names leaking to the screen | | `audit:dokumen` | Dead markdown links, the ADR index complete in both directions, an ADR's status agreeing with its file, the polish-surface list, file paths named by a document, **`ADR-NNNN` citations that resolve to their file** | | `audit:translation` | A stale mirror (an `.id.md` whose recorded hash no longer matches its English source), an orphan mirror whose source is gone, a document with no mirror that is not on the shrink-only ledger, and a ledger entry whose mirror now exists (ADR-0039) | diff --git a/AGENTS.id.md b/AGENTS.id.md index cf261e1..837c761 100644 --- a/AGENTS.id.md +++ b/AGENTS.id.md @@ -1,6 +1,6 @@ 🇮🇩 Bahasa Indonesia · 🇬🇧 [English (source)](AGENTS.md) - + # AGENTS.md — kontrak kerja `awcms-astro` @@ -499,7 +499,20 @@ yang [ADR-0034](docs/adr/0034-publik-secara-bawaan-admin-hanya-bila-dinyatakan.m — aturan `*` global hanya memangkas durasi, dan animasi 0,01 md tetap berkedip. Umpan balik hover juga aktif pada `:focus-visible`, sehingga pengguna keyboard tidak mendapat versi yang lebih miskin. -- **Mobile-first dari 360px.** +- **Mobile-first dari 360px.** Padding `.container` sendiri menyisakan lebar + bersih persis 320px pada lantai itu — sebuah track grid yang dipatok ke angka + yang sama punya sisa nol, dan itu yang dilakukan `.grid-cards` sampai + ditemukan dan dibungkus `min(320px, 100%)`. Aturan ini kini punya + pemeriksanya: `tests/lebar-360.test.mjs` membaca lantai 360px dan padding + `.container`, menurunkan lebar bersih itu dari CSS-nya sendiri, dan menolak + `minmax(…)` angka tetap lain atau `width`/`min-width` tetap di + `src/styles/global.css` yang mencapai atau melebihinya tanpa jalan keluar + `min(…, 100%)`, media query khusus layar lebar, atau `overflow-x` pada + dirinya sendiri. Ia gerbang statik atas TEKS CSS: tidak bisa membuktikan + keamanan render sungguhan — pembulatan sub-piksel, metrik font nyata, dan + scrollbar sungguhan butuh browser, yaitu mengasersikan + `document.documentElement.scrollWidth <= 360` pada halaman kunci setelah + build, terhadap `awcms` backend yang hidup. - **String antarmuka lewat katalog PO**, tidak pernah ditulis langsung di komponen. Ini berlaku juga bagi label yang datang dari konfigurasi: navigasi utama pernah merender nilai HURUF BESAR dari `src/config/site.ts`, sehingga @@ -607,7 +620,7 @@ merender `.visual-placeholder`, dan itu jujur. ilustrasi.** Situs dari template ini adalah portal independen, dan lambang negara di halamannya membantah pernyataan itu dalam satu pandangan. - **Teks terkecil di dalam SVG minimal 22px pada kanvas 800px.** Pada kartu - selebar 328px — viewport 360px — kanvas 800px tampil pada skala 0,41, jadi di + selebar 320px — viewport 360px — kanvas 800px tampil pada skala 0,40, jadi di bawah ambang itu teksnya tampil di bawah 9px dan praktis tidak terbaca. - **`src: undefined` adalah keadaan yang didukung.** Pemanggil merender `.visual-placeholder`. Ilustrasi yang hilang tidak boleh menjadi halaman yang @@ -803,7 +816,12 @@ performa: dengan tipe kontraknya menemukan seluruhnya dalam satu kali typecheck. - [ ] Locale default dan locale berprefiks menghasilkan jumlah halaman yang sama. - [ ] Gambar baru berasio `--ratio-visual`, ekstensinya sesuai isi berkas, tanpa - lambang instansi maupun data tiruan, dan teksnya terbaca pada lebar 360px. + lambang instansi maupun data tiruan, dan teksnya terbaca pada lebar + 360px — `bun run audit:konten` memeriksa yang terakhir terhadap ambang + 22px pada kanvas 800px; aritmetika lebar kartu asal ambang itu (320px, + skala 0,40) diperiksa terpisah oleh `tests/lebar-360.test.mjs`, yang + membaca CSS `.container` sendiri alih-alih mempercayai angka yang + ditulis di prosa. - [ ] Perubahan pada penyajian — header, CSP, `Cache-Control`, kompresi, port — dibuktikan `tests/penyaji.test.mjs`, bukan diperiksa dengan mata. **Sebuah `Vary` termasuk di dalamnya**: `Cookie` dan `Accept-Language` ditolak diff --git a/AGENTS.md b/AGENTS.md index ddc74c1..ec5b621 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -486,7 +486,19 @@ exactly what sped up — a global `*` rule only trims duration, and a 0.01 ms animation still flickers. Hover feedback is also active on `:focus-visible`, so keyboard users do not get a poorer version. -- **Mobile-first from 360px.** +- **Mobile-first from 360px.** `.container`'s own padding leaves an inner width + of exactly 320px at that floor — a grid track fixed to that same figure has + zero margin, which is what `.grid-cards` did until it was found and wrapped + in `min(320px, 100%)`. This rule now has a checker: `tests/lebar-360.test.mjs` + reads the 360px floor and `.container`'s padding, derives that inner width + from the CSS itself, and refuses any other fixed-px `minmax(…)` or fixed + `width`/`min-width` in `src/styles/global.css` that reaches or exceeds it + without a `min(…, 100%)` escape, a desktop-only media query, or a + self-contained `overflow-x`. It is a static gate over CSS TEXT: it cannot + prove real rendering safety — sub-pixel rounding, real font metrics, and + actual scrollbars need a browser, which would mean asserting + `document.documentElement.scrollWidth <= 360` on the key templates after a + build, against a live `awcms` backend. - **Interface strings come through the PO catalogue**, never written straight into a component. This applies to labels coming from configuration too: the main navigation once rendered UPPERCASE values from `src/config/site.ts`, so @@ -593,7 +605,7 @@ looking like an image chosen for it. A missing file renders illustrations.** A site from this template is an independent portal, and a state emblem on its pages contradicts that statement at a glance. - **The smallest text in an SVG is at least 22px on an 800px canvas.** On a card - 328px wide — a 360px viewport — an 800px canvas appears at 0.41 scale, so + 320px wide — a 360px viewport — an 800px canvas appears at 0.40 scale, so below that threshold its text appears under 9px and is practically unreadable. - **`src: undefined` is a supported state.** A caller renders `.visual-placeholder`. A missing illustration must not become a missing page — @@ -784,7 +796,11 @@ Four things to know before touching headers, cache, or the performance budget: - [ ] The default locale and prefixed locales produce the same number of pages. - [ ] A new image is at `--ratio-visual`, its extension matches its contents, it carries no institutional emblem or mock data, and its text is readable at - 360px wide. + 360px wide — `bun run audit:konten` checks the last of those against the + 22px-on-an-800px-canvas threshold; the card-width arithmetic that + threshold is derived from (320px, 0.40 scale) is checked separately by + `tests/lebar-360.test.mjs`, which reads `.container`'s own CSS rather + than trusting the number written in prose. - [ ] A change to serving — headers, CSP, `Cache-Control`, compression, port — is proven by `tests/penyaji.test.mjs`, not checked by eye. **A `Vary` is part of that**: `Cookie` and `Accept-Language` are refused outright diff --git a/README.id.md b/README.id.md index 238ec65..5e310b5 100644 --- a/README.id.md +++ b/README.id.md @@ -1,6 +1,6 @@ 🇮🇩 Bahasa Indonesia · 🇬🇧 [English (source)](README.md) - + # awcms-astro @@ -142,7 +142,7 @@ satu-satunya lockfile. | `bun run dev` | Server pengembangan Astro (HMR), `http://localhost:4321` | | `bun run check` | Gerbang lockfile lalu `astro check` | | `bun run check:lockfile` | Hanya gerbang lockfile — murni baca berkas | -| `bun test` | 39 berkas gerbang: kontrak `awcms`, peran situs, tanpa backend, tanpa runtime Node.js, kosakata `news`, feed, penyaji, versi toolchain, CSP keluaran, cermin terjemahan, dan meta-tes atas skrip audit | +| `bun test` | 40 berkas gerbang: kontrak `awcms`, peran situs, tanpa backend, tanpa runtime Node.js, kosakata `news`, feed, penyaji, versi toolchain, CSP keluaran, lantai lebar 360px, cermin terjemahan, dan meta-tes atas skrip audit | | `bun run audit:konten` | Gerbang audit: sumber gambar, dan keluaran build bila sudah ada | | `bun run audit:dokumen` | Gerbang dokumen: tautan markdown mati, indeks ADR, daftar permukaan kilau | | `bun run audit:graf` | Gerbang graf: artefak `graphify-out/` yang terlacak, dan nama komunitasnya | diff --git a/README.md b/README.md index e9c2ccd..b2c8944 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ is the only lockfile. | `bun run dev` | Astro development server (HMR), `http://localhost:4321` | | `bun run check` | Lockfile gate, then `astro check` | | `bun run check:lockfile` | The lockfile gate alone — pure file reading | -| `bun test` | 39 gate files: the `awcms` contract, site roles, no-backend, no Node.js runtime, the `news` vocabulary, feeds, the server, toolchain versions, output CSP, translation mirrors, and meta-tests over the audit scripts | +| `bun test` | 40 gate files: the `awcms` contract, site roles, no-backend, no Node.js runtime, the `news` vocabulary, feeds, the server, toolchain versions, output CSP, the 360px layout floor, translation mirrors, and meta-tests over the audit scripts | | `bun run audit:konten` | Audit gate: image sources, and the build output when it exists | | `bun run audit:dokumen` | Document gate: dead markdown links, the ADR index, the shine-surface list | | `bun run audit:graf` | Graph gate: the tracked `graphify-out/` artefacts, and their community names | diff --git a/docs/awcms-astro/checklist-repo-baru.id.md b/docs/awcms-astro/checklist-repo-baru.id.md index f72c2aa..cf689a0 100644 --- a/docs/awcms-astro/checklist-repo-baru.id.md +++ b/docs/awcms-astro/checklist-repo-baru.id.md @@ -1,6 +1,6 @@ 🇮🇩 Bahasa Indonesia · 🇬🇧 [English (source)](checklist-repo-baru.md) - + # Memulai Situs Baru di Atas awcms-astro @@ -76,7 +76,7 @@ Bila ilustrasi dibangkitkan sendiri, **jangan biarkan konfigurasinya menyisipkan Wajib tetap hijau: - [ ] `bun run check` — gerbang lockfile lalu `astro check`. -- [ ] `bun test` — 39 berkas gerbang. Yang paling sering memerahkan situs baru: +- [ ] `bun test` — 40 berkas gerbang. Yang paling sering memerahkan situs baru: katalog PO (`tests/katalog-po.test.mjs` — key yang dipakai kode tetapi tak ada di katalog, katalog locale yang tertinggal, `msgstr` kosong, key tab yang belum ditulis untuk locale mana pun), **peran situs** diff --git a/docs/awcms-astro/checklist-repo-baru.md b/docs/awcms-astro/checklist-repo-baru.md index 7bcc919..77914cb 100644 --- a/docs/awcms-astro/checklist-repo-baru.md +++ b/docs/awcms-astro/checklist-repo-baru.md @@ -74,7 +74,7 @@ If illustrations are generated, **do not let their configuration inject raw mark They must stay green: - [ ] `bun run check` — the lockfile gate, then `astro check`. -- [ ] `bun test` — 39 gate files. The ones that most often turn a new site red: +- [ ] `bun test` — 40 gate files. The ones that most often turn a new site red: the PO catalogues (`tests/katalog-po.test.mjs` — a key used by the code but absent from a catalogue, a locale catalogue left behind, an empty `msgstr`, a tab key not yet written for any locale), the **site role** diff --git a/docs/awcms-astro/ui-ux-design-system.id.md b/docs/awcms-astro/ui-ux-design-system.id.md index 591e704..726971e 100644 --- a/docs/awcms-astro/ui-ux-design-system.id.md +++ b/docs/awcms-astro/ui-ux-design-system.id.md @@ -1,6 +1,6 @@ 🇮🇩 Bahasa Indonesia · 🇬🇧 [English (source)](ui-ux-design-system.md) - + # awcms-astro — Design System @@ -243,7 +243,7 @@ Sapuan ini murni CSS. Ia tidak menambah satu byte JavaScript pun dan tidak berpe ### Responsif -Mobile-first dari 360px. Kartu memakai `grid-template-columns: repeat(auto-fill, minmax(320px, 1fr))`. Bagaimana masthead menata ulang dirinya pada lebar itu ada di §Bingkai halaman. +Mobile-first dari 360px. `padding: 0 1.25rem` milik `.container` sendiri menyisakan lebar bersih persis 320px pada lantai itu. Kartu memakai `grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr))` — `min(320px, 100%)`, bukan `320px` telanjang, karena track yang dipatok ke angka sama dengan ruang tempatnya duduk punya sisa nol: `box-sizing: border-box` menutupinya hari ini, tapi pembulatan sub-piksel atau border yang kelak ditambahkan ke `.card` tidak akan. Bagaimana masthead menata ulang dirinya pada lebar itu ada di §Bingkai halaman. `tests/lebar-360.test.mjs` membaca aritmetika yang sama ini dari CSS dan menolak track atau lebar tetap lain yang mencapai lantai ini tanpa jalan keluar yang sama — ia gerbang statik atas teks CSS, bukan bukti render sungguhan; itu butuh pemeriksaan headless-browser atas halaman yang sudah dibangun. **Shorthand `padding` pada elemen yang juga membawa `.container` diam-diam menghapus padding samping containernya**, dan kegagalannya tidak terlihat di layar desktop. `.header-top` menulis `padding: 0.85rem 0` selama berbulan-bulan: pada lebar berapa pun di atas `--max-width`, container sudah masuk ke dalam oleh margin otomatisnya sendiri, jadi tidak ada yang tampak salah; di 360px, tempat container selebar layar, nama situs duduk **menempel di tepi kiri kaca** — diukur di `x=0`, bukan ditaksir dari tangkapan layar. Kini ia `padding-block`. Periksa elemen lain mana pun yang membawa `.container` bersama kelasnya sendiri. @@ -257,7 +257,7 @@ Pemotongan tidak hilang karenanya. Bingkai memotong lewat `object-fit: cover` di **Satu rasio untuk seluruh situs, dipakai bingkai maupun sumber.** Di repo ini 16∶9. Bingkai memakai `object-fit: cover`, jadi sumber berasio lain tidak diperkecil — ia dipotong, diam-diam, di setiap ukuran layar. Sumber 1∶1 pada bingkai 16∶9 kehilangan 22% teratas, dan judul gambar hampir selalu ada di sana. -Teks di dalam gambar ikut menyusut bersama gambarnya. Pada kartu selebar 328px — viewport 360px — kanvas 800px tampil pada skala 0,41: teks 12px menjadi 5px. Tetapkan ambang tipografi dari lebar kartu tersempit, bukan dari tampilan di layar desktop. +Teks di dalam gambar ikut menyusut bersama gambarnya. Pada kartu selebar 320px — viewport 360px — kanvas 800px tampil pada skala 0,40: teks 12px menjadi 4,8px. Tetapkan ambang tipografi dari lebar kartu tersempit, bukan dari tampilan di layar desktop. Isi gambar tunduk pada ADR-0013 repo rujukan: tanpa lambang instansi, tanpa data tiruan, teks hanya label topik. diff --git a/docs/awcms-astro/ui-ux-design-system.md b/docs/awcms-astro/ui-ux-design-system.md index 825add6..aa37469 100644 --- a/docs/awcms-astro/ui-ux-design-system.md +++ b/docs/awcms-astro/ui-ux-design-system.md @@ -241,7 +241,7 @@ This sweep is pure CSS. It adds not one byte of JavaScript and has no effect on ### Responsive -Mobile-first from 360px. Cards use `grid-template-columns: repeat(auto-fill, minmax(320px, 1fr))`. How the masthead reflows at that width is in §The page frame. +Mobile-first from 360px. `.container`'s own `padding: 0 1.25rem` leaves an inner width of exactly 320px at that floor. Cards use `grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr))` — the `min(320px, 100%)`, not a bare `320px`, because a track fixed to the same figure as the space it sits in has zero margin: `box-sizing: border-box` covers it today, but sub-pixel rounding or a border added to `.card` later would not. How the masthead reflows at that width is in §The page frame. `tests/lebar-360.test.mjs` reads this same arithmetic from the CSS and refuses any other fixed-px track or width that reaches this floor without the same escape — it is a static gate over CSS text, not proof of real rendering; that needs a headless-browser check on a built page. **A `padding` shorthand on an element that also carries `.container` silently deletes the container's side padding**, and the failure is invisible on a desktop screen. `.header-top` wrote `padding: 0.85rem 0` for months: at any width above `--max-width` the container is already inset by its own auto margins, so nothing looked wrong; at 360px, where the container is the full screen, the site name sat **flush against the left edge of the glass** — measured at `x=0`, not guessed from a screenshot. It is `padding-block` now. Check any other element that carries `.container` together with a class of its own. @@ -255,7 +255,7 @@ Cropping does not disappear because of that. Frames crop through `object-fit: co **One ratio for the whole site, used by frames and sources alike.** In this repo, 16∶9. Frames use `object-fit: cover`, so a source at another ratio is not scaled down — it is cropped, silently, at every screen size. A 1∶1 source in a 16∶9 frame loses its top 22%, and an image's title is almost always there. -Text inside an image shrinks along with its image. On a card 328px wide — a 360px viewport — an 800px canvas appears at a scale of 0.41: 12px text becomes 5px. Set your typography threshold from the narrowest card width, not from how it looks on a desktop screen. +Text inside an image shrinks along with its image. On a card 320px wide — a 360px viewport — an 800px canvas appears at a scale of 0.40: 12px text becomes 4.8px. Set your typography threshold from the narrowest card width, not from how it looks on a desktop screen. An image's contents are subject to the reference repo's ADR-0013: no institutional emblems, no mock data, text only as a topic label. diff --git a/src/styles/global.css b/src/styles/global.css index 7f67f9f..112be1f 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -712,9 +712,19 @@ p { } /* Cards & Grid Layouts */ +/* `min(320px, 100%)`, bukan `320px` telanjang: pada lantai dukungan 360px, + `.container` (di atas) menyisakan persis 320px bersih — 360px dikurangi + 1.25rem padding di kedua sisi. Sebuah track selebar 320px pas di ruang + 320px TANPA sisa sama sekali; `box-sizing: border-box` menyelamatkannya + hari ini, tapi pembulatan sub-piksel atau border/outline/shadow yang kelak + ditambahkan ke `.card` akan mendorong gulir mendatar persis di lebar yang + dijanjikan repo ini untuk didukung. `min()` membatasi track pada milik + kontainernya sendiri sehingga grid tidak pernah bisa memaksa overflow, + sementara pada setiap lebar di atas 320px hasilnya identik dengan bentuk + lama — jangan disederhanakan kembali menjadi `320px` telanjang. */ .grid-cards { display: grid; - grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); gap: 1.75rem; margin: 1.5rem 0; } diff --git a/tests/lebar-360.test.mjs b/tests/lebar-360.test.mjs new file mode 100644 index 0000000..83a061a --- /dev/null +++ b/tests/lebar-360.test.mjs @@ -0,0 +1,277 @@ +/** + * Gerbang lantai 360px: lebar bersih `.container` dihitung dari CSS-nya + * sendiri, dan tak ada track grid atau lebar tetap di `src/styles/global.css` + * yang boleh melebihinya tanpa jalan keluar. + * + * ## Asal cacatnya — bukan hipotesis + * + * `AGENTS.md` §Antarmuka menulis "Mobile-first from 360px" sebagai lantai + * dukungan repo ini. `.container` (`src/styles/global.css`) memasang + * `padding: 0 1.25rem` — 20px tiap sisi, 40px total — sehingga di lantai itu + * ruang bersihnya PERSIS 320px. Sebelum berkas ini ada, `.grid-cards` menulis: + * + * grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + * + * Sebuah track selebar 320px di ruang 320px pas TANPA sisa sama sekali. + * `box-sizing: border-box` menyelamatkannya hari ini, tapi pembulatan + * sub-piksel, atau border/outline/shadow yang kelak ditambahkan ke `.card`, + * mendorong gulir mendatar persis di lebar yang dijanjikan repo ini untuk + * didukung — dan sebelum berkas ini, tidak ada satu gerbang pun yang akan + * memerah karenanya. `.grid-cards` sudah diperbaiki + * (`minmax(min(320px, 100%), 1fr)`, ADR yang sama dengan berkas ini); yang + * berikut adalah supaya baris kedua yang sama tidak mendarat tanpa ada yang + * sadar. + * + * ## Kenapa dihitung, bukan ditulis ulang sebagai angka tetap + * + * 360 adalah keputusan produk — lantai dukungan yang didokumentasikan + * `AGENTS.md`, bukan nilai yang muncul di CSS mana pun — jadi ia di-hardcode + * SATU kali di bawah, dengan rujukan ke dokumen itu. Padding sisi + * `.container`, sebaliknya, ADALAH nilai CSS, dan itulah yang benar-benar + * bisa hanyut tanpa alarm: `.header-top` pernah menghapus padding container + * lewat shorthand tanpa satu gerbang pun memerah (§Responsive di + * `ui-ux-design-system.md`). Berkas ini karena itu MEMBACA padding dari + * `.container`, bukan menulis ulang "320" sebagai angka lepas — kalau padding + * itu berubah, lebar bersih yang dihitung di bawah berubah bersamanya, dan + * tes kedua di bawah gagal dengan pesan yang menunjuk balik ke angka + * dokumentasi yang harus ikut diperbarui (persis cacat kedua di audit yang + * melahirkan berkas ini: `AGENTS.md` dan `ui-ux-design-system.md` menyebut + * "kartu 328px … skala 0.41" — aritmetika untuk padding 1rem yang sudah lama + * tidak sama dengan padding 1.25rem yang sungguh berlaku). + * + * ## Kenapa berkas baru, bukan perluasan `tests/analisis-statik.test.mjs` + * + * Seluruh isi `analisis-statik.test.mjs` adalah satu subjek sempit: workflow + * CodeQL menyatakan cakupannya dan dipin ke SHA (celah 7 ADR-0028). Lebar + * layar tidak punya hubungan dengan itu selain sama-sama "aturan tertulis + * yang butuh gerbang" (ADR-0030) — menumpangkannya ke sana membuat siapa pun + * yang mencari "kenapa CodeQL merah" harus menyaring asersi CSS yang tidak + * relevan, dan sebaliknya. Pola repo ini adalah satu berkas per subjek + * (`tests/versi-toolchain.test.mjs`, `tests/documented-counts.test.mjs`, + * `tests/analisis-statik.test.mjs` sendiri) — berkas ini mengikutinya. + * + * ## Apa yang TIDAK diperiksa — batas gerbang ini, dinyatakan terus terang + * + * Ini gerbang STATIK atas teks CSS. Ia TIDAK BISA membuktikan keamanan + * render sungguhan: pembulatan sub-piksel, metrik font nyata (lebar + * karakter berbeda per platform), dan scrollbar sungguhan (yang mencuri + * ruang horizontal di sebagian browser desktop) semuanya butuh browser + * sungguhan untuk diukur, bukan sebuah regex atas berkas sumber. Yang AKAN + * membuktikannya: sebuah pemeriksaan headless-browser yang mengasersikan + * `document.documentElement.scrollWidth <= 360` pada halaman-halaman kunci + * setelah `bun run build` — yang berarti butuh build dan `awcms` backend + * yang hidup untuk mengisinya, bukan sekadar `src/styles/global.css` yang + * dibaca sebagai teks. Ketiadaan pemeriksaan itu di repo ini adalah batas + * yang dinyatakan, bukan yang disembunyikan. + * + * Gerbang ini juga tidak menilai apakah 320px "cukup" untuk desain manapun — + * hanya bahwa CSS yang ada tidak mengaku menopang lebih dari yang bisa ia + * tampung tanpa gulir. + */ +import { describe, test } from "bun:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; + +const CSS = readFileSync("src/styles/global.css", "utf8"); +const AGENTS = readFileSync("AGENTS.md", "utf8"); + +/** Nomor baris 1-based dari sebuah indeks karakter — untuk pesan galat yang bisa ditelusuri. */ +function nomorBaris(teks, indeks) { + return teks.slice(0, indeks).split("\n").length; +} + +/** + * Lantai dukungan yang didokumentasikan (`AGENTS.md` §Antarmuka — + * "Mobile-first from 360px"). SATU-SATUNYA angka hardcode di berkas ini: + * tidak ada literal "360" di CSS manapun untuk dibaca balik — lihat docblock + * di atas untuk kenapa itu bukan cacat yang sama dengan padding di bawah. + */ +const LANTAI_DUKUNGAN_PX = 360; + +/** + * Padding sisi `.container`, dibaca dari CSS-nya sendiri — bukan ditulis + * ulang sebagai angka tetap. Diasumsikan `1rem` root = 16px, yaitu bawaan + * browser; `html`/`:root` di berkas ini tidak menimpa `font-size` + * (diperiksa saat berkas ini ditulis). Kalau itu berubah, asumsi ini adalah + * bagian dari batas "font nyata butuh browser" yang docblock di atas sudah + * nyatakan, bukan sesuatu yang gerbang statik ini bisa lihat sendiri. + */ +const BLOK_CONTAINER = CSS.match(/\.container\s*\{([^}]*)\}/); +const PADDING_COCOK = BLOK_CONTAINER?.[1]?.match(/padding:\s*0\s+([0-9.]+)(px|rem)\s*;/) ?? null; +const PADDING_PX = PADDING_COCOK + ? Number(PADDING_COCOK[1]) * (PADDING_COCOK[2] === "rem" ? 16 : 1) + : null; + +/** Lebar bersih di dalam `.container` pada lantai 360px — DIHITUNG, bukan ditulis ulang. */ +const LEBAR_BERSIH_PX = PADDING_PX === null ? null : LANTAI_DUKUNGAN_PX - PADDING_PX * 2; + +/** + * Rentang `[mulai, akhir)` dari setiap blok `@media (min-width: …)` — + * peningkatan khusus layar lebar, jadi sah melebihi lebar bersih 360px. + * `@media (max-width: …)` TIDAK dikecualikan: kondisi itu masih berlaku PADA + * dan DI BAWAH 360px, jadi isinya tetap diperiksa. + */ +function zonaMediaDesktop(teks) { + const zona = []; + const pola = /@media\s*\(([^)]*)\)\s*\{/g; + let m; + while ((m = pola.exec(teks))) { + if (!/min-width/.test(m[1])) continue; + let kedalaman = 1; + let i = m.index + m[0].length; + while (kedalaman > 0 && i < teks.length) { + if (teks[i] === "{") kedalaman++; + else if (teks[i] === "}") kedalaman--; + i++; + } + zona.push([m.index, i]); + } + return zona; +} + +const ZONA_DESKTOP = zonaMediaDesktop(CSS); +const dalamZonaDesktop = (indeks) => ZONA_DESKTOP.some(([a, b]) => indeks >= a && indeks < b); + +describe("lebar bersih .container di lantai 360px dihitung dari CSS-nya sendiri (asal: .grid-cards)", () => { + test("padding sisi .container terbaca dari CSS, bukan diasumsikan", () => { + assert.notEqual( + PADDING_PX, + null, + "tidak menemukan `padding: 0 rem;` di dalam blok `.container` " + + "src/styles/global.css. Kalau shorthand paddingnya ditulis ulang " + + "(mis. empat nilai, atau `padding-inline`), perbarui regex gerbang " + + "ini SEBELUM mempercayai angka lebar bersih yang ia hasilkan" + ); + }); + + test("lebar bersih di 360px kini 320px — kalau ini berubah, dua dokumen ikut berbohong", () => { + assert.equal( + LEBAR_BERSIH_PX, + 320, + `.container memberi padding ${PADDING_PX}px tiap sisi hari ini, jadi ` + + `lebar bersih pada lantai 360px yang terhitung adalah ${LEBAR_BERSIH_PX}px, ` + + "bukan 320px. Kalau `.container` memang sengaja diubah, perbarui juga " + + "angka kartu dan skala di AGENTS.md, AGENTS.id.md, " + + "docs/awcms-astro/ui-ux-design-system.md, dan .id.md-nya — itu " + + "persis cacat yang membuat berkas ini ditulis (kartu 328px/skala " + + "0.41 yang sudah lama tidak cocok dengan padding 1.25rem yang " + + "sungguh berlaku)" + ); + }); +}); + +describe("tidak ada minmax(px|rem, …) di src/styles/global.css yang melebihi lebar bersih 360px", () => { + test("setiap track grid berbasis angka tetap pas di lebar bersih, dibungkus min(…, 100%), atau khusus layar lebar", () => { + const pelanggar = []; + const pola = /minmax\(\s*([0-9.]+)(px|rem)\b/g; + let m; + while ((m = pola.exec(CSS))) { + const nilaiPx = Number(m[1]) * (m[2] === "rem" ? 16 : 1); + // `>=`, bukan `>`: sebuah track PERSIS selebar lebar bersih adalah + // kasus asal cacat ini — 320px di ruang 320px pas TANPA sisa sama + // sekali. Itu bukan batas aman, itu adalah pelanggarannya. + if (nilaiPx < LEBAR_BERSIH_PX) continue; + if (dalamZonaDesktop(m.index)) continue; + pelanggar.push(`baris ${nomorBaris(CSS, m.index)}: \`${m[0]}…\` (${nilaiPx}px)`); + } + assert.deepEqual( + pelanggar, + [], + `ditemukan minmax(, …) yang track minimalnya (${pelanggar.join("; ")}) ` + + `melebihi ${LEBAR_BERSIH_PX}px bersih di layar 360px, di luar @media ` + + "(min-width: …) — ini persis bentuk overflow tanpa sisa yang membuat " + + "gerbang ini ditulis (kasus asalnya: `.grid-cards`). Bungkus dengan " + + "`min(px, 100%)` seperti `.grid-cards` sekarang, atau taruh di " + + "dalam `@media (min-width: …)` kalau ia memang khusus layar lebar" + ); + }); +}); + +/** Blok selektor+isi yang memuat indeks karakter `indeks` — rule CSS tidak bersarang, + * jadi `}` pertama setelah `indeks` selalu menutup rule yang sama. */ +function blokAturan(teks, indeks) { + const mulaiKurung = teks.lastIndexOf("{", indeks); + const akhirKurung = teks.indexOf("}", indeks); + const tutupSebelumnya = teks.lastIndexOf("}", mulaiKurung); + return { + selektor: teks.slice(tutupSebelumnya + 1, mulaiKurung).trim(), + isi: teks.slice(mulaiKurung + 1, akhirKurung) + }; +} + +/** + * Kelas selektor yang sah melebihi lebar bersih 360px: menu/dropdown yang + * terbuka MENGAMBANG di atas isi (bukan bagian dari alur utama), dan target + * sentuh minimalnya yang selalu jauh di bawah 320px di repo ini tetapi tetap + * didaftar untuk jujur soal niatnya. + */ +const SELEKTOR_DIKECUALIKAN = /menu|dropdown|switcher|tooltip|popover|scrollbar|target/i; + +describe("tidak ada width/min-width tetap di alur konten utama src/styles/global.css tanpa jalan keluar", () => { + test("setiap deklarasi width/min-width piksel tetap pas, adalah menu/dropdown/target, khusus layar lebar, atau punya overflow-x sendiri", () => { + const pelanggar = []; + const pola = /^[ \t]*(min-width|width)\s*:\s*([0-9.]+)(px|rem)\s*;/gm; + let m; + while ((m = pola.exec(CSS))) { + const nilaiPx = Number(m[2]) * (m[3] === "rem" ? 16 : 1); + if (nilaiPx < LEBAR_BERSIH_PX) continue; + if (dalamZonaDesktop(m.index)) continue; + + const { selektor, isi } = blokAturan(CSS, m.index); + if (SELEKTOR_DIKECUALIKAN.test(selektor)) continue; + if (/overflow(-x)?\s*:\s*(auto|scroll)/.test(isi)) continue; + + pelanggar.push( + `baris ${nomorBaris(CSS, m.index)} (\`${selektor || "?"}\`): \`${m[0].trim()}\` (${nilaiPx}px)` + ); + } + assert.deepEqual( + pelanggar, + [], + `ditemukan width/min-width tetap yang lebih lebar dari ${LEBAR_BERSIH_PX}px ` + + `bersih di layar 360px, tanpa jalan keluar: ${pelanggar.join("; ")}. Ini ` + + "kelas cacat yang sama dengan tabel `min-width: 34rem` yang pernah " + + "menggulirkan seluruh halaman mendatar di 360px (lihat catatan " + + "`.content-body table` di ui-ux-design-system.md) — beri elemen ini " + + "`overflow-x: auto` pada dirinya sendiri, pindahkan ke `@media " + + "(min-width: …)` kalau memang khusus layar lebar, atau jelaskan di " + + "sini kenapa ia aman" + ); + }); +}); + +describe("aritmetika kartu/skala di AGENTS.md sinkron dengan lebar bersih yang dihitung di atas", () => { + test("AGENTS.md menyebut lebar kartu dan skala yang cocok dengan .container hari ini", () => { + // Hanya sumber Inggris yang dibaca: `audit:translation` menjaga cermin + // `.id.md` tetap SEUMUR dengan sumbernya lewat hash, bukan berkas ini — + // membaca kedua bahasa di sini akan menduplikasi pemeriksaan itu, dan + // salah satu akan menjadi berkas yang "kebetulan" tidak diperbarui. + const cocok = AGENTS.match( + /card\s+([0-9.]+)px wide.*?canvas appears at ([0-9.]+) scale/s + ); + assert.notEqual( + cocok, + null, + "tidak menemukan kalimat 'On a card px wide … appears at scale' " + + "di AGENTS.md. Kalau kalimatnya ditulis ulang, gerbang ini perlu " + + "regex baru SEBELUM ia bisa lagi menjaga angka itu tetap sinkron" + ); + if (cocok === null) return; + + const [, lebarDitulis, skalaDitulis] = cocok; + assert.equal( + Number(lebarDitulis), + LEBAR_BERSIH_PX, + `AGENTS.md menyebut kartu ${lebarDitulis}px, sementara .container hari ` + + `ini memberi lebar bersih ${LEBAR_BERSIH_PX}px pada lantai 360px — ` + + "persis cacat asal berkas ini (328px yang tidak pernah cocok dengan " + + "padding 1.25rem yang sungguh berlaku)" + ); + assert.equal( + Number(skalaDitulis), + Number((LEBAR_BERSIH_PX / 800).toFixed(2)), + `AGENTS.md menyebut skala ${skalaDitulis}, sementara ${LEBAR_BERSIH_PX}px ` + + "dibagi kanvas 800px menghasilkan angka yang berbeda" + ); + }); +}); From 3f0df124a4b3771d1f50e5ad5d773bbd5133ac71 Mon Sep 17 00:00:00 2001 From: ahliweb Date: Sat, 5 Sep 2026 20:01:25 +0700 Subject: [PATCH 2/3] fix(css): gerbang lantai 360px kini membaca ` dalam satu berkas `.astro` (biasa satu, kadang nol). */ +function blokStyle(isiAstro) { + const hasil = []; + const pola = /]*>([\s\S]*?)<\/style>/g; + let m; + while ((m = pola.exec(isiAstro))) { + if (m[1].trim().length > 0) hasil.push(m[1]); + } + return hasil; +} + +/** + * Setiap "dokumen CSS" yang diperiksa gerbang ini: `global.css` itu sendiri, + * plus isi setiap blok `