Skip to content

feat(dashboard): first-run and empty states where the user lands - #154

Open
UltimaPhoenix wants to merge 1 commit into
developfrom
feat/dashboard-empty-states
Open

UltimaPhoenix wants to merge 1 commit into
developfrom
feat/dashboard-empty-states

Conversation

@UltimaPhoenix

Copy link
Copy Markdown
Owner

Point 7 of the UI/UX review. Supersedes #133, closed on 1 Nov to go through the plan one point at a time. Same change, rebased on develop.

First run and empty states. A new user now lands on a page that says what to do next, not a blank map.

What changes

State Before After
/knowledge, not set up (empty knowledge table) an empty map under 0 lessons total · 0 / 2 today · 0 this week · Available now a Set up devcoach block: what devcoach does, say "set up devcoach" to your agent (with the Claude Code plugin: /devcoach:setup), keep working (pacing from Settings), and the diagnostic command for this install with Copy
/knowledge, set up, no lesson yet the map under the same row of zeros the map with one line above it: No lesson yet — the first one arrives after real work in your agent, at most {max_per_day} a day, {min_gap} apart. · What a lesson looks like → (/lessons)
/lessons, no lesson, no filter 📭 No lessons match the current filters. (no filter is set) No lessons yet: how lessons arrive, a greyed example lesson in the v2 card shape, the diagnostic command + Copy, Pacing: at most 2 lessons a day, 4 hours apart, … read from Settings; plus First, set up your profile while the knowledge table is empty
/lessons, filters hide everything the same 📭 line + Clear all filters names them: No lessons match 🔍 "kafka" in 🕐 Last 7 days. · Search all time · Clear every filter
  • The diagnostic command comes from core/runtime.ts: new doctorHint(channel) on top of classifyPath. /devcoach:setup under the plugin, devcoach doctor for Homebrew and npm-global, npx -y devcoach doctor for everything else. createApp reads the running process's channel once; AppOptions.channel overrides it for tests.
  • The copy is host-neutral ("in your agent"). Claude Code appears only next to /devcoach:setup, inside <span data-plugin-hint>, and a test enforces that.
  • Unchanged: ui_home=auto / resolveHomePath, DEFAULT_SETTINGS, the cue engine, and every hook. The empty states are pull-only. The <nav> is untouched.
  • Docs: docs/usage/web-ui.md (stats bar, before the first lesson, filtered-empty). CLAUDE.md mentions doctorHint.

Decisions

Decisions taken in the PR:

  1. "Search all time" appears only when a period or date range is set. Without one, the link would reload the same list. "Clear every filter" is always there. The test for "offers both links" uses ?search=kafka&period=week.
  2. The /knowledge stats line appears only once a lesson exists. Before the first lesson every number in it is 0 and "Available now" is always true. The set-up block or the strip takes its place: this is the ruling's "one line above the map", not two lines.
  3. The set-up hint says "With the Claude Code plugin, type /devcoach:setup", not "in Claude Code". The command only exists with the plugin, and a Claude Code user with a plain MCP entry has no such command. The dashboard's own install channel says nothing about which host the user's agent runs in, so the hint is always shown with that qualifier.
  4. Under the plugin, the diagnostic line is /devcoach:setup too, worded "In Claude Code, run /devcoach:setup: it checks the runtime, the server and your profile." That is what plugin/commands/setup.md does: the launcher's doctor first, then the server, then the profile.
  5. /lessons also says "First, set up your profile" while the knowledge table is empty. The ruling puts set-up on /knowledge, but someone who opens Lessons first would otherwise wait for lessons that cannot come. It is computed only when there are no lessons, so the knowledge lookup costs nothing in normal use.
  6. The empty-sandbox tests are in a new file, tests/web-first-run.test.ts. tests/setup.ts gives each test file its own HOME, and web.test.ts seeds a lesson for its whole file in a top-level beforeAll, so it can never be empty. The filtered-empty test stays in web.test.ts.
  7. doctorHint lives in core/runtime.ts, next to the channel table it maps, and app.ts calls it. The brief listed app.ts; this keeps install knowledge in one place, and core still never imports web.
  8. The chips row and the filtered-empty sentence read one activeFilters list, and anyFilter is now its length, so the sentence can never disagree with the chips. Labels are unchanged. PR 4's renames of FEEDBACK_LABELS / sharedLabel flow into both.
  9. The ghost lesson is the v2 sample (a) title (busy_timeout) with a shortened body and the 💡 tip. It is a <figure> with a <figcaption> "What a lesson looks like", not aria-hidden, so screen-reader users get the example too. The /knowledge strip links to /lessons as the ruling says, and this example sits there.
  10. Copy button: inline Alpine, which reads the command from data-copy, so nothing ends up in a JS literal. No new asset. /knowledge does not load share.js.

CHANGELOG

Suggested line for [Unreleased] → Added (not edited here, to avoid a conflict with the request-guard PR's CHANGELOG entry):

- Dashboard: first-run and empty states. `/knowledge` shows how to set up devcoach while your profile is empty, then one line about the first lesson until it arrives; `/lessons` with no lesson shows an example lesson, the diagnostic command for your install (with Copy) and the pacing from Settings; a list emptied by filters names them and offers *Search all time* · *Clear every filter* (it used to say "No lessons match the current filters" even with no filter set).

Tests

  • tests/web-first-run.test.ts (new, brand-new sandbox, run in order):
    • Empty sandbox: /knowledge has the set-up block and no stats line; /lessons has the ghost lesson, the doctor command and the pacing sentence.
    • Pacing follows Settings: 3/90 → "at most 3 lessons a day, 1 hour 30 minutes apart", 1/45 → singular, 5/0 → no "apart", 2/60 → "1 hour".
    • The diagnostic per channel: Homebrew, npm-global, Desktop extension, plugin.
    • No "Claude Code" outside the data-plugin-hint spans, for four channels × both pages, and every hint carries /devcoach:setup (mutation-checked: putting "Claude Code" in the lessons copy fails it).
    • Topics, no lesson: the strip sits above the map, links to /lessons, and the stats line is gone.
    • After the first lesson: the stats line and the list are back.
  • tests/web.test.ts: a filter that matches nothing names it, with both links (search + period), only Clear every filter without a period, "No lessons in 📅 …" for a date range alone, "a, b and c" for three filters, and the user's search text stays escaped.
  • tests/runtime.test.ts: doctorHint for every channel, and through classifyPath for a plugin runtime and a Cellar path (Homebrew wins over /node_modules/devcoach/).
  • npm run lint passes. The 3 infos are pre-existing, in tests/cli.test.ts.
  • npm run typecheck passes.
  • npm run test:cov: 403 passed, coverage 94.88 lines · 93.65 statements · 96.92 functions · 84.50 branches. Thresholds are 92 / 91 / 95 / 76.02 functions · 84.49 branches. Thresholds are 92 / 91 / 95 / 76.

Before / after

Slideshow — each screen BEFORE (red band: without this PR), then AFTER (green band: with this PR), with one line saying what changed. It plays by itself; click it to open it full size.

pr-133-slides-light
Dark pr-133-slides-dark

Videos — the first frame is the title; a full red BEFORE or green AFTER card opens every segment, and the band in the same colour stays above the recording until the segment ends.

Recorded in real Chrome (Playwright, channel: "chrome") on devcoach ui --port 7879 at 1200 × 800 and 420 × 800, light and dark, with the same script for both sides. BEFORE ran from a checkout of develop (2.7.1), AFTER from this branch. Every caption is read from the page. The three sandboxes are:

  1. a brand-new install (no restore);
  2. scripts/screenshots/fixture.zip restored with DELETE FROM lessons on the sandbox db;
  3. the full fixture with ?search=kafka&period=week.

At 420 px the nav still makes every page 555 px wide on both sides: the nav is a separate decision, not this PR's. The stills are clipped to the 420 px viewport.

Light, 3 min: BEFORE then AFTER at 1200 px, then BEFORE then AFTER at 420 px. Three sandboxes: a brand-new install (/ lands on /knowledge: an empty map under a row of zeros → the “Set up devcoach” block; /lessons: “No lessons match the current filters.” with no filter set → “No lessons yet”, an example lesson, npx -y devcoach doctor, the pacing), the fixture with its lessons deleted (row of zeros → one line above the map, whose link opens /lessons), and search “kafka” in the last 7 days (→ the filters named, Search all time · Clear every filter).

pr-133-before-after-light.mp4

Dark, the same run: BEFORE then AFTER at 1200 px, then at 420 px. Captions are read from the page. At 420 the nav still overflows on both sides; the nav is a separate decision.

pr-133-before-after-dark.mp4

Side by side — BEFORE on the left, AFTER on the right.

A brand-new install, /knowledge: BEFORE an empty map and “0 lessons total · 0 / 2 today · 0 this week · Available now”; AFTER the “Set up devcoach” block: say “set up devcoach” to your agent (plugin: /devcoach:setup), keep working (at most 2 a day, 4 hours apart), and npx -y devcoach doctor with Copy. (1200 px, light)

pr-133-knowledge-empty-1200-light
Dark pr-133-knowledge-empty-1200-dark

A brand-new install, /knowledge: BEFORE an empty map and “0 lessons total · 0 / 2 today · 0 this week · Available now”; AFTER the “Set up devcoach” block: say “set up devcoach” to your agent (plugin: /devcoach:setup), keep working (at most 2 a day, 4 hours apart), and npx -y devcoach doctor with Copy. (420 px, light; 420 px stills are clipped to the viewport)

pr-133-knowledge-empty-420-light
Dark pr-133-knowledge-empty-420-dark

A brand-new install, /lessons: BEFORE “No lessons match the current filters.” with no filter set; AFTER “No lessons yet”, a reminder to set up the profile first, a greyed example lesson, the doctor command with Copy, and the pacing read from Settings. (1200 px, light)

pr-133-lessons-empty-1200-light
Dark pr-133-lessons-empty-1200-dark

A brand-new install, /lessons: BEFORE “No lessons match the current filters.” with no filter set; AFTER “No lessons yet”, a reminder to set up the profile first, a greyed example lesson, the doctor command with Copy, and the pacing read from Settings. (420 px, light; 420 px stills are clipped to the viewport)

pr-133-lessons-empty-420-light
Dark pr-133-lessons-empty-420-dark

Topics, no lesson (fixture with its lessons deleted), /knowledge: BEFORE the same row of zeros above the map; AFTER one line: “No lesson yet — the first one arrives after real work in your agent, at most 2 a day, 4 hours apart. What a lesson looks like →”. (1200 px, light)

pr-133-knowledge-nolessons-1200-light
Dark pr-133-knowledge-nolessons-1200-dark

Topics, no lesson (fixture with its lessons deleted), /knowledge: BEFORE the same row of zeros above the map; AFTER one line: “No lesson yet — the first one arrives after real work in your agent, at most 2 a day, 4 hours apart. What a lesson looks like →”. (420 px, light; 420 px stills are clipped to the viewport)

pr-133-knowledge-nolessons-420-light
Dark pr-133-knowledge-nolessons-420-dark

A filter that matches nothing (search “kafka”, last 7 days): BEFORE “No lessons match the current filters.” + Clear all filters; AFTER “No lessons match 🔍 "kafka" in 🕐 Last 7 days.” + Search all time · Clear every filter. (1200 px, light)

pr-133-lessons-filtered-1200-light
Dark pr-133-lessons-filtered-1200-dark

A filter that matches nothing (search “kafka”, last 7 days): BEFORE “No lessons match the current filters.” + Clear all filters; AFTER “No lessons match 🔍 "kafka" in 🕐 Last 7 days.” + Search all time · Clear every filter. (420 px, light; 420 px stills are clipped to the viewport)

pr-133-lessons-filtered-420-light
Dark pr-133-lessons-filtered-420-dark

Verified by hand in real Chrome: Copy on /knowledge and /lessons puts npx -y devcoach doctor on the clipboard. The label reads "✓ Copied" and goes back to "Copy" after 1.5 s.

Not verified

  • VoiceOver / screen reader: not run. The blocks use section + aria-labelledby, and the example is a figure + figcaption.
  • The plugin and Homebrew variants of the diagnostic line are covered by tests (AppOptions.channel), not by a real plugin or Homebrew install.

🤖 Generated with Claude Code

/knowledge shows a set-up block while the knowledge table is empty, and one
line above the map once it is set up but no lesson exists yet. /lessons with
no lesson and no filter shows an example lesson, the diagnostic command for
this install and the pacing read from Settings. A filtered-empty list names
the filters that hid everything and offers Search all time / Clear every
filter. The command comes from core/runtime.ts doctorHint(channel); copy is
host-neutral outside the /devcoach:setup hint. ui_home=auto is unchanged.

This branch has not been deployed

No deployments
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.

1 participant