Skip to content

Commit 6641919

Browse files
committed
Final blog post draft
1 parent 5c8d455 commit 6641919

9 files changed

Lines changed: 131 additions & 0 deletions
91.8 KB
Loading

assets/img/blog/dashboard.png

208 KB
Loading

assets/img/blog/dish-select.png

212 KB
Loading
122 KB
Loading
165 KB
Loading
81.2 KB
Loading
179 KB
Loading

assets/img/blog/public-menu.png

318 KB
Loading

content/posts/final-thoughts.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
---
2+
title: "MiseOS: What Got Built, What I Learned, and What I'd Do Differently"
3+
date: 2026-06-01
4+
draft: false
5+
description: "The final post in the MiseOS development log. A full account of what was built, the architectural decisions that shaped it, and honest reflections on what I'd change."
6+
tags: ["miseOS", "react", "frontend", "reflection", "architecture"]
7+
categories: ["Project Log"]
8+
series: ["MiseOS Development"]
9+
series_order: 17
10+
---
11+
12+
This is the last post in the MiseOS development log. The project is in production, the exam is next week, and it's time to look back at what actually got built and what I learned building it.
13+
14+
---
15+
16+
## What got built
17+
18+
MiseOS started as a school project and became a system my colleagues use every day in a professional kitchen. That was never the plan. It happened because the domain was real and the problems were real, and solving them properly turned out to be more interesting than building a demo.
19+
20+
The full feature set, as it stands:
21+
22+
**Kitchen staff flows**
23+
- Dish suggestion workflow — propose a dish, track approval status, receive feedback
24+
- Ingredient requests — request specific items with quantity and note, track status
25+
- Weekly menu view — see the current published menu by day and station
26+
- AI menu inspiration — SSE-streamed dish ideas generated by Gemini via the backend
27+
28+
**Admin flows**
29+
- Dashboard with live counters via WebSocket
30+
- Dish suggestion management — approve, reject with feedback, edit
31+
- Dish bank — manage all approved dishes across stations
32+
- Weekly menu editor — build and publish the weekly menu slot by slot
33+
- Ingredient request management — approve, reject, coordinate with kitchen
34+
- Shopping list — AI-generated and normalized from approved ingredient requests
35+
- User management — role and station assignment
36+
- Station and allergen management
37+
- Takeaway offers — create daily offers from published menu dishes *(in progress)*
38+
39+
**Public flows**
40+
- Weekly menu — visible to guests without login
41+
- Takeaway ordering — browse and order today's offers *(in progress)*
42+
43+
Takeaway offers and ordering are still in progress, but the rest of the system is fully functional and in use. The public menu is live on a tablet in the kitchen. Staff log in to submit suggestions and requests. The head chef uses the admin dashboard to manage everything.
44+
45+
{{< gallery >}}
46+
<img src="/img/blog/dashboard.png" class="grid-w25" />
47+
<img src="/img/blog/menu-inspirations.png" class="grid-w25" />
48+
<img src="/img/blog/chef-suggestion.png" class="grid-w25" />
49+
<img src="/img/blog/dish-suggestions.png" class="grid-w25" />
50+
<img src="/img/blog/dish-suggestions-detail.png" class="grid-w25" />
51+
<img src="/img/blog/ingredient-request.png" class="grid-w25" />
52+
<img src="/img/blog/public-menu.png" class="grid-w25" />
53+
<img src="/img/blog/dish-select.png" class="grid-w25" />
54+
{{< /gallery >}}
55+
56+
---
57+
58+
## The decisions that shaped the project
59+
60+
**Feature-based architecture.** Every feature — dishes, menus, suggestions, ingredient requests — owns its own pages, components, services, and utils. Working on dish suggestions means opening `features/suggestions/` and seeing everything relevant in one place. The alternative (all pages together, all services together) forces you to jump between folders to work on a single feature. That friction compounds fast on a project this size.
61+
62+
The decision wasn't planned from the start. It came after two days of building the public menu and looking at a flat `src/components/` folder full of unrelated files. Refactoring to feature-based structure took a few hours. It saved far more than that across the following weeks.
63+
64+
**Minimal global state.** Three React Contexts, nothing more:
65+
66+
- `AuthContext` — JWT session, 401/403 handling, navigation
67+
- `NotificationContext` — global toast messages
68+
- `RealtimeNotificationsContext` — WebSocket-based live counters
69+
70+
Everything else is local component state. The rule was simple: data lives where it's used. Pages own their fetch logic. Components receive props and fire callbacks. Nothing reaches into a sibling's state.
71+
72+
This kept the codebase readable. At any point in the project I could open a page component and understand exactly where its data came from and what could change it.
73+
74+
**CSS Modules over utility-first.** I chose CSS Modules over Tailwind deliberately. The goal was to learn CSS properly and have full control over the design rather than assembling it from utility classes. Global design tokens in `:root` — colours, spacing, radii, typography — meant consistency across all modules without a preprocessor.
75+
76+
The hardest part was discipline. It's tempting to copy-paste styles rather than abstract shared ones. Towards the end I had shared stylesheets like `DashboardCards.module.css` used across all dashboard cards. Getting there required refactoring more than once.
77+
78+
---
79+
80+
## The technically interesting parts
81+
82+
**Realtime with WebSocket.** The dashboard shows live counts of pending suggestions and ingredient requests via a WebSocket connection. The interesting part wasn't the connection — it was deciding what "live" means. The solution was a snapshot model: on connect, the backend sends a complete state snapshot. From there, individual events update specific values. No polling, no full refetch.
83+
84+
**SSE streaming and AI.** The menu inspiration feature uses Gemini AI via the backend and streams the response as SSE — Server-Sent Events, a one-way HTTP stream. The user sees dish ideas appear in real time rather than waiting for a full response. It's technically the most satisfying feature in the app and the one colleagues find most useful.
85+
86+
The shopping list also uses AI — but differently. Approved ingredient requests from multiple cooks are sent to the backend, normalised and aggregated by AI into a clean consolidated list for the head chef to review. No streaming here — it's a single request and response, but the output quality is meaningfully better than a raw list would be.
87+
88+
**CSS print styles.** The shopping list has a print stylesheet — `@media print` — so the head chef can print a clean, formatted list for the kitchen. It strips navigation, colours, and UI chrome and leaves only the relevant content. I only implemented it on the shopping list, but it showed how much CSS can do that has nothing to do with the screen.
89+
90+
---
91+
92+
## Building for real users
93+
94+
The project is in production. That changed how I thought about almost everything.
95+
96+
Colleagues use the system during service. When something doesn't work, they can't do their job. Error messages, loading states, and empty states stopped being edge cases and became part of the product. Every page handles three states: loading, empty, and error. That sounds obvious until you're the one maintaining it under real conditions.
97+
98+
It also made building more interesting. There's something different about getting feedback from someone who actually uses what you built — not a review comment, but a real consequence. I don't know whether they'll keep using MiseOS after the semester ends. But building something with actual stakes made every decision feel more worth making carefully.
99+
100+
---
101+
102+
## What I'd do differently
103+
104+
**TypeScript from the start.** Plain JavaScript worked until it didn't. As services and API response shapes grew, the absence of types became real friction. Tracking down a bug caused by a property name mismatch between what the API returns and what the component expects is the kind of thing TypeScript catches before it runs. Next project starts with TypeScript.
105+
106+
**Structured error handling.** `apiClient.js` handles 401 and 403 globally. Feature-specific errors — 409 conflicts, 400 validation failures — are caught ad hoc in each page component. The result is similar error-handling code written slightly differently in a dozen places. A shared error mapper that translates API error shapes into typed exceptions would have made both the code and the debugging more consistent.
107+
108+
**A test strategy, early.** There are no automated frontend tests. This is the biggest gap in the project. With a production application, the critical flows — login, suggestion creation, menu publishing — deserve integration tests that run on every deploy. I didn't explore this during the semester, but it's clearly the next thing to understand properly.
109+
110+
---
111+
112+
## What didn't make it
113+
114+
Some things were on the list and didn't happen.
115+
116+
**Dark mode.** The design tokens in `:root` are exactly the right foundation for it — swapping a `data-theme` attribute would flip the colour variables. The architecture is ready. I just didn't prioritise it.
117+
118+
**`sessionStorage` vs `localStorage`.** The JWT is stored in `localStorage`, which persists across browser sessions. `sessionStorage` would clear it when the tab closes — a different security tradeoff worth exploring. I thought about it but left it as-is.
119+
120+
**Single-day menu view.** The menu editor shows a full week. During service, staff care about today. A day filter would be a rendering change, not a structural one — the data is already there. It's the one thing I'd add first if I came back to this.
121+
122+
---
123+
124+
## Final thoughts
125+
126+
This frontend ended up being a good capstone for the semester because it forced me to connect concepts that are easy to understand in isolation but harder to get right together: routing and layouts, JWT auth with role-based access, state placement, component responsibility boundaries, consistent error handling, and a UI that reflects real domain logic rather than just displaying data.
127+
The things that clicked most weren't the individual React APIs — it was the decisions around them. Where does state live? Who owns an error? When is a component too big? Those questions don't have answers in the documentation. You find them by building something real and feeling where the friction is.
128+
129+
---
130+
131+
*This is the final post in the MiseOS development log. The full series covers backend development from week 1 through frontend completion — seventeen posts, one semester, one production system.*

0 commit comments

Comments
 (0)