From 85655701a4b893f61f49b3d3a0176f210af77054 Mon Sep 17 00:00:00 2001 From: iamnbutler Date: Fri, 23 Oct 2026 08:32:48 -0400 Subject: [PATCH] fix: small workspace polish: hints for dead shortcuts, archived in All Projects, bold and strike while typing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The rail shows no account avatar until the host names its user, instead of a placeholder `A`. - ⌘3/⌘4 in a project without GitHub, and Cmd+O or Clone while disconnected, say why they did nothing. - All Projects lists your archived channels in an Archived group, as the archived notice promises. - `**bold**` and `~~strike~~` convert as their closing marker is typed. - The .gitignore card gives the git commands to copy. - Team pulse entries show when the run ended and cover at least the last day, so a reload no longer clears them. --- ...F4P375REE7-all-projects-and-project-vie.md | 6 ++-- ...GVV571Y6BJ-the-project-list-is-built-on.md | 6 ++-- ...G82D60Q37M-a-folder-without-a-commit-is.md | 6 ++-- ...T9001NFSC1-markdown-markers-become-form.md | 5 +-- apps/app/src/app.tsx | 35 +++++++++++++++++-- apps/app/src/dashboard.tsx | 25 ++++++++++--- apps/app/src/navigation.tsx | 5 ++- apps/app/src/open-project.tsx | 26 +++++++++----- .../ui/src/components/rich-text/commands.ts | 21 ++++++++++- 9 files changed, 104 insertions(+), 31 deletions(-) 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 e5bfcc35..c2421f88 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: 85707ad58ff21113 -raw_hash: 847e872f7c9185bd -vouched: 2026-10-22T22:40:41Z +body_hash: cffc6c2ce7bd7e6d +raw_hash: 14cee7a4562529f6 +vouched: 2026-10-23T12:32: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 ee653f64..39753cd7 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: 85707ad58ff21113 -raw_hash: 847e872f7c9185bd -vouched: 2026-10-22T22:40:41Z +body_hash: cffc6c2ce7bd7e6d +raw_hash: 14cee7a4562529f6 +vouched: 2026-10-23T12:32:53Z lines: 330-1237 --- diff --git a/.lane/memory/apps/app/src/open-project.tsx/01M5JNQ72H3WS44JG82D60Q37M-a-folder-without-a-commit-is.md b/.lane/memory/apps/app/src/open-project.tsx/01M5JNQ72H3WS44JG82D60Q37M-a-folder-without-a-commit-is.md index 83cf4438..07bd86ea 100644 --- a/.lane/memory/apps/app/src/open-project.tsx/01M5JNQ72H3WS44JG82D60Q37M-a-folder-without-a-commit-is.md +++ b/.lane/memory/apps/app/src/open-project.tsx/01M5JNQ72H3WS44JG82D60Q37M-a-folder-without-a-commit-is.md @@ -4,9 +4,9 @@ anchor: function InitGit created: 2026-10-22T20:44:24Z norm: '1' sig: bbe6f480fcc6cbd1 -body_hash: 0fc448d2189f968d -raw_hash: 517902d11c496496 -vouched: 2026-10-22T22:08:11Z +body_hash: bc4e9763ce66dd48 +raw_hash: 9a78d51c3d8b3e6f +vouched: 2026-10-23T12:32:53Z lines: 52-111 --- diff --git a/.lane/memory/packages/ui/src/components/rich-text/commands.ts/01M4YQHWK1MX9HXNT9001NFSC1-markdown-markers-become-form.md b/.lane/memory/packages/ui/src/components/rich-text/commands.ts/01M4YQHWK1MX9HXNT9001NFSC1-markdown-markers-become-form.md index 47a2d4c5..123e5abc 100644 --- a/.lane/memory/packages/ui/src/components/rich-text/commands.ts/01M4YQHWK1MX9HXNT9001NFSC1-markdown-markers-become-form.md +++ b/.lane/memory/packages/ui/src/components/rich-text/commands.ts/01M4YQHWK1MX9HXNT9001NFSC1-markdown-markers-become-form.md @@ -4,8 +4,9 @@ anchor: function markdownRules created: 2026-10-15T02:51:38Z norm: '1' sig: 0a8b81082cdfba48 -body_hash: 5c98630ded958a04 -raw_hash: 21ce709a783c8571 +body_hash: cdce281d3c2d2b7c +raw_hash: 30247d4f6ad4c50d +vouched: 2026-10-23T12:32:53Z lines: 221-240 --- diff --git a/apps/app/src/app.tsx b/apps/app/src/app.tsx index 47473350..eec87bde 100644 --- a/apps/app/src/app.tsx +++ b/apps/app/src/app.tsx @@ -479,7 +479,15 @@ export function App() { const linked = !!repo.value || (status === "open" && !!repo.error); const page = repo.value === null && (saved === "issues" || saved === "prs") ? "channels" : saved; function go(next: Page) { - if (linked || (next !== "issues" && next !== "prs")) setPage(next); + if (linked || (next !== "issues" && next !== "prs")) return setPage(next); + // ⌘3, ⌘4, and the menu reach Issues and PRs even where the rail hides them. + const what = next === "issues" ? "Issues" : "PRs"; + if (!current) return void toast(`${what} need a project on GitHub`); + if (repo.value === null) { + toast(`${label(current, hello.host)} isn't on GitHub`, { + description: `${what} need a project with a GitHub remote.`, + }); + } } const allProjects = all && page === "channels"; const visible = current @@ -603,6 +611,16 @@ export function App() { ]; }); groups.unshift(pinnedGroup(channels, pins, hello, collapsed, attention, online)); + // An archived channel says to unarchive it from the sidebar, so All Projects lists yours too. + const archived = channels.filter((value) => + value.state === "archived" && value.owner === hello.user + ).map((value) => row(value, hello.user, hello.host, online(value), attention.get(value.id))); + groups.push({ + id: "archived", + label: "Archived", + rows: archived, + collapsed: collapsed.archived, + }); } function chooseProject(id: string) { @@ -679,8 +697,18 @@ export function App() { if (value) remove(value); } + /** Opening and cloning go through the host, so a shortcut while disconnected says why it can't. */ + function disconnected(action: string) { + toast.error(`Can't ${action} while disconnected`, { + description: desktop + ? "Ace Helper isn't connected. Open Settings → This Mac to start it." + : "Ace is reconnecting to your host.", + }); + } + async function choose() { - if (!connected || picking.current) return; + if (!connected) return disconnected("open a folder"); + if (picking.current) return; if (!desktop) return setAdding(true); picking.current = true; try { @@ -707,7 +735,8 @@ export function App() { } function cloneUrl() { - if (connected) setSetup({}); + if (!connected) return disconnected("clone a repository"); + setSetup({}); } function startSetup(project: AppProject, start?: { text?: string; onCreated?: () => void }) { diff --git a/apps/app/src/dashboard.tsx b/apps/app/src/dashboard.tsx index b497c46c..21b239c9 100644 --- a/apps/app/src/dashboard.tsx +++ b/apps/app/src/dashboard.tsx @@ -88,23 +88,27 @@ const PLACES = [ }, ]; -/** Teammates' pulse covers what ended since this project's dashboard was last opened here. */ -const FIRST_VISIT = 24 * 60 * 60_000; +/** + * Teammates' pulse covers what ended since this project's dashboard was last opened here, and at + * least the last day, so a reload or a quick look doesn't clear what someone hasn't read yet. + */ +const DAY = 24 * 60 * 60_000; function useLastVisit(project: string): number { const key = `ace:dashboard-visit:${host.url}:${project}`; const [since] = useState(() => { + const day = Date.now() - DAY; try { - return Number(localStorage.getItem(key)) || Date.now() - FIRST_VISIT; + return Math.min(Number(localStorage.getItem(key)) || day, day); } catch { - return Date.now() - FIRST_VISIT; + return day; } }); useEffect(() => { try { localStorage.setItem(key, String(Date.now())); } catch { - // Without storage, every visit covers the last day. + // Without storage, every visit covers only the last day. } }, [key]); return since; @@ -209,6 +213,16 @@ function WorkRow({ work, owner, busy, onChannel, onStart }: { ); } +const CLOCK = new Intl.DateTimeFormat(undefined, { timeStyle: "short" }); +const DATED = new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }); + +/** When a run ended: the time today, with the date before that. */ +function ended(at: number) { + return new Date(at).toDateString() === new Date().toDateString() + ? CLOCK.format(at) + : DATED.format(at); +} + function TeammateRow({ teammate, onChannel }: { teammate: Teammate; onChannel: (channel: Listing) => void; @@ -219,6 +233,7 @@ function TeammateRow({ teammate, onChannel }: { {index > 0 && ", "} + {channel.ran && ` at ${ended(channel.ran.at)}`} )); return ( diff --git a/apps/app/src/navigation.tsx b/apps/app/src/navigation.tsx index 7811eb1a..901131dd 100644 --- a/apps/app/src/navigation.tsx +++ b/apps/app/src/navigation.tsx @@ -28,9 +28,8 @@ export function Navigation({ page, user, github, repo, onPage, onSettings }: { }) { return (