admin-mvp-1-ia-handover.html · Stack: React 19 + TanStack Start (SPA) · shadcn amber b1MJ9vRwY6
| Iter scope | Admin/ops backoffice refactor (greenfield rebuild) |
| Ship target | 2026-06-30 (5-week sprint) |
| Hard deadline driver | Tahun Ajaran Jul 2026 enrollment |
| Stack core (locked 2026-05-26) | React 19 + TanStack Start + shadcn/ui + Tailwind v4 · TanStack Form/Query/Router/Table · Cloudflare Workers (TS) · Hono + @hono/zod-openapi · Better Auth + Google OAuth + Negation ACL RBAC · Neon Postgres 18 + CF Hyperdrive · Drizzle ORM · Cloudflare R2 (presigned URL, private ACL) · SigNoz · Paraglide JS (BI iter-1, EN iter-2) · Bun Workspaces monorepo |
| Audience | Eng partner (Engineering Director) — Ralph loop Claude Code execution |
| Doc version | v1.3 (2026-05-25) |
XPrivate = Indonesian les-privat (private tutoring) marketplace. Operational platform existing dengan 4 sub-systems:
xp-backend — Spring Boot 3.1.10, Java 17, PostgreSQL 15xp-app — Angular 20 PWA buat siswa + tutorxp-backoffice — Angular 20 + Clarity v17 admin panel buat operatorxp-landingpage — Next.js 15 marketing + WhatsApp lead captureCustomer base: orangtua + siswa (SD - Mahasiswa - Profesional), tutor (independent contractor, golongan tier 1-4 + non-gol). Postpaid monthly billing. Sesi di rumah siswa atau online (Zoom manual). Indonesia Jakarta-region primarily.
Existing platform punya tech debt yang block fitur baru + ops scale:
/schedule/cancel ≠ terminate (re-queue ke SUB), /schedule/start no state validation (callable any status), 3 asymmetric /confirm/* endpoints behave significantly differentregistForm) wired ke HR email only, despite full MailTemplate inframobile:Xpedu123)Hard deadline driver: Tahun Ajaran Jul 2026 enrollment. Pricing matrix wajib aktif sebelum siswa daftar ulang TA baru. Selip = kehilangan enrollment cycle.
Decision lock: greenfield rebuild full, BUKAN selective refactor. Legacy 4 repos di-deprecate. Data migration deferred (fresh schema v2, bulk-import reference data only — Tutor, Siswa, Subject, Tingkat, Config — transactional history archive read-only).
Iter 1 = admin/ops backoffice ONLY. Customer-facing (siswa/tutor self-service) defer iter berikutnya. Reasoning:
useMemo/useCallback boilerplate)Engineering implementation micro-decisions (ORM, routing, validation lib, monorepo tool, repo structure, deployment pipeline, testing strategy) = partner authority (see Section 3).
Multi-voice council review (Architect + Skeptic + Pragmatist + Critic premortem + Forecaster + Red-Teamer) recommended Option (a) — industry standard zero pay + 3-strike access sanction (mirror Italki / Preply / Cambly pattern).
Founder override pilih Option (b) — zero pay + additional 1-sesi denda graduated (100%/50%/25% berdasar lead time). Rationale:
Full council voices + decision log: companion file 2026-05-25-xprivate-tutor-cancel-policy-v1.md (Downloads). Wajib baca kalo lu mau understand WHY policy keras ini chosen against council recommendation.
Legacy 4 sub-repos tetap accessible buat reference behavior current. Lu udah ada akses (per founder confirm 2026-05-25):
| Repo | Stack | Purpose in iter 1 |
|---|---|---|
xp-backend | Spring Boot 3.1.10 | Business logic source-of-truth current. Reference saat butuh "behavior current apa" tapi JANGAN port logic langsung — rewrite cleanly di TS Workers. |
xp-app | Angular 20 PWA | Consumer UX reference. Iter 1 admin-only = gak di-rebuild, tetap legacy. Iter 2 customer-facing = rewrite React + TanStack Start (Capacitor SPA mode for mobile). |
xp-backoffice | Angular 20 Clarity v17 | Admin UX reference. Total rewrite ke React 19 + TanStack Start + shadcn/ui + Tailwind v4 di iter 1. |
xp-landingpage | Next.js 15 | Marketing site. Keep separate, gak touch iter 1. |
Detailed code-level archaeology available on-demand: file 2026-05-23-business-flow-overview.md (~51KB, 600 lines). Cover 9 domain modules, endpoint inventory, state machine current behavior, file storage pipeline, security gaps. Kontak founder kalo lu butuh reference deep current behavior selama rebuild.
Legal entity status (penting buat finance + compliance):
Legal context cancellation policy:
Tutor relationship: independent contractor (perjanjian kerja sama, NOT UU Ketenagakerjaan employment). Contract clause WAJIB explicit untuk deduction enforce. Founder schedule legal counsel review (~1-2h consultation, budget Rp 1-3M, target Jun week 2) sebelum tutor re-sign.
Cultural context: tutor relationship lebih personal dari transactional di Indonesia formal tutoring. Strict policy bisa lose tutor faster. Goodwill belasungkawa budget (off-system, finance authority) untuk severe FM cases (keluarga inti meninggal, kecelakaan parah).
Founder originally pure PM role (75% product, 25% product-eng). Untuk meet hard deadline 2026-06-30, founder commit full-time eng + PM (~40h/week dev + 5-10h/week PM decisions) selama sprint. Combined dengan partner part-time (~20h/week) + AI augment = ~100-130 productive hours/week capacity. 5-week intensive sprint sustainable, beyond risky.
Sebelum full v2 rollout (post 2026-06-30 ship), founder pilot Option (b) cancel policy ke 5 candidate tutors. Re-onboarding conversation + Option (b) policy explanation + observe reaction. Kill criterion: ≥2 refuse re-sign citing cancel policy → revisit Option (b) → (a). Pilot target: Jun week 3-4. Founder owns.
| Item | Status |
|---|---|
| Spec design v1.3 (brainstorm + reviewer + RAT) | ✓ Done |
| Pricing matrix RAT (operator role-play, 5D keep) | ✓ Done |
| Council review cancel policy | ✓ Done |
| Multi-agent review (product + feature concept reviewer) | ✓ Done |
| Stack lock (React 19 + TanStack Start + shadcn/ui + CF Workers + Neon 18 + R2 + SigNoz, full list) | ✓ Done 2026-05-26 (revised per partner alignment) |
| Data migration strategy lock | ✓ Done |
| Existing repo access partner | ✓ Done |
| Information Architecture + design handover | ⏳ Coming |
| Jira tickets w/ detailed acceptance criteria | ⏳ Coming |
| Legal counsel cancel policy review | ⏳ Founder, Jun wk2 |
| Pre-launch tutor pilot 5 tutors | ⏳ Founder, Jun wk3-4 |
| Partner Sprint 0 (repo bootstrap + infra) | ⏳ Partner, before 2026-05-26 |
Companion docs di same Downloads folder:
admin-mvp-1-design-plan.md — clean markdown same content, Ralph-loop feedable ke Claude Code lu2026-05-25-xprivate-tutor-cancel-policy-v1.md — cancel policy decision log w/ council voices preserved (READ kalo butuh understand WHY Option b)On-demand reference (kontak founder kalo butuh):
XPrivate adalah Indonesian private tutoring (les-privat) marketplace. Existing platform (Spring Boot 3.1.10 + Angular 20 + Next.js + PostgreSQL) di-deprecate. v2 = full greenfield rebuild.
Iter 1 fokus: admin/ops backoffice ONLY. Customer-facing (siswa/tutor self-service signup, payment gateway, auto-notif) defer ke iter berikutnya.
| Skip | Rationale | Mitigation |
|---|---|---|
| 2FA admin (TOTP) | Rely on Google OAuth login w/ admin's Google account 2FA enabled | Policy/training enforce admin Google account 2FA on. Add to admin onboarding checklist. |
| Login audit + account lockout | Defer iter 1.5 | Mitigate: rate-limit at Cloudflare layer sebagai cheap workaround. |
| Indonesia invoice format (PPN, kuitansi) | Skip — belum berbadan hukum, belum PKP | Simple invoice format OK. Add formal compliance saat BH + PKP. |
| PDP UU 27/2022 formal compliance | Skip detailed — basic consent capture only in admin form | Add formal compliance + DSAR endpoint iter 2 saat customer-facing. |
| Detailed PPh withholding | Iter 1 = placeholder field only, handle deduction off-system manual | Formalize at finance system level + iter 2 honor breakdown. |
~/Documents/Obsidian Vault/Decisions/2026-05-26-xprivate-admin-mvp-architecture-alignment.md. Partner architecture page: xp-mvp-architecture.pages.dev. Partner-discussion doc: ~/Workspaces/XPrivate/handover/2026-05-26-partner-discussion-rbac-cron-invite.md (v3).
| Layer | Pick |
|---|---|
| Frontend framework | React 19 + TanStack Start (Vite-based fullstack React, CF Workers via Nitro adapter) |
| UI components | shadcn/ui (radix variant) + Tailwind CSS v4 |
| Theme | Light mode only iter-1 (dark mode defer iter-2 if demand surface) |
| Form library | TanStack Form + Zod standard-schema validator |
| Data fetching | TanStack Query (server-side pagination for schedule/audit/billing/payouts/students/tutors; client-side for bounded master data + pricing matrix per-subject) |
| Routing | TanStack Router |
| Tables | TanStack Table |
| i18n | Paraglide JS (BI iter-1, EN iter-2) — all UI strings via m.session_approve() style |
| Auth strategy | Better Auth + Google OAuth + Workspace domain restrict (@xprivate.education). Effective MFA via Google account 2FA |
| RBAC model | Permission-list-based + Negation ACL resolution (union → dedupe → negation, cached in WhoAmI). 3 preset role bundles hardcoded iter-1 (ADMIN superuser + OPS + FINANCE) |
| Storage ACL | Full private R2 + S3 presigned URL iter-1 (signed URL endpoint, 15-min expiry, per-photo-type auth check matrix). PDP UU 27/2022 compliance step |
| Naming conventions | snake_case (data) + camelCase (code) + kebab-case (files). No translate layer (data fields snake_case end-to-end) |
| Cron timezone | WIB (Asia/Jakarta UTC+7) for all scheduled jobs |
| Permission key format | <resource>:<action> lowercase, wildcard *, negation ! prefix, resource singular |
| Layer | Pick |
|---|---|
| Backend runtime | Cloudflare Workers (TypeScript) + Wrangler |
| API layer | Hono /api/* mounted via Nitro adapter on TanStack Start backend |
| API spec | @hono/zod-openapi auto-generated OpenAPI from Zod schemas |
| Database | Neon Postgres 18 + CF Hyperdrive (edge-cached connection pool) |
| ORM | Drizzle ORM + Drizzle Kit (migrations + schema management) |
| UUID strategy | uuidv7() PG18 native |
| Validation | Zod snake_case DTOs |
| Object storage | Cloudflare R2 + S3 presigned URL (single source of truth, no public ACL) |
| Observability | SigNoz OTEL telemetry-js |
| Unit tests | Vitest in CI |
| Integration tests | Vitest + Manual (MVP) + Neon Branch for test DB |
| E2E tests | Playwright |
| CI/CD | Cloudflare Workers CI |
| Monorepo tooling | Bun Workspaces (NOT runtime — Wrangler runtime CF Workers, Bun for dev/build only) |
| Repo structure | apps/web (TanStack Start + Hono) · apps/cron-<task-name> (CF Cron Workers) · packages/db (Drizzle schema + migrations + client) · packages/service (business logic + Zod) · packages/auth (RBAC resolution + middleware) |
@typescript-eslint/naming-convention for snake_case data props)| Term | Meaning |
|---|---|
| Sesi | Unit pengajaran (1 lesson, default 60-90 menit, default 1:1) |
| Tingkat | Tingkat pendidikan siswa (SD 1-6, SMP 7-9, SMA 10-12, Mahasiswa, Umum, Profesional) |
| Segmentasi | Tier layanan (REGULER, REGULER_PLUS, INTERNASIONAL) |
| Kategori | Grouping subject (REGULER_KELAS, PROGRAM_KHUSUS, SERTIFIKASI, BAHASA_ASING, MUSIK, KOMPUTER, ...) |
| Subject | Mata pelajaran konkret (e.g. "Bahasa Mandarin", "Piano", "CPNS") |
| SubjectLevel | Optional child per Subject (e.g. HSK 1-6 under Mandarin, Grade 1-8 under Piano) |
| Golongan | Tier tutor (GOL_1, GOL_2, GOL_3, GOL_4, NON_GOLONGAN; 1 tertinggi) |
| Tahun Ajaran | Jul-Jun (e.g. 2026/2027 = Jul 2026 - Jun 2027) |
| Tagihan | Student-side invoice per bulan (postpaid) |
| Honorarium | Tutor-side payout per bulan (internal — siswa never sees) |
| Slot | Available time window dari tutor |
| State | Meaning | Trigger by |
|---|---|---|
REQUESTED | Sesi diminta, nunggu admin approve | Auto on create |
APPROVED | Admin approve, slot di-lock, snapshot pricing config | Admin only |
REJECTED | Admin tolak (terminal) | Admin only |
IN_PROGRESS | Sesi mulai | Tutor (Start) |
COMPLETED | Sesi selesai + materi confirmed | Tutor confirm + admin validate |
CANCELLED | Dibatalin post-APPROVED, w/ punishment (terminal) | Siswa/tutor/admin |
RESCHEDULED | Old sesi closed, new linked sesi created | Siswa/tutor/admin |
Subject × SubjectLevel? × Tingkat × Segmentasi × Golongan. Atomic clone+bump UI + drill-down navigation mandatory (per RAT 2026-05-25 finding).
Pricing matrix di-snapshot ke schedule.pricing_matrix_id_student + schedule.pricing_matrix_id_tutor saat sesi APPROVED. Schedule immune dari pricing change kemudian. Tahun ajaran transition = bulk-clone + bump, atomic operation (1 click).
| Lead time | Denda | Strike |
|---|---|---|
| ≤ 1 jam | 100% of 1 sesi cost | +1 |
| ≤ 2 jam | 50% | +1 |
| ≤ 4 jam | 25% | +1 |
| > 4 jam | 0% (no penalty) | 0 |
| Lead time | Charge siswa | Honor tutor |
|---|---|---|
| < 1h | 100% | 100% (no transport) |
| < 2h | 50% | 50% |
| < 4h | 25% | 25% |
| ≥ 4h | 0 | 0 |
| Epic | Outcome | Sprint |
|---|---|---|
| M1 — User Management (Admin) | Operator login (Google OAuth + Workspace restrict) + CRUD admin + role/privilege | Sprint 1 |
| A1 — Audit Trail | Immutable audit middleware + 7y retention + entity search | Sprint 1 |
| M2 — Master Data Management | Tutor, Siswa, Subject+Level, Tingkat, Segmentasi, Kategori, Pricing Matrix 5D, Cancellation Policy Editor, Config, audit, validation | Sprint 2 (2wk) |
| M3 — Schedule Management | State machine v2 + approval queue + cancel calc engine + FM workflow + substitute matching + reschedule + materi log + calendar view + conflict detection + no-show + bulk reschedule | Sprint 3 |
| M4 — Reporting | Student progress + tagihan siswa + honorarium tutor + bulk mark-paid + outstanding aging + materi visibility + reminder broadcast + reconciliation export | Sprint 4 |
| M6 — HR Discipline + Termination | Strike escalation + suspend + terminate + final settlement + access revoke + archive + reactivation | Sprint 4 |
| M7 — Finance Workflow | Refund/credit note + period close + outstanding aging + PPh placeholder + honor payment slip + backup/DR | Sprint 4 |
| A2 — Notification Trigger | Manual welcome email trigger dari M1/M2 forms | Sprint 4 |
@xprivate.education)has_levels flagactor, action, entity_type, entity_id, before_state, after_state, timestamp, ipwelcome baru)Refund / credit note flow:
Financial period close:
Outstanding aging integration: reuse M4 dashboard + bad debt write-off flow (senior approval).
Reconciliation: export template (Excel finance-familiar) + adjustment audit trail.
PPh withholding placeholder: pph_withheld_amount field per honor batch, operator input manual.
Honor payment slip: tutor receive slip w/ breakdown (base + transport + extra-time + adjustments + PPh). Export PDF per tutor per month.
Backup + DR strategy: daily backup procedure + restore drill cadence + retention policy. RTO/RPO targets + implementation: partner authority.
1. Admin create sesi → REQUESTED
2. Admin review approval queue → APPROVED (or REJECTED)
3. Tutor mark Start → IN_PROGRESS (validation: only from APPROVED)
4. Tutor mark attendance + materi + foto report
5. Admin validate completion → COMPLETED (2-step)
6. Settlement auto-created (UNPAID, snapshot pricing dari APPROVED config)
7. Monthly billing cycle (M4):
- Tagihan siswa generated
- Siswa bayar offline, upload bukti
- Operator mark Settlement PAID (bulk capable)
8. Monthly honorarium cycle (M4):
- Honorarium tutor generated
- Finance batch transfer offline
- Operator mark SettlementTeacher PAID (bulk capable)
Tutor claim FM (via WhatsApp ke admin iter 1)
↓
Admin entry claim di backoffice + evidence
↓
L1 Operator review → APPROVE (no penalty) atau REJECT (normal escalate)
↓ (if REJECT)
Tutor banding dalam 14d window
↓
L2 Admin review → UPHOLD (final) atau OVERTURN (refund penalty)
↓
Audit log all events
Optional comp_notes field — finance off-system bayar belasungkawa
Tutor X cancel via WhatsApp → admin info
↓
Admin open schedule detail X → klik [Cari Sub]
↓
List candidates sorted by distance ascending:
Filter: subject match + slot bebas + status ACTIVE
Display: nama, distance km, golongan, last_active, [Pilih]
↓
Admin pilih candidate Z → dispatch via WhatsApp manual
↓
Z accept via WhatsApp → admin update sesi assignment ke Z
↓
Sesi jalan dengan Z
// Example syntax (Drizzle illustration — partner pick ORM)
const subject = pgTable('subject', {
id: serial('id').primaryKey(),
nama: text('nama').notNull(),
kategoriId: integer('kategori_id').references(() => kategori.id),
hasLevels: boolean('has_levels').default(false),
deskripsi: text('deskripsi'),
sortOrder: integer('sort_order').default(0),
status: text('status').default('ACTIVE'),
});
const subjectLevel = pgTable('subject_level', {
id: serial('id').primaryKey(),
subjectId: integer('subject_id').references(() => subject.id).notNull(),
nama: text('nama').notNull(),
kode: text('kode').notNull(),
sortOrder: integer('sort_order').default(0),
status: text('status').default('ACTIVE'),
});
const pricingMatrix = pgTable('pricing_matrix', {
id: serial('id').primaryKey(),
subjectId: integer('subject_id').references(() => subject.id).notNull(),
subjectLevelId: integer('subject_level_id').references(() => subjectLevel.id),
tingkatId: integer('tingkat_id').references(() => tingkat.id).notNull(),
segmentasiId: integer('segmentasi_id').references(() => segmentasi.id).notNull(),
audience: text('audience').notNull(), // STUDENT | TUTOR
golonganId: integer('golongan_id').references(() => golongan.id),
amount: numeric('amount', { precision: 20, scale: 4 }).notNull(),
effectiveFrom: date('effective_from').notNull(),
effectiveUntil: date('effective_until'),
status: text('status').default('ACTIVE'),
});
const schedule = pgTable('schedule', {
id: serial('id').primaryKey(),
status: text('status').notNull(), // 7 states
subjectId: integer('subject_id').references(() => subject.id).notNull(),
subjectLevelId: integer('subject_level_id').references(() => subjectLevel.id),
tutorId: integer('tutor_id').references(() => tutor.id).notNull(),
siswaId: integer('siswa_id').references(() => siswa.id).notNull(),
sesiDateTime: timestamp('sesi_date_time').notNull(),
sesiMode: text('sesi_mode').notNull(),
// Pricing snapshot (immune from later changes)
pricingMatrixIdStudent: integer('pricing_matrix_id_student').references(() => pricingMatrix.id),
pricingMatrixIdTutor: integer('pricing_matrix_id_tutor').references(() => pricingMatrix.id),
amountStudent: numeric('amount_student'),
amountTutor: numeric('amount_tutor'),
transportFee: numeric('transport_fee'),
// Cancellation policy snapshot
cancellationPolicyConfigId: integer('cancellation_policy_config_id'),
// Lifecycle
createdAt: timestamp('created_at').defaultNow(),
approvedAt: timestamp('approved_at'),
startedAt: timestamp('started_at'),
completedAt: timestamp('completed_at'),
cancelledAt: timestamp('cancelled_at'),
cancelReason: text('cancel_reason'),
cancelInitiator: text('cancel_initiator'),
cancelLeadTimeHours: numeric('cancel_lead_time_hours'),
cancelDendaAmount: numeric('cancel_denda_amount'),
cancelDendaAmountOverride: numeric('cancel_denda_amount_override'),
// Linked
rescheduledToScheduleId: integer('rescheduled_to_schedule_id'),
});
const cancellationPolicyConfig = pgTable('cancellation_policy_config', {
id: serial('id').primaryKey(),
policyType: text('policy_type').notNull(), // TUTOR_CANCEL | SISWA_CANCEL
effectiveFrom: date('effective_from').notNull(),
effectiveUntil: date('effective_until'), // NULLABLE = current
status: text('status').default('ACTIVE'),
notes: text('notes'),
});
const cancellationBracket = pgTable('cancellation_bracket', {
id: serial('id').primaryKey(),
configId: integer('config_id').references(() => cancellationPolicyConfig.id).notNull(),
sortOrder: integer('sort_order').notNull(),
thresholdHours: numeric('threshold_hours', { precision: 5, scale: 2 }).notNull(),
chargePctSiswa: numeric('charge_pct_siswa', { precision: 5, scale: 2 }), // SISWA_CANCEL
honorPctTutor: numeric('honor_pct_tutor', { precision: 5, scale: 2 }), // SISWA_CANCEL
substitutionDendaPct: numeric('substitution_denda_pct', { precision: 5, scale: 2 }), // TUTOR_CANCEL
basePenaltyEnabled: boolean('base_penalty_enabled').default(true),
strikeAdded: integer('strike_added').default(0),
notes: text('notes'),
});
const forceMajeureClaim = pgTable('force_majeure_claim', {
id: serial('id').primaryKey(),
scheduleId: integer('schedule_id').references(() => schedule.id).notNull(),
tutorId: integer('tutor_id').references(() => tutor.id).notNull(),
category: text('category').notNull(), // SAKIT | DUKA | DARURAT | BENCANA | LAINNYA
reasonText: text('reason_text'),
status: text('status').notNull(), // PENDING_L1 | APPROVED_L1 | REJECTED_L1 | PENDING_L2 | UPHELD | OVERTURNED
compNotes: text('comp_notes'), // optional, operator catat info comp manual
createdAt: timestamp('created_at').defaultNow(),
});
const forceMajeureReview = pgTable('force_majeure_review', {
id: serial('id').primaryKey(),
claimId: integer('claim_id').references(() => forceMajeureClaim.id).notNull(),
reviewerUserId: integer('reviewer_user_id').notNull(),
reviewLevel: text('review_level').notNull(), // L1 | L2
decision: text('decision').notNull(), // APPROVE | REJECT | UPHOLD | OVERTURN
reasonText: text('reason_text'),
createdAt: timestamp('created_at').defaultNow(),
});
const forceMajeureAppeal = pgTable('force_majeure_appeal', {
id: serial('id').primaryKey(),
claimId: integer('claim_id').references(() => forceMajeureClaim.id).notNull(),
tutorId: integer('tutor_id').references(() => tutor.id).notNull(),
additionalReason: text('additional_reason'),
submittedAt: timestamp('submitted_at').defaultNow(),
deadline: timestamp('deadline').notNull(), // claim.review.created_at + 14d
});
All editable via admin UI, audit-tracked. Defaults locked, runtime override allowed.
| Key | Default | Notes |
|---|---|---|
tutor_cancel.self_sub_window_hours | 4 | Window di mana tutor bisa cari pengganti sendiri |
tutor_cancel.no_penalty_threshold_hours | 4 | Cancel >X hours = no penalty |
siswa_cancel.no_penalty_threshold_hours | 4 | Same for siswa cancel |
strike_decay_days | 90 | Rolling window untuk strike count |
strike_threshold_terminate | 3 | Strike count untuk terminate kerjasama |
force_majeure.approval_levels | 2 | L1 + L2 banding |
force_majeure.appeal_window_days | 14 | Days tutor bisa appeal post-reject |
transport.bracket_short_km | 20 | < 20km = flat Rp 10k |
transport.bracket_short_amount | 10000 | IDR for short bracket |
transport.bracket_long_amount | 12000 | IDR for >20km bracket |
session.default_duration_minutes | 90 | Default 1.5 jam per sesi |
| Sprint | Scope | Duration | Target |
|---|---|---|---|
| Sprint 1 — Foundation | M1 + A1 | 1 week | 2026-06-01 |
| Sprint 2 — Master Data | M2 (full 5D + atomic clone+bump + drill-down + coverage detector) | 2 weeks | 2026-06-15 |
| Sprint 3 — Schedule Core | M3 (state machine + approval + cancel + FM + sub matching + reschedule + materi + calendar + conflict + no-show + bulk reschedule) | 1.5 weeks | 2026-06-25 |
| Sprint 4 — Reporting + HR + Finance + Polish | M4 + M6 + M7 + A2 + honor slip + hardening | 5 days | 2026-06-30 |
effective_until update on old rows + insert bumped new rows in single op (NOT 2 manual steps)| Item | Status | Owner / Date |
|---|---|---|
| Pricing matrix RAT executed | ✓ Done | 2026-05-25 (kept 5D, Sprint 2 extended) |
| Stack locked (revised 2026-05-26) | ✓ Done | React 19 + TanStack Start + shadcn/ui + Tailwind v4 · CF Workers · Neon Postgres 18 + Hyperdrive · R2 (presigned URL) · SigNoz · TanStack Form/Query/Router/Table · Better Auth + Negation ACL · Drizzle · Hono + @hono/zod-openapi · Paraglide JS · Bun Workspaces monorepo |
| Data migration strategy locked | ✓ Done | Fresh schema + bulk-import reference |
| Legal counsel cancel policy review | ⏳ TBD | Founder, Jun week 2 |
| Pre-launch tutor pilot plan | ⏳ TBD | Founder, Jun week 3-4 |
| Google Workspace OAuth domain restrict | ⏳ Sprint 1 (M1) | Eng partner |
| Mid-sprint health gate calendar block | ⏳ TBD | 2026-06-16 |
Founder gak prescribe folder layout. Partner pick monorepo tool + structure per familiar patterns + AI-augmented workflow.
Partner define per team standards + ops capacity. Founder hanya require:
Founder commits (this doc + companion markdown + IA handover — TBD):
Partner commits (Sprint 0, before 2026-05-26 kickoff):
Sprint 1 kickoff: 2026-05-26 — pair sync on Sprint 1 stories M1 + A1.