ClineAws is an Android mobile app for keeping track of household chores in a flatshare. Each household shares a list of chores, and each chore is worth a number of points. When a flatmate does a chore, they log it in the app. A dashboard then shows the history of completed chores and how the points are split between flatmates, so everyone can see who did what.
I spent less than 20 hours on this project. The way the work was split is a bit unusual: I did all of the architecture myself (the client / server split, the network protocol and the messages exchanged, the database model and the choice of tables, how identity is handled, how the server is deployed and hardened, and the screens and their layout), and Claude Code wrote the code from those choices, with me reviewing it: the C# scripts, the networking layer, the SQL queries and the shell scripts. In short, I decide and validate the architecture, the AI types the code.
There is no password and no email address. On first launch, the user
just enters a name: the server creates the account and returns a
device_token (a UUID). That token is kept on the phone
(PlayerPrefs) and sent again automatically on later
launches. To log back into an existing account from another phone, or
after the app’s data has been cleared, the only option is to type that
same UUID by hand. So an account whose UUID has been lost cannot be
recovered: all you can do is create a new one. That is a deliberate
choice for an app between flatmates: it avoids any password handling and
any guessable account identifier, in exchange for account recovery being
impossible.
The app reuses the architecture I had already used on ThousandsPong:
a single Unity project that produces two builds. The client build (the
phone) and the dedicated server build (#if UNITY_SERVER, on
Linux) share the same networking code.
All of the logic (account creation, household management, logging and
cancelling a completed chore, computing the points) runs on the server.
Messages go through custom Netcode messages (Netcode for GameObjects
with Unity Transport), serialised in binary, with a
RequestId tying each response to its request. The user
behind an action is always looked up on the server, never read from the
client’s request. In the same way, before touching a chore, the server
checks that this user actually belongs to the household the chore is in.
A household id or chore id sent by the client is never taken at face
value.
On the server, a client is always recognised by its
device_token (the UUID). That value is what authenticates a
request, never the account’s numeric id, so that a number
that is easy to guess cannot be used to pass for someone else.
A flatmate can create a household or join one with a short invitation
code: an integer picked at random in the range [0,10 000 000], easier to read out loud than
a UUID. The code is assigned automatically on creation by a PL/pgSQL
trigger, which also checks it is not already taken. Household membership
is stored in a join table, foyers_utilisateurs.
Each household has its own list of chores, each with a point value that can be adjusted. The app lets you log a chore as done or undo that (“Done” / “Cancel”), look at the history in a popup, and check the household statistics. How all of this is stored, and the choice not to freeze the points at the moment the chore is done, are explained in the Database section.
The data lives in a PostgreSQL database running on the server. Only
the server build talks to it, through Npgsql: the code that talks to the
database is compiled under #if UNITY_SERVER, so it is not
even present in the client. The connection string comes in through the
COLOC_DB_CONNECTION_STRING environment variable, it is
never written in the code. The starting schema is versioned as SQL
migrations for the utilisateurs and foyers
tables, and a setup-postgres.sh script sets up the
database, the application role and its privileges (just
SELECT, INSERT, UPDATE depending
on the table). The taches and realisations
tables were created by hand, outside those migrations.
There are five tables:
utilisateurs (id, nom,
device_token a unique UUID generated by default,
created_at). An account is a phone, recognised by its
UUID.
foyers (id, nom,
join_code a unique integer, created_at). The
join_code is the random integer described above, set by a
PL/pgSQL trigger on insert.
foyers_utilisateurs (foyer_id,
utilisateur_id). The join table between accounts and
households, with a primary key on both columns and an
ON DELETE CASCADE to both parent tables.
taches (id, nom,
points, foyer_id). A household’s list of
chores, with the current point value of each one.
realisations (id,
foyer_id, utilisateur_id,
tache_id, fait_le). The log of completed
chores. There is no points column: the points of a row are
always read back from taches.points. If you change a
chore’s value, the history and the totals show the new value for the old
completions too. That is on purpose.
The history and the per-flatmate point totals are computed on the
server, with queries that join realisations,
utilisateurs and taches together with a
GROUP BY.
The ClineAws dedicated server runs on the same personal server as
ThousandsPong and gets the same hardening: the process runs as a systemd
service (clineaws-server.service) under a dedicated user
with reduced privileges, its settings (database, certificate) are passed
to it through EnvironmentFile=, and a firewall limits the
open ports. The domain name comes from DuckDNS.
The client and the server talk over WebSocket rather than UDP, so
that a web build can connect from the browser. The server terminates TLS
itself (wss://) with a Let’s Encrypt certificate: Caddy
takes care of obtaining and renewing it, and a systemd path
unit redeploys the certificate and restarts the service on every
renewal. Clients connect straight to the server, with no reverse proxy
on the path.
The Android APK is available here: ClineAws.apk. Installing it requires allowing unknown sources on the phone.
A web version, which runs straight in the browser (including on mobile), is also embedded below: