Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how. | 1 | /** |
| 2 | * When each person was last on a workspace's Home page, kept in their own | |
| 3 | * feed (src/feed.ts) so it is the same on every device. Home reads it to | |
| 4 | * show what happened while they were away, and marks it once they have | |
| 5 | * looked (never on first paint), so a quick refresh does not wipe what | |
| 6 | * they have not seen. | |
| 7 | * | |
| 8 | * Marks close together are one visit: a refresh, or coming back to Home | |
| 9 | * from another page a few minutes later, keeps showing what happened | |
| 10 | * before the visit began. A visit ends once no mark has come for | |
| 11 | * `VISIT_GAP_MS`; the next one shows what happened since its last mark. | |
| 12 | */ | |
| 13 | ||
| 14 | /** The longest slug a visit is kept for. */ | |
| 15 | const MAX_SLUG = 64; | |
| 16 | /** How far behind now a mark may be: Home marks the time the page loaded, at most a day before. */ | |
| 17 | const MAX_BEHIND_MS = 24 * 60 * 60 * 1000; | |
| 18 | /** Marks closer than this are one visit. */ | |
| 19 | export const VISIT_GAP_MS = 30 * 60 * 1000; | |
| 20 | ||
| 21 | /** What is kept per workspace: the latest mark, and the last mark of the visit before this one. */ | |
| 22 | export type Visit = { seen_at: number; previous_at: number | null }; | |
| 23 | ||
| 24 | /** A workspace slug as visits key it, or null when it is not one. */ | |
| 25 | export function visitWorkspace(value: unknown): string | null { | |
| 26 | if (typeof value !== "string") return null; | |
| 27 | const slug = value.trim().toLowerCase(); | |
| 28 | return slug && slug.length <= MAX_SLUG && /^[a-z0-9][a-z0-9-]*$/.test(slug) ? slug : null; | |
| 29 | } | |
| 30 | ||
| 31 | /** | |
| 32 | * The visit to keep after a mark at `at` (RFC 3339 or epoch ms): never | |
| 33 | * later than now, never more than a day behind it, and never earlier than | |
| 34 | * what is kept, so an old tab marking late does not move it back. Null | |
| 35 | * when there is nothing to change. | |
| 36 | */ | |
| 37 | export function markVisit(kept: Visit | null, at: unknown, now: number): Visit | null { | |
| 38 | const given = typeof at === "string" ? Date.parse(at) : typeof at === "number" ? at : Number.NaN; | |
| 39 | if (!Number.isFinite(given)) return null; | |
| 40 | const mark = Math.min(now, Math.max(given, now - MAX_BEHIND_MS)); | |
| 41 | if (!kept) return { seen_at: mark, previous_at: null }; | |
| 42 | if (kept.seen_at >= mark) return null; | |
| 43 | // The same visit: what it shows still starts where it began. | |
| 44 | if (mark - kept.seen_at < VISIT_GAP_MS) return { seen_at: mark, previous_at: kept.previous_at }; | |
| 45 | return { seen_at: mark, previous_at: kept.seen_at }; | |
| 46 | } | |
| 47 | ||
| 48 | /** | |
| 49 | * When the person was last here, as Home counts from: during a visit, the | |
| 50 | * end of the one before it; after one, its last mark. Null when there was | |
| 51 | * none before. | |
| 52 | */ | |
| 53 | export function lastVisit(kept: Visit | null, now: number): number | null { | |
| 54 | if (!kept) return null; | |
| 55 | return now - kept.seen_at < VISIT_GAP_MS ? kept.previous_at : kept.seen_at; | |
| 56 | } |