diff --git a/.lane/memory/apps/app/src/app.tsx/01M4YXNCW4KGWAFS79MPNY1WCR-a-section-reads-offline-only.md b/.lane/attic/apps/app/src/app.tsx/01M4YXNCW4KGWAFS79MPNY1WCR-a-section-reads-offline-only.md similarity index 100% rename from .lane/memory/apps/app/src/app.tsx/01M4YXNCW4KGWAFS79MPNY1WCR-a-section-reads-offline-only.md rename to .lane/attic/apps/app/src/app.tsx/01M4YXNCW4KGWAFS79MPNY1WCR-a-section-reads-offline-only.md diff --git a/.lane/memory/apps/host/src/directory-server.ts/01M5GQBZBTZSAMHT4E82KNJ0KV-a-host-publishes-only-under.md b/.lane/attic/apps/host/src/directory-server.ts/01M5GQBZBTZSAMHT4E82KNJ0KV-a-host-publishes-only-under.md similarity index 100% rename from .lane/memory/apps/host/src/directory-server.ts/01M5GQBZBTZSAMHT4E82KNJ0KV-a-host-publishes-only-under.md rename to .lane/attic/apps/host/src/directory-server.ts/01M5GQBZBTZSAMHT4E82KNJ0KV-a-host-publishes-only-under.md diff --git a/.lane/memory/apps/app/src/app.tsx/01M4YT6XB7NQ89E6F4P375REE7-all-projects-and-project-vie.md b/.lane/memory/apps/app/src/app.tsx/01M4YT6XB7NQ89E6F4P375REE7-all-projects-and-project-vie.md index 65d8e570..bd92d446 100644 --- a/.lane/memory/apps/app/src/app.tsx/01M4YT6XB7NQ89E6F4P375REE7-all-projects-and-project-vie.md +++ b/.lane/memory/apps/app/src/app.tsx/01M4YT6XB7NQ89E6F4P375REE7-all-projects-and-project-vie.md @@ -4,9 +4,9 @@ anchor: function App created: 2026-10-15T03:38:04Z norm: '1' sig: 063d0f00fea02511 -body_hash: 0c4a35fb396f0dd1 -raw_hash: 95e503cbbd9a6cab -vouched: 2026-10-23T12:46:13Z +body_hash: fe9a115489fd2e3a +raw_hash: 4c1a930753d49cc2 +vouched: 2026-10-23T14:00:31Z lines: 235-824 --- diff --git a/.lane/memory/apps/app/src/app.tsx/01M5JMVG9G87F31FGVV571Y6BJ-the-project-list-is-built-on.md b/.lane/memory/apps/app/src/app.tsx/01M5JMVG9G87F31FGVV571Y6BJ-the-project-list-is-built-on.md index 9571965e..082d6017 100644 --- a/.lane/memory/apps/app/src/app.tsx/01M5JMVG9G87F31FGVV571Y6BJ-the-project-list-is-built-on.md +++ b/.lane/memory/apps/app/src/app.tsx/01M5JMVG9G87F31FGVV571Y6BJ-the-project-list-is-built-on.md @@ -4,9 +4,9 @@ anchor: function App created: 2026-10-22T20:29:16Z norm: '1' sig: 063d0f00fea02511 -body_hash: 0c4a35fb396f0dd1 -raw_hash: 95e503cbbd9a6cab -vouched: 2026-10-23T12:46:13Z +body_hash: fe9a115489fd2e3a +raw_hash: 4c1a930753d49cc2 +vouched: 2026-10-23T14:00:32Z lines: 330-1237 --- diff --git a/.lane/memory/apps/app/src/app.tsx/01M5MH0DNYMV7X11XCSTT06FY3-a-section-reads-offline-only.md b/.lane/memory/apps/app/src/app.tsx/01M5MH0DNYMV7X11XCSTT06FY3-a-section-reads-offline-only.md new file mode 100644 index 00000000..0f8e6a16 --- /dev/null +++ b/.lane/memory/apps/app/src/app.tsx/01M5MH0DNYMV7X11XCSTT06FY3-a-section-reads-offline-only.md @@ -0,0 +1,13 @@ +--- +id: 01M5MH0DNYMV7X11XCSTT06FY3 +anchor: function offline +created: 2026-10-23T14:00:32Z +norm: '1' +sig: b254d5cd06084c74 +body_hash: 2e2346b1929d9895 +raw_hash: 185a27b0644079d6 +lines: 243-245 +supersedes: 01M4YXNCW4KGWAFS79MPNY1WCR +--- + +a section reads Offline only when every row is offline; hosted rows never are, so a section with one stays unmarked. Offline is the group's note, which the sidebar never truncates with the label diff --git a/.lane/memory/apps/app/src/onboarding.tsx/01M5MH04Z2H3DMN17PJ11TSE77-people-re-asks-the-roster-on.md b/.lane/memory/apps/app/src/onboarding.tsx/01M5MH04Z2H3DMN17PJ11TSE77-people-re-asks-the-roster-on.md new file mode 100644 index 00000000..ad103df1 --- /dev/null +++ b/.lane/memory/apps/app/src/onboarding.tsx/01M5MH04Z2H3DMN17PJ11TSE77-people-re-asks-the-roster-on.md @@ -0,0 +1,12 @@ +--- +id: 01M5MH04Z2H3DMN17PJ11TSE77 +anchor: function useRoster +created: 2026-10-23T14:00:23Z +norm: '1' +sig: 8b4626d24a0f073f +body_hash: c5f2b6c96f923a02 +raw_hash: ccf5b36e7fabf6a1 +lines: 39-61 +--- + +People re-asks the roster on every listings and settings frame, since the host sends one when a peer comes or goes or the directory changes; the 15 s poll only finds new peers diff --git a/.lane/memory/apps/host/src/directory-server.ts/01M5MH04X7KJRTPMZRD3355B0H-a-host-s-first-publish-to-th.md b/.lane/memory/apps/host/src/directory-server.ts/01M5MH04X7KJRTPMZRD3355B0H-a-host-s-first-publish-to-th.md new file mode 100644 index 00000000..062c0428 --- /dev/null +++ b/.lane/memory/apps/host/src/directory-server.ts/01M5MH04X7KJRTPMZRD3355B0H-a-host-s-first-publish-to-th.md @@ -0,0 +1,12 @@ +--- +id: 01M5MH04X7KJRTPMZRD3355B0H +anchor: function handle +created: 2026-10-23T14:00:23Z +norm: '1' +sig: 4bdf740420c97daf +body_hash: 4d527d67d6667ace +raw_hash: 69fe0886bd43b8aa +lines: 106-145 +--- + +a host's first publish to the served directory calls joined, so the serving host re-reads its own directory now instead of on its heartbeat diff --git a/.lane/memory/apps/host/src/directory-server.ts/01M5MH0DD8WZ8X1APBFA87YYPN-a-host-publishes-only-under.md b/.lane/memory/apps/host/src/directory-server.ts/01M5MH0DD8WZ8X1APBFA87YYPN-a-host-publishes-only-under.md new file mode 100644 index 00000000..c9e0575e --- /dev/null +++ b/.lane/memory/apps/host/src/directory-server.ts/01M5MH0DD8WZ8X1APBFA87YYPN-a-host-publishes-only-under.md @@ -0,0 +1,13 @@ +--- +id: 01M5MH0DD8WZ8X1APBFA87YYPN +anchor: function handle +created: 2026-10-23T14:00:32Z +norm: '1' +sig: 4bdf740420c97daf +body_hash: 4d527d67d6667ace +raw_hash: 69fe0886bd43b8aa +lines: 106-145 +supersedes: 01M5GQBZBTZSAMHT4E82KNJ0KV +--- + +a host publishes only under its whois machine name and login; shares only as the caller's login; a host's first publish calls joined so the serving host re-reads its directory now 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 71f4aaee..cd6e018d 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: 05e5d9affea62a1b -raw_hash: b285921bed152d03 -vouched: 2026-10-23T12:46:13Z +body_hash: c240b7c2f39b2623 +raw_hash: 3915b3b7c7b5bb0f +vouched: 2026-10-23T14:00:32Z 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 9f9ad639..9b49d88f 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: 05e5d9affea62a1b -raw_hash: b285921bed152d03 -vouched: 2026-10-23T12:46:13Z +body_hash: c240b7c2f39b2623 +raw_hash: 3915b3b7c7b5bb0f +vouched: 2026-10-23T14:00:32Z lines: 1-1309 --- diff --git a/apps/app/src/app.tsx b/apps/app/src/app.tsx index a45f2c43..1ed99bea 100644 --- a/apps/app/src/app.tsx +++ b/apps/app/src/app.tsx @@ -240,10 +240,8 @@ function row( const person = (people: People, owner: string) => people[owner] || owner; /** A teammate's section says when none of its channels can open because their hosts are unreachable. */ -function heading(label: string, rows: SidebarRow[]) { - return rows.length > 0 && rows.every((value) => value.connection === "offline") - ? `${label} · Offline` - : label; +function offline(rows: SidebarRow[]): string | undefined { + if (rows.length > 0 && rows.every((value) => value.connection === "offline")) return "Offline"; } /** Pins belong to the person, not the channel, so teammates never see them. */ @@ -317,7 +315,7 @@ function projectGroups( const id: SessionSidebarGroupId = `team:${owner}`; const stored: SessionSidebarGroupId = `team:${owner}:archived`; return [ - { id, label: heading(name, rows), rows, collapsed: folded(collapsed, id) }, + { id, label: name, note: offline(rows), rows, collapsed: folded(collapsed, id) }, { id: stored, label: `${name} · Archived`, @@ -602,7 +600,8 @@ export function App() { const group: SessionSidebarGroupId = `${id}:team:${owner}`; return { id: group, - label: heading(`${name} · ${teammate}`, rows), + label: `${name} · ${teammate}`, + note: offline(rows), rows, sort: "none", collapsed: collapsed[group], diff --git a/apps/app/src/onboarding.tsx b/apps/app/src/onboarding.tsx index 3e8423cc..7f9392f1 100644 --- a/apps/app/src/onboarding.tsx +++ b/apps/app/src/onboarding.tsx @@ -37,6 +37,10 @@ export function useDirectory(): Diagnostics["directory"] | undefined { /** This host and everyone else running Ace on the tailnet, asked again while shown. */ export function useRoster(): Member[] | undefined { + // The host sends listings when a peer comes or goes and settings when the directory changes, so + // People turns Offline with the sidebar and lists a host that joins the directory this one serves. + const channels = useSyncExternalStore(host.subscribe, () => host.channels); + const version = useSyncExternalStore(host.subscribe, () => host.settingsVersion); const [roster, setRoster] = useState(); useEffect(() => { let live = true; @@ -52,7 +56,7 @@ export function useRoster(): Member[] | undefined { live = false; clearInterval(timer); }; - }, []); + }, [channels, version]); return roster; } diff --git a/apps/app/src/team-setup.tsx b/apps/app/src/team-setup.tsx index 931d2daf..70aa6746 100644 --- a/apps/app/src/team-setup.tsx +++ b/apps/app/src/team-setup.tsx @@ -64,7 +64,8 @@ export function TeamSetup({ github, invite, inviting, onClose }: { onClose: () => void; }) { const [step, setStep] = useState(invite ? "join" : inviting ? "invite" : "fork"); - const [starting, setStarting] = useState(true); + // Unset until chosen: a team already on the tailnet makes joining it the default. + const [chosen, setStarting] = useState(); const [listed, setListed] = useState<"hosted" | "peers">("hosted"); const [provider, setProvider] = useState("cloudflare"); const [joined, setJoined] = useState(); @@ -74,6 +75,8 @@ export function TeamSetup({ github, invite, inviting, onClose }: { const directory = checked?.directory.url ? checked.directory : undefined; // Until the host answers, the fork can't tell a first person from one already in a team. const pending = checks.open && !checked && !checks.error; + const peers = useTeamPeers(); + const starting = chosen ?? (!checked?.directory.offered && !peers.length); useEffect(() => { if (!inviting) return; @@ -99,7 +102,7 @@ export function TeamSetup({ github, invite, inviting, onClose }: {
- +