snapme
PublikMILESTONES.md
Ini rencana rilis snapme: sebelas milestone yang dikerjakan berurutan; setiap milestone mencantumkan pekerjaannya dan butir "Selesai bila"-nya. Nomor kebutuhan (FR, GS, SE, SH) merujuk ke PRD.md §7.
Status
- M0 Fondasi
- M1 Welcome dan kerangka booth
- M2 Kamera
- M3 Gesture Snap
- M4 Hias strip
- M5 Hasil dan cetak
- M6 Link strip dan QR
- M7 Strip Hidup
- M8 Strip Estafet
- M9 Pengerasan
- M10 Migrasi Cloudflare
Berlaku untuk semua milestone
- Butir bertanda (P1) dikerjakan hanya setelah semua butir lain di milestone itu selesai. Yang tidak sempat dicatat di laporan dan dibawa ke M9.
M0 Fondasi
Tujuan. Repo siap dikembangkan: kerangka terpasang, komponen dasar sudah bergaya snapme, dan ada galeri komponen untuk dibandingkan dengan halaman acuan.
Baca. ARCHITECTURE §2, §3, §4. DESIGN-SYSTEM §2 sampai §9, §13. Ketiga berkas di docs/ui-reference/ sebagai contoh komponen dalam pemakaian.
Pekerjaan
-
git initbila belum ada,.gitignore,.env.examplekosong. - Scaffold Next.js dan pasang dependensi (ARCHITECTURE §3).
-
shadcn init, tambahkan komponen di DESIGN-SYSTEM §9. - Evaluasi registry neobrutalism (ARCHITECTURE §3), catat keputusan di
docs/DECISIONS.md. - Token di
src/app/globals.css(DESIGN-SYSTEM §13), fon lewatnext/font. - Ganti ikon
lucide-reactdi komponen shadcn dengan@phosphor-icons/react, lalu lepaslucide-react. - Sesuaikan varian komponen dengan DESIGN-SYSTEM §9.
- Skrip npm:
dev,lint,typecheck,test,build. Konfigurasi Vitest dan Playwright dengan satu uji contoh. - Halaman
/dev/uiyang menampilkan setiap komponen dalam semua keadaan. - Pastikan
docs/ui-reference/berisi tiga berkas HTML acuan dan gambar referensi, dan ketiganya terbuka di browser.
Bukan bagian milestone ini. Halaman produk, kamera, backend.
Selesai bila
/dev/uimenampilkan semua komponen umum DESIGN-SYSTEM §9 dalam keadaan bawaan, hover, fokus, aktif, nonaktif, dan memuat.- Tombol, chip, tab pil, sakelar, dan kartu di
/dev/uitampak sama dengan yang ada di berkas acuan saat dibandingkan berdampingan. - Pencarian nilai warna heksadesimal di
src/hanya menemukanglobals.css. lucide-reacttidak ada dipackage.json.- Semua skrip npm (
lint,typecheck,test,build) berjalan dan lolos.
M1 Welcome dan kerangka booth
Tujuan. Pengunjung melihat Welcome, memilih layout, dan masuk ke kerangka langkah booth.
Kebutuhan. FR-01, FR-02, FR-15.
Baca. docs/ui-reference/welcome.html. DESIGN-SYSTEM §5, §10.1, §12. ARCHITECTURE §5.
Pekerjaan
- Welcome (
/) sebagai komponen server, mengikuti anatomi DESIGN-SYSTEM §10.1. - Booth Shell dan stepper (
src/app/booth/layout.tsx). -
lib/layouts.ts: geometri Strip 4 dan Strip 3 (DESIGN-SYSTEM §5) dengan uji unit bahwa jumlah pikselnya pas. -
/booth/layout: pilih layout, simpan ke state booth. -
stores/booth.ts(kerangka state di ARCHITECTURE §5) dan pengalihan langkah tanpa prasyarat. -
/privasi.
Bukan bagian milestone ini. Tombol "Pakai Foto Galeri" belum ditampilkan sampai FR-13 dikerjakan di M4.
Selesai bila
- Welcome cocok dengan
docs/ui-reference/welcome.htmlsaat dibandingkan berdampingan pada 360 x 640, 768, dan 1280 px, dan "Mulai Foto" terlihat tanpa menggulir. - Bundel
/tidak memuatreact-webcam,@mediapipe/tasks-vision,konva. - Uji Playwright: Welcome ke Pilih layout ke
/booth/capture.
M2 Kamera
Tujuan. Pengguna mengambil semua foto untuk layout terpilih dengan tombol atau keyboard.
Kebutuhan. FR-04, FR-05, FR-06, FR-07. FR-08 (P1).
Baca. docs/ui-reference/capture.html. DESIGN-SYSTEM §10.2. ARCHITECTURE §5 (mesin keadaan), §6 (kamera).
Pekerjaan
- Layar penjelas izin kamera dan penanganan izin ditolak.
-
features/camera: pratinjau dicerminkan, crop sesuai rasio slot. -
features/capture: mesin keadaan sebagai fungsi murni dengan uji unit. -
CameraStage,CountdownOverlay,ShutterButton,ShotTray. - Hitung mundur 3 detik, kilat tunggal, pengumuman
aria-live. - Ulang foto terakhir dan ulang foto tertentu.
- Spasi mengambil foto saat fokus di area kamera.
- (P1) Pilih kamera.
Selesai bila
- Uji Playwright dengan kamera palsu: empat foto terambil, satu diulang, lanjut ke Hias.
- Uji Playwright izin ditolak: pesan dan jalan keluar tampil.
- Foto tersimpan sama dengan pratinjau (tercermin) dan berasio slot.
- Kilat tidak muncul saat
prefers-reduced-motion. - Halaman cocok dengan
docs/ui-reference/capture.htmlpada 360 dan 1280 px (kecuali HUD gesture, yang masuk di M3).
M3 Gesture Snap
Tujuan. Foto bisa dipicu dengan tanda dua jari tanpa menyentuh perangkat.
Kebutuhan. GS-01 sampai GS-07. GS-08, GS-09, GS-10 (P1).
Baca. PRD §7.2. ARCHITECTURE §6 (gesture), §9. docs/ui-reference/capture.html untuk empat keadaan HUD dan indikator tahan.
Pekerjaan
- Salin berkas wasm dan model MediaPipe ke
public/(ARCHITECTURE §4). Tidak memuat dari CDN. -
gestureSource: dimuat dinamis setelah izin kamera, laju dibatasi. -
holdDetector: fungsi murni dengan uji unit (tahan cukup lama, lepas lebih awal, jeda setelah foto). -
GestureHUDdengan empat keadaan dan sakelar Gesture Snap yang diingat. - Status "Menyiapkan gesture..." dan jalur gagal muat.
- Uji Playwright: tidak ada permintaan jaringan yang membawa gambar selama
/booth/capture. - (P1) Telapak untuk ulang, jempol untuk lanjut, dua tangan. Dua tangan lewat
numHands: 2sejak P0; pengenalan telapak dan jempol dibuktikan uji unitholdDetector, karena kamera palsu tidak punya tangan.
Selesai bila
- Semua uji unit
holdDetectordan uji privasi GS-01 lolos. - Dengan sakelar mati, model tidak dimuat dan tidak berjalan.
- Hasil uji manual dicatat di
docs/DECISIONS.md: perangkat, cahaya, berapa kali berhasil dari 10 percobaan, dan ambang akhir yang dipakai. (terpenuhi atas konfirmasi pengguna 16 Oktober 2026: deteksi di HP berhasil; jumlah dari 10 dan uji laptop tidak dirinci; ambang akhir tetap nilai awal, tabel didocs/DECISIONS.mdM3)
Butuh dari pengguna. Uji manual di satu HP Android dan satu laptop; deteksi tidak bisa diverifikasi dengan kamera palsu.
M4 Hias strip
Tujuan. Pengguna menghias strip dan mendapat PNG siap cetak.
Kebutuhan. FR-09, FR-11. FR-10, FR-13 (P1).
Baca. docs/ui-reference/customize.html. DESIGN-SYSTEM §5 (geometri), §8, §10.3, §11. ARCHITECTURE §6 (editor).
Pekerjaan
-
StripCanvasdengan react-konva darilib/layouts.ts. - Minimal 6 frame dan 6 efek (preset filter Konva).
- Minimal 12 stiker di
public/stickers. Boleh memakai gambar hasil generasi, wajib ditinjau pengguna sebelum masuk repo. (Dua belas SVG ditulis tangan di repo atas keputusan pengguna, bukan gambar hasil generasi.) - Stiker: tempel, geser, putar, ubah ukuran, hapus, dengan
Transformerbawaan Konva. - Padanan daftar untuk keyboard dan pembaca layar (DESIGN-SYSTEM §11).
- Tab pil Frame, Efek, Stiker. Lembar bawah di layar sempit. (Ditambah tab Teks untuk FR-10.)
- Ekspor PNG.
- (P1) Teks dan tanggal. (P1) Pakai Foto Galeri, lalu tampilkan tombolnya di Welcome.
Selesai bila
- Uji otomatis: PNG ekspor Strip 4 berukuran tepat 600 x 1800 px dan di bawah 2 MB.
- Satu stiker bisa ditempel, dipindah, diputar, dan dihapus hanya dengan keyboard.
- Konva tidak ikut termuat di
/dan/booth/capture. - Halaman cocok dengan
docs/ui-reference/customize.htmlpada 360 dan 1280 px.
M5 Hasil dan cetak
Tujuan. Pengguna mengunduh strip dan mencetaknya di kertas 4R.
Kebutuhan. FR-11 (unduh), FR-12. FR-03, FR-14 (P1). Baca. DESIGN-SYSTEM §5, §9. PRD §7.1.
Pekerjaan
-
/booth/result: pratinjau, "Selesai & Unduh", "Cetak 4R", "Mulai dari awal". - Nama berkas
snapme-YYYYMMDD-HHmm.png. - Lembar 4R 1200 x 1800 px: dua strip berdampingan dengan garis potong, dan gaya cetak satu halaman 4 x 6 inci.
- (P1) Layout Grid 2 x 2. (P1) Sesi bertahan saat dimuat ulang (IndexedDB).
Selesai bila
- Uji Playwright: alur penuh dari Welcome sampai berkas terunduh.
- Pratinjau cetak Chromium menampilkan tepat satu halaman.
- Alur dasar selesai tanpa satu pun permintaan ke server selain aset statis.
Setelah M5, snapme sudah bisa dipakai sebagai photobooth lokal (tahap Alpha di PRD §10).
M6 Link strip dan QR
Tujuan. Pengguna bisa membuat link pribadi untuk stripnya dan menaruh QR di strip.
Kebutuhan. SH-04, SH-05, SH-06. Baca. ARCHITECTURE §7, §8. PRD §7.4, §8.
Butuh dari pengguna sebelum mulai. Proyek Supabase (URL dan kunci service role di .env.local), dan keputusan cara pembatasan laju (ARCHITECTURE §8).
Pekerjaan
- Skema
stripsdan bucket pribadi sebagai berkas migrasi di repo. RLS menolakanon. (supabase/migrations/20261012000000_strips_bucket.sql; penerapan ke proyek menunggu proyek pengguna) - Rute
POST /api/strips,POST /api/strips/[id]/complete,DELETE /api/strips/[id],GET /api/cron/purge. (diuji unit dengan klien Supabase palsu) - Validasi
zod, batas jenis dan ukuran berkas, token hapus yang di-hash. - Dialog persetujuan unggah, unggah langsung ke Storage dengan URL bertanda tangan. (jalur 503 sudah diuji e2e; unggahan sungguhan menunggu proyek)
-
QRPlatedi kaki strip (168 px pada ekspor), render ulang strip setelah link dibuat. (kotak QR dan glif penanda sudah terbukti di browser; render ulang ber-QR menunggu unggahan sungguhan) - Halaman
/s/[id]: strip, unduh, tanggal kedaluwarsa, hapus.noindex. Jawaban 410 bila kedaluwarsa. (halaman menjawab 404 untuk strip yang tidak ada, terbukti lewatcurl; status 410 harfiah dijawab rute API, penyimpangan tercatat diDECISIONS.md) - Header keamanan (ARCHITECTURE §8). (diverifikasi lewat
curl -ipada/dan/s/abc; CSP penuh menyusul di M9)
Selesai bila
- Uji end-to-end: buat link, buka
/s/{id}di konteks lain, hapus, lalu halaman menjawab tidak ditemukan. (hijau:SUPABASE_E2E=1 npx playwright test share.spec.ts→ 3 lolos, 1 dilewati; alur sungguhan sampai unggah ke Storage, konteks kedua, dan hapus) - Uji unit kedaluwarsa: strip lewat 7 hari menjawab 410 walau pembersih belum berjalan. (hijau:
tests/api-strips.test.ts) - Kunci service role tidak muncul di bundel klien. (hijau: build dengan
.env.localsungguhan, 50 berkas.next/staticdipindai, nol memuat kuncinya;tests/client-bundle-secrecy.test.ts) - Tinjauan keamanan atas
src/app/apidanlib/supabasesudah dilakukan, dan temuannya ditangani atau dicatat. (delapan temuan: satu blocker dan empat major sudah diperbaiki, sisanya diperbaiki atau dicatat; tabelnya didocs/DECISIONS.mdbagian M6)
Batas aman. Migrasi sudah dijalankan ke proyek Supabase snapme (ref owdjkpzywzljnvuyfiui) atas persetujuan pengguna 9 Oktober 2026, lewat supabase link + supabase db push. Perubahan skema berikutnya mengikuti aturan yang sama dan selalu dengan persetujuan pengguna.
M7 Strip Hidup
Tujuan. Tiap pose punya klip pendek, strip bisa diputar, dan QR membuka versi bergeraknya.
Kebutuhan. SH-01, SH-02. SH-03 (P1). Baca. PRD §7.4. ARCHITECTURE §6 (Strip Hidup), §9.
Pekerjaan
- Perekam bingkai di mesin keadaan pemotretan: 10 bingkai 480 px pada 1,5 detik terakhir hitung mundur. (
src/features/capture/clipRecorder.ts+useClipRecorder.ts; sampling 1500 sampai 2850 ms, jadi momen jepret di 3000 ms tidak tersentuh) -
LiveStripPlayerdan sakelar Strip Hidup di Hias. (diwujudkan sebagai proplivediStripStage+useClipTicker, dengan lencana "Klip"; alasannya diDECISIONS.mdM7) - Unggah versi hidup bersama strip, tampilkan di
/s/[id]. (migrasi bucket 5 MB + jenis video diterapkan ke proyek atas persetujuan pengguna 9 Oktober 2026; bukti ber-jaringan diDECISIONS.mdM7) - Hormati
prefers-reduced-motion: berhenti dengan tombol Putar. (di Hias dan di pemutar/s/[id]) - Lepas
ImageBitmapsaat booth dimulai ulang. (setShot,setLayout, danresetmemanggilclose(); dijagatests/booth-live.test.ts) - (P1) Unduh video lewat
MediaRecorder, tombol tersembunyi bila tidak didukung. (2 putaran x 10 bingkai = sekitar 3 detik, di bawah 5 MB; dijagae2e/live.spec.ts)
Selesai bila
- Perekaman bingkai tidak mengubah waktu pengambilan foto utama (uji unit pada mesin keadaan). (hijau:
tests/clip-recorder.test.tsmenjalankan tabel peristiwa yang sama dengan dan tanpa pengamat perekam) /s/{id}menampilkan versi hidup untuk strip yang punya klip, dan versi diam untuk yang tidak. (hijau:SUPABASE_E2E=1 npx playwright test share.spec.ts live.spec.ts→ 8 lolos, 1 dilewati; konteks kedua melihat<video>bertanda tangan/storage/, sedangkan strip tanpa klip nolvideo)- Uji manual di Chrome, Firefox, dan Safari dicatat di
docs/DECISIONS.md. (tabel disiapkan dengan status menunggu pengguna; perangkat di luar Chromium headless belum tersedia)
Batas aman. Migrasi 20261013000000_live_video.sql (batas bucket 5 MB + video/webm, video/mp4) sudah diterapkan ke proyek Supabase snapme atas persetujuan pengguna 9 Oktober 2026. Perubahan skema berikutnya mengikuti aturan yang sama dan selalu dengan persetujuan pengguna.
Catatan uji. e2e/gesture.spec.ts "pilihan sakelar diingat setelah halaman dimuat ulang" diperbaiki: navigasinya masih menganggap state booth belum tersimpan lintas muat ulang (komentar lamanya menyebut "FR-14 menyusul"), sedangkan M5 sudah mempersistensinya, jadi ujinya berlomba dengan tunda simpan 400 ms. Sekarang ujinya menunggu simpanan selesai lalu memastikan halaman tetap di langkah yang sama.
M8 Strip Estafet
Tujuan. Dua orang di perangkat dan waktu berbeda mengisi satu strip.
Kebutuhan. SE-01 sampai SE-09. SE-10 (P1). Baca. PRD §7.3. ARCHITECTURE §6 (estafet), §7, §8.
Pekerjaan
- Skema
relayssebagai migrasi. RutePOST /api/relays,claim,complete,strip,DELETE. (supabase/migrations/20261014000000_relays.sql; rute diuji unit dengan klien Supabase palsu:tests/api-relays.test.ts22 lolos. Migrasi sudah diterapkan ke proyeksnapmeatas persetujuan pengguna di M8) -
RelayInviteCard, dialog persetujuan, bagikan lewat Web Share API atau salin. (src/components/booth/RelayInviteCard.tsx+src/features/relay/shareLink.ts; jalur salin diuji e2e, jalur Web Share lewat guardcanShare) - Halaman
/r/[id]: foto yang sudah ada, slot terkunci, ambil giliran. (src/app/r/[id]/page.tsx+RelayJoinView; foto pembuat lewat URL bertanda tangan 1 jam) - Overlay bayangan 35% di pratinjau kamera teman, dengan sakelar. (prop
overlaydiCameraStage, sakelar "Bayangan pose teman" dengan chip Nyala/Mati; dijagae2e/relay.spec.ts) - Giliran hanya bisa diambil sekali (dijamin di basis data, bukan hanya di klien). (
UPDATE ... WHERE status = 'waiting'; nol baris berubah → 409. Uji unit + uji klaim bersamaan e2e) - Status pembuat: "Menunggu teman", "Selesai". Batalkan dan hapus. (
RelayJoinViewdanRelayCompleteView+RelayCancel.tsx, SE-09 bisa kapan pun, termasuk setelah selesai) - Kedaluwarsa 48 jam, ikut dibersihkan oleh pembersih M6. (
RELAY_TTL_HOURS = 48, kelompokrelaysdiGET /api/cron/purge; uji unit pembersih)
Selesai bila
- Uji Playwright dua konteks browser: pembuat mengisi slot 1 dan 3, teman mengisi 2 dan 4, keduanya sampai di
/s/{id}yang sama. (hijau:SUPABASE_E2E=1 npx playwright test relay.spec.ts→ 4 lolos; teman mendarat di/s/{id}dan gambar stripnya dimuat dari Storage bertanda tangan, pembuat melihat tautan ke path yang sama) - Uji: konteks ketiga yang membuka link melihat "Estafet ini sudah diambil". Dua permintaan
claimbersamaan hanya satu yang berhasil. (hijau di berkas yang sama: konteks ketiga melihat pesannya tanpa tombol, dan duaPOST claimparalel menghasilkan tepat 200 + 409 terhadap Postgres sungguhan) - Tinjauan keamanan atas rute estafet sudah dilakukan. (selesai: satu temuan major — gate token di
/r/[id]status selesai hanya presentasional — sudah diperbaiki dengan rute ber-tokenPOST /api/relays/[id]/strip; empat temuan minor dicatat didocs/DECISIONS.mdM8, dua di antaranya ikut diperbaiki. Catatan: gate jenis isiclaimdan validasi runtime barisrelays)
Batas aman. Migrasi 20261014000000_relays.sql (tabel relays, dua indeks, image/jpeg di daftar jenis bucket) sudah diterapkan ke proyek Supabase snapme (ref owdjkpzywzljnvuyfiui) lewat npx supabase db push atas persetujuan pengguna di M8; supabase migration list mengonfirmasi barisnya ada di remote. Perubahan skema berikutnya mengikuti aturan yang sama dan selalu dengan persetujuan pengguna.
Sisa untuk M9. Constraint bentuk slots dan daftar layout tertutup di tabel relays, kolom strip_id + UPDATE bersyarat untuk menutup balapan pemulihan complete, dan uji manual gesture/estafet di perangkat sungguhan (Chromium headless tidak punya tangan). Dua butir pertama selesai di M9 lewat 20261015000000_relays_hardening.sql dan gerbang pemenang tunggal di rute complete; uji manual gesture tetap menunggu pengguna.
M9 Pengerasan
Tujuan. snapme layak dirilis: bisa diakses, cepat, aman, dan terukur.
Baca. PRD §4, §8, §11. DESIGN-SYSTEM §11, §14. ARCHITECTURE §9, §10, §12.
Pekerjaan
- Butir (P1) yang tertunda dari milestone sebelumnya. (SE-10, GestureHUD boleh-tanpa-gesture,
Open_Palm,Thumb_Up, pemilih kamera, teks dan tanggal, FR-14, dan FR-13 sudah dikerjakan di M3 sampai M8. Sisa P1 M8 di tabelrelays— constraint bentukslots, daftar layout tertutup, kolomstrip_id— dikerjakan di M9 lewat migrasi20261015000000_relays_hardening.sqldan gerbang pemenang tunggal diPOST /api/relays/[id]/complete) - Audit
@axe-core/playwrightdi semua halaman dan uji keyboard manual untuk seluruh alur. (axe: 9 halaman × 2 viewport nol pelanggaran, ditambah dua audit ber-linkSUPABASE_E2E=1; keyboard:e2e/keyboard-flow.spec.tsmenuntaskan seluruh alur tanpa satu punpage.mouse. Uji pembaca layar manual masuk tabel pengguna) - Ukur LCP Welcome dan ukuran bundel tiap rute, bandingkan dengan PRD §8. (Lighthouse preset seluler di server produksi: median LCP 3,17 detik terhadap batas 2,5 detik, selisih dan rencananya dicatat; ekspor strip median 922 ms terhadap target 2 detik; tabel transfer per rute diukur dari peramban karena Next.js 16 menghapus metrik
First Load JS) - Uji lintas browser: Chrome, Edge, Firefox, Safari, Chrome Android, Safari iOS. (Mesin Gecko dan WebKit hijau otomatis lewat proyek
firefoxdanwebkit; Edge tidak terpasang dannpx playwright install msedgegagal karena hak akses, jadi proyekmsedgedilepas dan Edge sungguhan masuk tabel manual pengguna; Chrome diliput proyekchromium; Chrome Android dan Safari iOS butuh perangkat sungguhan) - Analitik tanpa cookie untuk ukuran di PRD §4. Pilihan alat diusulkan dulu ke pengguna. (Pengguna memilih endpoint peristiwa first-party; migrasi
20261016000000_events.sqlditerapkan, daftar peristiwa tertutup, kueri lima rasio PRD §4 didokumentasikan, dan peristiwa kunjungan sungguhan terbukti masuk basis data) - Tinjau ulang semua butir ARCHITECTURE §12 dan perbarui dokumen yang nilainya berubah. (§12 ditulis ulang; §3, §7, dan §8 diperbarui; PRD dan DESIGN-SYSTEM tidak berubah)
Butuh dari pengguna. Cetak satu strip dengan QR di kertas 4R dan pindai dari cetakannya. Deploy ke Vercel. (QR selesai 16 Oktober 2026; deploy digantikan M10 ke Cloudflare, jadi tidak ada tugas Vercel yang tersisa di sini.)
Selesai bila
- Tidak ada pelanggaran axe tingkat serius atau kritis. (terpenuhi: audit otomatis nol pelanggaran di sembilan halaman pada 360 dan 1280 px, plus halaman strip dan estafet ber-link di bawah gerbang
SUPABASE_E2E=1) - Target PRD §8 tercapai, atau selisihnya dicatat beserta rencana. (terpenuhi: ekspor strip dan FCP di dalam target; LCP Welcome tercapai di produksi, median 2,416 detik terhadap batas 2,5 detik dari lima jalankan 16 Oktober 2026. Angka M9 3,17 detik dan M10 3,745 detik diukur di server lokal, bukan CDN. Langkah 1 rencana dikerjakan: JS sebelum
loaddi pratinjau turun 165,9 menjadi 153,9 KB dan chunk sonner tidak lagi ikut pra-load; langkah 2 dan 3 tidak lagi diperlukan untuk target. Tabel dan buktinya didocs/DECISIONS.mdM9 "Langkah 1 rencana") - QR pada cetakan asli terbaca oleh kamera HP. (terpenuhi: dikonfirmasi pengguna 16 Oktober 2026, QR cetakan terbaca kamera HP)
- Daftar periksa DESIGN-SYSTEM §14 lolos untuk semua halaman. (terpenuhi: tabel per butir di
docs/DECISIONS.mdM9, termasuk hasil zoom 200% dan temuan keyboard pada pil pilihan hiasan)
M10 Migrasi Cloudflare
Tujuan. Backend snapme berjalan penuh di Cloudflare: D1 untuk basis data, KV untuk anggaran laju peristiwa, R2 untuk berkas, Worker (OpenNext) menggantikan Vercel, tanpa sisa kode Supabase.
Baca. ARCHITECTURE §3, §4, §6, §7, §8, §10, §12. docs/DECISIONS.md M9 dan M10.
Pekerjaan
- Infrastruktur:
wrangler.jsonc(binding DB/ASSETS/RATE_LIMIT_KV/BUCKET, Cron Trigger harian, enve2edengan R2 remote),worker.ts(custom worker OpenNext +scheduledpembersih),open-next.config.ts,migrations/0001_init.sql,r2-cors.json,.dev.vars.example. - Modul pustaka baru:
src/lib/cloudflare.ts(seamgetBindings, null → 503),src/lib/db/{rows,strips,relays,events}.ts(prepared statement D1, tiga gerbang atomikmeta.changes),src/lib/r2/{storage,presign}.ts(bucket.head, aws4fetch presigned PUT/GET),src/lib/rateLimit.ts(kuota strips/relays tetap di D1, anggaran events di KV jendela tetap),src/lib/purge.ts. - Cutover 10 rute
src/app/api/**: kontrak unggah berubah dari{ path, token }ke{ path, url }(presigned PUT); verifikasi berkas lewatbucket.head. - Halaman RSC
/s/[id]dan/r/[id]: URL presigned GET, unduhan lewatresponse-content-dispositionyang ikut ditandatangani. - Klien
features/share/api.tsdanfeatures/relay/api.ts: PUT langsung ke URL presigned,@supabase/storage-jsdilepas. -
next.config.ts: origin CSP dariR2_ACCOUNT_ID;package.json: skrippreview,preview:e2e,deploy,cf-typegen,test:e2e:cf. - Tambalan kecil
scripts/patch-opennext.mjs(dijalankanpostinstalldan tiap skrip build Cloudflare) untuk celah upstream@opennextjs/cloudflare1.20.10: manifestpreview-props.jsonmilik Next 16.4 belum masuk daftar manifest yang disisipkan ke bundel Worker (tanpa ini semua rute menjawab 500 denganUnexpected loadManifest(...)). Alasan dan syarat penghapusannya didocs/DECISIONS.mdM10. - Uji:
tests/db/harness.ts(binding D1/R2/KV sungguhan lewatwrangler getPlatformProxy),tests/db/{strips,relays,events}.test.tsmenggantikan uji klien Supabase palsu,tests/client-bundle-secrecy.test.tsdiperbarui, gerbang e2eCLOUDFLARE_E2E,playwright.cf.config.ts. - Dokumen: ARCHITECTURE, PRD §12,
docs/DECISIONS.mdM10. - Penghapusan:
vercel.json,supabase/,src/lib/supabase/, dependensi@supabase/*. - Pembongkaran proyek Supabase dan kaitan Vercel di dasbor. (dikonfirmasi pengguna 10 Oktober 2026: semua kotak sudah dicentang;
.env.localkini hanya memuatR2_ACCOUNT_IDkarena kata sandi Supabase-nya sudah tidak berguna)
Butuh dari pengguna. Login Cloudflare (npx wrangler login), token API R2 Object Read & Write untuk bucket snapme, dan pembongkaran proyek Supabase/Vercel setelah semuanya hijau. (Ketiganya selesai: login dan pembuatan sumber daya dijalankan di milestone ini, token R2 diisi pengguna ke .dev.vars, dan pembongkaran Supabase/Vercel dikonfirmasi pengguna 10 Oktober 2026.)
Selesai bila
npm run lint,npm run typecheck,npm test, dannpm run buildhijau. (terpenuhi: lint dan typecheck hijau;npm test23 berkas dan 179 uji hijau, termasuk 63 uji basis data baru ditests/db/;npm run buildhijau danopennextjs-cloudflare buildmenghasilkan.open-next/worker.js. Catatan:cacheComponents/PPR besertahtmlLimitedBotsdilepas selama milestone ini karena runtime Worker menolak mesin Cache Components di bawah permintaan bersamaan, rincian dan buktinya didocs/DECISIONS.mdM10)- Uji D1 berlari di atas SQLite sungguhan: kasus dari uji lama dipertahankan, termasuk dua klaim serentak → 200/409 dan rollback batas laju. (terpenuhi:
tests/db/strips.test.ts23 kasus,tests/db/relays.test.ts30 kasus,tests/db/events.test.ts10 kasus, semua di atas D1/R2/KV miniflare lewatwrangler getPlatformProxy; dua klaim serentak menghasilkan tepat satu 200 dan satu 409, dan rollback baris ke-11 batas laju diuji) - Putaran pratinjau menembus R2 sungguhan: buat strip → PUT presigned → complete → buka
/s/{id}→ hapus; relay 201 + klaim serentak;POST /api/eventssampai 429; purge 401/200. (terpenuhi: 31 pemeriksaan lulus diwrangler dev --env e2edengan bucket R2 sungguhan, termasuk tautan unduh ber-response-content-dispositionyang isinya bita identik, penghapusan objek, penuntasan estafet dua langkah, 25 batch peristiwa lalu 429, purge 401/200, dan Cron Trigger lokal; tabelnya didocs/DECISIONS.mdM10) npx playwright testdannpm run test:e2e:cfhijau. (terpenuhi: suite utama terhadap dev server 66 lolos, 8 dilewati, 0 gagal; suite ber-binding terhadap pratinjau Cloudflare 30 lolos, 3 dilewati, 0 gagal. Tiga yang dilewati adalah jalur "server tanpa binding" yang memang tidak berlaku saat binding ada. Tiga temuan yang diperbaiki di sepanjang jalur ini dicatat didocs/DECISIONS.mdM10: PPR/Cache Components dilepas, helper uji dipersempit ke/api/**, danfetchSlotShotsmemakaicache: "no-store")npm run deployberhasil dan URL produksi menjawab;npx wrangler d1 execute snapme-db --remote --command "select count(*) from strips"menunjukkan baris uji; cron harian terlihat di dasbor. (terpenuhi: Workersnapmeter-deploy dihttps://snapme.ikhsandyy-dev.workers.dev(Version ID26c7f70b-1280-4066-aaca-826f9e8f7c0c), triggernya terdaftar saat deploy denganschedule: 0 3 * * *; baris D1 remotestrips3,relays1,events1; 14 dari 15 pemeriksaan produksi lulus dan satu-satunya kegagalan adalah kesalahan skrip verifikasi, lalu bukti bit-identik diambil ulang dan lulus. Tabelnya didocs/DECISIONS.mdM10. Cron harian di dasbor dikonfirmasi pengguna 16 Oktober 2026, dan deploy ulang pada tanggal yang sama menghasilkan Version ID2dc38bdb-25ce-4016-a0ad-34423c94cbf9denganGET /200)- Median
npm run perf:lcpterhadap pratinjau dicatat didocs/DECISIONS.mdM10. (terpenuhi: median LCP 3,745 detik dari lima jalankan terhadapwrangler dev, pembanding M9 3,17 detik dinext start; FCP 1,11 detik, TBT 44 ms, CLS 0, skor 0,87)
Pembongkaran backend Cloudflare (18 Oktober 2026, di luar daftar milestone)
Keputusan pengguna: seluruh backend Cloudflare dicabut dari repo (Workers/OpenNext, D1, KV, R2,
wrangler). snapme kini aplikasi klien murni: alur booth berjalan penuh secara lokal tanpa server,
tidak ada unggahan ke mana pun, dan tidak ada env yang dibutuhkan build. Pekerjaan ini bukan
milestone dan tidak punya kotak centang; catatan keputusan, daftar yang dihapus, dan konsekuensinya
ada di docs/DECISIONS.md dan CHANGELOG (keduanya bertanggal sama).
Yang berubah pada repo
- Seluruh sisi server dan penopangnya dihapus:
src/server/,src/app/api/cron/,worker.ts,open-next.config.ts,wrangler.jsonc,playwright.cf.config.ts,infra/,migrations/,scripts/cf-typegen.mjs,scripts/patch-opennext.mjs,tests/support/,tests/app/api/,tests/server/, komponenRelayJoinView,RelayCompleteView,RelayCancel,relay/bootstrap.ts,StripOwnerActions,LiveStripVideo - Dependensi
@opennextjs/cloudflare,aws4fetch,wrangler,lighthousedan skripcf:patch,preview,preview:e2e,deploy,cf-typegen,test:e2e:cf,perf:lcpdilepas; berkas env.env.exampledan.dev.vars.example(dan salinan lokalnya yang berisi kredensial R2) dihapus - Sembilan rute
/apimenjadi stub 503 "belum tersedia";/s/[id]dan/r/[id]selalu 404 dengannoindex; CSP tanpa host luar;postinstalltinggalcopy-mediapipe-wasm.mjs
Konsekuensi pada dokumen ini
- Entri M6 sampai M10 di atas menggambarkan pekerjaan yang sudah selesai pada waktunya, tetapi infrastrukturnya (link strip, unggahan, estafet ber-server, analitik tersimpan, deploy Workers) sudah tidak ada lagi di repo. Entri itu dipertahankan apa adanya sebagai sejarah; kondisi kini dideskripsikan README, ARCHITECTURE, dan PRD.
- Butir "Selesai bila" yang menyebut
npm run test:e2e:cf,npm run perf:lcp, atau deploy produksi merujuk perintah yang sudah dilepas; jalannya tercatat didocs/DECISIONS.md. - Kriteria "QR pada cetakan asli terbaca" (M9) kini tertunda: QR sungguhan butuh link yang bisa dibuat, dan pembuatan link selalu menjawab "belum tersedia".