2026-03-17|6 min read|--xulangedu--devlog--firebase--debugging--solodev

The Night I Broke Everything (On Purpose): Rollback, Re-import, and How XuLang Edu Grew Up

The Night I Broke Everything (On Purpose)

It's past midnight. I just confirmed the same data import for the fourth time tonight.

Not because I kept making mistakes — well, not only because of that — but because I was chasing something important: a system that actually holds together. Teachers linked to classes. Students linked to the right rooms. Attendance records with real timestamps instead of strings that Firebase quietly ignores.

This is the story of one very long session on XuLang Edu — a school management system I'm building for a real tutoring center in Lạng Sơn.


## How We Got Here

A few sessions ago, I wrote about teaching this system to read Excel files. The parser could pull students, classes, and attendance from two types of spreadsheets: a fee statement file and an attendance file. It felt like a milestone.

But there's a difference between data going in and data making sense on the other side.

Tonight I found out just how large that gap was.


## The First Problem: Ghost Data

After confirming the import for February 2026, I navigated to the Classes page. Seventeen rows. No names. No teachers. All marked "Closed."

The Students page showed 300 names — but every single one had status "Withdrawn."

The data was there. Firebase had the documents. But the UI showed nothing useful.

The root cause was embarrassingly simple: the import was writing className to Firestore, but the UI was reading name. It was writing status: undefined, but the component checked for status === 'Active'. It was writing createdAt: new Date().toISOString() — a string — while every Firestore query used orderBy('createdAt', 'desc'), which silently drops documents that don't have a proper Timestamp in that field.

Three different bugs. Same symptom. Everything looks fine until nothing shows up.


## The Rollback Button

This is where I'm glad I built the rollback feature earlier in the session.

One click. A confirmation dialog. Done. Four collections wiped — classes, students, attendance, classsessions — while the staged data (the parsed Excel content sitting in Firestore's subcollection) stays untouched. Then I fix the confirm route, deploy, and re-import.

I did this loop four times tonight.

It sounds tedious. But honestly, having a clean rollback made me less afraid to fix things aggressively. I didn't have to worry about corrupted data accumulating. I could just burn it down and start fresh.


## The Teacher Problem

Once the field name fixes were in, classes showed names. Students showed statuses. Progress.

But teachers were still empty.

The design I'd built was: when you confirm an import, the system reads every teacher name from the attendance files and auto-creates teacher records if they don't exist yet. Teachers at this tutoring center aren't in the Excel files as IDs — they're just names like "Tùng" or "Nguyễn Thanh Dũng" buried in rows that say "GV dạy: Tùng - Xuân - Dũng."

The parser had already extracted this — a structure I called teacherSchedule, which maps each teacher name to the days they teach. The confirm route was supposed to read this and create teachers.

It wasn't working because of a subtle architectural issue: teacherSchedule was being merged at query time in the staged review endpoint, but never actually persisted into the staged documents. So when the confirm route read those staged documents directly, the teacher data wasn't there.

The fix: in the confirm route, also read the classinfos_* documents (from the attendance file) and merge teacherSchedule into the class data before processing. Same logic the review endpoint was already doing — just applied one step earlier.


## Linking It All Together

With teachers being created correctly, the next issue was the relationships.

  • >Classes had studentIds: []
  • >Students had classIds: []
  • >Attendance records had studentId: "" and sessionDate: "2026-02-01" (a string, not a Timestamp)

Each one a separate bug. Each one requiring a rollback-fix-reimport cycle.

The student-class linking was conceptually straightforward: every student record has a className field matching the sheet name of their class. Build a map. Loop. Update both sides.

The attendance Timestamp issue was trickier because I'd missed that Timestamp.fromDate() was needed — and that the attendance write was happening before the class-student mapping was built, so it didn't have access to the resolved IDs yet. Moving the attendance write step to after the linking step fixed it.


## What XuLang Edu Can Do Now

After all those cycles, here's where things stand:

Import flow is solid. Upload Excel files → review staged data with teacher names and schedules → confirm → the system automatically:

  • >Creates teacher records for any new names found
  • >Links students to their classes (both directions)
  • >Creates a ClassSession document for every actual teaching day, with the right teacher assigned based on the schedule
  • >Writes attendance records with proper Firestore Timestamps and real student/class IDs
  • >Gives you a rollback button if anything looks wrong

Multi-teacher scheduling works. Each class can have multiple teachers — one per time slot. The form shows a teacher dropdown next to each schedule row, and warns you in real time if that teacher is already assigned to another class at the same time.

The data is connected. Teachers link to classes. Classes link to students. Attendance links to both. This is the foundation that makes salary calculation and attendance reporting possible.


## What's Still Broken (Or Unbuilt)

Salary calculation currently counts sessions from attendance records — which doesn't account for which teacher actually taught each session. That's the next task: read from classsessions instead, where each document knows exactly which teacher showed up.

The attendance page still shows manual check-in UI rather than pre-populated data from the import. Worth fixing.

And the class form still has hardcoded defaults for subject: 'math' and gradeGroup: 'thcs' because the import doesn't know these values from Excel. That's a problem for a future session — or a future conversation with Dũng about his spreadsheet structure.


## The Unglamorous Part

I've written before about shipping fast with AI. What I don't always capture is how much of the work is just... iteration.

Tonight wasn't about a clever algorithm or a beautiful component. It was about chasing a bug through six layers of a data pipeline, rolling back the database three times, and staying awake long enough to see all 300 student names show up with status "Active" instead of "Withdrawn."

Not glamorous. But it's what building real software actually looks like.

The tutoring center in Lạng Sơn has real students. Real teachers. Real money flowing through these spreadsheets every month. Getting this right matters.

So I stay up until it works.


XuLang Edu is a school management system I'm building for a friend's tutoring center. It's one of several projects I'm working on as a solo designer-turned-developer. If you want to follow the build, the chaos, and the occasional midnight win — you're in the right place.