We Ported Game-Dev Logic to Telegram: Building a Tactile LifeOS with React and RxJS

Published: (January 30, 2026 at 09:17 AM EST)
5 min read
Source: Dev.to

Source: Dev.to

Introduction

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

PersonRoleNotes
Dmitriy Spirihin (Me)System Architecture & Full‑stackCame from 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 AvolstynyProduct Vision, Design & Vibe CodingGuardian of the “vibe”. Focused on aesthetics, tactile feedback, and seamless interaction logic 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.
  • How we leveraged AI to turn boring logs into actionable, “brutally honest” insights.

The Problem

The market is saturated with trackers:

  • A thousand habit trackers.
  • Another thousand gym logs.
  • A hundred meditation apps.

The problem? They are fragmented. 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.

Progress‑Calculation Logic (HabitCalendar.js)

// 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

  {day}

ScrollPicker: Defeating System Inputs

Standard <select> or <input type="date"> feel alien in a WebApp.

  • On iOS the system wheel pops up.
  • On Android it’s a modal.

Both kill immersion instantly. We wrote our own ScrollPicker to handle:

  • Scroll Snapping – items “stick” to the center.
  • Initial Mount – instantly jump to the current value without a jarring animation.
// 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);
    });
  }
}, []);

Persistent Storage – IndexedDB over localStorage

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.
  • 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.

Cloud Storage Limits → Compression & Custom Backend

Telegram’s CloudStorage has strict limits, and a year’s worth of training logs can get heavy. We needed control.

The Solution

  • Pako Compression (zlib) + Base64 encoding.
  • A 100 KB JSON shrinks by 70‑80 %, becoming a tiny string that uploads instantly even on a 2G connection.

Defensive Programming

Our recovery logic is a Swiss‑army‑knife that:

  1. Detects if data is compressed or “old” JSON.
  2. Cleans Base64 artifacts (thanks, Safari bugs).
  3. Handles schema migrations.

Backend Design

We keep the backend lean: PostgreSQL is used 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.

Reactive State Management – RxJS as the “Nervous System”

As a Unity developer I’m used to UniRx. When the project grew, I didn’t need a simple “store” (Zustand) — I needed a nervous system.

RxJS lets us treat user interaction as a stream of events, creating a “game loop” effect where the UI reacts instantly.

  • BehaviorSubjects – for long‑living state (theme, current page).
  • 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');
};

AI‑Powered Insights

We taught the app to think. It doesn’t just say “You slept 6 hours.” It says:

“Your squat dropped 15 % after nights with UltyMyLife is proof that even within the constraints of a WebView, you can deliver a next‑level user experience—no VPNs, no Notion, no friction.”

Glassmorphism Performance

“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 because it’s unreliable in some WebViews we added an opacity: 0.85 fallback.

  • To keep the app running at 60 FPS, glass effects are limited to the most critical UI elements:

    1. Navigation bar
    2. Modals
    3. Habit cards
  • Added internal gradients and box‑shadow to simulate “LED edge lighting,” giving panels the impression of floating above the interface.

XP System

Most apps give XP just for opening the app. In UltyMyLife, XP must be earned.

TotalXP = (Training × 50) + (Mental × 30) + (Sleep × 20) + (Habits × 10)
  • Levels (Level Up) are calculated exponentially, like an RPG: each level requires 15 % more XP than the previous one.
  • Instead of cartoon characters, we use a visual hierarchy of ranks and colors.
  • You aren’t playing a game; you are leveling up yourself.

Technology Stack

LayerTechnology
UltyMyLifeNot just a Telegram bot – it’s a LifeOS that lives where you are. No installation, no permissions, no friction. 60 FPS of pure discipline.

If you’re interested in the code for specific components (e.g., the swipeable habit cards or the custom picker), let me know in the comments!

👉 Check it out

t.me/UltyMyLife_bot/umlminiapp

Back to Blog

Related posts

Read more »