From Web App to CLI: A Case Study in Building a Developer Focus Tool
How user feedback on a simple web prototype led to a complete strategic pivot and a better product for a professional audience.
Like many developers, I often struggle with maintaining deep focus. The modern digital environment is a minefield of distractions, and the cost of a single interruption—the "context switching cost"—can be enormous. For my Design Thinking course at university, I decided to tackle this problem head-on: could I build a system to create a protected space for deep work?
What started as a straightforward web application became a journey in user research, strategic pivoting, and ultimately building a tool that serves its intended audience far better than my original vision. This is the story of how listening to users—especially when they tell you something you don't want to hear—led to a fundamentally better product.
Understanding the User: It's Not Just Me
Before building anything, I needed to validate that this wasn't just my personal problem. I conducted interviews with IT professionals across different roles—software engineers, system administrators, and technical leads. Their feedback confirmed the core pain points and revealed something crucial: this wasn't about productivity apps or time management techniques.
"What we call multitasking is in truth rapid context switching... The expectation to react to emails, chats, or calls in real-time fragments the workday enormously and makes it difficult to get into the 'flow' state essential for complex IT problem-solving."
This insight shaped everything that followed. I wasn't building a generic productivity tool—I was building for professionals who understood the true cost of interruption and valued deep, uninterrupted work above all else.
Prototype 1: The "Sensible" Web App
My initial approach seemed logical: build a web application that users could access from any device. The prototype featured a focus timer, break suggestions, and a "thought capture" modal that allowed users to quickly jot down distracting thoughts without breaking their flow state.
Focus timer with clean, minimal interface
Thought capture modal for quick notes
The implementation was straightforward—a JavaScript timer with local storage for persistence and a clean, distraction-free interface. Here's the core timer logic:
// Focus Timer Implementation
class FocusTimer {
constructor() {
this.duration = 25 * 60; // 25 minutes in seconds
this.timeLeft = this.duration;
this.isRunning = false;
this.interval = null;
}
start() {
if (!this.isRunning) {
this.isRunning = true;
this.interval = setInterval(() => {
this.timeLeft--;
this.updateDisplay();
if (this.timeLeft <= 0) {
this.complete();
}
}, 1000);
}
}
updateDisplay() {
const minutes = Math.floor(this.timeLeft / 60);
const seconds = this.timeLeft % 60;
document.getElementById('timer').textContent =
`${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
complete() {
this.isRunning = false;
clearInterval(this.interval);
this.showBreakSuggestion();
}
}
The web app worked as intended. It was accessible, easy to use, and followed established UX patterns. I was confident this would solve the problem. I was wrong.
The Turning Point: When the Solution is Part of the Problem
User testing revealed a critical flaw I hadn't anticipated. While the app itself was functional, the medium—a web browser—was fundamentally at odds with the goal of maintaining focus.
"As a digital minimalist, the requirement to always have a web browser open—and with it, the one-click possibility of opening YouTube or similar distractions—had a massively negative impact on me."
The feedback was consistent and damning:
- "The design feels generic—it could be any productivity app"
- "There's no pressure to stick to the ritual—I can just close the tab"
- "The browser itself is a source of distraction—bookmarks, tabs, notifications"
- "I need something that feels more intentional, more committed"
This was the moment I realized I had been solving the wrong problem. I wasn't building a tool for "normal users"—I was building for professionals who, like me, valued a distraction-free environment above convenience or accessibility.
The Pivot: Building for Professionals
The user feedback forced me to reconsider everything. Instead of trying to make the web app "better," I needed to ask a fundamental question: what medium would best serve my actual target audience?
I created a weighted decision matrix to evaluate different approaches:
Feature | Weight | Web App | Desktop App | Terminal App |
---|---|---|---|---|
No browser distractions | High | -- | + | ++ |
Professional feel | High | - | + | ++ |
Easy to access | Medium | ++ | + | + |
Cross-platform | Low | ++ | - | ++ |
Total Score | - | -2 | +3 | +7 |
The terminal application won decisively. For my target audience—developers and IT professionals—the command line wasn't a barrier; it was a feature. It signaled intentionality, professionalism, and most importantly, it eliminated the browser as a source of distraction.
Prototype 2: A Tool for Focused Work
I rebuilt the application from scratch using Go and the Bubble Tea framework for terminal user interfaces. The new version maintained all the core functionality while embracing the constraints and advantages of the terminal environment.
Clean terminal interface with focus timer
Thought capture without leaving the terminal
The Go implementation leveraged the language's excellent concurrency features and the Bubble Tea framework's elegant event-driven architecture:
package main
import (
"fmt"
"time"
tea "github.com/charmbracelet/bubbletea"
)
type model struct {
timer time.Duration
isRunning bool
phase string // "focus", "break", "complete"
thoughts []string
currentInput string
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "space":
if m.phase == "focus" {
m.isRunning = !m.isRunning
if m.isRunning {
return m, tea.Tick(time.Second, func(t time.Time) tea.Msg {
return tickMsg{}
})
}
}
case "t":
// Capture thought without breaking focus
return m, tea.Cmd(func() tea.Msg {
return thoughtCaptureMsg{}
})
}
case tickMsg:
if m.isRunning && m.timer > 0 {
m.timer -= time.Second
return m, tea.Tick(time.Second, func(t time.Time) tea.Msg {
return tickMsg{}
})
}
}
return m, nil
}
func (m model) View() string {
if m.phase == "focus" {
return fmt.Sprintf(
"🎯 Focus Session\n\n%s\n\n%s",
formatTime(m.timer),
"Press 't' to capture a thought • Space to pause",
)
}
return ""
}
Key improvements in the terminal version:
- Zero browser distractions: No tabs, bookmarks, or notification temptations
- Professional aesthetic: Feels like a serious tool for serious work
- Keyboard-driven: No mouse required, perfect for developer workflows
- Lightweight: Minimal resource usage, starts instantly
- Intentional usage: Requires deliberate action to start and stop
The response from users was dramatically different. Instead of generic feedback, I received specific, enthusiastic responses about how the tool "felt right" and "matched their workflow."
Lessons Learned from a Strategic Pivot
This project taught me more about product development than any course or book could. The journey from web app to CLI wasn't just a technical pivot—it was a fundamental shift in understanding who I was building for and what they truly valued.
Listen to your users, especially when they tell you something you don't want to hear
The feedback that the web app was "generic" was the most valuable data I received. It forced me to question not just the implementation, but the entire approach. Sometimes the most important insights come disguised as criticism.
Understand your target audience deeply
I initially thought "easy to use for everyone" was a strength, but my real users valued a "distraction-free" environment far more than accessibility. Building for everyone often means building for no one in particular.
Don't be afraid to pivot
Throwing away the first prototype felt like a failure, but it was the necessary step to building a much better product. The sunk cost fallacy is real, but so is the opportunity cost of persisting with the wrong solution.
Medium is part of the message
The choice of platform—web, desktop, or terminal—wasn't just about technical capabilities. It was about communicating values and setting expectations. The terminal app succeeded partly because it signaled seriousness and intentionality.
Most importantly, this project reinforced that good product development isn't about building what you think users need—it's about building what they actually need, even when that's different from what you originally envisioned.
Marwin Zoepfel
Systems Engineer & Technical Writer. I build and explain complex systems, from backend services in Go to bare-metal operating systems. When I'm not coding, I'm writing about the intersection of technology and human behavior.