---
name: awcms-ui-screen
description: Implementasikan layar/komponen UI AWCMS sesuai design system. Gunakan saat membangun halaman admin/POS/portal, komponen UI, island interaktif, atau memasang design token/theme. Menegakkan token doc 14, state pattern, a11y AA, i18n, dan aturan offline-first doc 15.
---

# AWCMS — UI Screen / Component

Ikuti **`docs/awcms/14_ui_ux_design_system.md`** (token, komponen, layout, layar) dan **`docs/awcms/15_frontend_architecture_integration.md`** (SSR/islands, API client, offline).

## Checklist implementasi layar

1. **Token dulu** — pakai CSS variables doc 14 (`--color-*`, `--sp-*`, `--fs-*`, termasuk `--color-primary-strong`/`--color-success-strong`/`--color-danger-strong` untuk teks putih di atas warna solid, Issue #434 — semua ≥4.5:1 terukur, jangan pakai varian polos untuk itu); jangan hardcode warna/ukuran. Theme via `data-theme` tanpa flash.
2. **Belum ada component library — pakai pola markup nyata yang sudah dipraktikkan** — repo ini **tidak** punya `src/components/ui` (tidak ada `Button`/`FormField`/`DataGrid`/`DataTable`/`Pagination`/`FilterBar`/`ActionBanner`/`StatusBadge`/`ConfirmDialog`; verifikasi sendiri dengan `find src/components -type d`) — jangan asumsikan atau rujuk komponen ini. Layar admin nyata (`src/pages/admin/offices.astro`, `roles.astro`, `users.astro`, dst.) memakai hand-rolled markup + CSS class konsisten: `<p class="state-notice" role="status|alert">` untuk state akses-ditolak/gagal-sementara (bedakan pesan per kasus, bukan komponen terpisah — jangan bikin blok `.permission-denied` ad-hoc lain); `<p class="admin-create-error" role="alert" hidden>` untuk error per-form/per-aksi yang di-toggle via JS; `<div class="data-table-scroll"><table class="data-table">…</table></div>` (dengan `<td class="data-table-empty">` untuk empty state) untuk list/tabel — **belum ada** pagination/filter UI terpasang di layar admin manapun hari ini (API sudah keyset-cursor, tapi halaman admin merender seluruh hasil apa adanya); `<span class="status-badge">` untuk status lifecycle. Untuk konfirmasi aksi destruktif, kode yang ADA SAAT INI memakai `window.confirm(...)` langsung (lihat `roles.astro`/`offices.astro`) — tidak ada dialog kustom; ikuti pola yang sudah ada untuk aksi destruktif baru, jangan diam-diam memperkenalkan komponen dialog baru tanpa keputusan desain eksplisit. Contoh layar admin nyata untuk dicontoh: `src/pages/admin/offices.astro` dan `src/pages/admin/roles.astro`.
3. **State pattern wajib** — loading (skeleton), empty (+CTA), error (`<p class="state-notice" role="status|alert">` — bedakan "akses ditolak" dari "gagal sementara, coba lagi", pesan aman ter-i18n dari error code doc 05), success/submitting.
4. **Island seperlunya** — halaman SSR; interaktivitas hanya di island (POS, form, chat). Data awal via SSR, mutation via API client.
5. **Form/mutation client-side** — pakai `lockElement`/`sendJson`/`postJson` (`src/lib/ui/admin-form-client.ts`; ekspor SATU-SATUNYA yang ada — verifikasi dengan `grep -n "^export" src/lib/ui/admin-form-client.ts` — jangan asumsikan `showBanner`/`submitJson`/`reloadAfterDelay`, ketiganya tidak ada) untuk form/tombol mutation di halaman admin. `lockElement` mencegah double-submit (disable tombol + label busy selama request, kembali ke semula termasuk saat gagal); `sendJson`/`postJson` mengembalikan `{ ok, errorCode }` narrow yang tidak pernah throw. **Import dari modul ini wajib, bukan sekadar DRY**: CSP repo ini `default-src 'self'` tanpa `'unsafe-inline'` (`astro.config.mjs`/security-headers middleware) — Astro meng-hoist `<script>` tanpa import menjadi inline (diblokir CSP), sedangkan script yang meng-import dari `admin-form-client.ts` dibundle jadi file eksternal `/_astro/*.js` yang diizinkan `'self'`; tanpa import ini, script halaman admin bisa "diam-diam mati" karena diblokir CSP tanpa error yang jelas. Jangan duplikasi implementasi ini per halaman, dan jangan `fetch` mentah.
6. **Navigasi role-aware** — filter menu dari permission `GET /auth/me`; backend tetap validasi (UI hiding bukan kontrol).
7. **i18n** — lihat skill `awcms-i18n` (katalog `.po` gettext, resolusi locale via middleware, formatter locale-aware, `LanguageSwitcher.astro`) — string UI statis **selalu** lewat `t("namespace.key")`, tidak pernah hardcode, termasuk komponen kecil (theme toggle, skip-link, dst. — Issue #434 menemukan `ThemeToggle.astro` lolos ekstraksi awal karena PR i18n tidak menyentuhnya).
8. **A11y (WCAG 2.1 AA)** — kontras ≥4.5:1, fokus terlihat, label eksplisit, dialog trap fokus + Esc, target sentuh ≥44px (mobile), status tidak hanya warna. Skip-link keyboard di layout admin (`AdminLayout.astro`, Issue #434). Sidebar admin responsif (Issue #693): di bawah `--bp-md` jadi off-canvas drawer dengan toggle `aria-expanded`/`aria-controls`, scrim penutup, `Esc` menutup + fokus kembali ke toggle, fokus pindah ke drawer saat dibuka, dan sisa halaman di-`inert`-kan selama drawer terbuka — jangan bangun drawer/dialog baru tanpa pola setara (lihat komentar `<script>` `AdminLayout.astro`).
9. **Masking** — data sensitif tampil lewat `MaskedText`; jangan cache PII mentah di IndexedDB.
10. **POS khusus** — keyboard map F1–F10 (doc 14), cart optimistic dengan rollback, offline outbox + `SyncIndicator` (doc 15).
11. **Tabel lebar** — bungkus dengan container scroll (`overflow-x: auto`), jangan biarkan tabel memaksa scroll horizontal seluruh halaman (Issue #434) — pola nyata: `<div class="data-table-scroll"><table class="data-table">` seperti dipakai `offices.astro`/`roles.astro`/`users.astro`.
12. **Kontrol capability-gated (Issue #693)** — jangan pernah render kontrol interaktif (dropdown, tombol) yang secara visual menyiratkan sebuah aksi/kapabilitas lalu men-`disabled`-kannya di client sebagai satu-satunya penjaga (contoh nyata: `TenantBadge.astro` menggantikan `TenantSwitcher.astro` yang dulu begitu). Kalau kapabilitasnya memang tidak ada untuk siapapun hari ini, jangan render bentuk kontrolnya sama sekali — render badge/teks statis. Kalau kapabilitasnya BISA ada untuk sebagian user, computed data (daftar opsi, izin) harus datang dari server berdasarkan otorisasi nyata, bukan flag/state klien; dan endpoint tujuan aksi tetap harus menolak permintaan dari user yang tidak berwenang meski UI-nya "kebetulan" tidak disembunyikan (defense-in-depth, backend adalah penegak sesungguhnya).

13. **Layar auth/login (doc 14 §Auth screen)** — `src/pages/login.astro` adalah pola kartu auth publik kanonis: brand header (`.auth-mark` + `.auth-wordmark`) + judul/subjudul, field tenant adaptif (readout single-tenant "Signing in to <name>" / `<select>` / manual, dibaca SSR dari tabel root `awcms_tenants`, dibatasi `TENANT_PICKER_LIMIT`), toggle show/hide password di-wire di script bundle (bukan `onclick` inline — CSP), `<select>` bergaya dengan caret CSS (bukan `data:` URI), dan entrance kartu `transform`-saja (`@keyframes auth-card-rise` — bukan `.fade-in-up` yang dari `opacity:0`; lihat doc 14 §Motion, hindari flag kontras axe pada teks utama). Pertahankan kontrak DOM (`#login-form`/`#tenant-id`/`#login-identifier`/`#password`/`#login-submit`/`#login-error`) saat menyentuhnya.
14. **Halaman auth publik selain login** — sejak Gelombang 2 ada tiga lagi: `forgot-password.astro`, `reset-password.astro`, `register.astro`. Gayanya **tidak lagi di-scope di `login.astro`**: kelas `.auth-*` hidup di `src/styles/auth.css` yang keempatnya `import`. Menambah halaman auth baru = import stylesheet itu, bukan menyalin blok `<style>` — dan tambahkan kelas baru ke sana, bukan ke halaman. Picker tenant dipakai bersama lewat `loadTenantPickerModel()` (`tenant-admin/application/tenant-picker-directory.ts`), jadi jangan query `awcms_tenants` sendiri. Tiap halaman punya kontrak DOM sendiri yang di-pin test kontrak — periksa file-nya sebelum me-rename id. Satu aturan yang mudah dilanggar: halaman-halaman ini menghadap penyerta anonim, jadi **pesan pasca-submit harus seragam** untuk kasus "berhasil" dan "tidak memenuhi syarat" — endpoint-nya memang dirancang tak bisa dipakai sebagai oracle, dan UI yang membedakan keduanya membatalkan jaminan itu.

## Wireframe & inventory

Layout shell (admin/POS/portal) dan tabel route→persona→API ada di doc 14 §Screen inventory — patuhi route dan komponen utamanya.

## Verifikasi

- Render 4 state (loading/empty/error/ready) dapat didemokan.
- Keyboard-only pass untuk POS; axe/kontras pass untuk AA.
- Tidak ada string hardcode; tidak ada warna literal; tidak ada `fetch` mentah.
- Offline: buka layar POS tanpa jaringan → tetap operasional, antrean terlihat.
- Layar admin baru/dimigrasikan: layout diverifikasi di 320px, tablet, desktop, zoom 200%, dan keyboard-only (Issue #693); smoke test aksesibilitas otomatis (`@axe-core/playwright`, lihat `tests/e2e/admin-a11y-smoke.e2e.ts`) tidak menemukan pelanggaran critical/serious.

## Skill terkait

`awcms-new-endpoint` (kontrak API), `awcms-i18n` (katalog `.po`, locale, formatter), `awcms-sensitive-data` (masking), `awcms-testing` (render/state test), `awcms-browser-test` (E2E Playwright + smoke aksesibilitas otomatis), `awcms-ux-review` (audit layar yang sudah ada), `awcms-wizard-form` (form multi-step — identitas/detail/lampiran/review sebelum submit).
