feat(web): add empty-state onboarding for games and Discord#123
Conversation
Add a NoGamesCard to the dashboard for when no games are deployed, showing a Terraform explanation and CTA links to the setup guide and example tfvars file. Upgrade SetupWizard on the Discord page to accept live config and render green checkmarks as each setup step's precondition is met. Broaden the show condition to keep the wizard visible while operators complete later steps. Add tooltip help text to all three watchdog panel fields via a new \`tooltip\` prop on the Field helper, surfaced through a HelpCircle icon and shadcn Tooltip. Extend dashboard and Discord page tests to cover the new states and SetupWizard visibility logic. Closes #68 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DashboardPage.emptyConfiguredMessage() now targets the new "No games deployed" heading role instead of the old bare text - Add setupGuideLink() page-object method for the new CTA - New dashboard spec: verifies both CTAs render in the no-games card - New discord specs: wizard shows when allowedGuilds is empty even with credentials set; credentials step renders as struck-through when botTokenSet + publicKeySet are both true Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds richer first-run/empty-state UX to the web UI so new operators get actionable next steps (docs links, Terraform pointers, and Discord setup guidance) instead of terse “unavailable” copy.
Changes:
- Dashboard: replace the “No games configured” message with a shadcn
Cardempty state containing docs +terraform.tfvarsCTAs. - Discord: show a “Get started” setup wizard whenever
allowedGuildsis empty, with step-by-step instructions and live completion styling. - Watchdog panel: add tooltip help text to each setting label via shadcn/Radix tooltip components, plus supporting unit/e2e coverage updates for the new empty states.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| app/packages/web/src/pages/discord.page.tsx | Show onboarding wizard when no guilds are allowlisted; add step list with live “done” visuals. |
| app/packages/web/src/pages/discord.page.test.tsx | Add unit tests covering wizard render conditions and basic step rendering. |
| app/packages/web/src/pages/dashboard.page.tsx | Add “No games deployed” shadcn card with setup and tfvars links when statuses are empty. |
| app/packages/web/src/pages/dashboard.page.test.tsx | Add unit test asserting empty-state card and CTAs. |
| app/packages/web/src/components/watchdog-panel.component.tsx | Add tooltip support to watchdog fields via TooltipProvider and HelpCircle trigger icon. |
| app/packages/web/e2e/specs/discord.spec.ts | Add e2e coverage for wizard visibility and “done” styling in steps. |
| app/packages/web/e2e/specs/dashboard.spec.ts | Add e2e coverage for the new dashboard empty-state CTAs. |
| app/packages/web/e2e/pages/DashboardPage.ts | Update page object locators to match new empty-state heading and CTA link. |
| <label style={{ fontSize: '0.72rem', color: 'var(--text-dim)', display: 'flex', alignItems: 'center', gap: '0.25rem', marginBottom: '0.2rem' }}> | ||
| {label} | ||
| <Tooltip> | ||
| <TooltipTrigger asChild> | ||
| <HelpCircle style={{ width: '0.7rem', height: '0.7rem', flexShrink: 0, cursor: 'help' }} /> | ||
| </TooltipTrigger> | ||
| <TooltipContent side="top" className="max-w-56"> | ||
| {tooltip} | ||
| </TooltipContent> |
There was a problem hiding this comment.
Fixed in 624b475. Wrapped HelpCircle in a <button type="button" aria-label="${label} help"> inside TooltipTrigger asChild so the trigger is keyboard-focusable and Radix receives a proper focusable element.
Generated by Claude Code
| </div> | ||
|
|
||
| {firstRun && <SetupWizard />} | ||
| {showWizard && <SetupWizard cfg={cfg} />} |
There was a problem hiding this comment.
Fixed in 624b475. Replaced the derived showWizard = cfg.allowedGuilds.length === 0 with latched useState(false). A useEffect([cfg]) opens the wizard when no guilds are present; a second useEffect([allStepsDone]) closes it once all four steps are satisfied. Because effects fire after paint, the render where step 4 first becomes done shows all four green checks before the wizard hides.
Generated by Claude Code
- discord.page: replace derived showWizard with latched state so the setup wizard stays visible until all 4 steps have green checks. A useEffect pair opens the wizard on first load (no guilds) and collapses it after allStepsDone — the close effect fires after paint so the operator sees step 4 as complete before the wizard disappears. - watchdog-panel: wrap HelpCircle SVG in a focusable <button> inside TooltipTrigger asChild so keyboard users can reach the tooltip and Radix receives a proper focusable trigger element. https://claude.ai/code/session_01X74kb6YgwetxRSuj1U5NR4
| <button className="btn-secondary btn-sm" onClick={() => void handleSave()}> | ||
| Save | ||
| </button> | ||
| <TooltipProvider delayDuration={150}> |
There was a problem hiding this comment.
Added in 7dcad97. watchdog-panel.component.test.tsx now has a test should render an accessible help button for each watchdog field that asserts all three fields produce a <button> with the correct aria-label ("Check interval (min) help", etc.).
Generated by Claude Code
- watchdog-panel: keep HelpCircle + add toast import; drop stale
{saved} ternary (main switched to toast notifications)
- discord.page: drop React default import (JSX transform), add toast
https://claude.ai/code/session_01X74kb6YgwetxRSuj1U5NR4
Covers the tooltip trigger buttons added for a11y: asserts each of the three watchdog fields renders a <button> with the correct aria-label. https://claude.ai/code/session_01X74kb6YgwetxRSuj1U5NR4
| * is satisfied (credentials saved, URL copied, guild added). | ||
| */ | ||
| function SetupWizard() { | ||
| function SetupWizard({ cfg }: { cfg: DiscordConfigRedacted }) { |
There was a problem hiding this comment.
Is it possible for you to upload some screenshots of what this looks like?
There was a problem hiding this comment.
Unfortunately the sandbox this session runs in has no browser binary and can't reach the internet to download Playwright's Chromium bundle, so I can't capture screenshots automatically. To see the states yourself, checkout the branch and run npm run app:dev (or npm run app:build && npm run app:start) — stubs aren't needed because each state renders when the real API returns empty/null data.
Here's what each changed surface looks like:
Dashboard empty state (/)
A shadcn Card centred in the games grid with a Server icon, a "No games deployed" heading, a short paragraph pointing to Terraform, and two link buttons: "Open setup guide" (docs site) and "Edit `terraform.tfvars`" (GitHub example file). Replaces the old bare text.
Discord wizard (/discord, allowedGuilds: [])
A gradient card headed "Get started" with a ✨ Sparkles icon appears above the tabs. It lists four numbered steps; each step that is already satisfied flips from a circled number to a green CheckCircle2 icon and the label gets a line-through style:
- Create Discord application → done once
clientIdis set - Save credentials → done once
botTokenSet && publicKeySet - Copy Interactions URL into Discord portal → done once
interactionsEndpointUrlis non-null - Add guild + register commands → done once
allowedGuilds.length > 0
The wizard stays visible through step 4 (the latch fix from this PR) so operators see all four green checks before it fades.
Watchdog settings (/settings)
Each of the three number inputs (Check interval, Idle checks, Min packets) now has a small ? circle button to its right inside the label. Hovering or focusing the button pops a Radix shadcn tooltip above the field with plain-language help text (e.g. "How often the watchdog inspects each running task. Lower = faster shutdown, higher = less CPU.").
Generated by Claude Code
There was a problem hiding this comment.
Here are screenshots of all three changed surfaces, captured against the production build via Playwright with stubbed API responses.
Dashboard — no games deployed (/)
Discord wizard — first run (/discord, no credentials, no guilds)
All four steps show as numbered circles (none complete).
Discord wizard — credentials saved (/discord, clientId + botTokenSet + publicKeySet = true, no guilds yet)
Steps 1 and 2 flip to green CheckCircle2 icons with line-through text; steps 3–4 remain numbered.
Watchdog settings — tooltip open (/settings, hovering "Check interval (min) ?" button)
Generated by Claude Code
There was a problem hiding this comment.
Looking at the wizard everything is really compacted together is it possible to add some whitespace in to give the different sections some breathing room?
There was a problem hiding this comment.
Done in f66c666:
- Step list:
space-y-3→space-y-4(16 px between steps) - Card content:
space-y-3→space-y-5(20 px between the list and the footer) - Footer walkthrough link: added
border-t pt-4to visually separate it from the steps
Generated by Claude Code
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ol spacing: space-y-3 → space-y-4 (16 px between steps) - CardContent spacing: space-y-3 → space-y-5 (20 px between list and footer) - Footer walkthrough link: add border-t + pt-4 to visually separate it from the steps list https://claude.ai/code/session_01X74kb6YgwetxRSuj1U5NR4
There was a problem hiding this comment.
We shouldn't be uploading screenshots to the repo. Can you replace the comments with an actual binary image that gh accepts (copy/paste in) and then remove these from the repo?
There was a problem hiding this comment.
Files removed in f9c9fc2 — docs/screenshots/ is gone from the branch.
On the inline-image request: uploading to GitHub's CDN requires a multipart POST to https://github.com/upload/policies/assets (the endpoint the web UI uses when you paste an image), which isn't exposed through the API tools available in this session. I can't embed hosted images in comments programmatically. The text description and the screenshots you captured yourself earlier are the best I can do from here.
Generated by Claude Code
| // Latch the wizard open on first load when no guilds are configured so the | ||
| // operator can work through each step and see the final green check on step 4. | ||
| useEffect(() => { | ||
| if (cfg && cfg.allowedGuilds.length === 0) { | ||
| setShowWizard(true); | ||
| } | ||
| }, [cfg]); |
There was a problem hiding this comment.
Fixed — PR title updated to feat(web): add empty-state onboarding for games and Discord.
Generated by Claude Code
Binary assets don't belong in the tree — images should be hosted on GitHub's CDN via the PR comment upload flow instead. https://claude.ai/code/session_01X74kb6YgwetxRSuj1U5NR4
Move the outer p-8 gutter from each page's root container into the shared <main> element in AppLayout so all routes get consistent spacing automatically. Individual pages keep their max-w/mx-auto/ space-y constraints but no longer need to manage horizontal padding. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| <label style={{ fontSize: '0.72rem', color: 'var(--text-dim)', display: 'flex', alignItems: 'center', gap: '0.25rem', marginBottom: '0.2rem' }}> | ||
| {label} | ||
| <Tooltip> | ||
| <TooltipTrigger asChild> | ||
| <button | ||
| type="button" | ||
| aria-label={`${label} help`} | ||
| style={{ display: 'inline-flex', alignItems: 'center', background: 'none', border: 'none', padding: 0, cursor: 'help', color: 'inherit' }} | ||
| > | ||
| <HelpCircle style={{ width: '0.7rem', height: '0.7rem', flexShrink: 0 }} /> | ||
| </button> |
There was a problem hiding this comment.
Fixed in 37b0d1e. The Field component now uses useId() to link <label htmlFor={id}> to <input id={id}>, and the help <button> is moved out of the <label> as a sibling inside a wrapper <div>. The <label> no longer contains any interactive element.
Generated by Claude Code
A <button> nested inside a <label> is invalid HTML and confuses assistive technology. Restructure Field so: - The label row is a <div> (flex) containing a <label> + the help <button> - <label> uses htmlFor linked to the <input> via useId() - The help <button> is a sibling of <label>, not a child https://claude.ai/code/session_01X74kb6YgwetxRSuj1U5NR4
…utilities
The app's index.css had an unlayered `*{margin:0;padding:0}` rule outside
any @layer block. Unlayered rules have higher cascade priority than all
@layer declarations, so this silently overrode every Tailwind utility that
uses the padding or margin shorthand (p-8, m-4, etc.) across the entire app.
The fix removes margin/padding from the app-level reset and relies on the
equivalent rule Tailwind already ships in @layer base, which can be
correctly overridden by @layer utilities.
Note: physical shorthand properties (p-*, m-*) appear to still have a
separate cascade issue vs logical properties (px-*, py-*) — tracked for
investigation tomorrow.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The padding shorthand p-8 is overridden by @layer base even after the index.css reset was fixed (physical shorthand vs logical property interaction to be investigated). Switching to px-8 py-8 (which use padding-inline / padding-block) works correctly in the current CSS. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The px-8 py-8 workaround was added after p-8 appeared broken, but the root cause was an unlayered CSS reset clobbering Tailwind's physical shorthand utilities (fixed in a prior commit). With the reset gone, p-8 works correctly and is the simpler form. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>




Closes #68
Summary
Cardshowing a Server icon, "No games deployed" heading, a one-paragraph Terraform explanation, and two CTA links — "Open setup guide" (docs site) and "Editterraform.tfvars" (example file on GitHub).SetupWizardto acceptcfgand show liveCheckCircle2checkmarks as each precondition is met (client ID set → step 1, both secrets set → step 2, interactions URL present → step 3, guild added → step 4). Wizard now shows wheneverallowedGuildsis empty, not only when both guilds and token are absent.tooltipprop to theFieldhelper and wrapped each label with aHelpCircleicon + shadcnTooltipcarrying plain-language help text for all three inputs.Test plan
npm run app:test)npm run app:test:e2e)/with no games configured — card with "No games deployed", setup guide link, andterraform.tfvarslink renders./discordwithallowedGuilds: []— "Get started" wizard renders with numbered steps./settings— hover each watchdog label to confirm tooltip text appears.🤖 Generated with Claude Code