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 0be2d0cd..78e60e64 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: d83ccb35867e607a -raw_hash: 90ddf2e883ff2d5c -vouched: 2026-10-23T14:03:54Z +body_hash: 01c00d19aa6a1d10 +raw_hash: 5464f979a3f6de84 +vouched: 2026-10-23T15:16:53Z 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 4f75ec4e..f3ef82fe 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: d83ccb35867e607a -raw_hash: 90ddf2e883ff2d5c -vouched: 2026-10-23T14:03:54Z +body_hash: 01c00d19aa6a1d10 +raw_hash: 5464f979a3f6de84 +vouched: 2026-10-23T15:16:54Z lines: 330-1237 --- diff --git a/.lane/memory/apps/app/src/app.tsx/01M5MH5GTCT454K45QT5RJE1H5-only-a-window-that-never-cho.md b/.lane/memory/apps/app/src/app.tsx/01M5MH5GTCT454K45QT5RJE1H5-only-a-window-that-never-cho.md index d8fd1338..5af4f5b2 100644 --- a/.lane/memory/apps/app/src/app.tsx/01M5MH5GTCT454K45QT5RJE1H5-only-a-window-that-never-cho.md +++ b/.lane/memory/apps/app/src/app.tsx/01M5MH5GTCT454K45QT5RJE1H5-only-a-window-that-never-cho.md @@ -4,9 +4,9 @@ anchor: function App created: 2026-10-23T14:03:19Z norm: '1' sig: 063d0f00fea02511 -body_hash: d83ccb35867e607a -raw_hash: 90ddf2e883ff2d5c -vouched: 2026-10-23T14:03:54Z +body_hash: 01c00d19aa6a1d10 +raw_hash: 5464f979a3f6de84 +vouched: 2026-10-23T15:16:54Z lines: 334-1357 --- diff --git a/.lane/memory/apps/app/src/projects.ts/01M4YQHHKT4G697PSH2EAWHFCK-a-peer-answers-project-repo.md b/.lane/memory/apps/app/src/projects.ts/01M4YQHHKT4G697PSH2EAWHFCK-a-peer-answers-project-repo.md index a1e0e945..f21ca7ba 100644 --- a/.lane/memory/apps/app/src/projects.ts/01M4YQHHKT4G697PSH2EAWHFCK-a-peer-answers-project-repo.md +++ b/.lane/memory/apps/app/src/projects.ts/01M4YQHHKT4G697PSH2EAWHFCK-a-peer-answers-project-repo.md @@ -4,9 +4,9 @@ anchor: function projects created: 2026-10-15T02:51:27Z norm: '1' sig: a2ec110fa31d81ee -body_hash: 61e3738b3ac3c6ad -raw_hash: edf6c307fa9849bb -vouched: 2026-10-22T22:08:11Z +body_hash: b2cb815c82d2f660 +raw_hash: 6939b16ea6db7a14 +vouched: 2026-10-23T15:16:54Z lines: 42-91 --- diff --git a/.lane/memory/apps/app/src/projects.ts/01M5MNC23AYSNE0E4FR7HXW7FF-projects-follow-channel-crea.md b/.lane/memory/apps/app/src/projects.ts/01M5MNC23AYSNE0E4FR7HXW7FF-projects-follow-channel-crea.md new file mode 100644 index 00000000..80c2a689 --- /dev/null +++ b/.lane/memory/apps/app/src/projects.ts/01M5MNC23AYSNE0E4FR7HXW7FF-projects-follow-channel-crea.md @@ -0,0 +1,12 @@ +--- +id: 01M5MNC23AYSNE0E4FR7HXW7FF +anchor: function projects +created: 2026-10-23T15:16:47Z +norm: '1' +sig: a2ec110fa31d81ee +body_hash: b2cb815c82d2f660 +raw_hash: 6939b16ea6db7a14 +lines: 65-122 +--- + +projects follow channel creation order, not the host's listing order, which moves an offline peer's listings behind the directory's diff --git a/apps/app/src/app.tsx b/apps/app/src/app.tsx index f71ea593..8d328d26 100644 --- a/apps/app/src/app.tsx +++ b/apps/app/src/app.tsx @@ -368,6 +368,7 @@ export function App() { const [offered, setOffered] = useLocalStorage("ace:team-setup", false); // Joining or inviting to a deployed directory, which needs the team secret. const [teaming, setTeaming] = useState(false); + const [returning, setReturning] = useState
(); const [directoryDialog, setDirectoryDialog] = useState(false); const [teammate, setTeammate] = useState(false); const [dismissed, setDismissed] = useLocalStorage("ace:directory-card", false); @@ -400,9 +401,11 @@ export function App() { return () => listeners.forEach((remove) => remove()); }, []); useEffect(() => { - const open = () => { + // Opened from a Settings section, closing Team setup returns there. + const open = (event: Event) => { setSettings(false); setTeaming(true); + setReturning((event as CustomEvent
).detail); }; const invite = () => { setSettings(false); @@ -613,7 +616,8 @@ export function App() { const group: SessionSidebarGroupId = `${id}:team:${owner}`; return { id: group, - label: `${name} · ${teammate}`, + label: name, + owner: teammate, note: offline(rows), rows, sort: "none", @@ -1344,6 +1348,7 @@ export function App() { onClose={() => { setOffered(true); setTeaming(false); + if (returning) setSettings(returning); }} /> )} diff --git a/apps/app/src/directory-settings.tsx b/apps/app/src/directory-settings.tsx index 846c4d7a..63372f6c 100644 --- a/apps/app/src/directory-settings.tsx +++ b/apps/app/src/directory-settings.tsx @@ -100,7 +100,11 @@ export function TeamSettings({ active }: { active: boolean }) { label="Team setup" description="Ask a teammate's host to let you in, or deploy a directory to Cloudflare or Fly.io." > - diff --git a/apps/app/src/projects.ts b/apps/app/src/projects.ts index 7702620e..2f3a1713 100644 --- a/apps/app/src/projects.ts +++ b/apps/app/src/projects.ts @@ -105,7 +105,10 @@ export function projects(opened: Project[], channels: Listing[], host: string): add(host, project.path, project.name, true, project.repo, project); } } - for (const channel of channels) { + // Creation order, not the host's: a peer's listings move behind the directory's while it is + // offline, which would re-sort projects known only through its channels. + const ordered = [...channels].sort((a, b) => a.created - b.created || a.id.localeCompare(b.id)); + for (const channel of ordered) { const path = root(channel); const name = channel.repo?.split("/")[1] || path.replace(/\/+$/, "").split("/").pop() || path; // Directory-only listings are offline or archived, and a hosted channel's host is a workspace. diff --git a/apps/app/src/team-setup.tsx b/apps/app/src/team-setup.tsx index 70aa6746..1b49ff87 100644 --- a/apps/app/src/team-setup.tsx +++ b/apps/app/src/team-setup.tsx @@ -76,7 +76,9 @@ export function TeamSetup({ github, invite, inviting, onClose }: { // 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); + // Someone on the tailnet is already in a team, so this person isn't the first. + const team = !!checked?.directory.offered || peers.length > 0; + const starting = chosen ?? !team; useEffect(() => { if (!inviting) return; @@ -126,10 +128,11 @@ export function TeamSetup({ github, invite, inviting, onClose }: { setStarting(true)} - title="Get your team started" + title={team ? "Set up a separate directory" : "Get your team started"} > - You're the first on this tailnet. Pick where the team's channels are listed. You can - change it later. + {team + ? "Your tailnet already has a team. Only list your channels apart from theirs if you mean to." + : "You're the first on this tailnet. Pick where the team's channels are listed. You can change it later."} + group.owner ? `${group.label} · ${group.owner}` : group.label; + function GroupHeaderContent({ group, collapsed }: { group: SessionSidebarGroup; collapsed: boolean; @@ -392,6 +398,7 @@ function GroupHeaderContent({ group, collapsed }: { )} {group.label} + {group.owner && {` · ${group.owner}`}} {group.note && {` · ${group.note}`}}