Skip to content

feat: ship Track B page-aware widget intelligence - #21

Merged
dfed25 merged 3 commits into
mainfrom
feature/track-b-widget-intelligence
Apr 26, 2026
Merged

dfed25 merged 3 commits into
mainfrom
feature/track-b-widget-intelligence

Conversation

@dfed25

@dfed25 dfed25 commented Apr 26, 2026

Copy link
Copy Markdown
Owner

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

  • New Features
    • Added hover-aware context indicator displaying "Looking at: [feature]" when hovering over tracked page elements.
    • New "What can I do here?" quick-action chip for instant help queries.
    • Chat now sends enhanced context including page title, URL, and currently hovered feature for more relevant responses.

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
@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@dfed25 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 49 minutes and 38 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a641ce25-ae54-438c-a40e-bfce8c8afae6

📥 Commits

Reviewing files that changed from the base of the PR and between 0bc83a9 and 72ed604.

📒 Files selected for processing (4)
  • public/runbook-embed.js
  • src/app/api/embed/chat/route.ts
  • src/components/EmbeddedRunbookAssistant.tsx
  • src/lib/embedDemoKnowledge.ts
📝 Walkthrough

Walkthrough

This PR implements hover-aware context capture for the embedded runbook assistant. The frontend now detects when users hover over elements marked with data-runbook-* attributes, tracks the hovered feature, and sends it alongside chat messages. The backend accepts this hovered feature context, incorporates it into API calls, and prioritizes it in the LLM system prompt when generating responses.

Changes

Cohort / File(s) Summary
Frontend Hover Tracking
public/runbook-embed.js, src/components/EmbeddedRunbookAssistant.tsx
Adds pointer listeners to detect and track hovered elements with data-runbook-* attributes. Displays a "Looking at: …" banner when hovering and debounces state updates. Captures hovered feature context and sends it to the chat API alongside existing page data.
API Route Processing
src/app/api/embed/chat/route.ts
Introduces sanitizeHoveredFeature validation and threads it through both demo and authenticated chat paths. Augments normalizePageContext to prepend page title, URL, and formatted hovered feature context. Passes hoveredFeature to downstream knowledge systems.
Knowledge & Demo Response
src/lib/embedDemoKnowledge.ts, src/lib/embedNorthstarChat.ts
Adds optional hoveredFeature parameter to demo response builder and Northstar chat input. Introduces early-return handlers for hovered-feature explanation queries and "What can I do here?" prompts. Updates LLM prompt construction to enforce context priority (current page → hovered feature → docs).
System Prompt
src/lib/prompts.ts
Updates system prompt to explicitly prioritize current page context first, hovered feature context second, and documentation third when selecting contextual information.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

A rabbit hovered near the text so small,
"Which feature shines?" it wondered at the call.
With context close and answers quick to share,
The assistant whispers, "You're looking there!" 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: shipping page-aware widget intelligence (Track B) that prioritizes live page context and hovered feature context.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/track-b-widget-intelligence

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

pageContext duplicates pageTitle/pageUrl already sent as separate fields.

pageContext is built as ${window.location.href}\n${document.title}\n${pageBody} and then pageTitle/pageUrl are also sent separately. The API route's normalizePageContext prepends Page URL: …\nPage title: … to pageContext, so URL and title end up in the prompt twice. Consider sending only the body in pageContext and 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.

sanitizeHoveredFeature accepts any object and coerces feature/title/description via String(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 require string type 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: pageContext already contains URL/title — sending them again duplicates them in the prompt.

The existing pageContext() helper (lines 408–449) already concatenates location.href, document.title, meta description, headings, and body text. With the new pageTitle/pageUrl fields, normalizePageContext in src/app/api/embed/chat/route.ts (lines 111–119) prepends Page URL: …\nPage title: … to that string, so URL and title end up in the LLM prompt twice. Consider either dropping the duplicates from the pageContext() output or sending only the structured pageTitle/pageUrl and a body-only pageContext.

🤖 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5720dba and 0bc83a9.

📒 Files selected for processing (6)
  • public/runbook-embed.js
  • src/app/api/embed/chat/route.ts
  • src/components/EmbeddedRunbookAssistant.tsx
  • src/lib/embedDemoKnowledge.ts
  • src/lib/embedNorthstarChat.ts
  • src/lib/prompts.ts

Comment thread public/runbook-embed.js
Comment thread public/runbook-embed.js
Comment thread src/app/api/embed/chat/route.ts
Comment thread src/lib/embedDemoKnowledge.ts
dfed25 and others added 2 commits April 25, 2026 20:13
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
@dfed25
dfed25 merged commit 4cc4e41 into main Apr 26, 2026
5 checks passed
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