🐾 Handlr ?

Guide › Customise

Customise your experience

Everything adjustable lives on one Settings page — the clock, the stamps, each module's rules and editors — plus a handful of environment variables for the instance itself.

/settings is the one place every rule and every editor lives. The play pages stay clean and carry a ⚙ that jumps to the right section. Each section here has a 📖 back to this page.

Clock

  • Quick-adjust buttons — the four ± buttons on the dashboard, in minutes, comma-separated. Default 15,60,360,1440. Any count works; four fit the row best.
  • Undo window — how far back undo reaches, in minutes. Default 15; 0 means no limit. Stamped photos quote the clock as it was, which is why the default is short.

Hygiene

When the cage comes off to be cleaned. The dashboard's 🛁 card does it in one tap: out, then 🔐 Locked back up.

  • Maintenance slots — the game setup decides when maintenance is scheduled. Times, optionally with days, comma-separated: 07:30, 21:00 or 07:30, sat/sun 10:00. Leave it empty for emergency-only. A slot it cannot read is refused when you save, never dropped.
  • Maintenance window — how long a scheduled window runs, in minutes. Default 30.
  • A slot stays open to start for — how long after its time a slot can still be started. Default 60, so a 07:30 slot can be started until 08:30.
  • Emergency hygiene window — default 20 minutes. The emergency door is always there: any time, any lock, never refused and never capped. It is logged as emergency, and that is all. Nothing makes it a penalty.

A Handler can dictate it. Grant your AI Handler or a pack's Handler Schedule maintenance on Consent and they can set the slots and the maintenance window (10–60 min, at most 6 slots, once a day by default). The section here says who set them. They can never change the emergency window or refuse one. Saving the section takes the schedule back, and so does revoking the grant or the safeword, which stops further changes.

The clock stops while you are out, like a freeze: you serve those minutes later. And the window is not locked time. Total and longest lock, the weekly bars, share and stamp figures, the badges built on them and the cage's worn hours all leave it out. A finish logged inside a window is not a caged one.

If the window runs out before you relock, the clock starts again. It never waits forever. The window stays open, though, because the app does not know the cage is back on. The card asks locked back up?, and ⏱ relocked earlier? lets you say when it really happened.

Stamps

  • Stamped photo layout — auto (by aspect ratio), or bar Hairline · hero Collar · badge Tag · dossier Case File · rail Strip. See Photos.
  • Stamped photo: stats to overlay — an ordered list of token ids. Default streak_current,tasks_verified,session_id,session_task.
  • Case File grid — the tokens the dossier layout lays out in its grid.
  • Session recap card — the tokens in the recap card's grid. Default base_seconds,adjust_seconds,frozen_seconds,sess_tasks,sess_spins,sess_photos.
  • Share card — the grid on a right now card. Default time_locked_d,streak_current,level,tasks_verified,total_locked,sessions_n.
  • Share card, the last stretch — the grid on a week card. Every id here is from the window group, because a grid mixing "3 locks this week" with "41 locks ever" is a grid nobody can read once the labels are abbreviated to fit. Default win_sessions,win_tasks,win_spins,win_reflections,win_photos,win_xp.
  • Share card, one badge — the grid on a milestone card, resolved as the numbers stood when the badge was earned. Default total_locked,streak_current,level,sessions_n,tasks_verified,longest_lock. These are the starting lists. The Share page can add, remove and reorder stats for one card without touching any of them — the choice lives in the link.

There is no theme setting for share cards any more (0.14 retired the old Share card: theme, which nothing ever read). The look a card starts in — theme, layout, stats, post style — is a preset, kept and chosen on the Share page itself.

A token that has no value at that moment (a session token on a photo taken while unlocked, a rate on a one-day-old log, a window token on a card that has no window) is simply dropped, so a list can be generous.

Token reference

Group Token Prints
moment date_long · time_hm · datetime_full · weekday when the photo was taken
moment place where the photo was taken, coarse — opt-in: never in a default list, never in post text, and only on a surface that carries the photo
session status LOCKED / FROZEN / TIME SERVED / UNLOCKED at that moment
session time_locked · time_locked_d elapsed in the lock (short / with days)
session day_n · day_n_cal day N of the lock (by 24 h / by calendar day)
session time_remaining · pct_served to go · percent served
session base_seconds the sentence — hidden while a hidden lock runs
session adjust_seconds · frozen_seconds net adjustments · time frozen
session session_id · session_task lock number · the task this photo verified — or, for an answer to a challenge, its word (proof · OTTER 47)
session sess_tasks · sess_spins · sess_photos · sess_xp counts inside this lock
lifetime total_locked · sessions_n · longest_lock lifetime locked · locks · longest
lifetime tasks_verified · tasks_failed training done / failed
lifetime reflections_n · checkins_n · spins_n · photos_n counts
lifetime time_added · time_removed totals
streak streak_current · streak_best days
rate tasks_per_week · task_fails_per_week · spins_per_week · locks_per_week per week
rate reflect_days_per_week · checkin_days_per_week · reflect_compliance days per week · % of locked days reflected
rate locked_hours_per_week · locked_pct_28d hours per week · % of the last 28 days
game level · rank · xp as of that moment
game standing your Mischief standing word at that moment — 🐾 Good boy · 👀 Watched · 🦮 Short leash · 🏠 In the doghouse — opt-in: in no default list; never the marks, their kinds or counts; nothing while Mischief is off
window win_days · win_locked · win_locked_pct the span · locked in it · % of it
window win_active_days · win_sessions days with something logged · locks begun
window win_tasks · win_task_fails · win_spins counts inside the span
window win_photos · win_reflections · win_checkins · win_badges counts inside the span
window win_xp · win_added · win_removed xp earned · time added · time removed
event event_name · event_day the event you were in — LOCKTOBER · LOCKTOBER · DAY 12/31
event event_photo_days days of the event so far with a check-in or verification photo, over the day — 12/12 is every day

Every value is computed as of the photo's timestamp — see the overlay is as-of the shutter.

The window group is the exception, and the only one that reads two moments: it counts what happened between them. Those tokens only mean anything on a week share card, so everywhere else they say nothing and are left out — the same treatment as a stat that has no value yet, rather than a zero that would claim a quiet week.

The event group says nothing outside an event you joined. Like everything else it is as of the photo: a photo from day 12 says DAY 12 however long after it is exported, and event_photo_days counts local calendar days, so a 23:30 photo and a 00:30 photo are two days.

Every id in this table (except place) also works as a {field} in a share card's post text — see Share cards.

Activities

An activity is a thing you want to count — reflections, sits, walkies, anything. Handlr ships with a starter set and you can add your own at the bottom of Settings.

The important part: an activity selects events you already have. It is a lens over your log, not a second log. Add one today and it is filled in from your whole history immediately — and removing one deletes nothing, because it never owned any of those events in the first place.

Each activity has:

  • icon and colour — the colour is what the Calendar draws. Two activities should not share one, or a day's colours stop telling you what you did.
  • slug — lower-case, no spaces. It becomes an event kind and the tail of five stat ids, so it is the one field worth thinking about before you save.
  • match module / match kind — which events belong to this activity. Leave module blank for any; put several kinds in, comma-separated, to fold them together. Both blank matches nothing, deliberately — a blank activity that matched everything would quietly claim your entire log.
  • streak — what a run is judged against: | cadence | a run continues while | |---|---| | daily | you do it every day. Today is grace — an empty today does not break it | | weekly_n | every week carries target separate days or more. The current week is grace | | every_n_days | you never leave a gap bigger than target days | | none | this has no streak, and none is shown | That last row is not a cop-out. A wheel-spin counter has no run to break, so showing it a 0 would be claiming one was broken.
  • time from — where "time doing this" is read: entry (headspace), media (recordings), trance (sits), kit (bouts), habit (a hand-logged, timed or calendar-imported bout — see Habits). Blank means this has no duration, and none is shown rather than 0s.
  • one-tap button — gives it a button on the dashboard and puts it on the Habits page, where it can be logged with minutes, run on a timer, or fed from a calendar. A habit is an activity with this ticked.
  • xp — awarded for a log made within two days of the thing happening. Older logs count everywhere and earn nothing.

Tracking something Handlr already does? Put that module's kind in match kind and untick one-tap button. Tracking something of your own? Leave match module as activity, set match kind to your slug, tick one-tap button, and set time from to habit if minutes matter — or just add it from the Habits page, which fills those in for you.

The five stats every activity gets

Saving an activity adds five ids to the token lists above, with no restart:

id prints absent when
since_<slug> how long since the last one it has never happened — never 0s, which would read as just now
days_<slug> days you have done it — (a genuine 0 is printed)
streak_<slug> the current run its cadence is none
total_<slug> time spent doing it it declares no time from
count_<slug> how many altogether —

So a new activity is shareable the moment it exists: put streak_walkies in the share-card list and it appears. The full list of ids your instance has right now is at the bottom of the Activities section in Settings.

Handler

The name, the reflection minimums, the check-in cadence, the missed-day penalty, and the whole prompt library. Full detail on The Handler. Three presets, if you want a starting point:

Gentle Firm (default) Strict
Minimum reflection 60 chars 120 300
Check-in every 8 h 4 h 2 h
Missed-reflection penalty 0 min 30 120
Minimum spoken reflection 15 s 30 60

Then rewrite the prompts in the voice you want. The seeded lines are firm but clean; the library is where the Handler's personality lives.

Wheel

The cooldown (minutes; 0 while testing) and the segment table — label, weight, effect, value, colour, enabled. See Wheel of Fortune.

Training

The default deadline for a task that has none (hours, default 24) and the task library — rewards, penalties, XP, photo required, per-task deadline, enabled. See Training.

Display name and other instance settings

These are environment variables on the container, not fields on the page:

Variable Meaning Default
HANDLR_APP_NAME the name in the header, the tab title, the footer Handlr
HANDLR_TZ the timezone every date is shown in and every when is typed in (events are stored as UTC epochs) America/Detroit
HANDLR_API_TOKEN the bearer token for /api/v1. Empty means the API is off. (empty)
HANDLR_TICK_SECONDS how often the scheduler sweeps freezes and deadlines 30
HANDLR_DATA_DIR where the database and media live /data in the container
HANDLR_DOCS_DIR where this guide's markdown lives /app/docs in the container
HANDLR_ALLOWED_HOSTS the names this box answers to besides IP addresses and localhost — see the names it answers to (empty)
HANDLR_WORKERS whether background workers may start (later features — pack sync, the AI Handler — run in them). 0 = none; the test suite runs with 0 1

Set them in .env next to docker-compose.yml and recreate the container. Renaming the app is a one-line change: HANDLR_APP_NAME=Kennel.

Themes

There is one look — dark, game-like, no build pipeline. app/handlr/web/static/style.css is a single file with CSS variables at the top (--accent, --accent2, --cyan, --bg) if you want to fork the palette. A theme system and a curated gallery look are on the roadmap.

This page is docs/customise.md in the repo. Spotted a mistake? Tell us how to report it →