From 045ef45ca8c18c2f1c6a5de79f4012aed82107b7 Mon Sep 17 00:00:00 2001 From: iamnbutler Date: Fri, 23 Oct 2026 08:27:29 -0400 Subject: [PATCH 1/3] fix(host): keep a teammate whose helper stops in People as Offline, and only call a channel offline when its host is Why: apps/host/src/gateway.ts#function listed | a directory or remembered channel whose host is a reachable peer is left out, not listed offline: the peer's own listing decides what it shares, and Offline must mean the same in People and the sidebar Why: apps/host/src/peers.ts#function members | People lists every peer that answered since this host started, online only while its listing connection is open, matching which channels unreachable() lists offline --- apps/host/src/gateway.ts | 8 ++++++-- apps/host/src/peers.ts | 26 +++++++++++++++++--------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/apps/host/src/gateway.ts b/apps/host/src/gateway.ts index fab4e061..19b18b9c 100644 --- a/apps/host/src/gateway.ts +++ b/apps/host/src/gateway.ts @@ -163,11 +163,15 @@ async function local(): Promise { }); } -/** Channels only the directory or a peer's last answer knows: their host is unreachable, unless they are hosted. */ +/** + * Channels only the directory or a peer's last answer knows: their host is unreachable, unless they + * are hosted. A reachable host's own listing leaves out what it no longer shares, which is not offline. + */ function listed(seen: Set): Listing[] { return [...directory.read().channels, ...peers.unreachable()] .filter((value) => { if (seen.has(value.id) || value.host === name) return false; + if (!value.hosted && peers.isReachable(value.host)) return false; seen.add(value.id); return true; }) @@ -726,7 +730,7 @@ async function handle( case "team-roster": { const roster: Member[] = [ { host: name, login: catalog.user, online: true, self: true }, - ...peers.online(), + ...peers.members(), ]; const seen = new Set(roster.map((member) => member.host)); for (const { name: host, login } of directory.read().hosts) { diff --git a/apps/host/src/peers.ts b/apps/host/src/peers.ts index 3b7096ef..12475027 100644 --- a/apps/host/src/peers.ts +++ b/apps/host/src/peers.ts @@ -29,6 +29,8 @@ const stalled = new Map(); const named: People = {}; /** Peers whose last hello carried no GitHub login, asked again until one does. */ const unnamed = new Set(); +/** Owners' Tailscale logins of peers that answered, so one that stops answering stays in People as offline. */ +const met = new Map(); function emit() { for (const listener of listeners) listener(); @@ -61,7 +63,10 @@ async function discover(signal: AbortSignal) { }); status = client.status; } - if (client.status === "open") remembered.set(name, client.channels); + if (client.status === "open") { + remembered.set(name, client.channels); + met.set(name, machine.login); + } emit(); }); hosts.set(name, host); @@ -136,9 +141,7 @@ export function listings(): Listing[] { /** Channels of peers that answered before but are unreachable now. */ export function unreachable(): Listing[] { - return [...remembered].flatMap(([name, channels]) => - hosts.get(name)?.client.status === "open" ? [] : channels - ); + return [...remembered].flatMap(([name, channels]) => isReachable(name) ? [] : channels); } /** Peers' owners' GitHub logins as they last answered, reachable or not. */ @@ -174,11 +177,16 @@ export async function teams(): Promise { return found.filter((peer) => !!peer); } -/** Hosts running Ace on the tailnet now; the gateway names their owners from every source. */ -export function online(): Member[] { - return [...hosts.values()].flatMap(({ machine, client }) => - client.status === "open" ? [{ host: machine.name, login: machine.login, online: true }] : [] - ); +/** + * Peers that answered since this host started, online while they answer, like their channels in + * `listings` and `unreachable`; the gateway names their owners from every source. + */ +export function members(): Member[] { + return [...met].map(([host, login]) => ({ host, login, online: isReachable(host) })); +} + +export function isReachable(name: string): boolean { + return hosts.get(name)?.client.status === "open"; } /** Waits for the peer's owner to approve; see asks.ts. */ From d17057e964ff45a9e042b33666fd4df7599a6e51 Mon Sep 17 00:00:00 2001 From: iamnbutler Date: Fri, 23 Oct 2026 08:27:33 -0400 Subject: [PATCH 2/3] chore: confirm lane notes for the presence fix --- ...4YVZ6KGNQ7MFS1RP81MQCE0-a-hosted-channel-s-location.md | 6 +++--- ...Z2BCKRNHVMYEMC0KH3Q6BN-a-hosted-channel-must-be-rea.md | 6 +++--- ...5AZZV2CRMNFXZKM0B3AWRVX-a-move-to-a-cloud-workspace.md | 6 +++--- ...D4BXMERGZ1EZ3EYAFM3YC4-worker-requests-worker-never.md | 6 +++--- ...HYM54QCSF4ESXJSWZK588G-every-project-op-that-change.md | 6 +++--- ...MBP1ATN8456SR3SX4RX6GV-a-directory-or-remembered-ch.md | 8 ++++++++ ...YQFNJCJ32A99NQC28NQ6T9-without-a-team-directory-a-p.md | 5 +++-- ...JBDT9ZCB6X9ANFR2K7NJBE-a-peer-s-hello-can-arrive-be.md | 6 +++--- ...JR1DMRY4D6Q3Z6KTX7JCG5-a-host-with-no-directory-kee.md | 5 +++-- ...MBP1CFQYYTY8BQ9SAYWFMB-people-lists-every-peer-that.md | 8 ++++++++ 10 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 .lane/memory/apps/host/src/gateway.ts/01M5MBP1ATN8456SR3SX4RX6GV-a-directory-or-remembered-ch.md create mode 100644 .lane/memory/apps/host/src/peers.ts/01M5MBP1CFQYYTY8BQ9SAYWFMB-people-lists-every-peer-that.md diff --git a/.lane/memory/apps/host/src/gateway.ts/01M4YVZ6KGNQ7MFS1RP81MQCE0-a-hosted-channel-s-location.md b/.lane/memory/apps/host/src/gateway.ts/01M4YVZ6KGNQ7MFS1RP81MQCE0-a-hosted-channel-s-location.md index 8b8de64f..b571ddc6 100644 --- a/.lane/memory/apps/host/src/gateway.ts/01M4YVZ6KGNQ7MFS1RP81MQCE0-a-hosted-channel-s-location.md +++ b/.lane/memory/apps/host/src/gateway.ts/01M4YVZ6KGNQ7MFS1RP81MQCE0-a-hosted-channel-s-location.md @@ -4,9 +4,9 @@ anchor: function handle created: 2026-10-15T04:08:49Z norm: '1' sig: e597b5fbc8e664ce -body_hash: e0d9256e67814623 -raw_hash: a6afeafd1ea5bd24 -vouched: 2026-10-22T22:21:15Z +body_hash: 177d0cc7c38c0a16 +raw_hash: 587e4316030f6250 +vouched: 2026-10-23T12:27:32Z lines: 266-519 --- diff --git a/.lane/memory/apps/host/src/gateway.ts/01M4Z2BCKRNHVMYEMC0KH3Q6BN-a-hosted-channel-must-be-rea.md b/.lane/memory/apps/host/src/gateway.ts/01M4Z2BCKRNHVMYEMC0KH3Q6BN-a-hosted-channel-must-be-rea.md index 049ba4db..2affc11d 100644 --- a/.lane/memory/apps/host/src/gateway.ts/01M4Z2BCKRNHVMYEMC0KH3Q6BN-a-hosted-channel-must-be-rea.md +++ b/.lane/memory/apps/host/src/gateway.ts/01M4Z2BCKRNHVMYEMC0KH3Q6BN-a-hosted-channel-must-be-rea.md @@ -4,9 +4,9 @@ anchor: function handle created: 2026-10-15T06:00:20Z norm: '1' sig: e597b5fbc8e664ce -body_hash: e0d9256e67814623 -raw_hash: a6afeafd1ea5bd24 -vouched: 2026-10-22T22:21:16Z +body_hash: 177d0cc7c38c0a16 +raw_hash: 587e4316030f6250 +vouched: 2026-10-23T12:27:32Z lines: 281-561 --- diff --git a/.lane/memory/apps/host/src/gateway.ts/01M5AZZV2CRMNFXZKM0B3AWRVX-a-move-to-a-cloud-workspace.md b/.lane/memory/apps/host/src/gateway.ts/01M5AZZV2CRMNFXZKM0B3AWRVX-a-move-to-a-cloud-workspace.md index 7199390f..c857f807 100644 --- a/.lane/memory/apps/host/src/gateway.ts/01M5AZZV2CRMNFXZKM0B3AWRVX-a-move-to-a-cloud-workspace.md +++ b/.lane/memory/apps/host/src/gateway.ts/01M5AZZV2CRMNFXZKM0B3AWRVX-a-move-to-a-cloud-workspace.md @@ -4,9 +4,9 @@ anchor: function handle created: 2026-10-19T21:09:57Z norm: '1' sig: e597b5fbc8e664ce -body_hash: e0d9256e67814623 -raw_hash: a6afeafd1ea5bd24 -vouched: 2026-10-22T22:21:16Z +body_hash: 177d0cc7c38c0a16 +raw_hash: 587e4316030f6250 +vouched: 2026-10-23T12:27:33Z lines: 384-700 supersedes: 01M5APK9YCQH467YNMKAC8BT5B --- diff --git a/.lane/memory/apps/host/src/gateway.ts/01M5D4BXMERGZ1EZ3EYAFM3YC4-worker-requests-worker-never.md b/.lane/memory/apps/host/src/gateway.ts/01M5D4BXMERGZ1EZ3EYAFM3YC4-worker-requests-worker-never.md index 69b2d085..99a70759 100644 --- a/.lane/memory/apps/host/src/gateway.ts/01M5D4BXMERGZ1EZ3EYAFM3YC4-worker-requests-worker-never.md +++ b/.lane/memory/apps/host/src/gateway.ts/01M5D4BXMERGZ1EZ3EYAFM3YC4-worker-requests-worker-never.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-10-20T17:04:56Z norm: '1' sig: 1acdc0f6a0895ac5 -body_hash: 07a0161028eb0d88 -raw_hash: 893db37db5fb95f0 -vouched: 2026-10-23T12:24:46Z +body_hash: bda882f4e56d7c30 +raw_hash: 291f5c1b799528ff +vouched: 2026-10-23T12:27:33Z lines: 1-1153 --- diff --git a/.lane/memory/apps/host/src/gateway.ts/01M5HYM54QCSF4ESXJSWZK588G-every-project-op-that-change.md b/.lane/memory/apps/host/src/gateway.ts/01M5HYM54QCSF4ESXJSWZK588G-every-project-op-that-change.md index 8d2f846e..2d6ea43b 100644 --- a/.lane/memory/apps/host/src/gateway.ts/01M5HYM54QCSF4ESXJSWZK588G-every-project-op-that-change.md +++ b/.lane/memory/apps/host/src/gateway.ts/01M5HYM54QCSF4ESXJSWZK588G-every-project-op-that-change.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-10-22T14:00:47Z norm: '1' sig: 1acdc0f6a0895ac5 -body_hash: 07a0161028eb0d88 -raw_hash: 893db37db5fb95f0 -vouched: 2026-10-23T12:24:47Z +body_hash: bda882f4e56d7c30 +raw_hash: 291f5c1b799528ff +vouched: 2026-10-23T12:27:33Z lines: 1-1309 --- diff --git a/.lane/memory/apps/host/src/gateway.ts/01M5MBP1ATN8456SR3SX4RX6GV-a-directory-or-remembered-ch.md b/.lane/memory/apps/host/src/gateway.ts/01M5MBP1ATN8456SR3SX4RX6GV-a-directory-or-remembered-ch.md new file mode 100644 index 00000000..3c7237ce --- /dev/null +++ b/.lane/memory/apps/host/src/gateway.ts/01M5MBP1ATN8456SR3SX4RX6GV-a-directory-or-remembered-ch.md @@ -0,0 +1,8 @@ +--- +id: 01M5MBP1ATN8456SR3SX4RX6GV +anchor: function listed +created: 2026-10-23T12:27:29Z +norm: '1' +--- + +a directory or remembered channel whose host is a reachable peer is left out, not listed offline: the peer's own listing decides what it shares, and Offline must mean the same in People and the sidebar diff --git a/.lane/memory/apps/host/src/peers.ts/01M4YQFNJCJ32A99NQC28NQ6T9-without-a-team-directory-a-p.md b/.lane/memory/apps/host/src/peers.ts/01M4YQFNJCJ32A99NQC28NQ6T9-without-a-team-directory-a-p.md index 2b499350..5c9235f6 100644 --- a/.lane/memory/apps/host/src/peers.ts/01M4YQFNJCJ32A99NQC28NQ6T9-without-a-team-directory-a-p.md +++ b/.lane/memory/apps/host/src/peers.ts/01M4YQFNJCJ32A99NQC28NQ6T9-without-a-team-directory-a-p.md @@ -4,8 +4,9 @@ anchor: function unreachable created: 2026-10-15T02:50:26Z norm: '1' sig: dafb01d962243d11 -body_hash: b1750761c8fca9d0 -raw_hash: 4da6cfbe96c64428 +body_hash: 81d4e7aafce6dcdc +raw_hash: b01930e371251a86 +vouched: 2026-10-23T12:27:33Z lines: 93-97 --- diff --git a/.lane/memory/apps/host/src/peers.ts/01M5JBDT9ZCB6X9ANFR2K7NJBE-a-peer-s-hello-can-arrive-be.md b/.lane/memory/apps/host/src/peers.ts/01M5JBDT9ZCB6X9ANFR2K7NJBE-a-peer-s-hello-can-arrive-be.md index e37e86d5..5f4e7051 100644 --- a/.lane/memory/apps/host/src/peers.ts/01M5JBDT9ZCB6X9ANFR2K7NJBE-a-peer-s-hello-can-arrive-be.md +++ b/.lane/memory/apps/host/src/peers.ts/01M5JBDT9ZCB6X9ANFR2K7NJBE-a-peer-s-hello-can-arrive-be.md @@ -4,9 +4,9 @@ anchor: function discover created: 2026-10-22T17:44:31Z norm: '1' sig: ab69f97386d0db50 -body_hash: 9b0a75f289475140 -raw_hash: 4713ceb74c23d1f3 -vouched: 2026-10-22T21:25:00Z +body_hash: 2507fdcc2496fd80 +raw_hash: 595cfbfed333995e +vouched: 2026-10-23T12:27:33Z lines: 37-74 supersedes: 01M4YX4FKJR9W30M9Y5HVTKBW4 --- diff --git a/.lane/memory/apps/host/src/peers.ts/01M5JR1DMRY4D6Q3Z6KTX7JCG5-a-host-with-no-directory-kee.md b/.lane/memory/apps/host/src/peers.ts/01M5JR1DMRY4D6Q3Z6KTX7JCG5-a-host-with-no-directory-kee.md index 9f177b65..9aab8c07 100644 --- a/.lane/memory/apps/host/src/peers.ts/01M5JR1DMRY4D6Q3Z6KTX7JCG5-a-host-with-no-directory-kee.md +++ b/.lane/memory/apps/host/src/peers.ts/01M5JR1DMRY4D6Q3Z6KTX7JCG5-a-host-with-no-directory-kee.md @@ -4,8 +4,9 @@ anchor: function discover created: 2026-10-22T21:24:56Z norm: '1' sig: ab69f97386d0db50 -body_hash: 9b0a75f289475140 -raw_hash: 4713ceb74c23d1f3 +body_hash: 2507fdcc2496fd80 +raw_hash: 595cfbfed333995e +vouched: 2026-10-23T12:27:33Z lines: 37-75 --- diff --git a/.lane/memory/apps/host/src/peers.ts/01M5MBP1CFQYYTY8BQ9SAYWFMB-people-lists-every-peer-that.md b/.lane/memory/apps/host/src/peers.ts/01M5MBP1CFQYYTY8BQ9SAYWFMB-people-lists-every-peer-that.md new file mode 100644 index 00000000..94695ab6 --- /dev/null +++ b/.lane/memory/apps/host/src/peers.ts/01M5MBP1CFQYYTY8BQ9SAYWFMB-people-lists-every-peer-that.md @@ -0,0 +1,8 @@ +--- +id: 01M5MBP1CFQYYTY8BQ9SAYWFMB +anchor: function members +created: 2026-10-23T12:27:29Z +norm: '1' +--- + +People lists every peer that answered since this host started, online only while its listing connection is open, matching which channels unreachable() lists offline From dfbf0a96bb845adcd7adaa8b4549303fd7211abb Mon Sep 17 00:00:00 2001 From: iamnbutler Date: Fri, 23 Oct 2026 08:27:35 -0400 Subject: [PATCH 3/3] lane: sync fix-presence memory --- ...01M5MBP1ATN8456SR3SX4RX6GV-a-directory-or-remembered-ch.md | 4 ++++ ...01M5MBP1CFQYYTY8BQ9SAYWFMB-people-lists-every-peer-that.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.lane/memory/apps/host/src/gateway.ts/01M5MBP1ATN8456SR3SX4RX6GV-a-directory-or-remembered-ch.md b/.lane/memory/apps/host/src/gateway.ts/01M5MBP1ATN8456SR3SX4RX6GV-a-directory-or-remembered-ch.md index 3c7237ce..256f4ffa 100644 --- a/.lane/memory/apps/host/src/gateway.ts/01M5MBP1ATN8456SR3SX4RX6GV-a-directory-or-remembered-ch.md +++ b/.lane/memory/apps/host/src/gateway.ts/01M5MBP1ATN8456SR3SX4RX6GV-a-directory-or-remembered-ch.md @@ -3,6 +3,10 @@ id: 01M5MBP1ATN8456SR3SX4RX6GV anchor: function listed created: 2026-10-23T12:27:29Z norm: '1' +sig: ae9e4719dcd64268 +body_hash: 151a865bdfdc6d8d +raw_hash: 89759af61ffab432 +lines: 170-185 --- a directory or remembered channel whose host is a reachable peer is left out, not listed offline: the peer's own listing decides what it shares, and Offline must mean the same in People and the sidebar diff --git a/.lane/memory/apps/host/src/peers.ts/01M5MBP1CFQYYTY8BQ9SAYWFMB-people-lists-every-peer-that.md b/.lane/memory/apps/host/src/peers.ts/01M5MBP1CFQYYTY8BQ9SAYWFMB-people-lists-every-peer-that.md index 94695ab6..d0a0b5e9 100644 --- a/.lane/memory/apps/host/src/peers.ts/01M5MBP1CFQYYTY8BQ9SAYWFMB-people-lists-every-peer-that.md +++ b/.lane/memory/apps/host/src/peers.ts/01M5MBP1CFQYYTY8BQ9SAYWFMB-people-lists-every-peer-that.md @@ -3,6 +3,10 @@ id: 01M5MBP1CFQYYTY8BQ9SAYWFMB anchor: function members created: 2026-10-23T12:27:29Z norm: '1' +sig: 9304eaec2689fb32 +body_hash: 2021266b63565369 +raw_hash: 22504780ae4cf5bb +lines: 184-186 --- People lists every peer that answered since this host started, online only while its listing connection is open, matching which channels unreachable() lists offline