Blog Β· release
Latch 0.14.0
Share, reworked. For Locktober, and for every event after it: a card that uses its space, a
drawn event stamp, post text with hashtags written for you, a Share page you can edit on a
phone without the screen jumping, and presets that keep a whole look. No migration, and the
Locktober 1.1.0 playset needs no update β everything event-shaped is derived from its [event].
Layouts
- Three layouts, and old links keep their look. Poster β the big timer β stays the
renderer default and draws the pixels 0.13.1 drew outside an event (a test holds it to
0.13.1's own output), except where 0.13.1 cut a value short or overflowed a label, where the
card's moment falls inside a lock that ended later (see Fixes), and in the tile of a large
photo, which is now decoded at the size the card needs. During an event the stamp is added;
seal=0takes it off. Dense is a 96 px timer with up to 9 stats beside a framed photo (12 without one). Check-in fills the card with the photo, holds up to 12 stats in a glass panel, and needs a right now card with a photo in story or square β asked for anywhere else it quietly draws as Dense. - π΄ A CHECK-IN CARD HAS ONE CLOCK: THE SHUTTER, clamped to when the photo reached the box. Every number on it is the photo's moment and that moment is printed on the photo, so it is the honest form of the overlay the P0 stamped photo got wrong β the stats are not today's stapled onto an old picture, they are the picture's. It ignores the page's clock: drawn again next month, the same bytes. The framed two-instant layouts keep their own caption plate, as before.
- Long values take two cells instead of being truncated ("LOCKTOBER Β· DAY 2/31", not "LOCKTOBEβ¦"), packed in your order with no backfill β on Check-in up to the whole row β and an event name too long even for that is cut before its DAY n/N, never after. Labels are fitted rather than cut, and on Dense and Check-in the gap between two values grows with the type, so neighbours never read as one phrase.
- Nothing is drawn twice. On Dense and Check-in the session's elapsed time (it is the big number), the level and rank (they are in the footer) and β when the stamp is on the card β the event's name and day are not drawn as stats; the page shows them as already on the card rather than letting the pick vanish. Poster is untouched.
- One plan, then paint. The card engine decides every clock, cell and span first and only then draws, so the page's "fits N", the post text and the pixels read one plan and cannot describe three different cards.
- Downloaded cards are named for the event day:
locktober-2026-day02-20261002-1205-story.jpg. A 12 MP photo is decoded at the size the card needs, not at full resolution.
The event stamp
- A drawn postage stamp for whatever event you are in: a perforated rim in the event's colour, a padlock, the name, the day in big numerals with /31, and the year. Drawn, never typed β the event's emoji sign is not in the card's font and is never printed. A long name breaks over two or three rows where it fits best instead of being cut short. Every event playset gets one with no new keys.
- π΄ A STAMP IS AS-OF THE SURFACE IT SITS ON. On a framed photo it is dated by the shutter,
so a photo from the 2nd says DAY 2 forever; in the header of a card with no photo it is the
card's moment; on a Check-in card the two are the same. No event at that moment means no
stamp β never a fallback to "the event running now".
seal=0(or Look β Event stamp) turns it off for one card. - β οΈ Re-drawing an old card asks the playset as it is now: switch it off or remove it and re-drawn cards lose the stamp. A file you already shared does not change.
Post text
- The words to go with the card, written on the box from the same card: the same moment, and up to four of the stats it actually drew, in its order. Three styles β Pup (three phrasings that take turns by event day, one quoting the playset's own line: Day 2 of 31. It's Locktober, pup. π), Stats and Minimal β or your own template. No model, no network.
- Hashtags:
#Locktober #Locktober2026from the event;#LocktoberDay2and the generic kink tags are offered and never on until tapped; the playset's catalogue tags are never hashtagged. Also an alt-text description, a content warning (chastity Β· Locktober day 2/31), and live counters for X, Bluesky and Mastodon. - Your edits stay yours. Switching a hashtag on or off in a post you have edited changes that tag and nothing else β your words and the tags you typed stay β and an edited post survives a reload or a change of photo in the same tab, with βΊ regenerate a tap away.
- π΄ THE TEMPLATE GRAMMAR NEVER CALLS
str.format. A pattern is walked, never evaluated, so{day:>999999999}costs its own length. A field with nothing to say drops its ownΒ·segment β never "Locked None" β and a hidden timer's time to go and sentence simply are not there, in the text or the alt. - New stat Photo days (
event_photo_days, "2/2"): how many days of the event carry a photo.
The Share page on a phone
- π΄ ONE TAP SHARES THE PICTURE AND PUTS THE TEXT ON THE CLIPBOARD. iOS drops the files when
text rides along in the share sheet, so on iPhone the sheet gets the picture only and the
post text is copied in the same tap; pick the app, paste. The picture is fetched before the
tap β a tap before it is ready says "one moment", never downloads β and a refused share keeps
its file so the next tap works at once. The same fix (no
title, the file kept) applies to every π€ button in the app. - Edits happen in place. A stat, a theme, a layout, a hashtag or a style changes the card without a reload and without moving the page under your thumb: twelve fixed slots that mirror the card, a fixed toolbar (earlier Β· later Β· remove), a catalogue showing each stat's value, and panels as tabs β Stats, Post, Look, Photo. The address bar follows every edit, so a reload, a bookmark or the back button lands on the card that was on screen. With JavaScript off, every control is still a link that lands back on its own panel.
- The preview, the download and the text are one moment: the page pins its clock into all three. The site header no longer sticks on a phone, so the preview and the buttons get the screen.
- π€ Share Day N on the dashboard for 12 hours after a photo check-in or a photo verification: one tap to the Share page with that photo, the Check-in card and the text ready. The gallery has a π card link per photo, opening the same preset. The Check-in starter picks today's check-in or verification photo for you β never a kit photo, which only reaches a card when you choose it.
- The Photo panel lists newest first and tags each photo with its event day.
Presets
- Named presets, kept on the box: the stats, the layout, the theme, the shape, the stamp, the post style, the hashtags, the template and the CW β never a photo or a location. Save from the chip row under the preview; a β on the active chip means the page has moved on from it, and a tap offers Update or Save as new. Rename, β default and delete under Look β Manage presets; an update or a delete can be undone from its message, and undoing the delete of your β default brings it back starred, in its place. The two built-in starters stay in the row after your own.
- A bare Share page opens your β default, else the built-in Daily starter (Dense); the dashboard's button uses the default too, else the Check-in starter. A link that names its own settings still means exactly what it says β a preset is a base, never a mask.
- Stored as one validated value in the settings table (no migration; it rides the full backup) and refused fail-closed β unknown keys at every level, a photo, a place. The dead Share card: theme setting, which nothing ever read, is retired.
Fixes
- π΄ A photo's own JPEG comment no longer travels. The stamped export copied the original file's comment marker into the image it handed out β text an editor or a phone app wrote there. Every image Latch draws (cards, stamps, recaps, thumbnails) is now saved with no metadata at all.
- An event name can no longer carry a line break or an invisible character into the post, the CW or the alt text: the playset validator refuses them, and the post scrubs them anyway.
- A past card no longer calls a lock that ended later UNLOCKED. Every pinned card, and every check-in card drawn after the unlock, read UNLOCKED with the whole lock's length as its elapsed time.
- Today's check-in is first in the photo list, not buried behind months of training photos (and past the end of the list once there were enough of them).
- β on the last stat leaves no stats, instead of bringing all six defaults back.
- The "fits N" hint comes from the card itself; it used to say 3 on a milestone card that drew 6.
- π΄ A playset's
[event].linecan no longer allocate a gigabyte. The validator checked a line by formatting it, so{day:>999999999}ran inside a function documented never to raise, and the stored line was formatted again at every page render. Lines are now read, not run, and a bad stored line falls back to the default. - The contributing guide's test command is
.venv/bin/python -m pytest -q(the barepytestnever put the repo root on the path).