From 448b68eb62f9d899d2a19884370cf8e46e147fab Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Sat, 21 Mar 2026 16:42:20 -0700 Subject: [PATCH] feat(tour): replace static welcome popup with guided feature tour Replaces the "Welcome to rSpace" popup with a 6-step interactive tour that spotlights key UI features (app switcher, MI bar, spaces, identity, canvas, tabs/tools). Skippable at any time via button, Escape, or clicking the backdrop. Navigable with Back/Next buttons and arrow keys. Co-Authored-By: Claude Opus 4.6 --- server/shell.ts | 286 +++++++++++++++++++++++++++++++------------- website/canvas.html | 217 ++++++++++++++++++--------------- 2 files changed, 322 insertions(+), 181 deletions(-) diff --git a/server/shell.ts b/server/shell.ts index 347e7d2..377b2dd 100644 --- a/server/shell.ts +++ b/server/shell.ts @@ -450,17 +450,138 @@ export function renderShell(opts: ShellOptions): string { document.addEventListener('auth-change', update); })(); - // ── Welcome overlay (first visit to demo) ── + // ── Welcome tour (guided feature walkthrough for first-time visitors) ── (function() { var currentSpace = '${escapeAttr(spaceSlug)}'; if (currentSpace !== 'demo') return; - if (localStorage.getItem('rspace_welcomed')) return; - var el = document.getElementById('rspace-welcome'); - if (el) el.style.display = 'flex'; + if (localStorage.getItem('rspace_tour_done')) return; + + var TOUR_STEPS = [ + { + target: 'rstack-app-switcher', + title: 'Welcome to rSpace', + msg: 'This is your collaborative workspace with 25+ interoperable tools called rApps. Use this switcher to jump between modules like Notes, Maps, Voting, Wallet, and more.' + }, + { + target: 'rstack-mi', + title: 'Meet mi \u2014 Your AI Guide', + msg: 'Press Cmd+K (or Ctrl+K) to open mi, your mycelial intelligence assistant. mi can create content, set up spaces, and help you build across all rApps.' + }, + { + target: 'rstack-space-switcher', + title: 'Spaces', + msg: 'Each space is a self-contained community with its own data, members, and encryption. Switch between spaces or create a new one here.' + }, + { + target: 'rstack-identity', + title: 'Passwordless Identity', + msg: 'Sign in with passkeys \u2014 no passwords or seed phrases. Your identity is cryptographic and portable across all spaces.' + }, + { + target: '#app', + title: 'The Canvas', + msg: 'This is your infinite canvas. Drag to pan, scroll to zoom, and use the toolbar to add shapes. Everything syncs in real-time using local-first CRDTs.' + }, + { + target: '.rstack-tab-row', + title: 'rApp Tabs', + msg: 'Each module has its own views accessible via tabs. Switch between canvas, list, and detail views depending on the active rApp.' + }, + ]; + + var step = 0; + var tourEl = document.getElementById('rspace-tour'); + var backdrop = document.getElementById('rspace-tour-backdrop'); + var spotlight = document.getElementById('rspace-tour-spotlight'); + var tooltip = document.getElementById('rspace-tour-tooltip'); + var titleEl = document.getElementById('rspace-tour-title'); + var msgEl = document.getElementById('rspace-tour-msg'); + var stepEl = document.getElementById('rspace-tour-step'); + var prevBtn = document.getElementById('rspace-tour-prev'); + var nextBtn = document.getElementById('rspace-tour-next'); + var skipBtn = document.getElementById('rspace-tour-skip'); + + function endTour() { + localStorage.setItem('rspace_tour_done', '1'); + if (tourEl) tourEl.style.display = 'none'; + } + + function showStep() { + if (!tourEl || !backdrop || !spotlight || !tooltip) return; + var s = TOUR_STEPS[step]; + var target = document.querySelector(s.target); + var rect = target ? target.getBoundingClientRect() : { left: window.innerWidth / 2 - 60, top: 8, width: 120, height: 40 }; + + // Spotlight + var pad = 6; + var sx = rect.left - pad, sy = rect.top - pad; + var sw = rect.width + pad * 2, sh = rect.height + pad * 2; + spotlight.style.left = sx + 'px'; + spotlight.style.top = sy + 'px'; + spotlight.style.width = sw + 'px'; + spotlight.style.height = sh + 'px'; + + // Backdrop clip-path (cut hole for spotlight) + backdrop.style.clipPath = 'polygon(0% 0%, 0% 100%, ' + + sx + 'px 100%, ' + sx + 'px ' + sy + 'px, ' + + (sx + sw) + 'px ' + sy + 'px, ' + + (sx + sw) + 'px ' + (sy + sh) + 'px, ' + + sx + 'px ' + (sy + sh) + 'px, ' + + sx + 'px 100%, 100% 100%, 100% 0%)'; + + // Tooltip position: below target, clamped to viewport + var ttTop = rect.top + rect.height + 16; + var ttLeft = Math.max(16, Math.min(rect.left, window.innerWidth - 380)); + // If tooltip would go off bottom, put it above + if (ttTop + 200 > window.innerHeight) { + ttTop = Math.max(16, rect.top - 220); + } + tooltip.style.top = ttTop + 'px'; + tooltip.style.left = ttLeft + 'px'; + + // Content + stepEl.textContent = (step + 1) + ' / ' + TOUR_STEPS.length; + titleEl.textContent = s.title; + msgEl.innerHTML = s.msg; + + // Buttons + prevBtn.style.display = step > 0 ? '' : 'none'; + nextBtn.textContent = step === TOUR_STEPS.length - 1 ? 'Get Started' : 'Next'; + } + + // Wire buttons + if (nextBtn) nextBtn.addEventListener('click', function() { + step++; + if (step >= TOUR_STEPS.length) { endTour(); return; } + showStep(); + }); + if (prevBtn) prevBtn.addEventListener('click', function() { + if (step > 0) { step--; showStep(); } + }); + if (skipBtn) skipBtn.addEventListener('click', endTour); + if (backdrop) backdrop.addEventListener('click', endTour); + + // Keyboard: Escape to skip, arrows to navigate + document.addEventListener('keydown', function(e) { + if (!tourEl || tourEl.style.display === 'none') return; + if (e.key === 'Escape') { endTour(); e.preventDefault(); } + if (e.key === 'ArrowRight' || e.key === 'Enter') { + step++; + if (step >= TOUR_STEPS.length) { endTour(); return; } + showStep(); e.preventDefault(); + } + if (e.key === 'ArrowLeft' && step > 0) { step--; showStep(); e.preventDefault(); } + }); + + // Start tour after a brief delay + setTimeout(function() { + if (tourEl) { tourEl.style.display = ''; showStep(); } + }, 800); })(); + // Legacy compat window.__rspaceDismissWelcome = function() { - localStorage.setItem('rspace_welcomed', '1'); - var el = document.getElementById('rspace-welcome'); + localStorage.setItem('rspace_tour_done', '1'); + var el = document.getElementById('rspace-tour'); if (el) el.style.display = 'none'; }; @@ -1270,32 +1391,21 @@ export function renderExternalAppShell(opts: ExternalAppShellOptions): string { `); } -// ── Welcome overlay (quarter-screen popup for first-time visitors on demo) ── +// ── Welcome tour (guided feature walkthrough for first-time visitors) ── function renderWelcomeOverlay(): string { return ` -