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 f3478689..348be767 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: e05dde251a7f4b1a -raw_hash: f16de82a81e3c77d -vouched: 2026-10-22T15:24:24Z +body_hash: 70b144740681d82c +raw_hash: 2962395e6c4e618a +vouched: 2026-10-22T15:45:21Z lines: 235-824 --- diff --git a/apps/app/src/app.tsx b/apps/app/src/app.tsx index 330bba53..217d0859 100644 --- a/apps/app/src/app.tsx +++ b/apps/app/src/app.tsx @@ -66,7 +66,7 @@ import { useDirectory, Welcome, } from "./onboarding"; -import { TeamSetup } from "./team-setup"; +import { TEAM_SETUP, TeamSetup, TeamSetupItem } from "./team-setup"; import { UpdateNotice } from "./updates"; import { useWindowStorage } from "./window-storage"; @@ -395,7 +395,7 @@ export function App() { const add = () => setTeammate(true); window.addEventListener(CREATE_DIRECTORY, create); window.addEventListener("ace:add-teammate", add); - window.addEventListener("ace:team-setup", open); + window.addEventListener(TEAM_SETUP, open); window.addEventListener("ace:team-invite", invite); window.addEventListener("ace:join", joining); const fragment = new URLSearchParams(location.hash.slice(1)).get("join"); @@ -406,7 +406,7 @@ export function App() { return () => { window.removeEventListener(CREATE_DIRECTORY, create); window.removeEventListener("ace:add-teammate", add); - window.removeEventListener("ace:team-setup", open); + window.removeEventListener(TEAM_SETUP, open); window.removeEventListener("ace:team-invite", invite); window.removeEventListener("ace:join", joining); }; @@ -858,7 +858,7 @@ export function App() { side="left" id="sidebar" className="-my-2 h-[calc(100%+1rem)]" - innerClassName={card ? "flex h-full min-h-0 flex-col" : "h-full min-h-0"} + innerClassName="flex h-full min-h-0 flex-col" > {page === "channels" ? ( @@ -947,6 +947,7 @@ export function App() { /> )} {card && setDismissed(true)} />} + )}
checks.refresh()} /> + + + )} diff --git a/apps/app/src/team-setup.tsx b/apps/app/src/team-setup.tsx index ac890501..8a238526 100644 --- a/apps/app/src/team-setup.tsx +++ b/apps/app/src/team-setup.tsx @@ -11,7 +11,7 @@ import { DialogTitle, Input, } from "@ace/ui"; -import { IconArrowUpRight, IconCheck, IconCopy, IconLoader } from "@ace/ui/icons"; +import { IconArrowUpRight, IconCheck, IconCopy, IconLoader, IconUsers } from "@ace/ui/icons"; import type { TeamPeer } from "@ace/host/protocol"; import { host } from "./host"; @@ -19,6 +19,7 @@ import { useDiagnostics } from "./host-settings"; import { type Invite as Team, link, page, parse } from "./invite"; const DOCS = "https://github.com/githubnext/ace/blob/main/docs/setup.md"; +export const TEAM_SETUP = "ace:team-setup"; const PROVIDERS = { cloudflare: { label: "Cloudflare" }, fly: { label: "Fly.io" }, @@ -40,6 +41,20 @@ const STEPS: Partial> = { * Membership stays the tailnet's: this only decides where the team's channels are listed. * `invite` opens on joining with an invite link's values; `inviting` opens on this team's invite. */ +/** Sidebar footer entry to team setup, where Ask to join and deploying a directory live. */ +export function TeamSetupItem() { + return ( + + ); +} + export function TeamSetup({ github, invite, inviting, onClose }: { github?: string; invite?: Team; diff --git a/docs/setup.md b/docs/setup.md index b2877d98..3845f4ba 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -65,7 +65,9 @@ tailnet finds it in this host's hello and joins it on its own, unless it left a Teams that set up a directory before can keep it deployed as a cell. Whoever sets up the team deploys the services once from a checkout; everyone else joins in `Settings → Team` (Join…) with -the directory's URL and the team's secret, shared privately. Refresh there syncs at once. From a +the directory's URL and the team's secret, shared privately, or uses **Ask to join** next to a +teammate's host in **Team setup** (at the bottom of the sidebar, or `Settings → Team → Set up…`), +which their owner approves in their Ace. Refresh in `Settings → Team` syncs at once. From a checkout, `printf %s "$SECRET" | bun ace key set ACE_SECRET` then `bun ace directory ` does the same. @@ -107,8 +109,10 @@ printf %s https://ace-app..workers.dev | bunx wrangler secret pu ### From the app -**Get your team started** (also under `Settings → Team`) can deploy to Cloudflare for you, with -no checkout. **Deploy** does this, and nothing else: +**Team setup**, at the bottom of the sidebar or `Settings → Team → Set up…`, can deploy to +Cloudflare for you, with no checkout: choose **Get your team started**, then a hosted directory on +Cloudflare. For Fly.io it shows the command to run from a checkout and joins with what it prints. +**Deploy** does this, and nothing else: - Signs in with your own `wrangler login`. If you aren't signed in, it opens Cloudflare's sign-in page in your browser. Ace never sees a Cloudflare password or token.