Just relax, take it easy..

0%

Beaver Habits Tracker Onboard

When switching from Android to iOS, I was unable to find a light-weighted but handy habit tracking app, so I decided to make one by myself :)

For the name of the project, it came from a game called "Against the Storm" (which I spent over 100 hours, highly recommended). In the game, my favorite city builder species is beaver, hoping this web app works as a beaver to save ur precious moments in your fleeting life.

GitHub: https://github.com/daya0576/beaverhabits/
Demo: https://beaverhabits.com/demo/
40740423-A4AB-4806-9A6A-6F1B896FC8AE_1_201_a

Tech stacks

Inspired the idea of "web UIs with plain Python" from Three Python trends in 2023, finally chose NiceGUI as the full-stack framework (based on Quasar, Tailwind CSS, FastAPI, ...).

So this web app is 100% built with Python <3

Some thoughts after several weeks development:

  1. Good things ✅
    • WebSocket based communication between client and server, works perfectly with Python asyncio.
    • Light-weighted session based storage provided, out of the box to use.
    • Plenty of UI components provided, straightforward and highly customizable.
    • ...
  2. Worries 🤔
    • "NiceGUI follows a backend-first philosophy: It handles all the web development details" -> high network latency would be a big issue.
    • ...

Persistent Storage

As mentioned above, NiceGUI handles everything in server side, high network latency would destroy user experiences.

Some solutions:

  1. Global CDN and edge servers
  2. Self-host option
  3. ...

In order to provide self-host and flexible backend storage options, storage interfaces were defined with various implementations, e.g. session-based file or user-based Sqlite.

BTW, the code below leverages the latest features of Python 3.12: PEP 695: Type Parameter Syntax :)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class CheckedRecord(Protocol):
@property
def day(self) -> datetime.date: ...

@property
def done(self) -> bool: ...

@done.setter
def done(self, value: bool) -> None: ...

def __str__(self):
return f"{self.day} {'[x]' if self.done else '[ ]'}"

__repr__ = __str__


class Habit[R: CheckedRecord](Protocol):
@property
def name(self) -> str: ...

@name.setter
def name(self, value: str) -> None: ...

@property
def priority(self) -> int: ...

@priority.setter
def priority(self, value: int) -> None: ...

@property
def records(self) -> List[R]: ...

def get_records_by_days(self, days: List[datetime.date]) -> List[R]: ...

async def tick(self, record: R) -> None: ...

def __str__(self):
return self.name

__repr__ = __str__

Future..

  1. Pages:
    • Index page
    • Habit list page
    • Habit detail page, e.g. records over years
  2. Storage:
    • Session-based file storage
    • User-based file storage
    • User-based sqlite storage
  3. CICD:
    • Custom domain
    • Global CDN
    • Self-hosting support
    • Unit tests & deployment pipeline
  4. Others:
    • Export & Import
    • User management
    • User timezone

1505DDDA-0669-46E6-9FDF-25FEDD1870C9