You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

14 KiB

name description
cleo-code-review Review struktur dan penulisan kode Cleo Minimarket (Next.js 16, Server Actions, Drizzle, MariaDB MyISAM) terhadap aturan CLAUDE.md dan bug yang pernah ditemukan. Pakai saat diminta "review", "cek kode", "cek commit yang belum dipush", atau sebelum push.

cleo-code-review

Review read-only atas perubahan kode di proyek ini. Tujuannya menangkap bug nyata dan pelanggaran aturan proyek sebelum di-push. Jangan memperbaiki file kecuali diminta.

Kerjakan langkah 1 sampai 6 berurutan. Langkah 3 berisi perintah yang wajib dijalankan, bukan sekadar dipertimbangkan; checklist di langkah 4 baru dibaca setelahnya.

1. Tentukan cakupan

  • Default: commit yang belum di-push plus perubahan yang belum di-commit.
    git status -sb
    git log --oneline '@{u}..HEAD'
    git diff --stat '@{u}'
    git diff '@{u}'
    
    File baru (?? di git status) tidak muncul di git diff; baca isinya langsung.
  • Kalau user menyebut commit, branch, atau file tertentu, pakai itu.
  • Abaikan old-source/, backup/, drizzle/, components/ui/, db/amigo/schema.ts (referensi, backup, atau generated), kecuali perubahannya memang di sana dan mencurigakan.

2. Jalankan pengecekan otomatis

pnpm exec tsc --noEmit
pnpm lint
pnpm exec prettier --check <file yang berubah>
pnpm build

Kutip baris error terpendek yang menentukan. Build perlu dijalankan karena cacheComponents membuat error Suspense baru muncul di build, bukan di tsc.

Kalau prettier --check gagal, pastikan dulu penyebabnya bukan akhir baris CRLF. Di Windows, core.autocrlf=true membuat file di disk ber-CRLF walau isi di git tetap LF:

pnpm exec prettier --check --end-of-line auto <file yang sama>
git ls-files --eol <file yang sama>

Kalau perintah pertama lolos dan kolom i/ di perintah kedua berisi lf, prettier dianggap lolos. Itu bukan temuan, jangan sarankan prettier --write atau mengubah config prettier. Laporkan sebagai temuan hanya kalau i/crlf (CRLF ikut ter-commit) atau --end-of-line auto tetap gagal.

3. Pengecekan mekanis wajib

Jalankan setiap pengecekan yang relevan dengan diff, lalu catat hasilnya. Pelanggaran yang juga ada di fitur acuan (master/pelanggan) tetap dilaporkan; sebutkan bahwa fitur acuan ikut kena, jangan dijadikan alasan untuk lolos.

3a. Referensi sebelum hapus

Untuk setiap fungsi delete<Fitur> yang baru atau berubah, cari tabel lain yang menyimpan kunci baris yang dihapus. Nama kolom penyimpannya sering berbeda dari nama kolom kunci (mis. no_barang.satuan menyimpan satuan_barang.idSatuanBarang), jadi cari lewat nama tabelnya di query lama:

grep -rn "<nama_tabel>" old-source --include=*.php

Baca query yang memakai tabel itu dalam JOIN atau WHERE a.x = b.y. Kalau ada tabel lain yang menunjuk ke baris ini dan action hapus tidak mengecek pemakaian dulu, laporkan sebagai Bug. Skenarionya: data di tabel lain jadi yatim, dan query JOIN di aplikasi PHP diam-diam menyembunyikan baris itu. Kalau DB lokal menyala, hitung baris yatim yang sudah ada sebagai bukti:

echo 'SELECT COUNT(*) FROM <tabel_lain> t LEFT JOIN <tabel> x ON t.<kolom> = x.<kunci> WHERE x.<kunci> IS NULL;' | docker exec -i cleo-mariadb sh -c 'mysql -uroot -p$MARIADB_ROOT_PASSWORD -N cleo'

3b. Session di setiap halaman dashboard

Setiap file di app/(dashboard)/ yang memanggil fungsi data/ (selain data/auth) untuk dirender harus ikut memanggil getCurrentUser() di rantai render-nya, yaitu di page itu sendiri atau di komponen server yang mengambil datanya:

grep -rn --include=*.tsx 'from "@/data/' "app/(dashboard)" | grep -v 'import type' | grep -v '@/data/auth'
grep -rln --include=*.tsx 'getCurrentUser' "app/(dashboard)"

Perintah pertama sudah membuang import type (hanya tipe, tidak membaca data). File yang muncul di daftar pertama tapi rantai render-nya tidak ada di daftar kedua adalah Bug. proxy.ts hanya mengecek ada/tidaknya cookie, dan SidebarUser di layout tidak dirender ulang saat navigasi client, jadi cookie kedaluwarsa tetap bisa melihat data.

3c. Pesan zod

Setiap aturan zod yang bisa gagal karena isian user (.min, .max, .regex, .email, z.enum, dan sejenisnya) harus membawa pesan bahasa Indonesia sendiri. Tanpa itu, zod menampilkan pesan bawaan bahasa Inggris (mis. "Too big: expected string to have <=30 characters").

grep -rn --include=actions.ts '\.min(\|\.max(\|\.regex(\|\.email(\|z\.enum(' app

Baris yang argumennya hanya angka, mis. .max(30) tanpa pesan, adalah temuan.

3d. Urutan import

Untuk setiap file yang berubah, termasuk file lama yang hanya diubah sedikit, cek urutan: package luar, baris kosong, @/..., lalu path relatif (./, ../). Import @/... yang ditulis setelah import relatif adalah temuan Kerapian.

4. Baca diff dengan checklist ini

Untuk temuan di sekitar kode, baca file utuhnya, jangan hanya potongan diff.

"Sama dengan PHP lama" bukan alasan untuk lolos. Aturan bisnis ditiru dari old-source/, tapi kalau perilaku lama merusak data (hapus tanpa cek referensi, penomoran bentrok, stok dihitung baca-hitung-tulis), tetap laporkan sebagai bug. Sebutkan bahwa PHP lama berperilaku sama supaya pembaca tahu ini bukan regresi.

Auth dan routing

  • proxy.ts hanya redirect berdasarkan ada/tidaknya cookie. Redirect "sudah login -> /" di proxy memicu loop saat cookie kedaluwarsa (proxy kirim ke /, getCurrentUser() kirim ke /login). Redirect seperti itu harus memakai session yang divalidasi (redirectIfLoggedIn() di data/auth.ts), dipanggil dari page di dalam <Suspense>.
  • Matcher proxy harus tetap mengecualikan login, api, dan aset statis.
  • Import dari path internal Next (next/dist/...) hampir selalu salah.
  • Page dan action yang butuh user memanggil getCurrentUser() (lihat 3b); layout tidak dipakai untuk cek session.
  • Komponen yang membaca headers()/cookies()/searchParams berada di dalam <Suspense>.

Server Actions

  • File bernama actions.ts (jamak), diawali "use server", dikolokasikan dengan fitur.
  • Setiap action: cek session, validasi zod sebelum menyentuh DB, panggil fungsi data/ (bukan query langsung), kembalikan objek kecil, revalidatePath sebelum redirect.
  • Error validasi dikembalikan per field (z.flattenError(err).fieldErrors), bukan hanya issues[0].message; kalau tidak, pesan tampil di field yang salah.
  • Setiap aturan zod membawa pesan bahasa Indonesia (lihat 3c).
  • Return yang gagal ikut membawa values (isian mentah), karena React 19 me-reset form setelah action selesai dan ketikan user hilang.
  • console.error berisi tag nama action, user.id, input relevan (tanpa password/secret), dan error asli sebagai argumen terpisah. Pesan ke UI bahasa Indonesia dan bisa ditindaklanjuti, tidak pernah pesan DB mentah.
  • Hasil getCurrentUser() disimpan ke variabel kalau dipakai di log.

Komponen client

  • Jangan menutup sheet/dialog lewat useEffect(() => { if (state.ok) setOpen(false) }, [state.ok]): tidak jalan lagi saat sukses kedua (true -> true) dan dilarang lint react-hooks/set-state-in-effect. Bungkus action di useActionState dan panggil setOpen(false) di sana.
  • Input form memakai defaultValue={state.values?.[name] ?? dataAwal ?? ""}; error per field ditampilkan di <FieldError> field itu, error umum di dekat tombol submit.
  • Tipe dari modul data/ (server-only) di-import dengan import type.
  • Konfirmasi destruktif memakai AlertDialog, bukan Sheet yang di-override class-nya. Tombol konfirmasi berupa Button type="submit" supaya error action sempat terlihat.
  • Prop yang tidak dipakai pemanggil mana pun dibuang.

Data Access Layer (data/)

  • Diawali import "server-only".
  • Fungsi yang diekspor tapi tidak dipakai di mana pun: tandai untuk dihapus.
  • Tipe input diturunkan dari tipe baris (Omit<...>) daripada ditulis ulang manual.
  • Query lewat Drizzle atau sql tag, tidak pernah string concatenation. Pola LIKE memakai escape \, %, _.
  • MariaDB cleo itu MyISAM: tidak ada transaksi dan FOR UPDATE tidak mengunci. Penomoran otomatis harus tahan bentrok dengan PHP lama (retry saat ER_DUP_ENTRY), update stok atomik satu statement, master + detail divalidasi penuh sebelum statement pertama.
  • Hapus baris yang masih direferensikan tabel lain harus ditolak dulu (lihat 3a).
  • Tanggal "hari ini" pakai zona Asia/Jakarta, bukan toISOString() (UTC).
  • Tanggal 0000-00-00 tetap string, tidak diubah ke Date.
  • Fungsi baca yang dipanggil beberapa komponen dalam satu render (mis. opsi select yang dipakai tombol tambah dan daftar) dibungkus cache dari react, bukan diambil dua kali. getCurrentUser() harus tetap cache React.
  • "use cache", unstable_cache, atau penyimpanan lintas request (variabel modul, Map global) untuk data cleo atau session adalah Bug: PHP lama ikut menulis ke DB sehingga data basi, dan session yang tersimpan membuat user yang sudah logout tetap dianggap login. Cek: grep -rn '"use cache"\|unstable_cache' app data lib.
  • Batas panjang zod sama dengan panjang kolom di db/amigo/schema.ts.
  • Aturan bisnis dicocokkan dengan kode lama di old-source/ (mis. format kode pelanggan).

Struktur dan maintainability

Bagian ini butuh konteks di luar diff. Kalau diff menambah atau mengubah fitur, baca juga fitur lain yang sejenis.

  • Fitur baru mengikuti pola fitur acuan app/(dashboard)/master/pelanggan/: page.tsx, actions.ts, _components/, dan satu file data/<fitur>.ts per domain. Penyimpangan dari pola itu harus punya alasan yang jelas.
  • Nama fungsi data/ seragam antar fitur: list<Fitur>, get<Fitur>, insert<Fitur>, update<Fitur>, delete<Fitur>. Istilah domain konsisten (mis. pelanggan, bukan campur customer), kecuali nama kolom/tabel lama di db/amigo/schema.ts.
  • Arah dependency:
    • app/ boleh import data/, components/, lib/; data/ boleh import db/ dan lib/. Tidak boleh sebaliknya (data/ atau lib/ import dari app/).
    • Fitur A tidak import _components/ atau actions.ts milik fitur B. Kalau dibutuhkan bersama, pindahkan komponen ke components/ atau logika ke lib//data/.
    • components/ (lintas fitur) tidak import dari app/.
  • Duplikasi lintas fitur: kode yang sama muncul di fitur kedua dipindah ke satu tempat bersama, bukan disalin. Kandidat umum: escape LIKE, tipe hasil action ({ ok, error, fieldErrors, values }), pola log error action, pagination (termasuk helper href halaman dan parsing ?page=), kotak pencarian, skema zod yang berulang. Jangan memindahkan saat baru dipakai satu fitur.
  • Aturan bisnis lama (kode huruf harga convHrgP/convProfit, format tanggal dd-mm-yyyy, tanggal "hari ini" Asia/Jakarta) punya satu implementasi di lib/, tidak ditulis ulang per fitur.
  • data/ tidak berisi logika tampilan (format rupiah, format tanggal untuk UI, teks label).
  • File di atas sekitar 250 baris, atau komponen yang mengerjakan beberapa hal sekaligus (form, daftar, dan dialog dalam satu file), ditandai untuk dipecah.
  • Navigasi: setiap href di app/(dashboard)/_components/nav-items.ts punya page.tsx, dan setiap page baru di (dashboard) terdaftar di nav (kecuali halaman detail/turunan).
  • Kalau perubahan memperkenalkan konvensi baru (folder, helper bersama di components/ atau lib/, pola action), cek apakah CLAUDE.md ikut diperbarui supaya fitur berikutnya memakai helper itu. Kalau belum, itu temuan Tidak sesuai aturan proyek.

Kerapian

  • Format sesuai Prettier (titik koma, baris tidak terlalu panjang, newline di akhir file).
  • Urutan import: package luar, baris kosong, lalu @/..., lalu path relatif (lihat 3d).
  • Nama file kebab-case, komponen PascalCase, import lintas folder lewat alias @/.

5. Verifikasi sebelum melapor

Untuk setiap calon temuan:

  1. Buka ulang file di baris yang akan disebut dan pastikan kodenya memang seperti yang diklaim. Nomor baris harus cocok dengan isi file saat ini.
  2. Untuk bug, pastikan skenarionya benar-benar bisa terjadi lewat jalur yang ada (UI, POST langsung ke action, atau aplikasi PHP yang memakai tabel yang sama).
  3. Buang temuan yang tidak terbukti. Temuan yang masuk akal tapi belum bisa dibuktikan boleh tetap ditulis dengan penanda "perlu dicek", bukan dinyatakan pasti.

6. Laporkan

Ikuti aturan output user (bahasa, nada) dari CLAUDE.md global. Struktur laporannya:

  • Kalimat pertama adalah verdict: "Siap di-push." atau "Belum siap di-push: .", diikuti hasil tsc/lint/prettier/build dalam satu kalimat. Jangan membuka dengan "Sudah saya review...", "Berikut hasil review...", atau sejenisnya.

  • Prettier yang gagal hanya karena CRLF (lihat langkah 2) ditulis "lolos", boleh disertai satu kalimat penjelasan, dan tidak membuat verdict jadi "belum siap".

  • Temuan dikelompokkan dan diberi nomor berurutan lintas kelompok:

    1. Bug: perilaku salah yang bisa dialami user. Wajib ada skenario konkret.
    2. Tidak sesuai aturan proyek: pelanggaran CLAUDE.md.
    3. Struktur dan maintainability: menyimpang dari pola fitur acuan, arah dependency salah, duplikasi lintas fitur, file terlalu besar, nav tidak sinkron.
    4. Kerapian: format, urutan import, kode mati, duplikasi kecil di dalam satu file.

    Kelompok yang kosong tidak ditulis.

  • Setiap temuan menyebut file:baris, apa yang salah, dan arah perbaikannya dalam satu atau dua kalimat. Bug boleh lebih panjang karena butuh skenario dan bukti.

  • Tutup dengan daftar singkat hal yang sudah benar, lalu saran urutan perbaikan (nomor temuan, bug lebih dulu), lalu satu tawaran konkret untuk dikerjakan berikutnya.

Contoh bentuk laporan (isi hanya ilustrasi):

Belum siap di-push: satuan yang masih dipakai barang bisa dihapus. tsc, lint, prettier,
dan build lolos (peringatan prettier hanya karena CRLF di disk, isi git tetap LF).

## Bug

1. **`data/satuan-barang.ts:46`**: `deleteSatuanBarang` tidak mengecek pemakaian di
   `no_barang.satuan`. Skenario: user menghapus satuan "PCS", lalu semua barang bersatuan
   PCS hilang dari daftar barang PHP karena query di `old-source/ajaxbarang.php:302` memakai
   inner join. DB lokal sudah punya 12 baris yatim. PHP lama berperilaku sama, jadi ini
   bukan regresi. Fix: hitung pemakaian dulu dan tolak dengan pesan "Satuan masih dipakai
   N barang".

## Kerapian

2. **`app/(dashboard)/master/pelanggan/page.tsx:5`**: import `@/components/cari-master`
   ditulis setelah import relatif. Pindahkan ke atas blok `./_components/...`.

## Yang sudah benar

- Action mengecek session, validasi zod, dan mengembalikan `fieldErrors` plus `values`.

Urutan perbaikan: 1, lalu 2. Mau saya kerjakan nomor 1 sekarang?