Skip to content

feat(app): offer Get your team started and deploy the team's services from the app - #553

Merged
iamnbutler merged 10 commits into
mainfrom
team-onboarding
Oct 22, 2026
Merged

iamnbutler merged 10 commits into
mainfrom
team-onboarding

Conversation

@iamnbutler

@iamnbutler iamnbutler commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

First run now opens a team setup dialog, built from the reviewed design in #549. It opens once, when there are no projects yet, and Settings → Team → Set up your team opens it again.

  • Fork: Tailscale and GitHub checks, then Get your team started, Join your team, or Just me for now.
  • Join your team: today's directory URL and team secret join form. Join a team without pasting a directory URL and secret #538 will redesign it.
  • Directory: a hosted directory on Cloudflare or Fly.io (recommended), or Peer to peer only, which lists what you give up. Host myself ↗ links to the celld docs.
  • Remote channels: hosted channels start off. Cloud workspaces is a disabled "Soon" row while the compute work is separate.
  • Set up (Cloudflare): a Deploy button. Ace Helper deploys the directory, and hosted channels if they're on, to your Cloudflare account with your wrangler login. It opens the browser sign-in if you aren't signed in. It makes the team secret, joins the directory, and lists each step as it runs. Nothing to copy or paste, and no checkout. Show the command reveals the equivalent bun services/deploy.ts cloudflare from feat: deploy the team's services to Cloudflare or Fly.io with one command #552.
  • Set up (Fly.io): still shows feat: deploy the team's services to Cloudflare or Fly.io with one command #552's script to run from a checkout, then Check and join. In-app Fly deploys would need flyctl and celld inside the app.
  • Naming: no team name to type. The services are named after the tailnet, so tail1234.ts.net gets ace-directory-tail1234.
  • Invite: download Ace, get on the tailnet, and join with the directory URL and secret (with copy buttons), plus a warning to send them privately. Choosing peer to peer skips setup and ends on a shorter version of this screen.

How it works: the desktop build prebuilds both services with wrangler deploy --dry-run --outdir into Resources/services/<service>/ (with a wrangler.json set to no_bundle). The directory is 12 KB and the channel service is 5 MB. A new team-deploy host op runs wrangler@4.140.0 through Ace Helper's own Bun (BUN_BE_BUN=1), so it needs no Node. It streams progress with waiting frames, the same way compute-start does, and only the owner's local app can call it, like the other keychain ops. A source run deploys from services/ instead.

Tested in the dev build (bun desktop dev): clicked through to Deploy with hosted channels off. The deploy signed in, deployed ace-directory-tail49836a, and joined in about 15 seconds. Settings then showed the directory, with the secret saved in Keychain. The Worker refused unauthenticated requests (401). Afterwards I deleted the Worker and left the directory.

Earlier, tested headlessly against a fresh host profile, in light and dark:

Depends on #552 for the Fly.io script and tailnet naming. Part of #549, #550.

Built in Ace

No. Claude Code in a terminal wrote this, drove a headless browser for the checks, and clicked through the dev build with Desktop Tools, because the session started outside Ace. It wasn't blocked by a missing Ace capability, so no issue was filed.

Also includes #561 (invite link) and #563 (ask to join over the tailnet), squash-merged into this branch.

…tarted

Ace Helper ships prebuilt directory and channel bundles and deploys them with the
owner's wrangler login, naming them after the tailnet, then joins the directory.
The Set up step shows progress instead of a command to copy. Fly.io still runs
the checkout script.

Why: apps/host/src/team.ts | Ace Helper runs wrangler as plain Bun via BUN_BE_BUN, and deploys the bundles apps/desktop/scripts/helper.ts writes to Resources/services; both layouts must stay in step.
@iamnbutler iamnbutler changed the title feat(app): offer Get your team started or Join your team on first run feat(app): offer Get your team started and deploy the team's services from the app Oct 21, 2026
#561)

* feat(app): invite teammates with one link that fills in Join your team

The Invite screen and Settings → Team → Invite… copy an ace://join link carrying the directory
URL and team secret. Pasting it into either join form fills both fields, and the desktop app
registers its channel's scheme so opening the link brings up Join your team, filled in, for the
person to check and join.

Why: apps/desktop/electrobun.config.ts | each build channel registers its own scheme (ace, ace-canary, ace-dev) so a dev or Canary build never answers stable's invite links; apps/app/src/invite.ts reads all of them.
Why: apps/app/src/invite.ts#function parse | an invite link only fills the join form; joining must stay an explicit Check and join, since a link can name any directory.

* lane: confirm notes for invite link

* lane: sync invite-link memory

* feat(app): send invites as an https link to the join page

Chat apps linkify https, not ace://. The link opens ace.githubnext.com/join, which offers the download and hands off to ace://join. Pasting either form still fills Join your team.

Why: apps/app/src/invite.ts#function link | the invite must stay in the URL fragment so the secret never reaches the site's server

* lane: confirm invite parse note

* lane: sync invite-link memory

* feat(app): ask a teammate's host to join the team over the tailnet (#563)

* feat(app): ask a teammate's host to join the team over the tailnet

A host that isn't in a team finds peers whose hello names a team directory and offers
"<person> on <machine> is in a team. Ask to join". The peer's owner approves or denies in
their app; on approval the directory URL and secret travel over the tailnet and the asking
host joins as with directory-set. Asks end on deny, disconnect, a repeat ask, or after five
minutes.

Why: apps/host/src/protocol.ts#type Hello | hello answers any tailnet peer, so team carries only the directory's host, never the secret
Why: apps/host/src/gateway.ts#function asksChanged | only the local app can answer an ask (team-answer is a keychain op), so asks reach local sockets only

* lane: confirm notes for ask to join

* lane: sync ask-to-join memory
# Conflicts:
#	.lane/memory/apps/host/src/gateway.ts/01M5D4BXMERGZ1EZ3EYAFM3YC4-worker-requests-worker-never.md
#	apps/app/src/directory-settings.tsx
@iamnbutler
iamnbutler merged commit c253447 into main Oct 22, 2026
1 check passed
@iamnbutler
iamnbutler deleted the team-onboarding branch October 22, 2026 00:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant