
The market is saturated with habit trackers, gym logs, and meditation apps. The problem? They are fragmented. Your poor sleep data doesn't affect your workout plan in another app. Your meditation practice lives in a vacuum, disconnected from your stress levels.
We wanted to build UltyMyLife—a unified LifeOS living right where you spend 90% of your time: Telegram. No VPNs, no Notion, no friction. But we hit a wall: standard Telegram Mini Apps often feel like websites from 2010. We needed that "Apple vibe"—premium, dark, with deep shadows and, above all, a tactile feel.
The Synergy Behind the Code
This project is the result of two worlds colliding:
Dmitriy Spirihin (Me) — System Architecture & Full-stack. I came from the world of Unity development. When you’re used to UniRx and game loops, standard web development feels "cardboardy". My mission was to port game-dev architecture into the React stack.
Demian Avolstyny — Product Vision, Design & Vibe Coding. Demian was the guardian of the "vibe." His focus was aesthetics, tactile feedback, and that seamless logic of interaction that makes an app feel like an extension of yourself.
In this article, we’ll dive into how a game-dev mindset helped us build a complex state system with RxJS, why Framer Motion is the secret to a tactile UI, and how we leveraged AI to turn boring logs into actionable, "brutally honest" insights.
The "One More Tracker" Problem
The market is saturated with trackers. A thousand habit trackers, another thousand gym logs, and a hundred meditation apps. The problem? They are fragmented. The data showing I slept poorly has zero impact on my workout plan in another app. Meditation lives in a vacuum, disconnected from my stress levels.
We wanted to create LifeOS — a unified self-management system living where I spend 90% of my time: Telegram. No VPNs, no Notion, no friction.
But there was a catch: standard Telegram WebApps often look like websites from 2010. I wanted an "Apple vibe"—premium, dark, with deep shadows and, most importantly, a tactile feel. Thus, UltyMyLife was born.
1. Interface: Tactility vs. "The Tap"
// Progress calculation logic in HabitCalendar.js
const dayKey = formatDateKey(new Date(cellYear, cellMonth, day));
let percentNum = 0;
if (Object.keys(AppData.habitsByDate).includes(dayKey)) {
const dayHabits = Array.from(Object.values(AppData.habitsByDate[dayKey]));
// Calculate percentage of completed (v > 0) habits
percentNum = dayHabits.length > 0
? Math.round((dayHabits.filter((v) => v > 0).length / dayHabits.length) * 100)
: 0;
}
// Render cell with dynamic color
<div style={{
...styles(theme).cell,
backgroundColor: getProgressColor(percentNum, theme),
border: today === day ? `2px solid ${Colors.get('currentDateBorder', theme)}` : 'transparent',
}}>
{day}
</div>```
ScrollPicker: Defeating System Inputs
Standard <select> or <input type="time"> feel alien in a WebApp. On iOS, the system wheel pops up; on Android, it’s a modal. This kills immersion instantly. We wrote our own ScrollPicker to handle:
Scroll Snapping: Items "stick" to the center.
Initial Mount: Instantly jumping to the current value without a jarring animation.
```jsx
// Instant scroll trick on mount
useEffect(() => {
if (scrollRef.current) {
const selectedIndex = items.findIndex(item => item === value);
if (selectedIndex !== -1) {
// Jump instantly
scrollRef.current.scrollTop = selectedIndex * ITEM_HEIGHT;
}
// Enable smooth scrolling for the user only after the jump
requestAnimationFrame(() => {
setIsLoaded(true);
});
}
}, []);
2. Data: Why LocalStorage is a Trap
Many Mini App developers fall for the localStorage trap. In Telegram, your app runs in a system WebView; if the device runs low on memory or clears its cache, localStorage can be wiped.
For UltyMyLife, we chose IndexedDB (via the idb wrapper). This allows us to store megabytes of data: years of workout history, custom icons, and sleep logs. We use class-transformer to serialize our business logic classes into flat JSON and back again, ensuring we get objects with working methods, not just raw data.
3. Sync: Why We Skipped Telegram CloudStorage
Telegram's CloudStorage has strict limits, and a year's worth of training logs can get heavy. We needed control.
The Solution: Pako Compression & Custom Backend To make backups fly even on a 2G connection, we compress data on the client using pako (zlib) and encode it in Base64. A 100KB JSON shrinks by 70-80%, becoming a tiny string that uploads instantly.
"Defensive" Programming: Our recovery logic is a Swiss Army knife. It detects if data is compressed or "old" JSON, cleans Base64 artifacts (thanks, Safari bugs), and handles schema migrations.
To keep the backend lean, we use PostgreSQL purely as a "Blind Keeper." The server doesn't parse your habits; it just stores a binary blob (BYTEA) linked to your ID. This makes scaling effortless.
4. The Reactive "Brain": RxJS over Redux
As a Unity developer, I’m used to UniRx. When the project grew, I didn't need a "store" (Zustand)—I needed a "nervous system." RxJS allows us to treat user interaction as a stream of events, creating a "game loop" effect where the UI reacts instantly.
We use BehaviorSubjects for state (theme, current page) and Subjects for one-off events (pop-ups, haptics).
// Our Event Bus (HabitsBus.js)
export const theme$ = new BehaviorSubject('dark');
export const setPage$ = new BehaviorSubject('LoadPanel');
export const showPopUpPanel$ = new BehaviorSubject({show:false, header:'', isPositive:true});
export const setPage = (page) => {
setPage$.next(page);
// Auto-switch bottom menu context
if(page.startsWith('Habit')) bottomBtnPanel$.next('BtnsHabits');
else if(page.startsWith('Training')) bottomBtnPanel$.next('BtnsTraining');
}
5. AI Coach: Making Data Speak
We taught the app to think. It doesn't just say "You slept 6 hours." It says, "Your squat dropped 15% after nights with <6h sleep—skip the heavy leg day tomorrow."
To find correlations, we build a "Medical Chart" prompt for the LLM, aggregating data from 12+ modules (habits, gym, sleep, mental, breathing) into a single context. We also solved the UTC Trap: users training at 11 PM often had data leak into the next day in logs. We normalized everything to the user's local ISO date at the prompt level.
We also enforced strict formatting for the AI:
NO: "Try to sleep more."
YES: "Shift your bedtime 20 minutes earlier."
6. Glassmorphism: "Glass" without the Lag
True glassmorphism in a browser is a performance nightmare. When Demian brought the designs, my inner Unity dev screamed "Draw calls!" but my inner full-stack dev got to work.
The Fix: A Dual-Layer Defense We used backdrop-filter: blur(15px), but since it's unreliable in some WebViews, we used an opacity: 0.85 fallback. To maintain 60 FPS, we only applied glass effects to critical elements: the navigation bar, modals, and habit cards.
We also added internal gradients and box-shadow to simulate "LED edge lighting," making the panels feel like they are floating above the interface.
7. Gamification: Discipline as an RPG
Most apps give XP for just opening the app. In UltyMyLife, XP must be earned.
TotalXP = (Training \times 50) + (Mental \times 30) + (Sleep \times 20) + (Habits \times 10)
Levels (Level Up) are calculated exponentially, just like an RPG: each level requires 15% more XP than the last. We didn't add cartoon characters; we added a visual hierarchy of ranks and colors. You aren't playing a game; you are leveling up yourself.
The Result: What’s Under the Hood?
Layer,Technology
Frontend,React + Framer Motion
State,"RxJS (The ""Unity"" Way)"
Storage,IndexedDB + Pako Compression
Visual,Apple-style Glassmorphism
AI (Fast),Groq + Llama 3
Backend,Node.js / Express / PostgreSQL
UltyMyLife isn't just a Telegram bot. It’s a LifeOS that lives where you are. No installation, no permissions, no friction. Just 60 FPS of pure discipline.
If you’re interested in the code for specific components (like the swipeable habit cards or the custom picker), let me know in the comments!
Screenshots
👉 Check it out here: t.me/UltyMyLife_bot/umlminiapp










Top comments (0)