Skip to content

feat(web): add empty-state onboarding for games and Discord#123

Merged
CoderCoco merged 13 commits into
mainfrom
worktree-claude+issue-68-empty-states-onboarding
May 8, 2026
Merged

feat(web): add empty-state onboarding for games and Discord#123
CoderCoco merged 13 commits into
mainfrom
worktree-claude+issue-68-empty-states-onboarding

Conversation

@CoderCoco
Copy link
Copy Markdown
Owner

Closes #68

Summary

  • Dashboard: replaced bare "No games configured" text with a shadcn Card showing a Server icon, "No games deployed" heading, a one-paragraph Terraform explanation, and two CTA links — "Open setup guide" (docs site) and "Edit terraform.tfvars" (example file on GitHub).
  • Discord wizard: enhanced SetupWizard to accept cfg and show live CheckCircle2 checkmarks 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 whenever allowedGuilds is empty, not only when both guilds and token are absent.
  • Watchdog: added a tooltip prop to the Field helper and wrapped each label with a HelpCircle icon + shadcn Tooltip carrying plain-language help text for all three inputs.
  • API token modal: already had the "Where do I find this?" link from feat(web): redesign API token modal with show/hide and inline retry #117 — no changes needed.

Test plan

  • Unit tests: 339 passing (npm run app:test)
  • E2e tests: 56 passing (npm run app:test:e2e)
  • Navigate to / with no games configured — card with "No games deployed", setup guide link, and terraform.tfvars link renders.
  • Navigate to /discord with allowedGuilds: [] — "Get started" wizard renders with numbered steps.
  • Configure client ID only — step 1 shows a green check, steps 2–4 remain numbered.
  • Add bot token + public key — step 2 gains a green check.
  • Navigate to /settings — hover each watchdog label to confirm tooltip text appears.

🤖 Generated with Claude Code

CoderCoco and others added 2 commits May 6, 2026 22:27
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>
Copilot AI review requested due to automatic review settings May 7, 2026 02:52
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Card empty state containing docs + terraform.tfvars CTAs.
  • Discord: show a “Get started” setup wizard whenever allowedGuilds is 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.

Comment on lines +89 to +97
<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>
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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} />}
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}>
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No unit test?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

claude added 2 commits May 7, 2026 03:30
- 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
Copilot AI review requested due to automatic review settings May 7, 2026 03:31
* is satisfied (credentials saved, URL copied, guild added).
*/
function SetupWizard() {
function SetupWizard({ cfg }: { cfg: DiscordConfigRedacted }) {
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for you to upload some screenshots of what this looks like?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Create Discord application → done once clientId is set
  2. Save credentials → done once botTokenSet && publicKeySet
  3. Copy Interactions URL into Discord portal → done once interactionsEndpointUrl is non-null
  4. 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

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are screenshots of all three changed surfaces, captured against the production build via Playwright with stubbed API responses.


Dashboard — no games deployed (/)

Dashboard empty state


Discord wizard — first run (/discord, no credentials, no guilds)

All four steps show as numbered circles (none complete).

Discord wizard — empty


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.

Discord wizard — partial


Watchdog settings — tooltip open (/settings, hovering "Check interval (min) ?" button)

Watchdog tooltip


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in f66c666:

  • Step list: space-y-3space-y-4 (16 px between steps)
  • Card content: space-y-3space-y-5 (20 px between the list and the footer)
  • Footer walkthrough link: added border-t pt-4 to visually separate it from the steps

Generated by Claude Code

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

CoderCoco and others added 2 commits May 6, 2026 23:51
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
Copilot AI review requested due to automatic review settings May 7, 2026 03:55
Comment thread docs/screenshots/01-dashboard-empty.png Outdated
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Files removed in f9c9fc2docs/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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 13 changed files in this pull request and generated 1 comment.

Comment on lines +115 to +121
// 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]);
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@CoderCoco CoderCoco changed the title feat(web): empty states + first-run onboarding for games and Discord feat(web): add empty-state onboarding for games and Discord May 7, 2026
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>
Copilot AI review requested due to automatic review settings May 7, 2026 04:35
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Comment on lines +93 to +103
<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>
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

claude and others added 2 commits May 7, 2026 04:41
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>
Copilot AI review requested due to automatic review settings May 7, 2026 04:52
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>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

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>
@CoderCoco CoderCoco merged commit 00892f1 into main May 8, 2026
7 checks passed
@CoderCoco CoderCoco deleted the worktree-claude+issue-68-empty-states-onboarding branch May 8, 2026 00:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(web): empty states + first-run onboarding for games and Discord

3 participants