Skip to content

Chrome widget - #15

Merged
ZhuBryan merged 10 commits into
mainfrom
chrome-widget
Apr 25, 2026
Merged

ZhuBryan merged 10 commits into
mainfrom
chrome-widget

Conversation

@ZhuBryan

@ZhuBryan ZhuBryan commented Apr 25, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Released Runbook Chrome extension with an onboarding widget that detects tasks and provides step-by-step guidance across web pages
    • Added integrated chat interface for asking questions within the widget with highlighted element detection
    • Added task progress tracking and completion marking functionality
  • Improvements

    • Enhanced error handling for API rate limits and quota issues
    • Improved source attribution in chat responses
    • Updated demo pages with action buttons

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

vercel Bot commented Apr 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
run-book Ready Ready Preview, Comment Apr 25, 2026 11:28pm
runbook Ready Ready Preview, Comment Apr 25, 2026 11:28pm

@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@ZhuBryan has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 38 minutes and 42 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 38 minutes and 42 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: 336d53aa-b318-438e-ba4d-576211bd8ac3

📥 Commits

Reviewing files that changed from the base of the PR and between 83069f3 and ed79af6.

📒 Files selected for processing (6)
  • public/extension/LOAD_EXTENSION.md
  • public/extension/background.js
  • public/extension/content.js
  • public/extension/manifest.json
  • public/extension/widget.css
  • src/app/api/widget/route.ts
📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
Chrome Extension Configuration & Documentation
public/extension/manifest.json, public/extension/manifest-minimal.json, public/extension/LOAD_EXTENSION.md
Manifest V3 configuration files declaring permissions, host access, content script injection, background worker setup, and developer documentation for local extension loading and testing.
Chrome Extension Implementation
public/extension/background.js, public/extension/content.js, public/extension/widget.css
Content script injects fixed-position widget UI for task detection, step-by-step guidance, chat interface, and element highlighting. Background worker handles API forwarding for extension requests. CSS provides complete overlay styling with animations, panels, buttons, and chat bubbles.
Widget API Integration
src/app/api/widget/route.ts
New POST endpoint that validates page metadata, detects onboarding tasks via Gemini AI or hardcoded demo routes, performs element localization, and returns structured task guidance with target element hints.
Chat Response Enhancement
src/app/api/chat/route.ts
Improves Gemini error handling with quota/rate-limit detection and fallback messaging; conditionally includes relevant source references (limited to 3) in responses only when sources exist.
Demo Pages with Action Elements
src/app/demo/github/page.tsx, src/app/demo/expenses/page.tsx
Adds submit/request-access buttons to demo pages for testing extension task detection and interaction flows.
Build Configuration
.gitignore
Excludes Playwright temporary browser profile directories (\.tmp-pw-user/, \.tmp-pw-user-headless/).

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 A chrome tab blooms with purple light,
Step-by-step guidance shining bright!
Gemini whispers where to go,
Onboarding paths now crystal show.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Chrome widget' is partially related to the changeset but lacks specificity. While the PR does add a Chrome extension with a widget, the title is vague and doesn't convey the main scope—it doesn't clarify that this introduces a complete Chrome extension for onboarding/Runbook functionality with multiple components (manifest, background service worker, content script, styling, and API routes). Consider a more descriptive title such as 'Add Chrome extension for Runbook widget' or 'Implement Runbook Chrome extension with onboarding widget' to better summarize the substantial feature addition.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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 chrome-widget

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.

Comment thread public/extension/content.js Fixed
Comment thread src/app/api/widget/route.ts Fixed
Comment thread src/app/api/widget/route.ts Fixed
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

@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: 9

🧹 Nitpick comments (3)
public/extension/manifest-minimal.json (1)

1-12: Drop or clearly mark this debug manifest.

Chrome only reads manifest.json from the unpacked folder, so this file is unused at runtime and creates confusion next to the real manifest.json. It also lacks widget.css and the background service worker, so anyone who copies it into place by mistake gets a styling-less extension with no API relay (the content script's apiRequest will 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.json outside public/extension/, or add a top-level note in LOAD_EXTENSION.md clarifying 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 calls chrome.scripting.* APIs (like executeScript, insertCSS, or registerContentScripts). Static content_scripts registration 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: Triple setTimeout(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 mutate document.body after DOMContentLoaded, a MutationObserver on document.documentElement (disconnected once injectWidget succeeds) 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4b686f4 and 83069f3.

📒 Files selected for processing (11)
  • .gitignore
  • public/extension/LOAD_EXTENSION.md
  • public/extension/background.js
  • public/extension/content.js
  • public/extension/manifest-minimal.json
  • public/extension/manifest.json
  • public/extension/widget.css
  • src/app/api/chat/route.ts
  • src/app/api/widget/route.ts
  • src/app/demo/expenses/page.tsx
  • src/app/demo/github/page.tsx

Comment thread public/extension/background.js
Comment thread public/extension/content.js Outdated
Comment thread public/extension/content.js
Comment thread public/extension/content.js
Comment thread public/extension/LOAD_EXTENSION.md Outdated
Comment thread public/extension/widget.css Outdated
Comment thread public/extension/widget.css Outdated
Comment thread src/app/api/widget/route.ts Outdated
Comment thread src/app/api/widget/route.ts
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
@ZhuBryan
ZhuBryan merged commit d265e50 into main Apr 25, 2026
8 checks passed
@ZhuBryan
ZhuBryan deleted the chrome-widget branch April 25, 2026 23:29
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.

2 participants