Skip to content

feat: upgrade onboarding UX and hire-scoped chat ingestion - #11

Merged
dfed25 merged 4 commits into
mainfrom
feature/ui-chat-intelligence-upgrade
Apr 25, 2026
Merged

dfed25 merged 4 commits into
mainfrom
feature/ui-chat-intelligence-upgrade

Conversation

@dfed25

@dfed25 dfed25 commented Apr 25, 2026

Copy link
Copy Markdown
Owner

Improve the manager and dashboard experience while making hire source sync ingest real URL content and return richer, scoped chat citations so the MVP feels polished and answers are better grounded.

Made-with: Cursor

Summary by CodeRabbit

  • New Features

    • URL document fetching for knowledge source enrichment with provider-based metadata
    • Enhanced chat citations with source URLs and extended excerpts
    • Access control enforcement for hire-specific chat conversations
  • Bug Fixes

    • Improved dashboard regression detection for task progress tracking
  • Style

    • Updated color palette and typography system
    • Sticky header layout with improved visual hierarchy
    • Unified UI component library for consistency
  • Documentation

    • Updated manager control-plane instructions for sync behavior

Improve the manager and dashboard experience while making hire source sync ingest real URL content and return richer, scoped chat citations so the MVP feels polished and answers are better grounded.

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 5:26pm
runbook Ready Ready Preview, Comment Apr 25, 2026 5:26pm

@coderabbitai

coderabbitai Bot commented Apr 25, 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 41 minutes and 53 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 41 minutes and 53 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: 496fd598-3d89-4464-8bd0-a83163d8999d

📥 Commits

Reviewing files that changed from the base of the PR and between 68fa179 and fa93dc8.

📒 Files selected for processing (10)
  • scripts/qa-flow.mjs
  • src/app/api/chat/route.ts
  • src/app/dashboard/page.tsx
  • src/app/globals.css
  • src/app/manager/page.tsx
  • src/app/manager/tasks/page.tsx
  • src/components/ui/AppButton.tsx
  • src/components/ui/StatusBadge.tsx
  • src/lib/ingestion/url.ts
  • src/lib/vectorizer.ts
📝 Walkthrough

Walkthrough

This pull request introduces hire-scoped knowledge source ingestion, access control enforcement, and UI component standardization. New components (AppButton, SectionCard, StatusBadge) replace inline button/container markup across multiple pages. The chat handler enforces access control via requireHireAccess and enriches source metadata with expanded excerpts and URLs. A new URL ingestion module fetches and parses documents from URLs with Google Docs preference, while the vectorizer adapts to resolve knowledge sources dynamically.

Changes

Cohort / File(s) Summary
New UI Primitives
src/components/ui/AppButton.tsx, src/components/ui/SectionCard.tsx, src/components/ui/StatusBadge.tsx
Introduces three reusable React components: AppButton with variant-driven styling, SectionCard for titled/subtitled container sections, and StatusBadge for tone-mapped status indicators.
Dashboard & Manager UI Refactoring
src/app/dashboard/page.tsx, src/app/manager/page.tsx, src/app/manager/tasks/page.tsx
Replaces inline <button> and <span> elements with AppButton, SectionCard, and StatusBadge components while preserving all existing click handlers, disabled conditions, and filtering logic. Updates chat source links to render as external links when URL is available.
Chat Handler & Types
src/app/api/chat/route.ts, src/lib/types.ts
Adds requireHireAccess check to enforce hire-scoped access control with auth-status-derived HTTP errors. Expands source excerpts (150→180 chars), adds url property to ChatSource type, and reformats contextual text with per-source indexing plus Provider/URL fields. Updates Gemini prompt for concise guidance.
URL Ingestion & Vectorization
src/lib/ingestion/url.ts, src/lib/vectorizer.ts
Creates new fetchUrlDocument module that parses URLs, preferentially extracts Google Docs content via export API, falls back to generic HTTP fetch with custom User-Agent, and extracts titles from HTML. Updates vectorizer to dynamically resolve hire-specific knowledge sources via URL fetch and compute hire-scoped external IDs as <hireId>:<source.id>.
Global Styling & Header Updates
src/app/globals.css, src/app/layout.tsx
Updates :root color tokens to new dark/ink palette and introduces --surface, --surface-border, --muted tokens. Adds .rb-surface and .rb-muted utility classes. Changes body font-family to prefer Geist Sans. Makes header sticky with increased z-index and stronger backdrop blur; updates navigation link rounding from rounded to rounded-md.
Documentation & QA
README.md, scripts/qa-flow.mjs
Clarifies that URL sources are fetched/parsed immediately with provider-based enrichment dependent on credentials. Refactors QA script to re-check dashboard progress when no Todo task exists and log high-severity regression bugs instead of medium-severity seeded-data warnings.

Sequence Diagram(s)

sequenceDiagram
    participant Mgr as Manager
    participant VEC as Vectorizer
    participant URL as URL Ingestion
    participant HTTP as HTTP/Google API
    participant SB as Supabase
    
    Mgr->>VEC: Sync hire-scoped source
    VEC->>URL: fetchUrlDocument(source.url)
    URL->>URL: Parse & validate URL
    alt Google Docs URL
        URL->>HTTP: GET docs.google.com/export?format=txt
        HTTP->>URL: Text content
    else Other URL
        URL->>HTTP: GET URL with User-Agent
        HTTP->>URL: HTML response
        URL->>URL: Extract title & strip HTML
    end
    URL->>VEC: {id, title, content, url}
    VEC->>VEC: Compute scopeToken & external_id
    VEC->>SB: Upsert document with hire scope
    SB->>VEC: Confirm
    VEC->>Mgr: Sync complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Enterprise integrations #8: Both PRs modify ingestion/vectorizer pipeline by adding URL/third-party document resolution and changing how external_ids are computed and upserted in Supabase.
  • Backend ai #6: Both PRs modify the chat POST handler in src/app/api/chat/route.ts to alter access control logic and enhance source metadata passed to clients and language models.
  • feat: add manager onboarding control plane with hire-scoped AI context #10: Both PRs implement hire-scoped onboarding features with overlapping changes to chat route access control, ChatSource.url type extension, and vectorizer knowledge source sync behavior.

Poem

🐰 URLs now hop through our system with glee,
Components reused—modular harmony!
Access control guards hire-scoped affairs,
While Gemini sources fetch answers from shares,
Design tokens bloom in our CSS care. 🌱

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ 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 'feat: upgrade onboarding UX and hire-scoped chat ingestion' accurately reflects the main changes: UI component refactoring across manager/dashboard pages, new shared UI components (SectionCard, AppButton, StatusBadge), URL ingestion for hire sources, and enhanced chat with richer citations and access control.
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/ui-chat-intelligence-upgrade

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 src/lib/ingestion/url.ts Fixed
Comment thread src/lib/ingestion/url.ts Fixed
Comment thread src/lib/ingestion/url.ts Fixed
…nescaping'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

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

🧹 Nitpick comments (8)
src/components/ui/StatusBadge.tsx (1)

1-4: Consider widening children to ReactNode.

Restricting children to string blocks future use cases like inline icons or <span> accents inside the badge. The render path doesn't depend on it being a string.

♻️ Proposed change
-import { ReactNode } from "react";
+import { ReactNode } from "react";
+
 type StatusBadgeProps = {
   tone: "success" | "warning" | "neutral";
-  children: string;
+  children: ReactNode;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/StatusBadge.tsx` around lines 1 - 4, The StatusBadgeProps
type restricts children to string which prevents passing icons or spans; change
its children type to React.ReactNode (or import { ReactNode } and use ReactNode)
and update the StatusBadge component/any usage signatures to accept that type so
the component can render elements as well as strings.
src/app/globals.css (1)

18-23: Dark-mode override is now identical to :root — consider removing or restoring a real light palette.

@media (prefers-color-scheme: dark) :root redeclares --background/--foreground to the same values already set on :root. If the app is intentionally dark-only, drop this block; otherwise, define distinct light values on :root so dark mode actually overrides.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/globals.css` around lines 18 - 23, The dark-mode media block (`@media`
(prefers-color-scheme: dark) { :root { --background; --foreground; } })
currently duplicates the same values as :root, so either remove this dark-mode
block if the app is dark-only, or restore a distinct light palette by moving
dark values into the media block and setting different light values on :root
(update the --background/--foreground variable declarations accordingly); locate
the :root declarations and the `@media` (prefers-color-scheme: dark) { :root {
--background; --foreground; } } block and apply one of the two fixes.
src/lib/vectorizer.ts (2)

55-75: Consider chunking large fetched documents before embedding.

resolveSourceDocument accepts whatever fetchUrlDocument returns and hands the full body to generateEmbedding(doc.content) later. A long Google Doc / web page will exceed the embedding model's input limit, causing the whole source to be dropped (embeddingFailed += 1) without partial coverage. A simple chunker (e.g. ~1–2k chars with overlap, one upsert row per chunk with external_id = ${hireId}:${source.id}#${chunkIdx}) would keep retrieval useful for long sources.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/vectorizer.ts` around lines 55 - 75, resolveSourceDocument currently
returns the entire fetched document content which can exceed embedding model
limits; change resolveSourceDocument to chunk fetched.content into ~1500–2000
char chunks with ~200 char overlap and return a list of SyncDocument-like chunk
objects (or otherwise emit multiple documents) instead of a single doc,
assigning unique ids using sourceExternalId(source) + `#${chunkIdx}` and
preserving title/url/scope/scopeToken per chunk; ensure downstream code that
calls generateEmbedding and upsert handles multiple chunks (loop over chunks,
call generateEmbedding per chunk and upsert each with the chunked external_id)
so large pages are partially embedded rather than dropped.

47-49: inferProvider is just a pass-through to providerForType.

Drop the wrapper and call providerForType directly at the call site to remove indirection.

♻️ Proposed fix
-function inferProvider(type: KnowledgeSourceType): "notion" | "google_drive" | "slack" | "manual" {
-  return providerForType(type);
-}
-
@@
-    provider: inferProvider(source.type),
+    provider: providerForType(source.type),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/vectorizer.ts` around lines 47 - 49, The function inferProvider is a
redundant passthrough to providerForType; remove the inferProvider declaration
and update all call sites to invoke providerForType(...) directly (search for
inferProvider and replace with providerForType), ensuring imports/exports remain
correct and no other references to inferProvider remain in the codebase.
src/lib/ingestion/url.ts (1)

11-22: HTML entity decoding is incomplete.

The function only decodes 4 named entities (&nbsp;, &amp;, &lt;, &gt;) and does not handle:

  • Numeric entities (&#NN;, &#xNN; e.g., &#39;, &#x2F;)
  • Common named entities (&quot;, &apos;, &mdash;, etc.)

This causes incomplete HTML entity decoding in fetched content, polluting data quality. Consider using a lightweight library like string-strip-html (actively maintained, ~130KB, handles both named and numeric entities) to handle both tag stripping and entity decoding in one pass. Alternatively, combine existing libraries like he for entity decoding only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/ingestion/url.ts` around lines 11 - 22, The stripHtmlTags function
only decodes a few named entities and misses numeric and many named entities;
replace or augment it to perform full HTML entity decoding (including &#NN; and
&#xNN; forms and common named entities like &quot;, &apos;, &mdash;). Locate the
stripHtmlTags function and either (A) use a lightweight library that both strips
tags and decodes entities (e.g., string-strip-html) to handle tags + entities in
one pass, or (B) keep tag-stripping but run the result through a robust HTML
entity decoder (e.g., he.decode) before trimming; ensure the updated
implementation still removes script/style blocks and collapses whitespace as
before and expose the same function name stripHtmlTags for callers.
src/app/manager/page.tsx (1)

109-135: Consider extracting the filter-pill style or adding a dedicated AppButton variant.

The hire filter buttons combine AppButton variant="ghost" with a long custom className that overrides several base utilities (rounded, py, border color, bg, text color). Two consecutive call sites duplicate this exact selection-state logic, and the overrides depend on Tailwind class source order to resolve conflicts (e.g., rounded-full over base rounded-md, py-1 over base py-2). This works today but is fragile if AppButton's defaults change.

Consider either:

  • Adding a pill/filter variant to AppButton to avoid the override-stack pattern, or
  • Extracting a small HireFilterButton component that encapsulates the active/inactive styling.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/manager/page.tsx` around lines 109 - 135, The filter pill buttons
duplicate complex Tailwind overrides on AppButton (see AppButton usage,
selectedHireId, setActiveHireId and people mapping) which is fragile; fix by
extracting the styling and state logic into a single reusable component or
variant: either add a new AppButton variant (e.g., "pill" or "filter") that
encapsulates rounded-full, px-3 py-1 and the active/inactive border/bg/text
classes, or create a small HireFilterButton component that accepts props
(hireId, name, isActive, onClick) and applies the correct classes internally,
then replace both the "All Employees" and people.map call sites to use that new
variant/component so the style logic is centralized.
src/app/dashboard/page.tsx (1)

315-326: Recommend rel="noopener noreferrer" on external links.

rel="noreferrer" already implies noopener in modern browsers, but spelling both out is the conventional, defensive form for target="_blank" links and avoids any ambiguity for older user agents. Since source.url originates from manager-attached input, it's worth being explicit here.

🛡️ Proposed change
-                            <a
-                              href={source.url}
-                              target="_blank"
-                              rel="noreferrer"
-                              className="text-xs font-semibold text-cyan-200 underline-offset-2 hover:underline"
-                            >
+                            <a
+                              href={source.url}
+                              target="_blank"
+                              rel="noopener noreferrer"
+                              className="text-xs font-semibold text-cyan-200 underline-offset-2 hover:underline"
+                            >

Note: React 19 rejects javascript: URLs in href automatically, so script-injection via the URL itself is already mitigated by the framework.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/dashboard/page.tsx` around lines 315 - 326, Update the external link
anchor rendering that uses source.url (the <a> element with target="_blank"
showing source.title) to include rel="noopener noreferrer" instead of just
"noreferrer"; this keeps target="_blank" links defensive across user agents and
avoids potential window.opener issues—locate the anchor that checks source.url
and replace the rel value accordingly.
src/app/manager/tasks/page.tsx (1)

211-215: Inconsistent button refactor: hire/duplicate pill buttons remain as raw <button>.

These pill-style selectors (lines 211–215 hire selection and 275–278 duplicate-target selection) are still raw <button> elements with hardcoded Tailwind, while every other action control in this file was migrated to AppButton. Same situation in src/app/manager/page.tsx lines 109–135.

For consistency and to avoid the override-stack pattern (custom rounded-full overriding AppButton's base rounded-md), consider either keeping all pill toggles as raw buttons (and not migrating any) or introducing a small pill/toggle variant in AppButton.

Also applies to: 275-279

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/manager/tasks/page.tsx` around lines 211 - 215, The hire and
duplicate-target pill selectors (the map rendering that calls setSelectedHireId
and updates setForm, referencing selectedHireId, hires, and the duplicate-target
block) are still raw <button> elements while other controls use AppButton;
either revert the other AppButton migrations to raw buttons or add a pill/toggle
variant to AppButton and use it here to avoid class-override conflicts: extend
AppButton (e.g., add a "pill" or "toggle" variant) to apply rounded-full, px-3
py-1, text-xs and conditional selected styles, then replace the raw buttons in
the hires map and the duplicate-target selection to call the same onClick
handlers (setSelectedHireId, setForm) but via AppButton with the new variant so
styling and behavior are consistent with other controls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/qa-flow.mjs`:
- Around line 103-115: The no-Todo branch currently re-reads dashboard progress
into after via getDashboardProgress(page) immediately after before, making the
comparison a no-op; either remove the redundant getDashboardProgress call and
replace it with an informational log (e.g., use logBug at info level or a log
message) stating "no Todo tasks available, skipping progress check" so the QA
run intentionally skips validation, or insert a deliberate action between
readings (for example a short wait and a page.reload or a navigation) so that
calling getDashboardProgress(page) again can detect transient regressions;
update the branch around todoTask.count(), getDashboardProgress(page), and
logBug accordingly to implement one of these two behaviors.

In `@src/app/api/chat/route.ts`:
- Around line 63-67: The excerpt always appends "..." regardless of length;
update the mapping that builds sources (the validDocs.map that creates
ChatSource objects) to only append "..." when d.content.length > 180 (e.g., use
a conditional to take the first 180 chars and add "..." only if longer), and
guard null/undefined d.content by defaulting to an empty string before slicing
so the excerpt logic in route.ts is robust.
- Line 85: The response currently may set sources to undefined which violates
the ChatResponse type (sources: ChatSource[]); update the return in route.ts
(the NextResponse.json call in the chat route) to always return an array (e.g.,
use sources || [] or sources.length ? sources : []) so ChatResponse.sources is
never undefined, or alternatively update the ChatResponse type to make sources
optional (sources?: ChatSource[]) if undefined is intended—prefer returning an
empty array to keep the existing non-optional type.

In `@src/components/ui/AppButton.tsx`:
- Around line 16-23: The AppButton component currently leaves the native button
type unset which defaults to "submit" inside forms; update the AppButton
function to set a default type of "button" (while still allowing callers to
override via props) by ensuring the rendered <button> includes a type attribute
that uses props.type || "button" (locate in the AppButton function and
AppButtonProps usage to adjust the defaulting behavior).

In `@src/lib/ingestion/url.ts`:
- Around line 41-51: fetchGoogleDocText may store HTML signin/permission pages
for non-public Google Docs because the export endpoint can return 200 with HTML;
update fetchGoogleDocText to detect non-plain-text responses and return null so
callers can fallback. After fetching (in fetchGoogleDocText), check
res.headers.get("content-type") and ensure it indicates text/plain (or starts
with "text/") and additionally inspect the body string for obvious HTML markers
(e.g., startsWith "<!DOCTYPE", "<html", or contains "accounts.google.com" /
"Sign in") and if any of these conditions are true return null rather than the
HTML; keep existing behavior of returning the trimmed text when it is valid
plain text. Ensure you still use googleDocIdFromUrl to skip invalid URLs and
preserve USER_AGENT header usage.
- Around line 41-51: Both fetch paths (notably fetchGoogleDocText) lack an
AbortSignal timeout and a response-size guard; update fetchGoogleDocText and the
other generic fetch function to use an AbortController with a short timeout
(e.g., configurable ms) and to read the response body as a stream, counting
bytes and aborting + returning null if the byte count exceeds a configured max
(size cap) before calling .text(); preserve the USER_AGENT header and ensure the
controller is cleaned up on success/failure and that non-ok responses still
return null.
- Around line 53-108: The fetchUrlDocument function currently fetches arbitrary
URLs and must be hardened: restrict allowed schemes to only http/https (validate
new URL(rawUrl).protocol), resolve parsed.hostname to IP(s) (use DNS lookup) and
reject loopback, RFC1918/private, link-local, and other reserved ranges (e.g.
127.0.0.0/8, ::1, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16,
fc00::/7, fe80::/10) before making the request, and set fetch options to disable
automatic redirects (redirect: "manual") then validate any Location header on
redirects against the same host/IP allowlist before following; also add a
request timeout (e.g. AbortSignal.timeout(30000)) and a response body size cap
(stop/abort if body grows past a safe limit before calling res.text()), and
improve stripHtmlTags/entity decoding to handle numeric (&#xNN;/&#NN;) and other
named entities so injected content can't leak — apply these changes around
fetchUrlDocument (and the fetch call), fetchGoogleDocText interaction,
USER_AGENT usage, and stripHtmlTags implementation.

In `@src/lib/vectorizer.ts`:
- Line 151: The ternary that sets external_id is redundant (external_id:
doc.scope === "global" ? doc.id : doc.id); simplify the assignment by replacing
the ternary with a single value assignment so external_id is set directly from
doc.id (locate the external_id assignment in the document construction within
src/lib/vectorizer.ts, e.g., where external_id is assigned from doc).

---

Nitpick comments:
In `@src/app/dashboard/page.tsx`:
- Around line 315-326: Update the external link anchor rendering that uses
source.url (the <a> element with target="_blank" showing source.title) to
include rel="noopener noreferrer" instead of just "noreferrer"; this keeps
target="_blank" links defensive across user agents and avoids potential
window.opener issues—locate the anchor that checks source.url and replace the
rel value accordingly.

In `@src/app/globals.css`:
- Around line 18-23: The dark-mode media block (`@media` (prefers-color-scheme:
dark) { :root { --background; --foreground; } }) currently duplicates the same
values as :root, so either remove this dark-mode block if the app is dark-only,
or restore a distinct light palette by moving dark values into the media block
and setting different light values on :root (update the
--background/--foreground variable declarations accordingly); locate the :root
declarations and the `@media` (prefers-color-scheme: dark) { :root { --background;
--foreground; } } block and apply one of the two fixes.

In `@src/app/manager/page.tsx`:
- Around line 109-135: The filter pill buttons duplicate complex Tailwind
overrides on AppButton (see AppButton usage, selectedHireId, setActiveHireId and
people mapping) which is fragile; fix by extracting the styling and state logic
into a single reusable component or variant: either add a new AppButton variant
(e.g., "pill" or "filter") that encapsulates rounded-full, px-3 py-1 and the
active/inactive border/bg/text classes, or create a small HireFilterButton
component that accepts props (hireId, name, isActive, onClick) and applies the
correct classes internally, then replace both the "All Employees" and people.map
call sites to use that new variant/component so the style logic is centralized.

In `@src/app/manager/tasks/page.tsx`:
- Around line 211-215: The hire and duplicate-target pill selectors (the map
rendering that calls setSelectedHireId and updates setForm, referencing
selectedHireId, hires, and the duplicate-target block) are still raw <button>
elements while other controls use AppButton; either revert the other AppButton
migrations to raw buttons or add a pill/toggle variant to AppButton and use it
here to avoid class-override conflicts: extend AppButton (e.g., add a "pill" or
"toggle" variant) to apply rounded-full, px-3 py-1, text-xs and conditional
selected styles, then replace the raw buttons in the hires map and the
duplicate-target selection to call the same onClick handlers (setSelectedHireId,
setForm) but via AppButton with the new variant so styling and behavior are
consistent with other controls.

In `@src/components/ui/StatusBadge.tsx`:
- Around line 1-4: The StatusBadgeProps type restricts children to string which
prevents passing icons or spans; change its children type to React.ReactNode (or
import { ReactNode } and use ReactNode) and update the StatusBadge component/any
usage signatures to accept that type so the component can render elements as
well as strings.

In `@src/lib/ingestion/url.ts`:
- Around line 11-22: The stripHtmlTags function only decodes a few named
entities and misses numeric and many named entities; replace or augment it to
perform full HTML entity decoding (including &#NN; and &#xNN; forms and common
named entities like &quot;, &apos;, &mdash;). Locate the stripHtmlTags function
and either (A) use a lightweight library that both strips tags and decodes
entities (e.g., string-strip-html) to handle tags + entities in one pass, or (B)
keep tag-stripping but run the result through a robust HTML entity decoder
(e.g., he.decode) before trimming; ensure the updated implementation still
removes script/style blocks and collapses whitespace as before and expose the
same function name stripHtmlTags for callers.

In `@src/lib/vectorizer.ts`:
- Around line 55-75: resolveSourceDocument currently returns the entire fetched
document content which can exceed embedding model limits; change
resolveSourceDocument to chunk fetched.content into ~1500–2000 char chunks with
~200 char overlap and return a list of SyncDocument-like chunk objects (or
otherwise emit multiple documents) instead of a single doc, assigning unique ids
using sourceExternalId(source) + `#${chunkIdx}` and preserving
title/url/scope/scopeToken per chunk; ensure downstream code that calls
generateEmbedding and upsert handles multiple chunks (loop over chunks, call
generateEmbedding per chunk and upsert each with the chunked external_id) so
large pages are partially embedded rather than dropped.
- Around line 47-49: The function inferProvider is a redundant passthrough to
providerForType; remove the inferProvider declaration and update all call sites
to invoke providerForType(...) directly (search for inferProvider and replace
with providerForType), ensuring imports/exports remain correct and no other
references to inferProvider remain in the codebase.
🪄 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: cb4e6171-f7f4-4e98-b5d8-af90a3bf9c43

📥 Commits

Reviewing files that changed from the base of the PR and between e07ef72 and 68fa179.

📒 Files selected for processing (14)
  • README.md
  • scripts/qa-flow.mjs
  • src/app/api/chat/route.ts
  • src/app/dashboard/page.tsx
  • src/app/globals.css
  • src/app/layout.tsx
  • src/app/manager/page.tsx
  • src/app/manager/tasks/page.tsx
  • src/components/ui/AppButton.tsx
  • src/components/ui/SectionCard.tsx
  • src/components/ui/StatusBadge.tsx
  • src/lib/ingestion/url.ts
  • src/lib/types.ts
  • src/lib/vectorizer.ts

Comment thread scripts/qa-flow.mjs Outdated
Comment thread src/app/api/chat/route.ts
Comment thread src/app/api/chat/route.ts Outdated
Comment thread src/components/ui/AppButton.tsx
Comment thread src/lib/ingestion/url.ts
Comment thread src/lib/ingestion/url.ts
Comment thread src/lib/vectorizer.ts Outdated
Address security and review findings by adding SSRF-safe URL fetching, content chunking for embeddings, and UI/button consistency fixes while keeping chat/source typing and QA behavior robust.

Made-with: Cursor
Comment thread src/lib/ingestion/url.ts Fixed
Comment thread src/lib/ingestion/url.ts Fixed
Resolve remaining regex and URL host-sanitization findings in the URL ingestion pipeline to satisfy high-severity security checks.

Made-with: Cursor
@dfed25
dfed25 merged commit 908e38d into main Apr 25, 2026
8 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.

2 participants