From 3e4b46fa80ca44a3232f383a8c9e778e50865c7a Mon Sep 17 00:00:00 2001 From: iamnbutler Date: Fri, 23 Oct 2026 08:46:08 -0400 Subject: [PATCH 1/2] fix: name the build's command when signed out, report ace open and directory joins, rejoin a served directory, and drop a deleted message's toolbar Why: apps/host/src/helper.ts | Ace Helper's config.command must match the link apps/desktop/src/index.ts installs: ace for stable, ace- otherwise --- ...F4P375REE7-all-projects-and-project-vie.md | 6 ++--- ...GVV571Y6BJ-the-project-list-is-built-on.md | 6 ++--- ...T78X1E0KAE-every-command-but-serve-must.md | 6 ++--- ...22YT2HMZDZ-ace-info-prints-agent-access.md | 6 ++--- ...3EYAFM3YC4-worker-requests-worker-never.md | 6 ++--- ...XJSWZK588G-every-project-op-that-change.md | 6 ++--- ...ADSQF5H00A-helpers-must-stay-first-on-t.md | 6 ++--- ...RC5G1TQZMHG-ace-helper-s-config-command.md | 8 ++++++ apps/app/src/app.tsx | 7 +++--- apps/app/src/host.ts | 4 +-- apps/app/src/onboarding.tsx | 25 +++++++++++-------- apps/host/src/cli.ts | 4 +-- apps/host/src/gateway.ts | 3 ++- apps/host/src/helper.ts | 5 +++- .../ui/src/components/timeline/timeline.tsx | 15 ++++++++--- 15 files changed, 69 insertions(+), 44 deletions(-) create mode 100644 .lane/memory/apps/host/src/helper.ts/01M5MCR68GHN6F4RC5G1TQZMHG-ace-helper-s-config-command.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 c2421f88..65d8e570 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: cffc6c2ce7bd7e6d -raw_hash: 14cee7a4562529f6 -vouched: 2026-10-23T12:32:53Z +body_hash: 0c4a35fb396f0dd1 +raw_hash: 95e503cbbd9a6cab +vouched: 2026-10-23T12:46:13Z 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 39753cd7..9571965e 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: cffc6c2ce7bd7e6d -raw_hash: 14cee7a4562529f6 -vouched: 2026-10-23T12:32:53Z +body_hash: 0c4a35fb396f0dd1 +raw_hash: 95e503cbbd9a6cab +vouched: 2026-10-23T12:46:13Z lines: 330-1237 --- diff --git a/.lane/memory/apps/host/src/cli.ts/01M4YY5SXJFQK8VXT78X1E0KAE-every-command-but-serve-must.md b/.lane/memory/apps/host/src/cli.ts/01M4YY5SXJFQK8VXT78X1E0KAE-every-command-but-serve-must.md index 9ea4e10a..e67edf80 100644 --- a/.lane/memory/apps/host/src/cli.ts/01M4YY5SXJFQK8VXT78X1E0KAE-every-command-but-serve-must.md +++ b/.lane/memory/apps/host/src/cli.ts/01M4YY5SXJFQK8VXT78X1E0KAE-every-command-but-serve-must.md @@ -4,9 +4,9 @@ anchor: function main created: 2026-10-15T04:47:23Z norm: '1' sig: 7d5d3f63a3bddde4 -body_hash: cb848608fe3526ea -raw_hash: 97a687d2dea1a6e3 -vouched: 2026-10-23T12:25:08Z +body_hash: 5b0f98c8033bcdda +raw_hash: ec46efe56f9ba131 +vouched: 2026-10-23T12:46:13Z lines: 253-482 --- diff --git a/.lane/memory/apps/host/src/cli.ts/01M5J0BZWP8SCBK522YT2HMZDZ-ace-info-prints-agent-access.md b/.lane/memory/apps/host/src/cli.ts/01M5J0BZWP8SCBK522YT2HMZDZ-ace-info-prints-agent-access.md index 09ce6f84..a701b737 100644 --- a/.lane/memory/apps/host/src/cli.ts/01M5J0BZWP8SCBK522YT2HMZDZ-ace-info-prints-agent-access.md +++ b/.lane/memory/apps/host/src/cli.ts/01M5J0BZWP8SCBK522YT2HMZDZ-ace-info-prints-agent-access.md @@ -4,9 +4,9 @@ anchor: function main created: 2026-10-22T14:31:16Z norm: '1' sig: 7d5d3f63a3bddde4 -body_hash: cb848608fe3526ea -raw_hash: 97a687d2dea1a6e3 -vouched: 2026-10-23T12:25:08Z +body_hash: 5b0f98c8033bcdda +raw_hash: ec46efe56f9ba131 +vouched: 2026-10-23T12:46:13Z lines: 332-614 --- 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 99a70759..71f4aaee 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: bda882f4e56d7c30 -raw_hash: 291f5c1b799528ff -vouched: 2026-10-23T12:27:33Z +body_hash: 05e5d9affea62a1b +raw_hash: b285921bed152d03 +vouched: 2026-10-23T12:46:13Z 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 2d6ea43b..9f9ad639 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: bda882f4e56d7c30 -raw_hash: 291f5c1b799528ff -vouched: 2026-10-23T12:27:33Z +body_hash: 05e5d9affea62a1b +raw_hash: b285921bed152d03 +vouched: 2026-10-23T12:46:13Z lines: 1-1309 --- diff --git a/.lane/memory/apps/host/src/helper.ts/01M4V5N7FNEDME70ADSQF5H00A-helpers-must-stay-first-on-t.md b/.lane/memory/apps/host/src/helper.ts/01M4V5N7FNEDME70ADSQF5H00A-helpers-must-stay-first-on-t.md index f41acb0f..c261a415 100644 --- a/.lane/memory/apps/host/src/helper.ts/01M4V5N7FNEDME70ADSQF5H00A-helpers-must-stay-first-on-t.md +++ b/.lane/memory/apps/host/src/helper.ts/01M4V5N7FNEDME70ADSQF5H00A-helpers-must-stay-first-on-t.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-10-13T17:41:10Z norm: '1' sig: 4b03c8eb05d682d4 -body_hash: d3b539175eba5e12 -raw_hash: 6e008c8c2632d2b4 -vouched: 2026-10-22T21:10:47Z +body_hash: f52ecd279152f67f +raw_hash: a18302a5ba37aa01 +vouched: 2026-10-23T12:46:13Z lines: 1-39 --- diff --git a/.lane/memory/apps/host/src/helper.ts/01M5MCR68GHN6F4RC5G1TQZMHG-ace-helper-s-config-command.md b/.lane/memory/apps/host/src/helper.ts/01M5MCR68GHN6F4RC5G1TQZMHG-ace-helper-s-config-command.md new file mode 100644 index 00000000..02e69332 --- /dev/null +++ b/.lane/memory/apps/host/src/helper.ts/01M5MCR68GHN6F4RC5G1TQZMHG-ace-helper-s-config-command.md @@ -0,0 +1,8 @@ +--- +id: 01M5MCR68GHN6F4RC5G1TQZMHG +anchor: '@file' +created: 2026-10-23T12:46:08Z +norm: '1' +--- + +Ace Helper's config.command must match the link apps/desktop/src/index.ts installs: ace for stable, ace- otherwise diff --git a/apps/app/src/app.tsx b/apps/app/src/app.tsx index eec87bde..a45f2c43 100644 --- a/apps/app/src/app.tsx +++ b/apps/app/src/app.tsx @@ -145,11 +145,11 @@ function Disconnected() { const answers = useAnswers(); const refusal = useRefused(); if (desktop) return <>Disconnected from Ace Helper. Open Settings → This Mac to start it.; - if (typeof refusal === "object") return <>{refusal.other}; - if (refusal === "signed-out") { + if (refusal && "other" in refusal) return <>{refusal.other}; + if (refusal) { return ( <> - This browser isn't signed in to your host. Run ace open{" "} + This browser isn't signed in to your host. Run {refusal.command} open{" "} on that Mac, or open its sign-in link again. ); @@ -1066,6 +1066,7 @@ export function App() { )} {card && ( setDismissed(true)} onChange={() => setSettings("team")} diff --git a/apps/app/src/host.ts b/apps/app/src/host.ts index 0f3e64cc..75354b34 100644 --- a/apps/app/src/host.ts +++ b/apps/app/src/host.ts @@ -36,12 +36,12 @@ window.addEventListener("hashchange", () => { * with a new token, or because it is a teammate's host (403, whose text names its owner). A * browser hides a WebSocket's status, so this asks over HTTP with what the socket sends. */ -export async function refused(): Promise<"signed-out" | { other: string } | undefined> { +export async function refused(): Promise<{ command: string } | { other: string } | undefined> { const response = await fetch(gateway!.replace(/^ws/, "http"), { cache: "no-store", ...(token ? { headers: { authorization: `Bearer ${token}` } } : {}), }).catch(() => undefined); - if (response?.status === 401) return "signed-out"; + if (response?.status === 401) return { command: await response.text() }; if (response?.status === 403) return { other: await response.text() }; } diff --git a/apps/app/src/onboarding.tsx b/apps/app/src/onboarding.tsx index f191e910..3e8423cc 100644 --- a/apps/app/src/onboarding.tsx +++ b/apps/app/src/onboarding.tsx @@ -243,11 +243,12 @@ export function Welcome({ github, onClose }: { github?: string; onClose: () => v } /** - * Sidebar footer while this host has no directory: join the one a teammate's host names, or - * create one when nobody has. + * Sidebar footer while this host has no directory: rejoin the one it still serves, join the one a + * teammate's host names, or create one when nobody has. */ export function DirectoryCard( - { offered, onDismiss, onChange }: { + { serving, offered, onDismiss, onChange }: { + serving: boolean; offered?: { url: string; host: string }; onDismiss: () => void; onChange: () => void; @@ -256,11 +257,13 @@ export function DirectoryCard( const [joining, setJoining] = useState(false); const [error, setError] = useState(""); - async function join(url: string) { + async function join(url?: string) { setJoining(true); setError(""); try { - await host.request({ op: "directory-set", url }); + await host.request( + url ? { op: "directory-set", url } : { op: "directory-serve", serve: true }, + ); } catch (error) { setError((error as Error).message); } finally { @@ -282,13 +285,15 @@ export function DirectoryCard( - {offered + {/* `ace directory off` leaves the directory this Mac still serves; Join serves it again. */} + {serving || offered ? ( <>

- {offered.host}{" "} - serves your team's directory. This Mac isn't in it, so its channels aren't listed - while it's offline. + {serving + ? "This Mac serves your team's directory but isn't in it" + : `${offered?.host} serves your team's directory. This Mac isn't in it`}, so its + channels aren't listed while it's offline.

{error &&

{error}

}
@@ -296,7 +301,7 @@ export function DirectoryCard( size="sm" className="flex-1" disabled={joining} - onClick={() => void join(offered.url)} + onClick={() => void join(serving ? undefined : offered?.url)} > {joining ? "Joining…" : "Join"} diff --git a/apps/host/src/cli.ts b/apps/host/src/cli.ts index fccb3b5e..ba4c7ee5 100755 --- a/apps/host/src/cli.ts +++ b/apps/host/src/cli.ts @@ -558,7 +558,7 @@ async function main() { } const { directory: status, tailscale } = await local({ op: "diagnostics" }); if (!status.url) return console.log("No directory"); - console.log(status.url); + console.log(url ? `Joined ${status.url}` : status.url); if (tailscale.loopback) { return console.log(dim("This host runs with --loopback, so it doesn't sync it.")); } @@ -594,7 +594,7 @@ async function main() { url.hash = new URLSearchParams({ token: token() }).toString(); const result = Bun.spawnSync([process.platform === "darwin" ? "open" : "xdg-open", url.href]); if (!result.success) throw new Error("Could not open Ace in your browser"); - return; + return console.log(`Opened ${url.origin} in your browser, signed in to this host.`); } case "serve": return serve(Number(flags.port || config.port), !!flags.loopback, flags.demo); diff --git a/apps/host/src/gateway.ts b/apps/host/src/gateway.ts index 19b18b9c..a609250d 100644 --- a/apps/host/src/gateway.ts +++ b/apps/host/src/gateway.ts @@ -1053,7 +1053,8 @@ function owner(port: number): Server { }); } if (url.pathname === "/ws") { - if (!access.authorizes(request)) return new Response("Unauthorized", { status: 401 }); + // The body names this build's command, so a signed-out page can say what to run. + if (!access.authorizes(request)) return new Response(config.command, { status: 401 }); const protocols = request.headers.get("sec-websocket-protocol")?.split(",").map((value) => value.trim() ); diff --git a/apps/host/src/helper.ts b/apps/host/src/helper.ts index 9e6d052e..a9e454bf 100644 --- a/apps/host/src/helper.ts +++ b/apps/host/src/helper.ts @@ -24,9 +24,12 @@ config.version = ACE_VERSION; config.app = join(dirname(process.execPath), "..", "Resources", "app", "web"); config.services = join(dirname(process.execPath), "..", "Resources", "services"); config.worker = [process.execPath, "--worker"]; +// The app links its command as `ace` for stable and `ace-` otherwise, as its identifier ends. +config.command = cli + ? basename(process.argv0) + : ACE_IDENTIFIER.replace("dev.ace.desktop", "ace").replace(".", "-"); if (cli) { - config.command = basename(process.argv0); await import("./cli"); } else if (worker) { // A compiled executable dispatches its worker entry itself, rather than running a TS file. diff --git a/packages/ui/src/components/timeline/timeline.tsx b/packages/ui/src/components/timeline/timeline.tsx index dea2c610..8b810679 100644 --- a/packages/ui/src/components/timeline/timeline.tsx +++ b/packages/ui/src/components/timeline/timeline.tsx @@ -734,19 +734,26 @@ function GroupView(props: GroupProps) { onRowEnter(node?.dataset.messageId || uid); }) : undefined; - let toolbar = (id: string, fit: number, sticky?: boolean) => - active === id && ( + let toolbar = (id: string, fit: number, sticky?: boolean) => { + if (active !== id) return false; + let actions = typeof props.actions === "function" ? props.actions(id) : props.actions; + let canEdit = own && !!editable?.has(id); + // A message with nothing left to act on, such as a deleted one, gets no toolbar. + let shared = (["react", "copy", "permalink"] as const).every((key) => actions?.[key] === false); + if (shared && !canEdit && !(own && actions?.delete !== false)) return false; + return ( onToolbarAction?.(id, action)} onOpenChange={onOpenChange} /> ); + }; return (
Date: Fri, 23 Oct 2026 08:46:18 -0400 Subject: [PATCH 2/2] lane: sync fix-leftovers memory --- .../01M5MCR68GHN6F4RC5G1TQZMHG-ace-helper-s-config-command.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.lane/memory/apps/host/src/helper.ts/01M5MCR68GHN6F4RC5G1TQZMHG-ace-helper-s-config-command.md b/.lane/memory/apps/host/src/helper.ts/01M5MCR68GHN6F4RC5G1TQZMHG-ace-helper-s-config-command.md index 02e69332..e03125cd 100644 --- a/.lane/memory/apps/host/src/helper.ts/01M5MCR68GHN6F4RC5G1TQZMHG-ace-helper-s-config-command.md +++ b/.lane/memory/apps/host/src/helper.ts/01M5MCR68GHN6F4RC5G1TQZMHG-ace-helper-s-config-command.md @@ -3,6 +3,10 @@ id: 01M5MCR68GHN6F4RC5G1TQZMHG anchor: '@file' created: 2026-10-23T12:46:08Z norm: '1' +sig: 4b03c8eb05d682d4 +body_hash: f52ecd279152f67f +raw_hash: a18302a5ba37aa01 +lines: 1-52 --- Ace Helper's config.command must match the link apps/desktop/src/index.ts installs: ace for stable, ace- otherwise