Guide βΊ Modules
Habits
The things you want to be better at β guitar, yoga, a walk, a stretch β logged with one tap, a timer, a Shortcut, or read out of a calendar another app already keeps. Your Handler judges each one, rewards consistency and has a word when you slack.
π― Habits is the daily face of what Latch is for. The cage gives the day an edge; the Handler gives it a voice; this is the work. Practice the instrument. Do the yoga. Take the walk. Log it, and the game watches it the way it watches everything else β a streak on the calendar, a colour in the heatmap, hours per week on the charts, a line from your Handler when the board is empty at nine in the evening.
A habit is an activity with a button
There is no separate habit system. A habit is an activity β a
named, coloured lens over your event log β with its one-tap button switched on, and,
if minutes matter, its time from set to habit. That is why a habit you add today already
has a streak rule, a place on the Calendar, a heat strip on
Progress, five stat tokens for share cards,
and an hours-per-week chart if it is timed β none of it written twice.
Add one on the Habits page (name, an icon, a rhythm, timed or not) or in Settings β Activities, where the full editor lives: colour, sort order, and the matcher that decides which events count. Everything the customise page says about activities applies.
Three ways to log
| Door | When |
|---|---|
| One tap on the Habits page or the dashboard tile | you did the thing, just now. A timed habit logs your default length (Settings β Habits, 25 minutes out of the box) |
| The form β minutes, a note, a when | you want the exact length, or it happened earlier |
| The timer β βΆ start, do it, β stop | practice, a sit, a walk. It keeps running if you close the page. Stop writes the minutes for you; discard writes nothing |
There is also the API for a phone Shortcut, and β the reason this module exists β a calendar another app already writes.
Where a timed log lands
A bout is stamped when it ended, carrying its length β exactly like a headspace session or a kit bout. A practice that ran 23:40 to 00:20 is on the second day's board. The start is kept too, and shown in the log.
Reading another app's calendar
Practice trackers, habit apps and time trackers nearly all publish what they record as a
calendar β look for subscribe, publish, export to calendar, a webcal:// link. Each entry
in that feed is a thing you did, with a start, an end and a title. That is a habit log,
already kept, by an app you already use.
Settings β Habits β Calendar feeds. Paste the link, choose the habit it feeds, and say
which entry titles count (title contains: Guitar catches both Guitar and
Guitar - Guitar Practice; blank means every entry). Latch fetches it on the schedule you set
β every 30 minutes by default; 0 turns fetching off β and:
- every matching, finished entry becomes one log, stamped at its end, with its length;
- an entry is written once β fetching the same feed again writes nothing again;
- an entry that later moves or grows has its old log struck through and a new one written. The log says what it was told, both times; nothing is edited in place;
- an all-day entry is a check-off on that day with no length;
- an entry still in progress is left for the next fetch;
- a cancelled entry is skipped.
Several habits can read the same calendar: one feed row per habit, each with its own title contains. The calendar is fetched once however many rows share it.
Recurring entries are skipped, and that is a rule
A recurring "Yoga, every Tuesday at 19:00" is a schedule β an intention β and importing it would write fifty-two yoga sessions nobody has done yet into a log whose whole claim is that every row is something that happened. Only concrete entries count. The feed's status line tells you how many were skipped, so you can see the rule working.
No subscription link? Import a file. Export an .ics from the other app and upload it
under the feeds table. Same rules, same de-duplication, and no network call at all.
A late entry counts, but it is not rewarded
The day you add a feed, a year of practice history may arrive at once. It all counts β the streak, the calendar, the totals take every row β but anything that arrives more than two days after it ended earns no XP. The same rule applies to a log you type in with a when a week back. XP is for showing up today; the record is for everything.
Praise and a talking-to
Your Handler does not only want you locked. It wants you to be the best pup you can be, and that takes consistent practice. So every habit is judged β and the judgement is what the Handler says, what it writes into your log, and what Insight reports, all from one computation.
The verdicts
| State | What it means | Tone |
|---|---|---|
| fresh | added less than a week ago with fewer than three logs. Nothing is said about it yet β a Handler that scolds you for a habit you added yesterday has not earned the right | β |
| consistent | three or more days in a row (daily), two or more weeks hitting the target (weekly), three windows in a row (every N days) | praise |
| on track | the period is still winnable β a three-a-week done once on a Tuesday is on track, not behind | β |
| slipping | one miss, or every remaining day of the week now has to count | a warning |
| slacking | three or more days gone (daily), two weeks under target (weekly), past twice the window (every N days), or added a week ago and never done | stern |
| idle | rhythm none β counted, never judged |
β |
Each verdict is judged against the period the habit declared. A weekly habit is judged by weeks and the days left in this one; a daily habit by the gap since the last log; an every-N-days habit by that gap against N. That is what keeps the Handler a coach rather than a nag.
What it does about it
- Consistency is rewarded. The moment a weekly target is hit, and at every seventh day of a
daily streak (fourth window of an every-N-days one), the Handler writes a Consistency
rewarded event: praise, XP (
habits.streak_xp, 15) and β if you are locked β minutes off the clock (habits.streak_minutes, 10). Once per period, never twice for the same week or run. - Slacking earns a word. A week that closed under target, a daily habit gone three days, a
habit added a week ago and never done: the Handler writes A word from your Handler into
your log, once per lapse, with the sentence it said. It is words by default.
habits.slack_penalty_minutes(0) turns the word into minutes on the clock too, if you want the sting β and like every penalty in the game it is withheld on a recovery day (short sleep, low mood, from Health), and the event says so. - Nothing is judged from before the game was watching. The review only looks at periods that closed in the last two weeks, so the day you add a feed with a year of practice in it, you get at most one reward for the week just gone and no scolding for any quiet week of last year.
Where you hear it
The Habits card and the Habits page carry the line. The Handler's own page and card carry it too: when the day's duties are done, the Handler talks about the work β praise for a run, a stern word for slacking β and when you are not locked, slacking is still called out: Not locked β but you're still my pup. The talking-to and the praise are the same sentence in the log, on the card and on the page, because they are computed once.
Insight
Habits contribute to Progress β Insight, under the same floors as everything else there: whether a habit is trending up or down over the last four weeks against the four before; whether you do it more often on days you are locked; whether headspace or mood run higher on days you did it. Associations over one log, said once β never a cause.
This module reaches the network β here is exactly how
The core of Latch makes zero outbound calls. Habits is the first module that declares egress, and the declaration is on Settings β Habits, in one paragraph, above every knob:
Fetches the calendar feed URLs you add under Feeds, on the schedule you set. It sends nothing but the request, stores no response bodies, and never fetches anything until you add a URL.
With no feed configured, nothing is fetched β a fresh install is exactly as sealed as before this module existed. The file import makes no network call ever. On a hosted instance a feed must live on the public internet: private and local addresses are refused, so a tester cannot use the server to probe the host's own network. See Privacy for how the declaration is shown and what it can and cannot promise.
Settings
| Key | Default | What |
|---|---|---|
habits.feed_minutes |
30 | how often calendar feeds are fetched; 0 never |
habits.default_minutes |
25 | the length a one-tap log of a timed habit records |
habits.streak_xp |
15 | XP for a week hit or a streak milestone |
habits.streak_minutes |
10 | minutes off the clock for consistency, while locked; 0 = praise only |
habits.slack_penalty_minutes |
0 | minutes on the clock for slacking; 0 = a stern word only. Withheld on a recovery day |
Plus the feeds table, the file import, and β because a habit is an activity β everything under Activities.
Export
habit_logs and habit_feeds ride in the full export (latch.json and the bundle). Neither
is in the share export: a feed URL is yours, and a log's note is prose.
This page is docs/habits.md in the repo. Spotted a mistake? Tell us how to report it β