From a28767d323575169090ec2bd96193d1ea3572cd1 Mon Sep 17 00:00:00 2001 From: xarmian Date: Wed, 29 Apr 2026 09:20:29 -0400 Subject: [PATCH] feat(web): ConnectWorkspaceModal + empty-workspace and avatar surfaces (TASK-861) (#283) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(web): ConnectWorkspaceModal + empty-workspace and avatar surfaces (TASK-861) Web side of the web-first onboarding on-ramp from PLAN-859 / IDEA-750. Gives a user who created a workspace via the web UI a one-line copy-paste to connect that workspace to their local repo, exposed in the two zero-state surfaces where they'd look for it. Changes: - New `` Svelte 5 component (web/src/lib/components/ConnectWorkspaceModal.svelte). Reusable, no host-page coupling. Matches ShareDialog's modal pattern (overlay + centered modal, native, open = $bindable(), Escape closes). Props: serverUrl, workspaceSlug, workspaceName?. Renders Step 1 (OS-tabbed install — macOS/Linux/Windows/Docker, default tab from detected platform) and Step 2 (pad init --url ... --workspace ... snippet with a copy button on the full snippet). Footer links to docs + troubleshooting. - New web/src/lib/utils/platform.ts — tiny dependency-free OS detection helper. SSR-safe (defaults to "macos" with no navigator). - Mounted in the workspace landing page as a "Connect your local project" card directly under in the empty- workspace .onboarding-wrapper. Modal itself is mounted unconditionally at the page root so it survives re-renders of the conditional empty state. - Mounted in TopBar.svelte's user menu (both desktop and mobile branches): "Connect a project..." entry between Theme/Cloud-support links and the Sign-out divider. Modal lives outside the dropdown so it doesn't unmount when the dropdown closes. Both gated on workspaceStore.current?.slug since the modal needs a workspace to interpolate. Docs URLs in the modal footer (getpad.dev/docs/install, getpad.dev/docs/connect-local-project) are placeholders; TASK-863 in PLAN-859 will publish those pages and we'll wire the final URLs then. Test plan: - go build ./... && go test ./... clean - cd web && npm run build clean - make install clean, server restarted - Svelte MCP autofixer ran on all four touched files — no findings Parent: PLAN-859. Driving idea: IDEA-750. * fix(web/connect-modal): correct brew tap + point placeholder docs links to README (Codex round 1) Two findings from Codex review on PR #283: 1. macOS install command was `brew install xarmian/pad/pad`, but the actual tap is `PerpetualSoftware/tap/pad` (per README.md and skills/INSTALL.md). Users would have hit a failing install. 2. Footer links pointed at `getpad.dev/docs/install` and `getpad.dev/docs/connect-local-project` — pages TASK-863 will publish but don't exist yet. Until they do, point at the GitHub README's #installation and #getting-started anchors so clicks at least land somewhere useful instead of 404. The TASK-863 follow-up will swap these back to the dedicated docs URLs once the pages ship. * fix(web/connect-modal): use real install commands from README (Codex round 2) Round 2 caught that Linux/Windows/Docker commands were fabricated: - Linux/Windows pointed at a getpad.dev/install.sh that doesn't exist - Docker used wrong volume mount (/root/.pad vs the image's /data) and didn't publish ports All four tabs now mirror the README's Installation section exactly: - macOS + Linux: brew install PerpetualSoftware/tap/pad - Windows: pointer to the GitHub releases page (no first-party one-liner) - Docker: docker run -p 127.0.0.1:7777:7777 -v pad-data:/data ghcr.io/perpetualsoftware/pad --- .../components/ConnectWorkspaceModal.svelte | 404 ++++++++++++++++++ web/src/lib/components/layout/TopBar.svelte | 58 +++ web/src/lib/utils/platform.ts | 36 ++ .../[username]/[workspace]/+page.svelte | 87 ++++ 4 files changed, 585 insertions(+) create mode 100644 web/src/lib/components/ConnectWorkspaceModal.svelte create mode 100644 web/src/lib/utils/platform.ts diff --git a/web/src/lib/components/ConnectWorkspaceModal.svelte b/web/src/lib/components/ConnectWorkspaceModal.svelte new file mode 100644 index 00000000..e3e1da6f --- /dev/null +++ b/web/src/lib/components/ConnectWorkspaceModal.svelte @@ -0,0 +1,404 @@ + + + + +{#if open} + + +
(open = false)}> + +
+{/if} + + diff --git a/web/src/lib/components/layout/TopBar.svelte b/web/src/lib/components/layout/TopBar.svelte index 9bff3058..73413640 100644 --- a/web/src/lib/components/layout/TopBar.svelte +++ b/web/src/lib/components/layout/TopBar.svelte @@ -10,12 +10,14 @@ import { goto } from '$app/navigation'; import PadLogo from '$lib/components/layout/PadLogo.svelte'; import WorkspaceSwitcher from '$lib/components/layout/WorkspaceSwitcher.svelte'; + import ConnectWorkspaceModal from '$lib/components/ConnectWorkspaceModal.svelte'; import { workspaceRestoreTarget } from '$lib/utils/workspace-route'; let { mobile = false }: { mobile?: boolean } = $props(); let userMenuOpen = $state(false); let currentTheme = $state<'dark' | 'light'>('dark'); + let connectOpen = $state(false); let currentSlug = $derived(workspaceStore.current?.slug ?? ''); @@ -872,6 +874,25 @@ Status {/if} + + {#if workspaceStore.current?.slug} + + {/if} + {/if} {:else if totalItems === 0 && onboardingDismissed}
@@ -421,6 +436,18 @@ {/if}
+ + +