The Status Field That Grew Three Heads (And How We Fixed It)
Source: Dev.to
The Original Sin
When I first built import/export configs, the status was obvious:
type ImportConfig struct {
Status string `json:"status"` // "draft" | "active" | "paused"
}
Clean. Simple. Done.
Then users started running imports. I needed to know if the last run succeeded or failed:
type ImportConfig struct {
Status string `json:"status"` // "draft" | "active" | "paused"
LastRunStatus string `json:"lastRunStatus"` // "success" | "failed" | "running"
}
Still manageable. Two fields, two concerns.
Then I built the wizard. Users needed to save progress mid‑flow without creating broken configs. Easy fix:
type ImportConfig struct {
Status string `json:"status"`
LastRunStatus string `json:"lastRunStatus"`
IsDraft bool `json:"isDraft"` // wizard in progress
}
Three heads. One monster.
The Combinatorial Nightmare

Here’s the problem with three fields: they create a state matrix.
Status: draft | active | paused | disabled | completed
LastRunStatus: success | failed | running | (empty)
IsDraft: true | false
That’s 5 × 4 × 2 = 40 possible states. Most of them were nonsense. What does status=active, lastRunStatus=running, isDraft=true mean?
The frontend had this gem:
function getDisplayStatus(config: ImportConfig): string {
if (config.isDraft) return 'draft';
if (config.lastRunStatus === 'running') return 'running';
if (config.status === 'paused') return 'paused';
if (config.lastRunStatus === 'failed') return 'failed';
if (config.status === 'active') return 'ready';
return 'draft'; // ¯\_(ツ)_/¯
}
That function was wrong. I just didn’t know which cases were wrong.
The Recognition Moment
I was adding a new feature when I realized I couldn’t answer a basic question:
“Is this import ready to run?”
The answer required checking three fields, understanding their precedence, and hoping I got the logic right.
That’s when I knew: I hadn’t modeled status. I’d accumulated symptoms.
The Fix: One Field to Rule Them All
The refactor was simple in concept:
Before: 3 fields tracking overlapping concerns
After: 1 field with 5 mutually exclusive states
const (
StatusDraft = "draft" // Wizard incomplete, missing required fields
StatusReady = "ready" // Complete config, can be run
StatusRunning = "running" // Currently executing
StatusPaused = "paused" // User paused execution
StatusFailed = "failed" // Last run failed, needs attention
)
type ImportConfig struct {
Status string `json:"status"` // One of the above. That's it.
}
No matrix. No precedence. No guessing.
“Is this import ready to run?” → config.Status == StatusReady
But Wait, What About the Wizard?
The isDraft field existed because wizards need to save partial progress. Removing it meant solving a different problem: where does wizard state live?
Answer: in a DraftData field that only exists when Status == StatusDraft:
type ImportConfig struct {
Status string `json:"status"`
DraftData *DraftData `json:"draftData,omitempty"` // nil unless Status=draft
}
type DraftData struct {
CurrentStep string `json:"currentStep"`
PendingSchema *PendingSchema `json:"pendingSchema,omitempty"`
PendingDataType *PendingDataType `json:"pendingDataType,omitempty"`
}
The key insight: pending resources live in draft data, not in real tables. When you’re mid‑wizard creating a schema, that schema doesn’t exist yet. It’s a pending schema stored in DraftData. Only when you finalize does it become real. No more orphaned schemas from abandoned wizards.
The Damage Report
Fixing this touched:
- 193 files
- 11,786 additions
- 4,179 deletions
- Backend models, services, handlers
- Frontend types, hooks, wizards
- Tests across both
Was it worth it? The getDisplayStatus function is now:
function getDisplayStatus(config: ImportConfig): string {
return config.status;
}
Yes. It was worth it.
Lessons
- State fields multiply. One becomes two becomes three. Each addition feels small. The complexity is combinatorial.
- “What state is this?” should be trivial to answer. If you need a flowchart, you have a modeling problem.
- Pending resources aren’t real resources. Wizard state is draft data, not partially‑created entities.
- Big refactors are just many small changes. 193 files sounds scary. It was really “update Status constant” × 193.
Building Flywheel – data pipelines for startups. If you’ve ever watched a “simple” field grow three heads, I’d love to hear your war stories.