Feature/alive onboarding demo - #23
Conversation
Add a proactive nudge, guided tour overlays, local interactive demo state changes, and widget-to-page context syncing so users get clear next-step onboarding on /embed-demo. Made-with: Cursor
Convert the /embed-demo tour from passive next-clicks into required-action auto-advance with live checklist/app-state updates, and pass app state into embed chat so next-step guidance stays page-first and reliable. Made-with: Cursor
Stabilize the dynamic header knowledge label across SSR/CSR and switch guided tour steps to explicit card-level targets with clearer highlight visuals so onboarding cues land on the right controls. Made-with: Cursor
Allow the walkthrough to resume at the first incomplete step, add explicit demo reset controls, reduce overlay dimming, and stop external suggestion events from auto-opening the widget while preserving live status updates. Made-with: Cursor
Move embed-demo initialization to deterministic SSR-safe defaults with deferred client hydration, and forward card attributes so explicit tour targets render consistently and the walkthrough continues past the first action. Made-with: Cursor
Emit structured UI actions from embed chat responses and execute them on /embed-demo so guide and next-step intents can drive highlights and tour step entry points in a reliable, state-aware flow. Made-with: Cursor
Add a shared next-action engine, intelligent intent guards, live widget status/events, activity feed updates, and local assistant fallbacks so the onboarding demo feels proactive and remains reliable without LLM responses. Made-with: Cursor
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 43 minutes and 53 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
📝 WalkthroughWalkthroughThis PR implements an interactive onboarding flow for the embedded runbook demo by adding client-side app state tracking (GitHub integration, API key, workflow, deployment status), server-side UI action derivation from user intent patterns and app state, localStorage persistence, a guided tour with progress gating, and context-aware assistance responses tailored to user prerequisites. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant DemoPage as Demo Page<br/>(Frontend)
participant ChatAPI as Chat API<br/>(Backend)
participant Assistant as Assistant<br/>Component
participant WindowEvents as Window Events<br/>(Pub/Sub)
User->>DemoPage: Interacts with feature/button
DemoPage->>DemoPage: Update appState<br/>(github/api/workflow/deployed)
DemoPage->>DemoPage: Persist to localStorage
DemoPage->>WindowEvents: Emit feature/status events
User->>Assistant: Types message or clicks "Guide me"
Assistant->>Assistant: Collect appState from DemoPage<br/>via window.__runbookAppState
Assistant->>ChatAPI: Send message + appState<br/>+ externalFeature context
ChatAPI->>ChatAPI: Regex pattern match on message
ChatAPI->>ChatAPI: Inspect appState flags<br/>(github/api/workflow/deployed)
ChatAPI->>ChatAPI: Derive uiAction<br/>{type, stepId, feature}
ChatAPI-->>Assistant: Return response + uiAction
Assistant->>WindowEvents: Dispatch runbook-ui-action<br/>event if uiAction present
WindowEvents->>DemoPage: Receive uiAction event
DemoPage->>DemoPage: Highlight/focus target step
DemoPage->>DemoPage: Mark step complete if matched
Assistant-->>User: Display chat response
sequenceDiagram
participant Tour as Guided Tour<br/>(DemoPage)
actor User
participant Step as Tour Step<br/>(Overlay + Action)
participant AppState as App State<br/>Tracker
loop For each TOUR_STEP
Tour->>Tour: Find target element<br/>by selector
Tour->>Tour: Scroll into view +<br/>show overlay rectangle
Tour->>Step: Display instructions &<br/>completion requirements
User->>Step: Click action button<br/>(or performs prerequisite)
alt Prerequisites not met
Step-->>User: Show interrupt warning<br/>+ next required step
else Prerequisites met
Step->>AppState: Update state flag<br/>(e.g., githubConnected=true)
AppState->>Tour: Emit completion event
Tour->>Tour: Mark step complete<br/>+ show success feedback
end
end
Tour->>Tour: All steps complete?
alt Yes
Tour-->>User: Display completion banner<br/>+ next action guidance
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Remove sync setState calls from effects and derive the activity status line from current demo state so eslint react-hooks set-state-in-effect checks pass in CI. Made-with: Cursor
Use crypto.getRandomValues for demo API key token suffixes and replace regex-based next-action context matching with safe string includes to avoid insecure randomness and potential ReDoS alerts. Made-with: Cursor
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (9)
src/app/embed-demo/page.tsx (2)
102-103:viewportHeightis captured once and not recomputed on resize.
viewportHeight = typeof window !== "undefined" ? window.innerHeight : 900runs only at render time of the first mount and is reused at line 814 to position the tour panel. Resizing or rotating the device leaves the panel anchored to the original height. Either move this read intoupdateTourTarget(which already runs onresize) or track viewport size with a small effect.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/embed-demo/page.tsx` around lines 102 - 103, viewportHeight is read once at mount and never updated, so the tour panel positioning (used with currentTourStep/TOUR_STEPS) breaks on resize; fix by reading window.innerHeight where positioning happens (move the read into updateTourTarget which already runs on "resize") or by tracking viewportHeight in state via a small effect that listens to window "resize" and updates the value; update references to viewportHeight in the tour positioning logic (e.g., inside updateTourTarget and any usage that positions the tour panel) so the latest height is used when computing layout.
397-452:else if (… stepComplete)branches read staleappState.In
connectGithub/createApiKey/createWorkflow/deployStaging, the call tosetAppState((s) => ({ ...s, X: true }))is asynchronous, butstepCompletewas computed byuseMemoagainst the previous render'sappState. Theelse if (tourActive && currentTourStep && stepComplete)fallback therefore runs against pre-update state and effectively never matches when this handler is what just made the step complete. It does no harm today because the first branch handles every concrete id, but the dead fallback is misleading and will mask bugs if step ids ever drift.Either remove the
else ifbranch or compute completeness directly from the post-update flag (e.g. recomputestepCompletefrom a localnextstate object).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/embed-demo/page.tsx` around lines 397 - 452, The fallback else-if checks using the memoized stepComplete are stale after you call setAppState in the handlers (connectGithub, createApiKey, createWorkflow, deployStaging); either remove the redundant else-if branch entirely or compute completeness from the post-update state before calling setAppState (e.g., build a local nextState object with the updated flag, derive stepCompleteNext from that and use it instead of the memoized stepComplete), then call advanceTourAfterSuccess(currentTourStep) based on that local computation; apply the same change to connectGithub, createApiKey, createWorkflow and the inner timeout callback in deployStaging and keep the explicit id check (currentTourStep?.id === "...") as-is.src/lib/embedDemoKnowledge.ts (1)
366-373: Overly broaddeploy/stagingmatcher.
m.includes("deploy") || m.includes("staging")catches unrelated questions like "explain staging health" or "how does deploy rollback work?", which would otherwise fall through to the default journey response. Consider tightening to verbs/intents (e.g.\b(deploy|deploy now|push to staging)\b) or moving this branch below the explanatory checks so generic mentions don't hijack the response.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/embedDemoKnowledge.ts` around lines 366 - 373, The current branch using m.includes("deploy") || m.includes("staging") is too broad and hijacks queries; update the condition in the block that returns createResult(...) to either use a stricter regex on m (e.g., match verbs/intents with word boundaries like \b(deploy to staging|deploy now|push to staging)\b) or move this branch below the explanatory/health-check branches so questions like "explain staging health" or "rollback" fall through; reference the same variables (m, createResult, product, excerptFromContent) when changing the matcher so the returned answer/sources/steps logic remains intact.src/app/api/embed/chat/route.ts (3)
181-184: Direct-command regexes don’t respect word boundaries.
/deploy|staging/and/api key|create key|generate key/will fire on questions like "what is staging?" or "explain api keys", returning astart_stepuiActionthat activates the guided tour for an unrelated query. Tighten to verb-led intents (e.g.\b(deploy now|push to staging)\b,\b(create|generate)\s+(an?\s+)?api\s+key\b) or require an imperative cue (how do i …,start …) before emittingstart_step.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/api/embed/chat/route.ts` around lines 181 - 184, The current intent-matching for variable m in src/app/api/embed/chat/route.ts uses loose regexes that match substrings (e.g., the checks that return start_step for stepId "deploy-staging" and "create-api-key"), causing false positives; update the regexes used in that message-to-action mapping to require word boundaries and verb-led or imperative cues (e.g., wrap patterns with \b...\b, use groups like \b(create|generate)\s+(an?\s+)?api\s+key\b for the create-api-key branch and \b(deploy(?:\s+now)?|push\s+to\s+staging)\b or require an imperative prefix like ^(how do i|start|deploy)\b for the deploy-staging branch) so only explicit commands trigger start_step UI actions.
158-186: Consider validatingappStateshape and reusinggetNextAction.
firstIncompleteFeaturere-implements the same prerequisite chain asgetNextActioninsrc/lib/embedDemoNextAction.ts, but uses different feature/step keys (workflow-buildervscreate-workflow). Sharing one function (and theDemoAppStatetype) would prevent the two halves of the system from drifting — thetargetFeaturemismatch noted inembedDemoNextAction.tsis a direct symptom.Also, since
appStateis typed asunknown, a tiny guard like(state as Partial<DemoAppState>)keepsBoolean(...)honest while documenting the contract fromChatBody.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/api/embed/chat/route.ts` around lines 158 - 186, Replace the ad-hoc firstIncompleteFeature logic with a call to the shared getNextAction and type the incoming state as Partial<DemoAppState> to validate shape: update deriveUiAction to cast appState to Partial<DemoAppState> (or guard it) and call getNextAction(...) to obtain feature/stepId instead of duplicating logic in firstIncompleteFeature; remove or collapse firstIncompleteFeature and ensure derived step/feature keys match getNextAction (e.g., "create-workflow" vs "workflow-builder") so both functions reuse the same enum/strings and stay in sync.
123-127: Dead conditional — both branches now produce the same string.After the refactor, the
if/fallback paths return the identical[pageSection, appStateSection, hoverSection].join("\n\n"). Theif (typeof body.pageContext === "string" && body.pageContext.trim())check is no longer doing anything.♻️ Simplify
- if (typeof body.pageContext === "string" && body.pageContext.trim()) { - return [pageSection, appStateSection, hoverSection].join("\n\n"); - } return [pageSection, appStateSection, hoverSection].join("\n\n");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/api/embed/chat/route.ts` around lines 123 - 127, The conditional checking `if (typeof body.pageContext === "string" && body.pageContext.trim())` is dead because both branches return the same value; remove the if/else and return the joined sections directly. Update the function in route.ts (the block that builds/returns the response using pageSection, appStateSection, hoverSection) to simply `return [pageSection, appStateSection, hoverSection].join("\n\n");`, eliminating the redundant `body.pageContext` check and any unreachable branch code.src/components/demo/FeatureCard.tsx (1)
19-29: Place{...rest}before the explicitdata-runbook-*attributes.Spreading
restafter the explicit attributes lets a caller silently overridedata-runbook-feature/-title/-description(and evenclassName, exceptclassNameis destructured) by passing the same key. Putting the spread first preserves the component's invariant thatfeature/title/descriptionare authoritative.♻️ Proposed change
<section + {...rest} className={className} data-runbook-feature={feature} data-runbook-title={title} data-runbook-description={description} - {...rest} >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/demo/FeatureCard.tsx` around lines 19 - 29, The JSX in the FeatureCard component allows callers to override the component's authoritative data attributes because {...rest} is spread after the explicit data-runbook-feature/title/description props; move the {...rest} spread before those explicit attributes in the returned <section> so feature, title, and description passed to FeatureCard cannot be silently overridden (refer to the FeatureCard component's return JSX and the data-runbook-feature / data-runbook-title / data-runbook-description attributes).src/components/EmbeddedRunbookAssistant.tsx (1)
388-416:triggerWhatNextduplicates the server-side prerequisite chain.The same boolean ladder lives in three places now:
getNextAction(src/lib/embedDemoNextAction.ts),firstIncompleteFeature(src/app/api/embed/chat/route.ts), and here. They already disagree on the feature key for the build-workflow step (see comment inembedDemoNextAction.ts), and any future change has to be made in lock-step. Consider exporting a single helper fromembedDemoNextAction.tsand consuming it client-side as well.Additionally, the bullets
["It moves setup forward", "It unlocks the next step", "I highlighted where to click"]are unconditional — they read oddly when the answer is"You are ready to launch."Consider making them state-aware.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/EmbeddedRunbookAssistant.tsx` around lines 388 - 416, triggerWhatNext duplicates the prerequisite boolean ladder that already exists in getNextAction and firstIncompleteFeature; export the shared helper (e.g., getNextAction or a new computeNextAction helper) from src/lib/embedDemoNextAction.ts and import it into EmbeddedRunbookAssistant.tsx to compute githubConnected/apiKeyCreated/workflowCreated/deployed and the final answer so all callers use a single source of truth (ensure the exported helper uses the canonical feature key for the build-workflow step). Also make the bullets in triggerWhatNext state-aware: return contextual bullets only for actionable states and use an alternative (or none) when the helper returns the terminal "ready to launch" state so the assistant text reads correctly.src/app/embed-demo/layout.tsx (1)
93-97:firstSentencewill truncate at common abbreviations.
/^.*?[.!?](?:\s|$)/stops at the first period followed by whitespace, so inputs like"e.g. connect GitHub. then deploy."collapse to"e.g.". Given inputs come fromcompactSentence(bestSentence)derived from imported repo content, this is a realistic foot-gun for tooltips. Consider requiring at least a few words before the terminator, or skipping single-letter / common-abbreviation cases.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/embed-demo/layout.tsx` around lines 93 - 97, The current firstSentence function truncates at abbreviations like "e.g." because its regex /^.*?[.!?](?:\s|$)/ stops at the first period; update firstSentence to require a more realistic sentence terminator by either (a) using a regex that only matches a terminator if there are at least N words/characters before it (e.g., require 2–3 words before the punctuation) or (b) post-process the initial match to detect and skip common abbreviations (e.g., "e.g.", "i.e.", "Mr.", "Dr.") and continue searching for the next terminator; make this change in the firstSentence function (which is fed compactSentence(bestSentence)) so tooltips don't collapse on common abbreviations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/app/embed-demo/page.tsx`:
- Around line 783-798: The modal controlled by showWorkflowModal lacks any
dismiss mechanism; add a Cancel button that calls setShowWorkflowModal(false)
alongside the existing Create workflow button, make the backdrop div (the
element rendered when showWorkflowModal is true) handle clicks to close the
modal by calling setShowWorkflowModal(false) when the click is outside the modal
content, and register a keydown listener (mounted when showWorkflowModal is true
and cleaned up) to close on Escape; ensure createWorkflow remains unchanged and
that event propagation is stopped on the inner modal container so clicking the
form/buttons doesn't trigger backdrop close.
In `@src/components/EmbeddedRunbookAssistant.tsx`:
- Around line 172-178: The onPulse handler currently calls window.setTimeout
without tracking the timer; change it to store the timeout id (e.g.,
pulseTimeoutRef) when calling window.setTimeout in onPulse, clear any existing
timeout before creating a new one, and call
clearTimeout(pulseTimeoutRef.current) in a cleanup (useEffect return) so
setPulseLaunch(false) cannot run after the component unmounts or when pulses
stack; update references to onPulse/setPulseLaunch and ensure the ref is cleared
after clearing the timeout.
In `@src/lib/embedDemoKnowledge.ts`:
- Around line 129-157: inferNextActionFromContext currently inspects a
serialized page string with fragile regexes that match JSON keys and UI chrome;
change it to accept the typed appState booleans instead and use those booleans
for branching. Update inferNextActionFromContext signature to take an object
(e.g., { githubConnected, apiKeyCreated, stagingHealthy } or the exact appState
type), replace regex checks (/github.*connected/i, /api key|api keys/i,
/deploy|deployment/i and the rk_live_/staging checks) with direct boolean logic,
and adjust callers (buildNorthstarDemoResponse and the route that calls it) to
pass the parsed appState through so the function mirrors
firstIncompleteFeature/getNextAction semantics. Ensure return shape ({ answer,
feature, step }) stays the same.
In `@src/lib/embedDemoNextAction.ts`:
- Around line 8-42: The build-workflow step in getNextAction uses the wrong
targetFeature string ("create-workflow") which breaks feature lookups; update
the DemoNextAction.targetFeature union and the getNextAction return for the
"build-workflow" branch (function getNextAction, type DemoNextAction) to use
"workflow-builder" instead of "create-workflow" (and ensure both occurrences are
changed), or better yet replace the literal with a shared feature-id
enum/constant used across the codebase so the build-workflow step consistently
returns targetFeature: "workflow-builder".
---
Nitpick comments:
In `@src/app/api/embed/chat/route.ts`:
- Around line 181-184: The current intent-matching for variable m in
src/app/api/embed/chat/route.ts uses loose regexes that match substrings (e.g.,
the checks that return start_step for stepId "deploy-staging" and
"create-api-key"), causing false positives; update the regexes used in that
message-to-action mapping to require word boundaries and verb-led or imperative
cues (e.g., wrap patterns with \b...\b, use groups like
\b(create|generate)\s+(an?\s+)?api\s+key\b for the create-api-key branch and
\b(deploy(?:\s+now)?|push\s+to\s+staging)\b or require an imperative prefix like
^(how do i|start|deploy)\b for the deploy-staging branch) so only explicit
commands trigger start_step UI actions.
- Around line 158-186: Replace the ad-hoc firstIncompleteFeature logic with a
call to the shared getNextAction and type the incoming state as
Partial<DemoAppState> to validate shape: update deriveUiAction to cast appState
to Partial<DemoAppState> (or guard it) and call getNextAction(...) to obtain
feature/stepId instead of duplicating logic in firstIncompleteFeature; remove or
collapse firstIncompleteFeature and ensure derived step/feature keys match
getNextAction (e.g., "create-workflow" vs "workflow-builder") so both functions
reuse the same enum/strings and stay in sync.
- Around line 123-127: The conditional checking `if (typeof body.pageContext ===
"string" && body.pageContext.trim())` is dead because both branches return the
same value; remove the if/else and return the joined sections directly. Update
the function in route.ts (the block that builds/returns the response using
pageSection, appStateSection, hoverSection) to simply `return [pageSection,
appStateSection, hoverSection].join("\n\n");`, eliminating the redundant
`body.pageContext` check and any unreachable branch code.
In `@src/app/embed-demo/layout.tsx`:
- Around line 93-97: The current firstSentence function truncates at
abbreviations like "e.g." because its regex /^.*?[.!?](?:\s|$)/ stops at the
first period; update firstSentence to require a more realistic sentence
terminator by either (a) using a regex that only matches a terminator if there
are at least N words/characters before it (e.g., require 2–3 words before the
punctuation) or (b) post-process the initial match to detect and skip common
abbreviations (e.g., "e.g.", "i.e.", "Mr.", "Dr.") and continue searching for
the next terminator; make this change in the firstSentence function (which is
fed compactSentence(bestSentence)) so tooltips don't collapse on common
abbreviations.
In `@src/app/embed-demo/page.tsx`:
- Around line 102-103: viewportHeight is read once at mount and never updated,
so the tour panel positioning (used with currentTourStep/TOUR_STEPS) breaks on
resize; fix by reading window.innerHeight where positioning happens (move the
read into updateTourTarget which already runs on "resize") or by tracking
viewportHeight in state via a small effect that listens to window "resize" and
updates the value; update references to viewportHeight in the tour positioning
logic (e.g., inside updateTourTarget and any usage that positions the tour
panel) so the latest height is used when computing layout.
- Around line 397-452: The fallback else-if checks using the memoized
stepComplete are stale after you call setAppState in the handlers
(connectGithub, createApiKey, createWorkflow, deployStaging); either remove the
redundant else-if branch entirely or compute completeness from the post-update
state before calling setAppState (e.g., build a local nextState object with the
updated flag, derive stepCompleteNext from that and use it instead of the
memoized stepComplete), then call advanceTourAfterSuccess(currentTourStep) based
on that local computation; apply the same change to connectGithub, createApiKey,
createWorkflow and the inner timeout callback in deployStaging and keep the
explicit id check (currentTourStep?.id === "...") as-is.
In `@src/components/demo/FeatureCard.tsx`:
- Around line 19-29: The JSX in the FeatureCard component allows callers to
override the component's authoritative data attributes because {...rest} is
spread after the explicit data-runbook-feature/title/description props; move the
{...rest} spread before those explicit attributes in the returned <section> so
feature, title, and description passed to FeatureCard cannot be silently
overridden (refer to the FeatureCard component's return JSX and the
data-runbook-feature / data-runbook-title / data-runbook-description
attributes).
In `@src/components/EmbeddedRunbookAssistant.tsx`:
- Around line 388-416: triggerWhatNext duplicates the prerequisite boolean
ladder that already exists in getNextAction and firstIncompleteFeature; export
the shared helper (e.g., getNextAction or a new computeNextAction helper) from
src/lib/embedDemoNextAction.ts and import it into EmbeddedRunbookAssistant.tsx
to compute githubConnected/apiKeyCreated/workflowCreated/deployed and the final
answer so all callers use a single source of truth (ensure the exported helper
uses the canonical feature key for the build-workflow step). Also make the
bullets in triggerWhatNext state-aware: return contextual bullets only for
actionable states and use an alternative (or none) when the helper returns the
terminal "ready to launch" state so the assistant text reads correctly.
In `@src/lib/embedDemoKnowledge.ts`:
- Around line 366-373: The current branch using m.includes("deploy") ||
m.includes("staging") is too broad and hijacks queries; update the condition in
the block that returns createResult(...) to either use a stricter regex on m
(e.g., match verbs/intents with word boundaries like \b(deploy to staging|deploy
now|push to staging)\b) or move this branch below the explanatory/health-check
branches so questions like "explain staging health" or "rollback" fall through;
reference the same variables (m, createResult, product, excerptFromContent) when
changing the matcher so the returned answer/sources/steps logic remains intact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e51ede2a-fab5-4f77-915e-47b1a0f48e15
📒 Files selected for processing (7)
src/app/api/embed/chat/route.tssrc/app/embed-demo/layout.tsxsrc/app/embed-demo/page.tsxsrc/components/EmbeddedRunbookAssistant.tsxsrc/components/demo/FeatureCard.tsxsrc/lib/embedDemoKnowledge.tssrc/lib/embedDemoNextAction.ts
Harden modal/resize behavior, remove stale tour completion branches, unify next-action derivation across page/widget/API, tighten uiAction intent matching, and improve fallback sentence/next-step parsing consistency. Made-with: Cursor
Summary by CodeRabbit
New Features
Bug Fixes