Inspiration
As a student, I kept running into the same three problems:
- I didn't know what to study next — my tasks lived in my head, in chat messages, and on scraps of paper.
- I couldn't hold focus. "Study for two hours" is an intention, not a plan, and my phone always won.
- I had no feedback loop. After a week of studying I genuinely could not tell which subject got my time and which got ignored.
Most study apps solve only one of these. I wanted one small tool that closes the whole loop: plan a task, focus on it with a timer, then watch the data accumulate so the next decision is based on evidence instead of a feeling.
This is my first hackathon. I'm an Electrical Engineering and Automation student, and for me the appeal of this project was that it's small enough to actually finish, but complete enough to force me through the entire process — frontend, backend, testing, and documentation. I didn't want a toy that only worked on my machine. I wanted something I would keep using after the submission deadline, and that turned out to be a much stricter standard than "it runs."
I also came in wanting to build a platform rather than a feature list. My instinct was that each capability — the timer, the tasks, the charts — should be something that could be added or removed without disturbing a core, the way plugins work. What I actually shipped is three working modules, not a plugin system. But I let that instinct shape the foundation: tasks carry a generic subject field rather than a fixed list of school subjects, and a focus record is just a subject plus a duration, which is a shape any future module could write into. Those choices cost nothing to make early and would have been expensive to retrofit.
What it does
FocusStudy has three connected modules:
TASK MANAGEMENT Create tasks with a name, subject, priority, and deadline. Edit, complete, and delete them inline. Overdue tasks are flagged, and completed ones get a strikethrough so progress is visible at a glance.
POMODORO FOCUS TIMER A circular countdown with start / pause / reset. Focus and break durations are adjustable (25/5 by default) and remembered across sessions. You can bind the timer to a specific task, so when a Pomodoro completes the record is automatically tagged with that task's subject. The app warns you before you leave the page mid-session, and today's summary stays correct even if the tab stays open past midnight.
LEARNING DATA VISUALIZATION Summary cards for total minutes, Pomodoros completed, subjects covered, and task completion rate. A bar chart of the last 7 days and a donut chart of time per subject, plus a detail table for reviewing individual sessions.
The whole thing works OFFLINE. Data is written to localStorage first, so the app is fully usable with no backend running at all. When the FastAPI server is reachable, changes sync to it in the background. A network failure degrades silently instead of blocking the UI — because a study tool that breaks when the network does becomes a distraction at exactly the wrong moment.
How I built it
Frontend: Vue 3 (Composition API + <script setup>), Vite, Vue Router,
Element Plus, ECharts wrapped in one reusable BaseChart component.
Backend: Python + FastAPI for routing and Pydantic validation, with all aggregation logic extracted into a separate pure-function module (stats.py) so it can be unit-tested without starting a server.
Storage: deliberately database-free. localStorage on the client, JSON files on the server. A judge can clone the repo and run it in under two minutes without installing MySQL or Postgres. For a single-user study tool, a database would add setup friction and buy nothing.
Testing: 24 pytest cases covering the pure statistics logic and the HTTP layer through FastAPI's TestClient — including dirty-data tolerance (missing fields, None, numeric strings, bool sneaking in as an int subclass), 422 validation, 404 handling, partial-update semantics, and recovery when the JSON data file is corrupted on disk.
Challenges I ran into
The bug that taught me the most was a timezone problem.
My "today's focus time" counter was showing zero between midnight and 8am, and
the minutes were appearing under the previous day. The cause was
new Date().toISOString().slice(0, 10) — I assumed something called
"toISOString" would give me a sensible date, but it returns the UTC date. In
UTC+8, a Pomodoro finished at 00:30 local time was being filed under the day
before. It silently corrupted exactly the data the whole app exists to collect.
I fixed it by building the date string from local timezone components, and made
the same change on the Python side (date.today() instead of UTC).
The second one was architectural: I had only written GET and POST endpoints.
So creating a task synced to the server, but editing, completing, and deleting
tasks only changed localStorage — the server's copy quietly drifted out of
date. I added PUT and DELETE, and made PUT a partial update (only the fields
actually sent get overwritten), so ticking a checkbox sends {"done": true}
without wiping the task's name and subject.
The third was that my test suite showed "9 passed" alongside 15 errors. Reading the traceback all the way to the last line showed the failures were in teardown, not in my assertions — pytest's temp-directory cleanup was hitting a permission error on this machine. Learning to distinguish ERROR from FAILED saved me a lot of time.
What I learned
This was my first hackathon. I'm an Electrical Engineering and Automation student, and before this project my coding experience was mostly coursework — assignments with a fixed specification and a known correct answer. Building FocusStudy was the first time I had to decide for myself what "done" even means.
I should be honest about my level, and I found it useful to borrow someone else's vocabulary for that. I once read an essay that mapped programmer growth onto the cultivation stages of Chinese xianxia novels — Body Tempering, Qi Refining, Foundation Building, Golden Core, Nascent Soul — where each stage is defined not by how much you know but by the kind of question you can answer. It isn't my framework, and I'm not claiming any credit for it, but it gave me a way to locate myself that "beginner" and "not a beginner" never did.
By that scale I would put myself at the end of Body Tempering: I can make code work across a few languages and finish a project, but I can't yet explain why my design choices are the right ones. What this project gave me is the honest experience of standing in that gap. Once FocusStudy had a task list, a timer, and a chart, it worked. It did something useful. But I could not have told you whether my data model would survive growth, or whether my aggregation logic was correct under conditions I hadn't happened to test. That gap — between "it runs" and "it is correct, and I can tell you why" — is the most useful thing I found in this project, and it is what I intend to close next.
Having that scale also turned out to be practically useful. Because I could see where I was on it, I could be deliberate about what to reach for and what to leave alone. I did not try to write a compiler or an operating system out of ambition; I tried to make a small system correct and explainable, and to avoid mistakes I could not yet detect. Deciding what not to attempt felt like the real engineering decision in this project.
The most useful habit I built was routing before searching. When I need to learn something new, I don't start by hunting for a tutorial. I first map out the territory — what the learning path looks like, what the concepts depend on, which parts are load-bearing and which are details I can defer — and only then go looking for the specific document or implementation I need. The course materials in this repository are themselves an example: a nineteen-lesson path, not a pile of links. Searching for a tutorial gives you an answer that fits one person's situation; building the map first means you can tell whether the answer you found is even the right question.
My electrical engineering background also kept showing up in useful ways. In measurement and data acquisition you learn early that a number is meaningless without knowing how and when it was captured. That instinct is exactly why the timezone bug bothered me so much: the app was recording a timestamp, and it was recording it wrong. It also made the "database or not" decision feel obvious. Choosing your storage is like choosing an instrument — the most capable option isn't automatically the right one, and added setup cost is a real cost.
The bug I'm most proud of finding was the UTC date problem. It didn't throw an error. It didn't break the page. It just quietly filed my focus sessions under the wrong day, and I only caught it because I sat down and compared the numbers against what I actually did. That experience changed how I work: I stopped trusting "the page loads and nothing is red in the console" as evidence that something is correct. I wrote 24 tests afterwards, partly to check the logic and partly to make that kind of silent corruption impossible to miss again. Separating the pure aggregation logic into its own module was a direct consequence — I couldn't test the logic while it was tangled up with HTTP routing.
One design decision I'm glad I made early: I kept thinking of FocusStudy as a platform rather than a feature list. My instinct was that everything should be a plugin — the timer, the tasks, and the charts should each be something that could be added or removed without touching a core. I did not come close to building that in full; what I built is three working modules. But letting that idea shape the foundation paid off in small, real ways. Tasks carry a generic "subject" field instead of a hardcoded list of school subjects. Focus records are stored as a subject plus a duration, which is a shape any future module could write into. The API is organized per resource. Adding a fourth module would mean adding endpoints and a route, not redesigning the data.
Finally, I learned that documentation is part of the engineering, not paperwork after it. I rewrote my README from scratch after realizing a reviewer would open the repository cold and have no idea how to run anything. Writing down why I skipped a database and why the backend is optional forced me to check whether those decisions actually held up. Some of them did. The ones that didn't, I fixed or listed honestly under "Known Limitations."
What's next
- Store session start/end timestamps so durations are verifiable, not just asserted
- A timestamp-based timer to eliminate background-throttling drift
- Frontend tests (Vitest + Vue Test Utils)
- JSON export/import so students can back up or move devices
- Weekly/monthly trend views beyond the current 7-day window
- PWA support for genuine offline installation
Built With
- css
- echarts
- element-plus
- fastapi
- github
- html
- javascript
- localstorage
- pydantic
- pytest
- python
- uvicorn
- vite
- vue
- vue-router
Log in or sign up for Devpost to join the conversation.