feat: ship Track B page-aware widget intelligence - #21
Conversation
Make the embedded assistant prioritize live page context and hovered feature context over imported docs, add lightweight hover-aware UX cues, and keep responses compact while preserving existing studio/embed flows. 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 49 minutes and 38 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 (4)
📝 WalkthroughWalkthroughThis PR implements hover-aware context capture for the embedded runbook assistant. The frontend now detects when users hover over elements marked with Changes
Sequence DiagramsequenceDiagram
participant User as User
participant Embed as Frontend Embed
participant Component as React Component
participant API as Chat API
participant Knowledge as Knowledge System
User->>Embed: Move pointer over feature element
Embed->>Embed: Detect data-runbook-* attribute
Embed->>Embed: Update hoveredFeature state
Embed->>Component: Pass hoveredFeature via hook/ref
Component->>Component: Display "Looking at: …" banner
User->>Component: Submit chat message
Component->>Component: Construct pageContext + hoveredFeature
Component->>API: POST with message, pageContext, hoveredFeature
API->>API: Sanitize hoveredFeature
API->>API: Build normalizedContext (page + hovered)
API->>Knowledge: Call buildNorthstarDemoResponse(message, context, hoveredFeature)
Knowledge->>Knowledge: Check if hovered-feature explanation
alt Hovered Feature Query
Knowledge->>Knowledge: Return workflow continuation steps
else What Can I Do Here
Knowledge->>Knowledge: Return page actions list
else Default Query
Knowledge->>Knowledge: Process standard intent routing
end
Knowledge->>API: Return response
API->>Component: Send response to user
Component->>User: Display answer with hovered context applied
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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 |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/EmbeddedRunbookAssistant.tsx (1)
212-234:⚠️ Potential issue | 🟡 Minor
pageContextduplicatespageTitle/pageUrlalready sent as separate fields.
pageContextis built as${window.location.href}\n${document.title}\n${pageBody}and thenpageTitle/pageUrlare also sent separately. The API route'snormalizePageContextprependsPage URL: …\nPage title: …topageContext, so URL and title end up in the prompt twice. Consider sending only the body inpageContextand letting the route compose the structured header.🛠️ Suggested fix
- const pageContext = - pageContextOverride || - (typeof window !== "undefined" - ? [window.location.href, document.title, pageBody].filter(Boolean).join("\n") - : pageBody); + const pageContext = pageContextOverride || pageBody;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/EmbeddedRunbookAssistant.tsx` around lines 212 - 234, The pageContext currently duplicates pageTitle and pageUrl because pageContext is built from window.location.href and document.title and those are also sent separately; update the payload construction in EmbeddedRunbookAssistant (the pageContext, pageBody, pageTitle, pageUrl usage) so that pageContext contains only the page body (or pageContextOverride if provided) and does not include URL/title — keep pageBody built as before (document.body.innerText trimmed/sliced) and still send pageTitle and pageUrl separately; this prevents normalizePageContext from causing duplicated URL/title in the prompt.
🧹 Nitpick comments (2)
src/app/api/embed/chat/route.ts (1)
124-132: Sanitization is permissive — consider validating field shape.
sanitizeHoveredFeatureaccepts any object and coercesfeature/title/descriptionviaString(obj.x || ""). If a caller passes nested objects/arrays (e.g.,{ title: { foo: "bar" } }),String(...)yields"[object Object]"which then gets joined into the prompt. Trim/strip control chars and requirestringtype to keep prompt content predictable.🛠️ Tighter sanitization
function sanitizeHoveredFeature(raw: unknown): string { if (!raw || typeof raw !== "object") return ""; const obj = raw as Record<string, unknown>; - const feature = String(obj.feature || "").trim(); - const title = String(obj.title || "").trim(); - const description = String(obj.description || "").trim().slice(0, 400); + const asStr = (v: unknown) => (typeof v === "string" ? v : "").replace(/[\u0000-\u001f]/g, " ").trim(); + const feature = asStr(obj.feature); + const title = asStr(obj.title); + const description = asStr(obj.description).slice(0, 400); const joined = [title || feature, description].filter(Boolean).join(" — "); return joined.slice(0, 520); }🤖 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 124 - 132, sanitizeHoveredFeature currently coerces any value to a string (e.g., objects become "[object Object]") and allows control chars; change it to only accept primitive strings for feature/title/description, skip or default non-string fields, trim and limit lengths (e.g., title/feature to ~120, description to 400), and strip control characters (use a simple regex to remove non-printable chars) before joining; update the function sanitizeHoveredFeature to validate typeof values === "string" for obj.feature/obj.title/obj.description, apply stripping/trimming/length caps, and then join and final-slice to 520 to ensure predictable prompt content.public/runbook-embed.js (1)
509-516:pageContextalready contains URL/title — sending them again duplicates them in the prompt.The existing
pageContext()helper (lines 408–449) already concatenateslocation.href,document.title, meta description, headings, and body text. With the newpageTitle/pageUrlfields,normalizePageContextinsrc/app/api/embed/chat/route.ts(lines 111–119) prependsPage URL: …\nPage title: …to that string, so URL and title end up in the LLM prompt twice. Consider either dropping the duplicates from thepageContext()output or sending only the structuredpageTitle/pageUrland a body-onlypageContext.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@public/runbook-embed.js` around lines 509 - 516, The body object is sending pageUrl and pageTitle in addition to pageContext(), which already includes URL and title, causing duplicate data in the LLM prompt; update the payload creation in the body (where body is assembled) to either remove pageUrl and pageTitle and send only pageContext(), or change pageContext() to return only the page body (rename/adjust helper or add a new pageBody helper) and keep structured pageTitle/pageUrl fields so normalizePageContext (in src/app/api/embed/chat/route.ts) receives non-duplicated data; modify whichever you choose consistently (payload assembly and pageContext() implementation or create pageBody()) to avoid sending URL/title twice.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@public/runbook-embed.js`:
- Around line 21-24: The includeBodyText expression currently forces true due to
the trailing "|| true", making the data-include-body-text attribute checks dead;
remove the "|| true" from the assignment to includeBodyText so that
includeBodyText is computed solely from
script.hasAttribute("data-include-body-text") and the
document.querySelector('script[src*="runbook-embed.js"][data-include-body-text]')
check (or, if the intended default is to include body text, instead remove the
attribute checks and document that document.body.innerText is always sent by the
chat request code that reads document.body.innerText).
- Around line 243-260: The pointerout handler clears hoveredFeature too
aggressively; update trackHoverEvents so handleOut accepts the PointerEvent,
checks evt.relatedTarget (ensure it's an Element) and finds its closest ancestor
matching
"[data-runbook-feature],[data-runbook-title],[data-runbook-description]"; if
relatedTarget is still inside the same ancestor as the original element (the one
computed in handleOver), do nothing, otherwise call updateHoveredFeature(null).
Implement this by changing handleOut to receive the event, locating the current
target ancestor via evt.target.closest(...) and comparing it to
relatedTarget.closest(...), and keep document.addEventListener("pointerout",
handleOut, true) so events are passed through.
In `@src/app/api/embed/chat/route.ts`:
- Around line 108-122: normalizePageContext currently injects a "Hovered
feature: …" line into the assembled pageContext when body.pageContext is a
string, which causes duplication because the same sanitized hovered value is
also passed separately to runNorthstarEmbedChat; fix by removing/skipping the
hovered insertion in normalizePageContext for the branch that handles string
pageContext (use sanitizeHoveredFeature only where needed), so the
hoveredFeature is provided only via the dedicated argument to
runNorthstarEmbedChat and not duplicated in normalizePageContext (refer to
function normalizePageContext and call site runNorthstarEmbedChat to locate the
changes).
In `@src/lib/embedDemoKnowledge.ts`:
- Around line 114-121: The handler that matches "what can i do here" currently
returns hard-coded bullets/steps (via createResult) irrespective of page
context; update the branch in embedDemoKnowledge where m.includes("what can i do
here") to consult pageContext/ctx and hovered state (and product when available)
before emitting actions: if ctx or hovered indicate the demo dashboard produce
dashboard-specific bullets/steps, otherwise either synthesize bullets from
discoverable controls in pageContext/hovered or return a neutral deferral answer
(e.g., suggest visible CTAs, navigation, or "use the primary CTA" fallback)
instead of the fixed list; ensure you reference and use product,
pageContext/ctx, and hovered to build the result passed to createResult.
---
Outside diff comments:
In `@src/components/EmbeddedRunbookAssistant.tsx`:
- Around line 212-234: The pageContext currently duplicates pageTitle and
pageUrl because pageContext is built from window.location.href and
document.title and those are also sent separately; update the payload
construction in EmbeddedRunbookAssistant (the pageContext, pageBody, pageTitle,
pageUrl usage) so that pageContext contains only the page body (or
pageContextOverride if provided) and does not include URL/title — keep pageBody
built as before (document.body.innerText trimmed/sliced) and still send
pageTitle and pageUrl separately; this prevents normalizePageContext from
causing duplicated URL/title in the prompt.
---
Nitpick comments:
In `@public/runbook-embed.js`:
- Around line 509-516: The body object is sending pageUrl and pageTitle in
addition to pageContext(), which already includes URL and title, causing
duplicate data in the LLM prompt; update the payload creation in the body (where
body is assembled) to either remove pageUrl and pageTitle and send only
pageContext(), or change pageContext() to return only the page body
(rename/adjust helper or add a new pageBody helper) and keep structured
pageTitle/pageUrl fields so normalizePageContext (in
src/app/api/embed/chat/route.ts) receives non-duplicated data; modify whichever
you choose consistently (payload assembly and pageContext() implementation or
create pageBody()) to avoid sending URL/title twice.
In `@src/app/api/embed/chat/route.ts`:
- Around line 124-132: sanitizeHoveredFeature currently coerces any value to a
string (e.g., objects become "[object Object]") and allows control chars; change
it to only accept primitive strings for feature/title/description, skip or
default non-string fields, trim and limit lengths (e.g., title/feature to ~120,
description to 400), and strip control characters (use a simple regex to remove
non-printable chars) before joining; update the function sanitizeHoveredFeature
to validate typeof values === "string" for
obj.feature/obj.title/obj.description, apply stripping/trimming/length caps, and
then join and final-slice to 520 to ensure predictable prompt content.
🪄 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: c3f5f491-692a-45e8-b010-313685d42223
📒 Files selected for processing (6)
public/runbook-embed.jssrc/app/api/embed/chat/route.tssrc/components/EmbeddedRunbookAssistant.tsxsrc/lib/embedDemoKnowledge.tssrc/lib/embedNorthstarChat.tssrc/lib/prompts.ts
Remove duplicated URL/title from widget page context, tighten hovered feature sanitization, stabilize hover pointerout behavior in the embed script, and make "What can I do here" fallback actions context-aware. Made-with: Cursor
Make the embedded assistant prioritize live page context and hovered feature context over imported docs, add lightweight hover-aware UX cues, and keep responses compact while preserving existing studio/embed flows.
Made-with: Cursor
Summary by CodeRabbit
Release Notes