Chrome widget - #15
Conversation
Make the extension reliable across sites by scoping injected UI to its own root, routing scan/find flows through deterministic demo fallbacks, and restoring chat/task interactions. Add concrete demo page CTA elements and backend GitHub/Slack fallback task detection so scan and highlight remain stable during live demos. Made-with: Cursor
Enhance extension page scraping with structured text and clickable-element hints, upgrade element matching with visibility-aware scoring, and harden widget API parsing/fallback handling for more reliable find-on-page guidance across real websites. Made-with: Cursor
Detect GitHub profile pages as a dedicated onboarding flow and provide deterministic element highlights for repositories, stars, and profile settings so find-on-page remains actionable and reliable during demos. Made-with: Cursor
Enable chat prompts to trigger element-finding and guided highlight overlays on the current page, so users can ask where actions are and immediately see targets. Add resilient chat API quota fallback responses to avoid temporary-unavailable failures during Gemini rate-limit spikes. Made-with: Cursor
For page-layout requests, attempt element location before final chat rendering and hide generic missing-documentation responses when a valid target is successfully highlighted, keeping the assistant output actionable and non-contradictory. 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 38 minutes and 42 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 (6)
📝 WalkthroughWalkthroughThis PR introduces the Runbook Chrome extension for in-page onboarding guidance. It includes a background service worker, content script with UI overlay, widget styling, and a new backend API route for task detection. Demo pages have action buttons added for testing the extension functionality. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Content as Content Script
participant Backend as Backend API
participant Gemini as Gemini AI
participant DOM as Page DOM
User->>Content: Page loads / Widget opens
Content->>Content: Extract page text & interactive elements
Content->>Backend: POST /api/widget (url, pageText, elements)
alt Demo Route
Backend->>Backend: Match demo URL
Backend-->>Content: Return predefined task
else AI Detection
Backend->>Gemini: Generate task from page content
Gemini-->>Backend: Task metadata + element selector
Backend->>Gemini: Localize target element
Gemini-->>Backend: Element text & instruction
Backend-->>Content: Return { found, elementText, instruction }
end
Content->>DOM: Highlight target element with overlay
Content->>Content: Render step card & chat panel
User->>Content: Interact with next/chat/complete
alt Next Step / Complete
Content->>DOM: Move overlay to next step
else Chat Question
Content->>Backend: POST /api/chat (question)
Backend->>Gemini: Answer with context
Gemini-->>Backend: Response + sources
Backend-->>Content: { ok, data, sources }
Content->>DOM: Highlight element if found
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 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 |
Delete accidentally tracked .tmp-pw-user* browser profile artifacts and add ignore rules so generated Playwright cache/state files never appear in commits again. Made-with: Cursor
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (3)
public/extension/manifest-minimal.json (1)
1-12: Drop or clearly mark this debug manifest.Chrome only reads
manifest.jsonfrom the unpacked folder, so this file is unused at runtime and creates confusion next to the realmanifest.json. It also lackswidget.cssand thebackgroundservice worker, so anyone who copies it into place by mistake gets a styling-less extension with no API relay (the content script'sapiRequestwill throw "Chrome runtime unavailable"-style errors on every call).If it's intentional scaffolding for a stripped-down test build, please either remove it from the shipped folder, rename it to something like
manifest.debug.jsonoutsidepublic/extension/, or add a top-level note inLOAD_EXTENSION.mdclarifying which file Chrome consumes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@public/extension/manifest-minimal.json` around lines 1 - 12, This minimal manifest (manifest-minimal.json) is a confusing debug artifact because Chrome ignores it in favor of manifest.json and it lacks required assets (widget.css) and the background service worker that the content script (content.js) depends on; either remove this file from public/extension/, rename it to manifest.debug.json and move it out of public/extension/ to avoid accidental use, or keep it but add a clear top-level comment and an entry in LOAD_EXTENSION.md stating Chrome reads manifest.json and that manifest-minimal.json is a stripped test scaffold (also note missing entries like "background" and the widget.css dependency and that content_scripts → js: ["content.js"] expects a runtime relay), so no one copies it into place by mistake.public/extension/manifest.json (1)
6-7: Remove the unused"scripting"permission.The
"scripting"permission is not needed in MV3 unless your code callschrome.scripting.*APIs (likeexecuteScript,insertCSS, orregisterContentScripts). Staticcontent_scriptsregistration in the manifest does not require it. Dropping this unused permission removes the associated user warning on install and aligns with least-privilege principles.The
host_permissions: ["<all_urls>"]is appropriate for the "run on every page" functionality.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@public/extension/manifest.json` around lines 6 - 7, Remove the unused "scripting" permission from the manifest.json by deleting the "scripting" entry in the "permissions" array (leave host_permissions: ["<all_urls>"] intact); ensure no code relies on chrome.scripting.* (e.g., executeScript/insertCSS/registerContentScripts) before removing to avoid runtime errors.public/extension/content.js (1)
743-750: TriplesetTimeout(init, …)is a code smell.
init()is idempotent (early-returns if the root exists), so this is harmless, but it papers over a race rather than fixing it. Since the only reason for the retries appears to be SPAs that mutatedocument.bodyafterDOMContentLoaded, aMutationObserverondocument.documentElement(disconnected onceinjectWidgetsucceeds) would be more deterministic and avoid CPU work on the happy path.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@public/extension/content.js` around lines 743 - 750, The triple setTimeout retries for init() are a fragile workaround; replace them with a MutationObserver that watches document.documentElement (or document.body) for subtree/childList mutations and calls init() when relevant, disconnecting the observer as soon as injectWidget succeeds (init already early-returns when the root exists) so you avoid repeated timers and unnecessary CPU work; keep the existing DOMContentLoaded handling and ensure the observer is added after DOMContentLoaded if needed and is disconnected inside the success path of init()/injectWidget.
🤖 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/extension/background.js`:
- Around line 7-36: Validate and restrict request.url to a known backend origin
before calling fetch (e.g., parse new URL(request.url).origin and compare
against an allowlist constant like RUNBOOK_API_ORIGIN) and reject/sendResponse
error if it doesn't match; wrap the fetch in an AbortController with a timeout
(create AbortController, pass signal to fetch, set a setTimeout to call
controller.abort() after a sensible ms, and clear the timer on resolution) so
the chrome.runtime.onMessage.addListener handler (the API_REQUEST branch) always
either sendResponse with an error on invalid origin or aborted/timeout, or with
the fetched data, and keep returning true to keep the response channel open.
In `@public/extension/content.js`:
- Around line 175-204: The overlay currently sets overlay.innerHTML with
untrusted stepLabel/text causing XSS; replace that with building DOM nodes via
document.createElement (e.g., create a label div and a text div), set their
textContent (not innerHTML) from stepLabel/text, append them to overlay, and
keep the rest of the logic (overlay.id="rb-step-overlay", activeOverlayEl
assignment, reposition function, overlayRepositionHandler, and scroll/resize
listeners) unchanged; ensure any places that pass these values (findOnPage,
sendChat, currentTask.steps[*]) continue to provide strings but never get
injected via innerHTML.
- Around line 466-485: completeTask currently replaces the entire
`#rb-task-content` subtree via content.innerHTML, removing child nodes (e.g.,
`#rb-task-title`, `#rb-step-number`, `#rb-step-text`, `#rb-progress-fill`,
`#rb-progress-label`, `#rb-next-btn`, `#rb-complete-btn`) that updateTaskPanel
expects; instead, modify completeTask to either (A) render the success UI into a
new/sibling element inside `#rb-task-content` and toggle its visibility (hide the
original children but keep them in the DOM), or (B) call the same render
function that builds the full task panel (the one updateTaskPanel uses) after
showing the transient success state so all expected IDs are restored; ensure you
update references to byId("rb-task-content"), preserve the existing child nodes
or re-create them before any future calls to updateTaskPanel, and keep the
existing setTimeout(clearHighlight, 2000) and error handling.
- Line 4: The code currently hardcodes RUNBOOK_API in
public/extension/content.js which breaks non-local deployments; replace the
hardcoded string by loading the base URL from a configurable source (preferably
runtime chrome.storage with a fallback to a build-time env var or
manifest-injected value) so all calls to RUNBOOK_API (used by /api/widget,
/api/chat, /api/tasks/update) use the configured endpoint; implement a small
initializer in content.js that reads chrome.storage (or a window/global injected
by the background/options page) and assigns RUNBOOK_API before any API calls,
and add an options page or build-time switch to set/save the URL so it isn’t
fixed to "http://localhost:3000".
In `@public/extension/LOAD_EXTENSION.md`:
- Around line 20-31: Replace the hard-coded local path with a portable,
repo-relative instruction (e.g., instruct users to navigate to and select the
"public/extension" folder in their cloned repository rather than
"C:\Users\bryan\..."), and change the single-author phrasing "Send it to me" to
a team-friendly action such as "file an issue in the repo or post the screenshot
in the project/team channel" so the troubleshooting section is OS-agnostic and
suitable for multiple contributors.
In `@public/extension/widget.css`:
- Line 261: The CSS rule uses the deprecated declaration "word-break:
break-word"; replace that declaration with the modern equivalent by removing
"word-break: break-word" and adding "overflow-wrap: anywhere" (or
"overflow-wrap: break-word" if preferred) so text wraps correctly; locate the
rule containing the "word-break: break-word" declaration and swap it to use
"overflow-wrap: anywhere" to resolve the stylelint warning.
- Around line 3-7: The CSS selector using `#runbook-root` is stale and must match
the actual injected root id; update the selector in public/extension/widget.css
from `#runbook-root` to `#runbook-extension-root` so the wildcard rule (box-sizing,
font-family, line-height) applies to the real root, and search for any other
occurrences of `#runbook-root` (or duplicate inline styles) to consolidate to
`#runbook-extension-root`; confirm this aligns with the root creation in
content.js where the element is created with id="runbook-extension-root".
In `@src/app/api/widget/route.ts`:
- Around line 158-202: The POST handler is exposing a Gemini-backed path without
authentication or rate limiting; update the POST function to gate any branch
that uses process.env.GEMINI_API_KEY or forwards pageText/taskDescription to
Gemini by requiring an authenticated hire or validated server-side token (mirror
requireHireAccess(hireId) or validate a shared bearer secret), perform coarse
rate limiting per hire or IP before invoking the Gemini branches, and
sanitize/validate pageText/taskDescription (length, allowed chars, and explicit
consent flag) to reduce PII/exfiltration risk; keep the demo short-circuit
branches (the "/demo/github" and "/demo/expenses" returns) open if needed but
ensure all real-Gemini calls are only reachable after auth, rate-limit, and
input checks.
- Line 42: The substring checks on haystack (the combined `${url}\n${pageText}`)
are too broad; update the conditional in route.ts (the if that currently checks
haystack.includes("/demo/github") || haystack.includes("github") ||
haystack.includes("eng-access")) to parse the URL with new URL(url) and perform
tight hostname/path checks (e.g., urlObj.hostname === "github.com" or
urlObj.pathname.startsWith("/demo/github") or exact anchored phrases in pageText
like "\nRequest GitHub access" rather than any "github"), and similarly tighten
any "expense" checks (see the similar check around the other occurrence) to
either hostname/path checks or anchored/whole-word phrases in pageText so
unrelated pages with the word "github" or "expense" are not misclassified.
---
Nitpick comments:
In `@public/extension/content.js`:
- Around line 743-750: The triple setTimeout retries for init() are a fragile
workaround; replace them with a MutationObserver that watches
document.documentElement (or document.body) for subtree/childList mutations and
calls init() when relevant, disconnecting the observer as soon as injectWidget
succeeds (init already early-returns when the root exists) so you avoid repeated
timers and unnecessary CPU work; keep the existing DOMContentLoaded handling and
ensure the observer is added after DOMContentLoaded if needed and is
disconnected inside the success path of init()/injectWidget.
In `@public/extension/manifest-minimal.json`:
- Around line 1-12: This minimal manifest (manifest-minimal.json) is a confusing
debug artifact because Chrome ignores it in favor of manifest.json and it lacks
required assets (widget.css) and the background service worker that the content
script (content.js) depends on; either remove this file from public/extension/,
rename it to manifest.debug.json and move it out of public/extension/ to avoid
accidental use, or keep it but add a clear top-level comment and an entry in
LOAD_EXTENSION.md stating Chrome reads manifest.json and that
manifest-minimal.json is a stripped test scaffold (also note missing entries
like "background" and the widget.css dependency and that content_scripts → js:
["content.js"] expects a runtime relay), so no one copies it into place by
mistake.
In `@public/extension/manifest.json`:
- Around line 6-7: Remove the unused "scripting" permission from the
manifest.json by deleting the "scripting" entry in the "permissions" array
(leave host_permissions: ["<all_urls>"] intact); ensure no code relies on
chrome.scripting.* (e.g., executeScript/insertCSS/registerContentScripts) before
removing to avoid runtime errors.
🪄 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: b24d58f5-f60b-404f-a081-3a45b6793cfc
📒 Files selected for processing (11)
.gitignorepublic/extension/LOAD_EXTENSION.mdpublic/extension/background.jspublic/extension/content.jspublic/extension/manifest-minimal.jsonpublic/extension/manifest.jsonpublic/extension/widget.csssrc/app/api/chat/route.tssrc/app/api/widget/route.tssrc/app/demo/expenses/page.tsxsrc/app/demo/github/page.tsx
Harden extension request relay with origin allowlist and timeout, remove XSS-prone overlay rendering, preserve task panel structure after completion, tighten URL host/path task matching, and make widget API base runtime-configurable via chrome.storage. Also clean up extension docs/styles and remove unused minimal manifest. Made-with: Cursor
Require a valid widget shared secret or hire-scoped auth for non-demo Gemini paths in /api/widget, add coarse per-client rate limiting, and propagate widget secret through the extension background relay while preserving origin allowlist and timeout protections. Made-with: Cursor
Summary by CodeRabbit
New Features
Improvements