Skip to content

sync: paperclip upstream (2026-04-17)#109

Open
TSavo wants to merge 1 commit intomainfrom
sync/paperclip-upstream-2026-04-17
Open

sync: paperclip upstream (2026-04-17)#109
TSavo wants to merge 1 commit intomainfrom
sync/paperclip-upstream-2026-04-17

Conversation

@TSavo
Copy link
Copy Markdown
Contributor

@TSavo TSavo commented Apr 17, 2026

Automated upstream sync — Paperclip

Synced with latest from paperclipai/paperclip upstream.

What this does

  • Pulls in latest upstream changes (features, bug fixes, refactors)
  • Resolves any merge conflicts (preserving hostedMode guards)
  • Scans for new UI elements that leak infra without hostedMode guards
  • Fixes any gaps found

Verify

  • Build passes
  • hostedMode still hides all infra UI
  • No adapter/model selection visible in hosted mode

Summary by Sourcery

Tighten hosted mode restrictions in the Paperclip UI to hide agent infrastructure details and configuration surfaces.

Bug Fixes:

  • Prevent adapter type and infrastructure details from being displayed in hosted mode across inbox, approval payloads, and org chart views.

Enhancements:

  • Redirect hosted users away from agent, invite, and routine configuration pages to the home page to keep infrastructure management server-side.
  • Hide the agent configuration form entirely in hosted mode so infrastructure cannot be modified from the UI.

Summary by CodeRabbit

  • New Features
    • Implemented hosted mode support with conditional UI behavior across the application
    • Agent configuration, agent detail, routine detail, and invite landing pages are inaccessible or redirect to home in hosted mode
    • Adapter type information is hidden from configuration forms, approval payloads, inbox displays, and organization chart views in hosted mode

Copilot AI review requested due to automatic review settings April 17, 2026 07:52
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Apr 17, 2026

Reviewer's Guide

Syncs the Paperclip sidecar UI with upstream while tightening hostedMode protections so adapter/agent infrastructure details and configuration UIs are hidden or redirected in hosted deployments.

Sequence diagram for hostedMode redirect on agent detail page

sequenceDiagram
  actor User
  participant Router
  participant AgentDetail
  participant HostedModeHook as useHostedMode
  participant HomePage

  User->>Router: Navigate to /agents/:agentId
  Router->>AgentDetail: Render AgentDetail
  AgentDetail->>HostedModeHook: get isHosted
  HostedModeHook-->>AgentDetail: isHosted = true
  AgentDetail->>Router: Return Navigate to /
  Router->>HomePage: Render HomePage
  HomePage-->>User: Show home UI (no agent config)
Loading

Class diagram for components gated by hostedMode

classDiagram
  class useHostedMode {
    +boolean isHosted
  }

  class JoinRequestAdapterSpan {
    +JoinRequestAdapterSpan(adapterType string)
  }

  class JoinRequestInboxRow {
    +joinRequest
    +onApprove()
    +onReject()
  }

  class HireAgentPayload {
    +HireAgentPayload(payload Record_string_unknown)
  }

  class AgentConfigForm {
    +AgentConfigForm(props AgentConfigFormProps)
  }

  class AgentDetail {
    +AgentDetail()
  }

  class InviteLandingPage {
    +InviteLandingPage()
  }

  class OrgChart {
    +OrgChart()
  }

  class RoutineDetail {
    +RoutineDetail()
  }

  JoinRequestAdapterSpan ..> useHostedMode : uses
  JoinRequestInboxRow ..> JoinRequestAdapterSpan : renders
  HireAgentPayload ..> useHostedMode : uses
  AgentConfigForm ..> useHostedMode : uses
  AgentDetail ..> useHostedMode : uses
  InviteLandingPage ..> useHostedMode : uses
  OrgChart ..> useHostedMode : uses
  RoutineDetail ..> useHostedMode : uses
Loading

File-Level Changes

Change Details Files
Hide adapter type information for join requests in hosted mode
  • Introduce a JoinRequestAdapterSpan component that reads hosted mode via useHostedMode and suppresses rendering when hosted
  • Replace inline adapterType span in JoinRequestInboxRow to use the new hosted-mode-aware span component
sidecars/paperclip/ui/src/pages/Inbox.tsx
Guard adapter type display in hire-agent approval payloads under hosted mode
  • Import useHostedMode into ApprovalPayload
  • Use isHosted flag to skip rendering the Adapter row when in hosted mode
sidecars/paperclip/ui/src/components/ApprovalPayload.tsx
Hide agent configuration UI entirely in hosted mode
  • Read isHosted via useHostedMode at the top of AgentConfigForm
  • Short-circuit render in hosted mode to return null so the form never appears
sidecars/paperclip/ui/src/components/AgentConfigForm.tsx
Redirect config/management-style pages away in hosted mode
  • In AgentDetail, gate the component with isHosted and redirect to home via when hosted
  • In InviteLandingPage, gate the component with isHosted and redirect to home to avoid exposing user/agent bootstrap flows
  • In RoutineDetail, gate the component with isHosted and redirect to home to hide routine configuration UI
sidecars/paperclip/ui/src/pages/AgentDetail.tsx
sidecars/paperclip/ui/src/pages/InviteLanding.tsx
sidecars/paperclip/ui/src/pages/RoutineDetail.tsx
Hide adapter labels in the org chart for hosted mode
  • Read isHosted from useHostedMode in OrgChart
  • Conditionally render the adapter label under each agent only when not hosted
sidecars/paperclip/ui/src/pages/OrgChart.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 17, 2026

📝 Walkthrough

Walkthrough

The changes introduce hosted mode detection across multiple UI components in Paperclip. When hosted mode is enabled via useHostedMode(), the application conditionally hides agent configuration interfaces, adapter type displays, and redirects certain detail/landing pages to the home route.

Changes

Cohort / File(s) Summary
Early Redirects
sidecars/paperclip/ui/src/pages/AgentDetail.tsx, sidecars/paperclip/ui/src/pages/RoutineDetail.tsx, sidecars/paperclip/ui/src/pages/InviteLandingPage.tsx
Added useHostedMode() hook and early return with redirect to home route (<Navigate to="/" replace />) when in hosted mode, preventing detail/landing page rendering and associated data queries.
Form & Configuration Rendering
sidecars/paperclip/ui/src/components/AgentConfigForm.tsx
Added useHostedMode() check with early return of null, preventing agent configuration form and its internal query/mutation logic from executing in hosted mode.
Adapter Type Field Visibility
sidecars/paperclip/ui/src/components/ApprovalPayload.tsx, sidecars/paperclip/ui/src/pages/OrgChart.tsx, sidecars/paperclip/ui/src/pages/Inbox.tsx
Added useHostedMode() hook to conditionally display adapter type information only when not in hosted mode. Changes include modified rendering logic in HireAgentPayload, introduction of JoinRequestAdapterSpan wrapper component, and updated field visibility in agent displays.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A bunny hops through hosted lands so bright,
Where forms and adapters vanish from sight,
With hooks that detect the mode with care,
The UI adapts with floppy-eared flair! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'sync: paperclip upstream (2026-04-17)' accurately reflects the main objective of this pull request, which is to synchronize upstream changes from the paperclip repository with this fork, as confirmed by the PR description and commit messages.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sync/paperclip-upstream-2026-04-17

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.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • The repeated useHostedMode checks and early returns/redirects across multiple pages suggest an opportunity to introduce a shared route/component-level guard (e.g., a HostedInfraGuard wrapper) to centralize this logic and keep views more focused on their domain concerns.
  • The new JoinRequestAdapterSpan component does only a hosted-mode check plus a simple span; consider either inlining the useHostedMode logic where it’s used or generalizing this into a reusable HostedHidden/HostedVisibility helper to avoid very fine-grained single-use components.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The repeated `useHostedMode` checks and early returns/redirects across multiple pages suggest an opportunity to introduce a shared route/component-level guard (e.g., a `HostedInfraGuard` wrapper) to centralize this logic and keep views more focused on their domain concerns.
- The new `JoinRequestAdapterSpan` component does only a hosted-mode check plus a simple span; consider either inlining the `useHostedMode` logic where it’s used or generalizing this into a reusable `HostedHidden`/`HostedVisibility` helper to avoid very fine-grained single-use components.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 17, 2026

Greptile Summary

This PR adds hostedMode guards across the Paperclip UI to hide infrastructure-facing elements (adapter types, agent config, routine/invite pages) when running in hosted proxy mode. The intent is correct, but four components (AgentConfigForm, AgentDetail, RoutineDetail, InviteLandingPage) implement the guard with an early return placed before their remaining hook calls, which violates React's Rules of Hooks.

  • P0 — Rules of Hooks crash in hosted mode: useHostedMode() returns isHosted: false on the first render (health query cache miss), then flips to true once the query resolves. On re-render React finds fewer hooks than recorded on the first paint and throws "Rendered fewer hooks than expected," crashing the component tree in any hosted deployment on a cold load or cache expiration. The fix is a wrapper/inner-component split so all hooks stay unconditionally called.

Confidence Score: 2/5

Not safe to merge — P0 Rules of Hooks violation will crash the app on cold loads in hosted mode

Four of the seven changed components use an early-return guard after calling useHostedMode() but before their remaining hooks. Since useHostedMode resolves asynchronously, isHosted starts false and transitions to true on re-render, causing React to detect a hooks-count mismatch and throw. This is a confirmed runtime crash path in hosted deployments.

AgentConfigForm.tsx, AgentDetail.tsx, RoutineDetail.tsx, InviteLandingPage.tsx — all need the wrapper/inner-component pattern before this is safe to merge

Important Files Changed

Filename Overview
sidecars/paperclip/ui/src/components/AgentConfigForm.tsx Adds hostedMode guard but violates React Rules of Hooks — early return fires after first render causes hooks-count crash in hosted mode
sidecars/paperclip/ui/src/pages/AgentDetail.tsx Same Rules of Hooks violation — early return before 15+ hook calls that only run on first render
sidecars/paperclip/ui/src/pages/RoutineDetail.tsx Same Rules of Hooks violation — early return before many hook calls; crashes on health query resolution in hosted mode
sidecars/paperclip/ui/src/pages/InviteLandingPage.tsx Same Rules of Hooks violation — early Navigate return before useState/useQuery/useMutation calls
sidecars/paperclip/ui/src/pages/OrgChart.tsx hostedMode guard applied correctly — isHosted used inline in JSX, all hooks called unconditionally
sidecars/paperclip/ui/src/components/ApprovalPayload.tsx hostedMode guard applied correctly — isHosted used inline in JSX conditional, no early return before other hooks
sidecars/paperclip/ui/src/pages/Inbox.tsx hostedMode guard applied correctly via JoinRequestAdapterSpan helper — only useHostedMode called before the conditional return

Sequence Diagram

sequenceDiagram
    participant Browser
    participant Component as AgentDetail / AgentConfigForm / RoutineDetail / InviteLanding
    participant useHostedMode
    participant ReactQuery as TanStack Query (health cache)

    Browser->>Component: Mount (cold load)
    Component->>useHostedMode: call hook
    useHostedMode->>ReactQuery: useQuery(queryKeys.health) — cache MISS
    ReactQuery-->>useHostedMode: { data: undefined, isSuccess: false }
    useHostedMode-->>Component: { isHosted: false, modeKnown: false }
    Note over Component: isHosted=false → NO early return<br/>Calls 10+ more hooks (useState, useQuery, …)
    Component-->>Browser: Renders full page

    ReactQuery->>ReactQuery: Health API resolves → hosted_proxy
    ReactQuery->>Component: Re-render triggered
    Component->>useHostedMode: call hook (hook #1)
    useHostedMode-->>Component: { isHosted: true }
    Note over Component: isHosted=true → early return fires<br/>Hooks #2–N never called ❌
    Component-->>Browser: React throws Rendered fewer hooks than expected
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: sidecars/paperclip/ui/src/components/AgentConfigForm.tsx
Line: 165-167

Comment:
**React Rules of Hooks violation — early return skips subsequent hook calls**

`useHostedMode()` resolves via a `useQuery` call, so on the first render `isHosted` is `false` (cache miss → `health` is `undefined`). After the health query settles in hosted mode, `isHosted` flips to `true` and the early `return null` fires — but by then React has already recorded a larger hooks list from the first render. React throws "Rendered fewer hooks than expected" and crashes the component tree.

The same pattern is present in `AgentDetail.tsx`, `RoutineDetail.tsx`, and `InviteLandingPage.tsx`.

The idiomatic fix is a thin wrapper that calls only `useHostedMode`, renders `null`/redirects, or delegates to an inner component that owns all remaining hooks:

```tsx
export function AgentConfigForm(props: AgentConfigFormProps) {
  const { isHosted } = useHostedMode();
  if (isHosted) return null;
  return <AgentConfigFormInner {...props} />;
}

function AgentConfigFormInner(props: AgentConfigFormProps) {
  const { mode, adapterModels: externalModels } = props;
  const { selectedCompanyId } = useCompany();
  // ... all remaining hooks and JSX
}
```

Apply the same split to `AgentDetail`, `RoutineDetail`, and `InviteLandingPage`.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: add hostedMode guards for new upstr..." | Re-trigger Greptile

Comment on lines +165 to +167
const { isHosted } = useHostedMode();
// Hide agent configuration form in hosted mode — infrastructure is managed server-side
if (isHosted) return null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P0 React Rules of Hooks violation — early return skips subsequent hook calls

useHostedMode() resolves via a useQuery call, so on the first render isHosted is false (cache miss → health is undefined). After the health query settles in hosted mode, isHosted flips to true and the early return null fires — but by then React has already recorded a larger hooks list from the first render. React throws "Rendered fewer hooks than expected" and crashes the component tree.

The same pattern is present in AgentDetail.tsx, RoutineDetail.tsx, and InviteLandingPage.tsx.

The idiomatic fix is a thin wrapper that calls only useHostedMode, renders null/redirects, or delegates to an inner component that owns all remaining hooks:

export function AgentConfigForm(props: AgentConfigFormProps) {
  const { isHosted } = useHostedMode();
  if (isHosted) return null;
  return <AgentConfigFormInner {...props} />;
}

function AgentConfigFormInner(props: AgentConfigFormProps) {
  const { mode, adapterModels: externalModels } = props;
  const { selectedCompanyId } = useCompany();
  // ... all remaining hooks and JSX
}

Apply the same split to AgentDetail, RoutineDetail, and InviteLandingPage.

Prompt To Fix With AI
This is a comment left during a code review.
Path: sidecars/paperclip/ui/src/components/AgentConfigForm.tsx
Line: 165-167

Comment:
**React Rules of Hooks violation — early return skips subsequent hook calls**

`useHostedMode()` resolves via a `useQuery` call, so on the first render `isHosted` is `false` (cache miss → `health` is `undefined`). After the health query settles in hosted mode, `isHosted` flips to `true` and the early `return null` fires — but by then React has already recorded a larger hooks list from the first render. React throws "Rendered fewer hooks than expected" and crashes the component tree.

The same pattern is present in `AgentDetail.tsx`, `RoutineDetail.tsx`, and `InviteLandingPage.tsx`.

The idiomatic fix is a thin wrapper that calls only `useHostedMode`, renders `null`/redirects, or delegates to an inner component that owns all remaining hooks:

```tsx
export function AgentConfigForm(props: AgentConfigFormProps) {
  const { isHosted } = useHostedMode();
  if (isHosted) return null;
  return <AgentConfigFormInner {...props} />;
}

function AgentConfigFormInner(props: AgentConfigFormProps) {
  const { mode, adapterModels: externalModels } = props;
  const { selectedCompanyId } = useCompany();
  // ... all remaining hooks and JSX
}
```

Apply the same split to `AgentDetail`, `RoutineDetail`, and `InviteLandingPage`.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Automated upstream sync for the Paperclip sidecar UI, with additional hosted-mode gating to prevent infrastructure/configuration surfaces (and adapter details) from appearing when running in hosted_proxy deployments.

Changes:

  • Added hosted-mode redirects for infrastructure/configuration pages (routine detail, agent detail, invite landing).
  • Hid adapter type labels/details in OrgChart, Inbox join requests, and approval payload UI when hosted.
  • Suppressed the agent configuration form entirely in hosted mode.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
sidecars/paperclip/ui/src/pages/RoutineDetail.tsx Redirects away from routine configuration in hosted mode.
sidecars/paperclip/ui/src/pages/OrgChart.tsx Hides agent adapter type label when hosted.
sidecars/paperclip/ui/src/pages/InviteLanding.tsx Redirects away from invite/user bootstrapping in hosted mode.
sidecars/paperclip/ui/src/pages/Inbox.tsx Hides join-request adapter type display when hosted.
sidecars/paperclip/ui/src/pages/AgentDetail.tsx Redirects away from agent configuration in hosted mode.
sidecars/paperclip/ui/src/components/ApprovalPayload.tsx Hides adapter type from approval payloads when hosted.
sidecars/paperclip/ui/src/components/AgentConfigForm.tsx Suppresses agent config form rendering when hosted.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +253 to +254
const { isHosted } = useHostedMode();
// Redirect to home in hosted mode — routine configuration is infrastructure
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

This hosted-mode guard only checks isHosted. Since isHosted is false until the health query resolves (health is initially undefined), hosted users can briefly render routine infrastructure UI before the redirect occurs. Consider also using modeKnown to fail closed (render nothing/loading until known, then redirect when hosted).

Suggested change
const { isHosted } = useHostedMode();
// Redirect to home in hosted mode — routine configuration is infrastructure
const { isHosted, modeKnown } = useHostedMode();
// Fail closed until hosted mode is known, then redirect in hosted mode —
// routine configuration is infrastructure
if (!modeKnown) return <PageSkeleton />;

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +43
const { isHosted } = useHostedMode();
// Redirect to home in hosted mode — user management and agent bootstrapping is server-side
if (isHosted) return <Navigate to="/" replace />;
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

This hosted-mode redirect relies only on isHosted, which is false until the health query resolves. In hosted deployments this can briefly show invite/user-management bootstrapping UI before redirecting. Use modeKnown to fail closed until deployment mode is known (then redirect if hosted).

Copilot uses AI. Check for mistakes.
}

export function AgentDetail() {
const { isHosted } = useHostedMode();
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

This hosted-mode redirect checks only isHosted. Because isHosted is false until the health query resolves, hosted users can briefly see agent configuration UI on first paint. Consider gating on modeKnown (render null/loading until known, then redirect if hosted).

Suggested change
const { isHosted } = useHostedMode();
const { isHosted, modeKnown } = useHostedMode();
// Avoid rendering agent configuration UI until hosted mode is known.
if (!modeKnown) return null;

Copilot uses AI. Check for mistakes.
Comment on lines +427 to +428
{/* Hide adapter type in hosted mode — agent infrastructure is managed server-side */}
{agent && !isHosted && (
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

This hides the adapter label only when isHosted is true. Since isHosted is false until the health query resolves, hosted users may briefly see adapter infrastructure labels on first paint. Consider also using modeKnown and hiding while !modeKnown (fail closed).

Suggested change
{/* Hide adapter type in hosted mode — agent infrastructure is managed server-side */}
{agent && !isHosted && (
{/* Hide adapter type until mode is known, and in hosted mode — agent infrastructure is managed server-side */}
{agent && modeKnown && !isHosted && (

Copilot uses AI. Check for mistakes.
Comment on lines +632 to +633
const { isHosted } = useHostedMode();
if (isHosted) return null;
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

JoinRequestAdapterSpan only hides when isHosted is true. Because isHosted is false until the health query resolves, hosted users may briefly see adapter type here on first paint. Consider using modeKnown and returning null when !modeKnown || isHosted (fail closed).

Suggested change
const { isHosted } = useHostedMode();
if (isHosted) return null;
const { isHosted, modeKnown } = useHostedMode();
if (!modeKnown || isHosted) return null;

Copilot uses AI. Check for mistakes.
Comment on lines +93 to 96
{/* Hide adapter type in hosted mode — agent infrastructure is managed server-side */}
{!isHosted && !!payload.adapterType && (
<div className="flex items-center gap-2">
<span className="text-muted-foreground w-20 sm:w-24 shrink-0 text-xs">Adapter</span>
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

This hides adapterType only when isHosted is true. Because isHosted is false until the health query resolves, hosted users may briefly see adapter type here on first paint. Consider also using modeKnown and hiding while !modeKnown (fail closed).

Copilot uses AI. Check for mistakes.
Comment on lines +165 to +167
const { isHosted } = useHostedMode();
// Hide agent configuration form in hosted mode — infrastructure is managed server-side
if (isHosted) return null;
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

This returns null only when isHosted is true. Because isHosted is false until the health query resolves, hosted users can briefly see the agent config form on first render. Consider also gating on modeKnown (fail closed until deployment mode is known).

Suggested change
const { isHosted } = useHostedMode();
// Hide agent configuration form in hosted mode — infrastructure is managed server-side
if (isHosted) return null;
const { isHosted, modeKnown } = useHostedMode();
// Hide agent configuration form in hosted mode — infrastructure is managed server-side.
// Fail closed until deployment mode is known to avoid briefly rendering the form for hosted users.
if (!modeKnown || isHosted) return null;

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
sidecars/paperclip/ui/src/pages/Inbox.tsx (1)

631-635: LGTM — isolating the hook in a tiny subcomponent is a clean pattern.

Extracting JoinRequestAdapterSpan avoids polluting Inbox's hook list and keeps the guard local. Same fail-closed suggestion as other files: consider using modeKnown to avoid a brief flash of adapter: … on first paint.

-  const { isHosted } = useHostedMode();
-  if (isHosted) return null;
+  const { isHosted, modeKnown } = useHostedMode();
+  if (!modeKnown || isHosted) return null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sidecars/paperclip/ui/src/pages/Inbox.tsx` around lines 631 - 635,
JoinRequestAdapterSpan currently reads isHosted from useHostedMode and can
briefly render before the hosted-mode is known; change it to also read modeKnown
from useHostedMode and short-circuit until modeKnown is true. In other words,
inside JoinRequestAdapterSpan (which calls useHostedMode), return null when
modeKnown is false or when isHosted is true, and only render the <span>adapter:
{adapterType}</span> when modeKnown is true and isHosted is false.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@sidecars/paperclip/ui/src/components/AgentConfigForm.tsx`:
- Around line 164-168: AgentConfigForm currently calls useHostedMode() and
returns early if isHosted, which causes a Rules of Hooks violation when isHosted
transitions and skips the subsequent hooks; fix by removing the early return and
instead either (A) let the parent component (AgentDetail) fully gate rendering
so AgentConfigForm is never mounted in hosted mode, or (B) keep useHostedMode()
but move the hosted-mode gate to after all hook declarations and use the
mode-known flag (e.g., modeKnown from useHostedMode) to render a closed state
until the mode is resolved; update AgentConfigForm (and its useHostedMode usage)
accordingly to avoid conditional early returns before hook calls.

In `@sidecars/paperclip/ui/src/pages/AgentDetail.tsx`:
- Around line 616-619: AgentDetail currently calls useHostedMode() and returns
early before running the rest of its hooks which causes a Rules of Hooks
violation when isHosted changes; to fix, remove the in-component early return
(the Navigate return) or move that hosted-mode guard to after all hook calls in
AgentDetail so hooks execute in the same order on every render, or better yet
implement a route-level guard (e.g., a HostedModeGate used in the router) so
AgentDetail (and RoutineDetail, InviteLanding, NewAgent) never mounts in hosted
mode; locate useHostedMode, the Navigate return, and the subsequent hooks
(useParams, useCompany, usePanel, useDialog, useBreadcrumbs, useQueryClient,
useNavigate, useState, useSidebar, useMemo, useCallback, useQuery, useEffect)
and apply one of these fixes.

In `@sidecars/paperclip/ui/src/pages/InviteLanding.tsx`:
- Around line 40-44: The early return in InviteLandingPage causes a Hooks order
violation because isHosted can flip after the first render; call useHostedMode()
at the top as before but do not early return — instead read both isHosted and a
modeKnown (or similar) flag from useHostedMode/useQuery (the same
queryKeys.health/healthQuery used elsewhere) so hooks always run; run all local
hooks (useState ×6, useQuery ×3, useMemo, useEffect, useMutation)
unconditionally, then after those hooks check if modeKnown && isHosted and if so
return <Navigate to="/" replace />; if modeKnown is false render a
loading/placeholder to avoid flashing the invite UI in hosted mode or move the
redirect into the router level instead.

In `@sidecars/paperclip/ui/src/pages/OrgChart.tsx`:
- Line 135: useHostedMode() currently exposes isHosted which is false until the
health query resolves causing a brief incorrect render; switch to the
fail-closed pattern by also using the modeKnown flag from useHostedMode and only
render the adapter-type UI when modeKnown is true. Update OrgChart.tsx (and the
same pattern in ApprovalPayload.tsx and Inbox.tsx for JoinRequestAdapterSpan) to
check modeKnown before reading isHosted or rendering the adapter span so the
component waits for the health/query result instead of flashing a wrong adapter
type.

In `@sidecars/paperclip/ui/src/pages/RoutineDetail.tsx`:
- Around line 252-256: The early return in RoutineDetail using isHosted causes a
Hooks ordering crash because useHostedMode (backed by useQuery) can change after
initial render; declare all hooks first (e.g., useParams, useRef, useState,
useEffect, useMemo, your useQuery/useMutation calls) and only perform the
redirect after those hooks, using useHostedMode().modeKnown to "fail closed"
(i.e., if modeKnown is false do not redirect yet; when modeKnown is true and
isHosted is true then return <Navigate ... />). Alternatively, move this guard
out of RoutineDetail and implement a router-level HostedModeGuard that prevents
the component subtree from mounting in hosted mode.

---

Nitpick comments:
In `@sidecars/paperclip/ui/src/pages/Inbox.tsx`:
- Around line 631-635: JoinRequestAdapterSpan currently reads isHosted from
useHostedMode and can briefly render before the hosted-mode is known; change it
to also read modeKnown from useHostedMode and short-circuit until modeKnown is
true. In other words, inside JoinRequestAdapterSpan (which calls useHostedMode),
return null when modeKnown is false or when isHosted is true, and only render
the <span>adapter: {adapterType}</span> when modeKnown is true and isHosted is
false.
🪄 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: a7739f15-daa1-4260-bfd8-730c44d24f61

📥 Commits

Reviewing files that changed from the base of the PR and between 9894f45 and 0b44e5a.

📒 Files selected for processing (7)
  • sidecars/paperclip/ui/src/components/AgentConfigForm.tsx
  • sidecars/paperclip/ui/src/components/ApprovalPayload.tsx
  • sidecars/paperclip/ui/src/pages/AgentDetail.tsx
  • sidecars/paperclip/ui/src/pages/Inbox.tsx
  • sidecars/paperclip/ui/src/pages/InviteLanding.tsx
  • sidecars/paperclip/ui/src/pages/OrgChart.tsx
  • sidecars/paperclip/ui/src/pages/RoutineDetail.tsx

Comment on lines 164 to +168
export function AgentConfigForm(props: AgentConfigFormProps) {
const { isHosted } = useHostedMode();
// Hide agent configuration form in hosted mode — infrastructure is managed server-side
if (isHosted) return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Rules of Hooks violation — early return precedes many hooks.

Same issue as RoutineDetail and InviteLanding: useHostedMode() is itself a useQuery subscription, so isHosted transitions from falsetrue after /health resolves. When that transition occurs, return null skips all the hooks below this point (useState ×5, useQuery ×4, useMutation ×3, useEffect ×2, useRef, useMemo, useCallback ×2), triggering a hook-count mismatch crash. This will manifest the first time a hosted deployment loads any page that mounts AgentConfigForm (e.g., AgentDetail).

Also, because isHosted is initially false, the form's queries will fire once (hitting adapterModels, detectModel, secrets.list, etc.) before the component bails — not ideal if those endpoints aren't reachable in hosted mode.

Fix: have the parent (AgentDetail.tsx) gate rendering (parent already early-returns in hosted mode per the AI summary, so in practice this may be unreachable today — but the guard here still crashes if ever reached). Alternatively, move the if (isHosted) check after all hooks are declared and use modeKnown to fail closed. If AgentDetail already redirects in hosted mode, consider simply deleting this guard to avoid the hook-ordering hazard entirely.

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

In `@sidecars/paperclip/ui/src/components/AgentConfigForm.tsx` around lines 164 -
168, AgentConfigForm currently calls useHostedMode() and returns early if
isHosted, which causes a Rules of Hooks violation when isHosted transitions and
skips the subsequent hooks; fix by removing the early return and instead either
(A) let the parent component (AgentDetail) fully gate rendering so
AgentConfigForm is never mounted in hosted mode, or (B) keep useHostedMode() but
move the hosted-mode gate to after all hook declarations and use the mode-known
flag (e.g., modeKnown from useHostedMode) to render a closed state until the
mode is resolved; update AgentConfigForm (and its useHostedMode usage)
accordingly to avoid conditional early returns before hook calls.

Comment on lines +616 to +619
const { isHosted } = useHostedMode();
// Redirect to home in hosted mode — agent configuration is infrastructure
if (isHosted) return <Navigate to="/" replace />;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm hook-ordering pattern in the sibling files mentioned by the summary,
# and check whether a route-level hosted-mode guard already exists.
fd -t f 'useHostedMode.ts' sidecars/paperclip/ui/src | head
rg -nP -C2 'useHostedMode\(\)' sidecars/paperclip/ui/src
rg -nP -C3 'isHosted\s*\)\s*return\s*<Navigate' sidecars/paperclip/ui/src
# Router config: is there a central place to gate routes?
fd -t f -e tsx -e ts . sidecars/paperclip/ui/src | xargs rg -nP -l 'createBrowserRouter|RouterProvider|<Route\b' 2>/dev/null

Repository: wopr-network/platform

Length of output: 26129


🏁 Script executed:

# View useHostedMode implementation to understand hook stability
cat -n sidecars/paperclip/ui/src/hooks/useHostedMode.ts

Repository: wopr-network/platform

Length of output: 867


🏁 Script executed:

# View AgentDetail.tsx to see the actual hook sequence and count
head -n 700 sidecars/paperclip/ui/src/pages/AgentDetail.tsx | tail -n 100

Repository: wopr-network/platform

Length of output: 4124


🏁 Script executed:

# Check if a route-level hosted guard exists anywhere
rg -n 'HostedModeGate|HostedRoute|privateRoute|ProtectedRoute' sidecars/paperclip/ui/src --type tsx --type ts

Repository: wopr-network/platform

Length of output: 92


🏁 Script executed:

# Find the router configuration to check for route-level guards
rg -n 'createBrowserRouter|createHashRouter' sidecars/paperclip/ui/src -A 20 --type tsx --type ts

Repository: wopr-network/platform

Length of output: 92


🏁 Script executed:

# Check if AgentDetail is wrapped in a component that prevents re-renders
rg -B5 -A2 'AgentDetail' sidecars/paperclip/ui/src/App.tsx | head -40

Repository: wopr-network/platform

Length of output: 1056


🏁 Script executed:

# Check useQuery behavior and if React Query memoizes the hook return
rg -n 'staleTime|gcTime|retry' sidecars/paperclip/ui/src/hooks/useHostedMode.ts

Repository: wopr-network/platform

Length of output: 109


🏁 Script executed:

# Verify the actual hook count after the early return in AgentDetail
sed -n '615,750p' sidecars/paperclip/ui/src/pages/AgentDetail.tsx | grep -c 'use[A-Z]'

Repository: wopr-network/platform

Length of output: 67


Rules of Hooks violation: early return precedes many subsequent hooks.

AgentDetail calls useHostedMode() and then conditionally returns before the block of hooks below (useParams, useCompany, usePanel, useDialog, useBreadcrumbs, useQueryClient, useNavigate, useState×multiple, useSidebar, useMemo, useCallback, useQuery×multiple, useEffect×multiple). Since useHostedMode uses useQuery with a 5-minute stale time, the isHosted value can change between renders (e.g., after cache expires and refetch occurs). If the condition changes for a mounted instance, React will throw "Rendered fewer hooks than previous render" and crash the page.

Two safer options:

  1. Hoist the hosted-mode gate to the router level so AgentDetail never mounts in hosted mode (preferred — prevents agent data fetching and aligns with the "infra UI must not load" intent).
  2. Keep the guard in-component but place it after all hooks have run.

The same root cause applies to RoutineDetail, InviteLanding, NewAgent, and other pages using this pattern. Consider consolidating behind a single route-level <HostedModeGate> or equivalent guard to prevent multiple components from facing this violation.

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

In `@sidecars/paperclip/ui/src/pages/AgentDetail.tsx` around lines 616 - 619,
AgentDetail currently calls useHostedMode() and returns early before running the
rest of its hooks which causes a Rules of Hooks violation when isHosted changes;
to fix, remove the in-component early return (the Navigate return) or move that
hosted-mode guard to after all hook calls in AgentDetail so hooks execute in the
same order on every render, or better yet implement a route-level guard (e.g., a
HostedModeGate used in the router) so AgentDetail (and RoutineDetail,
InviteLanding, NewAgent) never mounts in hosted mode; locate useHostedMode, the
Navigate return, and the subsequent hooks (useParams, useCompany, usePanel,
useDialog, useBreadcrumbs, useQueryClient, useNavigate, useState, useSidebar,
useMemo, useCallback, useQuery, useEffect) and apply one of these fixes.

Comment on lines 40 to +44
export function InviteLandingPage() {
const { isHosted } = useHostedMode();
// Redirect to home in hosted mode — user management and agent bootstrapping is server-side
if (isHosted) return <Navigate to="/" replace />;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Rules of Hooks violation — same pattern as RoutineDetail.

isHosted starts false and may flip to true once the health query resolves. When it flips, this early return skips every hook below (useState ×6, useQuery ×3, useMemo, useEffect, useMutation), producing the React "Rendered fewer hooks than during the previous render" error.

Additionally, this component already issues healthApi.get() via its own healthQuery; useHostedMode uses the same queryKeys.health so they share cache, but the guard still happens after the first render cycle — causing a flash of the invite UI in hosted mode.

Fix: redirect at the router level, or restructure so the hosted check runs after all hooks and use modeKnown to fail closed (see the RoutineDetail comment for shape).

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

In `@sidecars/paperclip/ui/src/pages/InviteLanding.tsx` around lines 40 - 44, The
early return in InviteLandingPage causes a Hooks order violation because
isHosted can flip after the first render; call useHostedMode() at the top as
before but do not early return — instead read both isHosted and a modeKnown (or
similar) flag from useHostedMode/useQuery (the same queryKeys.health/healthQuery
used elsewhere) so hooks always run; run all local hooks (useState ×6, useQuery
×3, useMemo, useEffect, useMutation) unconditionally, then after those hooks
check if modeKnown && isHosted and if so return <Navigate to="/" replace />; if
modeKnown is false render a loading/placeholder to avoid flashing the invite UI
in hosted mode or move the redirect into the router level instead.

// ── Main component ──────────────────────────────────────────────────────

export function OrgChart() {
const { isHosted } = useHostedMode();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor: brief adapter-type flash before /health resolves.

useHostedMode() returns isHosted === false until the health query resolves, so on first paint the adapter type may render briefly before being hidden. For hosted-mode leak prevention, prefer fail-closed using modeKnown:

-  const { isHosted } = useHostedMode();
+  const { isHosted, modeKnown } = useHostedMode();
...
-                    {agent && !isHosted && (
+                    {agent && modeKnown && !isHosted && (

Same pattern applies to ApprovalPayload.tsx and Inbox.tsx's JoinRequestAdapterSpan.

Also applies to: 427-432

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

In `@sidecars/paperclip/ui/src/pages/OrgChart.tsx` at line 135, useHostedMode()
currently exposes isHosted which is false until the health query resolves
causing a brief incorrect render; switch to the fail-closed pattern by also
using the modeKnown flag from useHostedMode and only render the adapter-type UI
when modeKnown is true. Update OrgChart.tsx (and the same pattern in
ApprovalPayload.tsx and Inbox.tsx for JoinRequestAdapterSpan) to check modeKnown
before reading isHosted or rendering the adapter span so the component waits for
the health/query result instead of flashing a wrong adapter type.

Comment on lines 252 to +256
export function RoutineDetail() {
const { isHosted } = useHostedMode();
// Redirect to home in hosted mode — routine configuration is infrastructure
if (isHosted) return <Navigate to="/" replace />;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Rules of Hooks violation — early return before subsequent hooks.

useHostedMode() is itself backed by useQuery, so isHosted starts as false on first render (before /health resolves) and can flip to true on a later render. When that flip happens, this early return skips all the hooks below (useParams, useRef, useState, useEffect, useMemo, multiple useQuery / useMutation), so React will throw: "Rendered fewer hooks than during the previous render." This is a hard crash, not a lint warning.

Secondary issue: because isHosted is false until health resolves, the full routine detail (including its queries that hit infra endpoints) will render/fire for a moment before the redirect — defeating the hostedMode guard's intent. useHostedMode already exposes modeKnown precisely to fail closed.

Fix: gate the redirect at the route/router level (so the child component never mounts), or perform the guard after all hooks are declared and use modeKnown to fail closed. Example of the in-component shape:

Proposed shape
 export function RoutineDetail() {
-  const { isHosted } = useHostedMode();
-  // Redirect to home in hosted mode — routine configuration is infrastructure
-  if (isHosted) return <Navigate to="/" replace />;
-
-  const { routineId } = useParams<{ routineId: string }>();
+  const { isHosted, modeKnown } = useHostedMode();
+  const { routineId } = useParams<{ routineId: string }>();
   // ... all other hooks unchanged ...
+
+  // After all hooks: fail closed until we know the mode, redirect when hosted.
+  if (!modeKnown || isHosted) {
+    return isHosted || !modeKnown ? <Navigate to="/" replace /> : null;
+  }

Preferred alternative: wrap the route element in a <HostedModeGuard> at the router so the whole subtree (including its queries) never mounts in hosted mode.

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

In `@sidecars/paperclip/ui/src/pages/RoutineDetail.tsx` around lines 252 - 256,
The early return in RoutineDetail using isHosted causes a Hooks ordering crash
because useHostedMode (backed by useQuery) can change after initial render;
declare all hooks first (e.g., useParams, useRef, useState, useEffect, useMemo,
your useQuery/useMutation calls) and only perform the redirect after those
hooks, using useHostedMode().modeKnown to "fail closed" (i.e., if modeKnown is
false do not redirect yet; when modeKnown is true and isHosted is true then
return <Navigate ... />). Alternatively, move this guard out of RoutineDetail
and implement a router-level HostedModeGuard that prevents the component subtree
from mounting in hosted mode.

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