Add guided tips feature discovery system#673
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Implements the central registry for tip definitions with 5 starter tips covering Quick Phrases, Personas, Brain, Automations, and Media Sidebar. Each tip includes ID, title, body, optional docs reference, version introduced (since), and surfaces (inline/ambient). Includes getTipById helper and comprehensive test suite validating structure and uniqueness. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Create tips-state.ts with three persistent atoms for tips feature: - tipsEnabledAtom: master toggle (defaults to true) - dismissedTipsAtom: tracks dismissed tip IDs (defaults to []) - lastSeenVersionAtom: enables version-gating (defaults to null) All atoms use atomWithLocalStorage for JSON serialization and cross-tab sync. Export atomWithLocalStorage from store.ts to support these atoms. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add @testing-library/react and jsdom dev deps, configure vitest with react plugin and __DISPATCH_VERSION__ define, and implement the useTip hook that combines tip registry, state atoms, and version comparison to drive inline and ambient tip visibility. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Two bugs prevented the inline popover from rendering: 1. React StrictMode double-invokes effects (run → cleanup → run). The mountedRef guard persisted across the cleanup cycle, so the second invocation returned early and never started the open timer. Fix: remove the mountedRef and let the effect lifecycle handle it. 2. PopoverTrigger with asChild tried to forward a ref to the child component, but function components without forwardRef silently fail. Fix: wrap children in a <span> so Radix can attach its ref. Also latch eligibility via eligibleRef so the popover survives lastSeenVersion atom updates from TipsVersionInit. Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Replace display:contents span with inline-flex so Radix has a bounding box to anchor the popover against - Add PopoverArrow pointing from popover to the trigger element - Center-align the Quick Phrases popover on the icon instead of start-aligning Co-Authored-By: Claude Opus 4.6 <[email protected]>
Hitting "Reset dismissed tips" in settings now also sets lastSeenVersion to "0.0.0", so inline tips reappear on the next page load. Previously it only cleared the dismissed list, which meant inline tips stayed hidden because they weren't considered "new" relative to the current version. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Use fill-[hsl(var(--card))] to match the popover background and stroke-white/20 on the SVG polygon to match the glassOverlay border. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Fill the arrow with white/20 to match the glassOverlay border color, making it look like the border itself extends outward as a triangle. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Arrow border uses purple-500/20 to match popover's border-purple-500/20, and fill uses --popover background instead of --card. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Replace double-triangle technique with a rotated square clipped to show only the arrow half. The clip edge aligns with the popover border so the base is invisible — only the two diagonal edges are visible, making the arrow look like the popover border bulging outward. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Replace CSS pseudo-element rotated square with an inline SVG element. The SVG draws a filled polygon (popover bg) with an open path (border color, no base line) so the base edge is invisible. Removes backdrop- filter and inset shadow from the tip popover so the SVG fill matches the popover background exactly. Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Learn More button navigates to /settings/help/:section#anchor with hash-based deep linking to specific doc headings - Internalize navigation in TipSpot and AmbientTipBar via useNavigate (removed onOpenDocs prop plumbing) - Arrow SVG offset tracks trigger position so it points at the referenced element, not the popover center - Tip won't open when trigger is occluded by mobile sidebar overlay (uses elementFromPoint check) - Polling retry ensures tip appears after sidebar closes - Fix docsSection mappings: quick-phrases → agents#quick-phrases, brain → tools#brain - Add id prop to H3 doc primitive for anchor targets Co-Authored-By: Claude Opus 4.6 <[email protected]>
…lity Single triggerReachable state driven by MutationObserver + resize listener. Both "wait to open" and "close when occluded" react to the same state — no setInterval polling. Co-Authored-By: Claude Opus 4.6 <[email protected]>
…bserver Replace document-wide MutationObserver with an IntersectionObserver scoped to the trigger element. The IO tracks viewport visibility; an elementFromPoint gate at open time prevents showing behind overlays. No document-level observers — zero overhead when the tip's target element isn't rendered. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Drop IntersectionObserver entirely. Instead, check elementFromPoint during render to gate the popover — if the trigger is covered by an overlay, the popover doesn't show. Any re-render from normal app activity (agent status, SSE events, navigation) re-evaluates automatically. No observers, no polling. Co-Authored-By: Claude Opus 4.6 <[email protected]>
The render-time elementFromPoint check read stale DOM from the previous commit, so opening the sidebar showed the tip incorrectly because the overlay wasn't in the DOM yet during that render. Moving the check to useLayoutEffect ensures it runs after the DOM commits but before paint. Also guards handleOpenChange so reachability-driven closes don't permanently dismiss the tip. Co-Authored-By: Claude Opus 4.6 <[email protected]>
…ew tips Add on-demand lightbulb button (desktop only) to request tips without waiting for idle timer. Rework tip bar layout: 3-column flexbox with inline Learn more link, Disable/Dismiss text actions, fade transitions, and proper vertical centering in the footer area. Hide ambient tip surface on mobile. Add 4 new ambient tips (keyboard shortcuts, worktrees, personalities, notifications). Move job prompt files from .dispatch/job-prompts/ to docs/jobs/. Co-Authored-By: Claude Opus 4.6 <[email protected]>
…ersionInit - Add aria-labels to lightbulb and Learn more buttons (a11y) - Add pointer-events-none to tip bar wrapper, pointer-events-auto on interactive children - Throttle mousemove idle timer resets (10s debounce) - Add Escape key handler to dismiss TipSpot popovers - Extract TipsVersionInit from App.tsx into tips/tips-version-init.tsx Co-Authored-By: Claude Opus 4.6 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
TipSpotwrapper component that auto-opens a popover on upgrade-eligible features (version-gated vialastSeenVersionvstip.since). Only one popover at a time viaTipQueueProvider. Includes sparkle icon, description, "Learn more →" docs link, dismiss ✕, and "Don't show tips" affordance.tips.tswith 5 starter tips (Quick Phrases, Personas, Brain, Automations, Media Sidebar). Each tip specifiessinceversion and surfaces (inline/ambient).tipsEnabled,dismissedTips,lastSeenVersion) persisted viaatomWithLocalStorage. First-time users (nolastSeenVersion) see only ambient tips, not inline popovers.Test plan
pnpm run check— type check cleanpnpm run finalize:web— production build succeedspnpm --filter @dispatch/web test— 180/180 tests pass (includes 23 new tip tests)pnpm run test:e2e— 168/168 pass, 12 skipped🤖 Generated with Claude Code