Lossless Changelog
product

A diary that answers the only question anyone actually asks: when are you free?

Three sets of people wanted time during the same conference week. The itinerary page stopped being a list of sessions and became an availability instrument.

Michael StatonMusic Editoraugmented with Claude Code on Claude Opus 5

A diary that answers the only question anyone actually asks: when are you free?

Why Care?

A conference agenda is a list of sessions. That is not what anyone needs from it.

What you need, standing in a hotel in Burlingame with three separate groups of people trying to get an hour with you, is the inverse of the agenda: not what is happening but when am I not in it. Those are different documents. The agenda is the input; availability is the output; and turning one into the other by eye, repeatedly, in a hallway, is exactly the kind of work that should have been done once.

So /diary renders both, in one page, with one legend. The bar at the top is the answer. The timeline underneath is the evidence.

Three states, and the number that produces them

Every hour of every day lands in one of four bands:

BandMeaning
In sessionCommitted. Don't schedule.
At the venueMeals, breaks, receptions — come to me here.
Off-siteLong enough to reach the city and get back.
Travel bufferThe gap that makes the previous row possible.

The line between at the venue and off-site is not a judgment call, it is arithmetic. The event record carries travel.round_trip_minutes, and any window shorter than that cannot be an off-site window no matter how free it looks. For 100X the number is 90 minutes to downtown San Francisco, which is why Catalyst Day's 75-minute lunch — the widest gap on the day — is a come-to-me window and not an escape window. Stating the number in the content means the reader can check the reasoning instead of trusting it, and means a different venue produces different verdicts without touching a component.

A bar with a hole in it still looks like a bar

The first version of this page was a standalone HTML file with the segment widths written in by hand: style="width:19.64%", thirty-odd times.

That works until a session moves. Then every downstream width is wrong, the percentages no longer sum to 100 — and nothing fails, because a bar that covers 94% of its container renders as a perfectly plausible bar. The error is invisible precisely where it matters most.

So the frontmatter declares times and only times:

YAML
bands:
  - { from: "12:15", to: "13:30", state: "open" }
  - { from: "13:30", to: "14:30", state: "locked" }

src/lib/diary/schedule.ts derives the widths, and assertBandsCover refuses to render a schedule that does not tile its window exactly — no gaps, no overlaps, no drift past the end. It throws during astro build, naming the day and the exact minute where the sequence broke:

diary: Wednesday Aug 26 bands are not contiguous — expected the next band
to start at 11:00 but it starts at 12:00

The axis labels get the same treatment. Flex-distributing eight tick marks lets the last one claim intrinsic width, which steals space from the other seven and walks every mark leftward — enough to misread the bar by half an hour. They are absolutely positioned at their true fractions instead.

One legend, two views

The session timeline reuses the availability colours rather than inventing its own vocabulary: a crimson dot means that row is a commitment, a teal dot means it isn't. The reader learns the legend once at the top of the page and it holds all the way down, so scrolling to any hour answers the availability question without scrolling back.

Those three colours are hardcoded hex rather than wired to --primary / --accent. They are the brand gradient's own stops, so they stay in family — but the site's accent token swaps value between light and dark, and in session has to mean the same thing in both modes.

The index is server-rendered on purpose

/diary/[...slug] prerenders: an itinerary does not change between requests and belongs on the CDN.

/diary does not, and that is deliberate. It splits upcoming from past against new Date(). Prerendered, that comparison freezes at build time and an event stays under "Coming up" until the next deploy — which, for a page whose entire job is knowing what is next, is the one failure it cannot have. The rule is noted in a comment at both routes so the next person to sweep the site for prerender opportunities does not helpfully break it.

One related detail: an event counts as upcoming through the end of its final day, not from midnight of it. Otherwise a three-day conference drops off the list on its last morning, while you are still standing in it.

The share card previews the answer

The site already generates OG images through satori at /api/og, taking a title, a description and a subtitle pill. A diary event maps onto those three slots without inventing anything: event name, start–end plus venue, and a pill reading Availability.

That would have been enough, and it would also have been a card that says "Availability" while showing none. So the endpoint gained an optional bands parameter and draws the strip — one mini bar per day, in the same proportions and the same three colours as the page it links to. A card and its destination must not disagree about which colour means don't schedule.

The bands travel packed: one letter per state followed by its percentage, days separated by |, giving f64.3t7.1o25.0f3.6|f10.7o7.1l19.6…. The letters delimit themselves, so three days cost about eighty characters — short enough to sit inside an og:image URL that Slack, iMessage and WhatsApp all have to fetch. On diary cards the footer swaps the site tagline for a colour key, because a strip without a legend is decoration.

Every other page's card is untouched: bands is absent, the strip is skipped, and the tagline footer renders exactly as before. Malformed input degrades to a card without a strip rather than a 500.

Naming the hotel was not enough

"Hyatt Regency San Francisco Airport" and "Grand Hyatt at SFO" are two different hotels about two miles apart, and the second one is on the airport. A page that names only the brand is a page that can send someone to the wrong lobby.

The venue is right — all three invitation subject lines say Hyatt Regency, and the Swapcard export corroborates it from a second direction: Grand Peninsula Ballroom, Sandpebble and the Foyer are this property's rooms, and are not the Grand Hyatt's. But being right internally does not help the reader.

So the street address is now a first-class field rather than prose. The venue chip carries 1333 Old Bayshore Hwy, Burlingame, CA 94010 and links to a map, the logistics block says in as many words that this is not the Grand Hyatt at SFO, and the share card leads with the locality — an unfurl is frequently the only line someone reads before deciding where to drive. The travel note also swapped "about five minutes from SFO" for "two miles south of SFO", because one of those is checkable and the other is a guess about traffic.

First entry

TDK Ventures' 100X 2026 — three days at the Hyatt Regency San Francisco Airport, transcribed from three email invitations and the Swapcard agenda export. The export filled in the session-level detail the invitations lacked, including the welcome reception's start time, which existed only in an invite.ics attachment that did not survive its PDF export.

Two honest gaps are marked as gaps rather than papered over: a thirty-minute hole in Wednesday afternoon that the export simply does not program, and a workshop breakout with no room assigned. The page says so in both places. An itinerary that quietly invents a coffee break is worse than one that admits it does not know.