Skip to content

feat(web): no-install mode, the dashboard tab drains the delivery queue - #8

Merged
hallelx2 merged 3 commits into
mainfrom
halleluyaholudele/hal-2371-no-install
Oct 8, 2026
Merged

hallelx2 merged 3 commits into
mainfrom
halleluyaholudele/hal-2371-no-install

Conversation

@hallelx2

@hallelx2 hallelx2 commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Makes the browser tab a real executor, so BridgeHook works with nothing installed.

What changes

  • QueueDrainer (apps/web/src/lib/drain.ts) drains GET /api/channels/:id/events?pending=1 oldest first with a keyset cursor: claim (20 s heartbeat), forward, report. It stops at the first event when localhost is down or blocks CORS, so order holds and outages never use up attempts. A poison event is skipped after 3 real failures.
  • forwardForDrain sorts out what went wrong with two probes, since a page sees every failure as the same TypeError: a CORS GET succeeds → a real failure; a no-cors GET succeeds → CORS is blocked; neither → the server is down. A no-cors delivery fallback was rejected: it drops headers and rewrites the content type.
  • LocalhostBanner shows "can't reach localhost:N, X webhooks queued", or a CORS guide with copyable snippets for Express, Hono, Next.js, FastAPI, Flask, Django, Rails and Go, with this origin filled in.
  • The bridge page is reachable again at /dashboard/bridge. It was only rendered in self-host mode after the account dashboard took /dashboard. Links added in the nav and in the empty Channels card.
  • Replays go to the channel's own webhook URL.

Verified

  • vitest: 7/7 drainer tests (multi-page backlog in order exactly once, outage resume, CORS hold, poison skip, claims held elsewhere, wake coalescing).
  • Web typecheck, Biome and vite build clean.
  • Browser e2e against a local relay with D1, 15/15:
    • server down: 6 webhooks queued and the banner shows the count; once the server starts they arrive in arrival order, each exactly once, and the banner clears
    • live event forwarded
    • no CORS: the guide shows with the origin, and the FastAPI snippet is correct; enabling CORS drains the queue
    • sync channel: the sender gets the local reply through the tab

Closes HAL-2371

Summary by Sourcery

Make the dashboard browser tab a reliable no-install webhook executor for local development.

New Features:

  • Enable the dashboard browser tab to act as a no-install webhook executor that drains queued deliveries to localhost in order.
  • Add localhost availability and CORS status guidance with framework-specific, copyable configuration snippets.
  • Restore the browser bridge at /dashboard/bridge and expose it through dashboard navigation and empty-channel onboarding.

Bug Fixes:

  • Ensure queued deliveries pause safely during localhost outages, CORS blocking, competing claims, and relay reporting failures without incorrectly consuming delivery attempts.
  • Route event replays to the active channel's webhook URL.

Enhancements:

  • Add ordered queue draining with event claiming, heartbeats, retries, poison-event skipping, and response reporting.
  • Classify localhost forwarding failures as server outages, CORS restrictions, or real handler failures.

Tests:

  • Add Vitest coverage for ordered multi-page draining, retries, outages, CORS blocking, claims, poison events, reporting, timeouts, and wake coalescing.

Summary by CodeRabbit

  • New Features
    • Added a Browser bridge page for forwarding webhooks to a local server without installing additional software.
    • Added localhost status updates, queued-event counts, and setup guidance with configuration examples for common frameworks.
    • Added bridge setup guidance when the hosted dashboard has no channels.
  • Improvements
    • Webhook events are forwarded in order, with retries when the local server is unavailable or forwarding fails.
    • Large responses are adjusted for delivery when necessary.

The browser bridge forwarded each poll snapshot in parallel, so a
backlog went out of order, a refresh could double-send, and a stopped
local server burned every event's attempts. It was also unreachable for
signed-in users: the account dashboard took over /dashboard and the
bridge page was only rendered in self-host mode.

QueueDrainer walks ?pending=1 oldest first with a keyset cursor, claims
each event (heartbeat while localhost thinks), forwards, reports, and
stops at the first event when localhost is down or blocks CORS, so
order holds and outages cost no attempts. A poison event is skipped
after three real failures. forwardForDrain tells the cases apart with
two probes, since every fetch failure is the same TypeError.

LocalhostBanner says the server is down with the queued count, or shows
the CORS line for eight frameworks with this origin filled in. The page
is back at /dashboard/bridge, in the nav and in the empty Channels card.
Replays go to the channel's own URL.
@sourcery-ai

sourcery-ai Bot commented Oct 8, 2026

Copy link
Copy Markdown

Sorry @hallelx2, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 5 days and 7 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Oct 8, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR turns the authenticated dashboard tab into a no-install webhook executor by adding an ordered, claim-based delivery queue with outage/CORS-aware retry behavior, exposing the bridge route and diagnostics UI, and routing replays through each channel’s webhook URL.

Sequence diagram for ordered browser-tab webhook draining

sequenceDiagram
    participant Dashboard
    participant QueueDrainer
    participant Relay
    participant Localhost

    Dashboard->>QueueDrainer: wake()
    QueueDrainer->>Relay: getPendingEvents(channelId, after)
    Relay-->>QueueDrainer: oldest events and nextCursor
    QueueDrainer->>Relay: claimEvent(eventId, clientId)
    Relay-->>QueueDrainer: claim granted
    QueueDrainer->>Localhost: forwardForDrain(event, port)
    alt Localhost responds
        Localhost-->>QueueDrainer: response
        QueueDrainer->>Relay: sendResponse(channelId, eventId, response)
        QueueDrainer->>Dashboard: onDelivered(event, response)
    else Localhost is down or CORS blocked
        Localhost-->>QueueDrainer: down or cors
        QueueDrainer->>Dashboard: onLocalhost(state, message)
        Note over QueueDrainer: Stop at this event and retry later
    else Real forwarding failure
        Localhost-->>QueueDrainer: failed
        QueueDrainer->>QueueDrainer: retry up to MAX_ATTEMPTS
        QueueDrainer->>Relay: sendResponse(channelId, eventId, status 0)
    end
    QueueDrainer->>Relay: getPendingEvents(channelId, nextCursor)
Loading

Flow diagram for localhost failure classification

flowchart TD
    A[forwardForDrain] --> B{Webhook request succeeds}
    B -->|Yes| C[Return response]
    B -->|No| D[GET localhost root with CORS]
    D -->|Succeeds| E[Return failed]
    D -->|Fails| F[GET localhost root with no-cors]
    F -->|Succeeds| G[Return cors]
    F -->|Fails| H[Return down]
    G --> I[QueueDrainer stops and retries without consuming attempts]
    H --> I
    E --> J[Retry event and skip after MAX_ATTEMPTS]
Loading

File-Level Changes

Change Details Files
Added an ordered, claim-aware queue drainer for browser-tab webhook execution.
  • Fetches pending events oldest-first using paginated cursors.
  • Claims events with a 20-second heartbeat and prevents concurrent execution.
  • Stops on localhost outage or CORS failure without consuming attempts.
  • Retries real forwarding failures and skips poison events after three attempts.
  • Coalesces wake-ups and retries stalled drains.
apps/web/src/lib/drain.ts
apps/web/src/lib/drain.test.ts
apps/web/src/lib/relay.ts
apps/web/src/hooks/useBridge.ts
Implemented browser-to-localhost delivery diagnostics and user guidance.
  • Classifies local delivery failures with CORS and no-CORS probes.
  • Displays queued counts and down/CORS states in the bridge UI.
  • Provides copyable CORS configuration snippets for eight frameworks with the current origin.
apps/web/src/lib/relay.ts
apps/web/src/components/dashboard/LocalhostBanner.tsx
apps/web/src/pages/Dashboard.tsx
Restored and exposed the browser bridge as a hosted dashboard route.
  • Adds the authenticated /dashboard/bridge route.
  • Adds browser bridge navigation and empty-channel entry points.
  • Clarifies that the browser tab can operate without an installation.
apps/web/src/App.tsx
apps/web/src/components/DashboardLayout.tsx
apps/web/src/pages/DashboardHome.tsx
apps/web/src/pages/Dashboard.tsx
Updated replay routing to target the active channel webhook.
  • Builds replay URLs from the channel's webhook URL.
  • Removes legacy relay hook prefixes while preserving event paths and query strings.
apps/web/src/hooks/useBridge.ts
Added web test tooling and focused drainer coverage.
  • Adds Vitest to the web package and exposes a test script.
  • Covers ordered pagination, outage/CORS holding, retry limits, claim arbitration, and wake coalescing.
apps/web/package.json
pnpm-lock.yaml
apps/web/src/lib/drain.test.ts

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

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 524bacf8-f174-479a-a2b4-abccb9e04b9c
📥 Commits

Reviewing files that changed from the base of the PR and between 88ef5a0 and baec38c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • apps/web/package.json
  • apps/web/src/App.tsx
  • apps/web/src/components/DashboardLayout.tsx
  • apps/web/src/components/dashboard/LocalhostBanner.tsx
  • apps/web/src/hooks/useBridge.ts
  • apps/web/src/lib/drain.test.ts
  • apps/web/src/lib/drain.ts
  • apps/web/src/lib/relay.ts
  • apps/web/src/pages/Dashboard.tsx
  • apps/web/src/pages/DashboardHome.tsx
 __________________________________________________________
< If your code was a carrot, I'd bury it and forget where. >
 ----------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

Review follow-ups on no-install mode:
- While localhost is down or blocks CORS (or after a failed attempt),
  probe before claiming, so a tab that cannot deliver stops refreshing
  its claim and the extension takes the event over after 60s.
- The readability probe GETs the event's path with the event's header
  names, so it needs the same CORS preflight as the real forward; a
  server that only answers simple requests now gets the CORS guide
  instead of having every webhook dropped as status 0.
- A claim held elsewhere stops the pass (order holds) and the hook
  re-wakes in 15s, so an executor that died mid-forward is taken over.
- sendResponse throws when the relay refuses the answer and shrinks
  oversize bodies/headers on 413/431; the drainer re-sends an owed
  answer instead of calling the handler again.
- Forward and report no longer use the bridge's abort signal, so leaving
  the page mid-forward does not re-run the handler on return.
- A 5-minute timeout is recorded once (status 0), not retried while the
  handler may still be on a breakpoint.
- A heartbeat that finds the claim lost skips the report.
- connect resets the localhost banner; a stopped drainer and its probes
  (now abortable) cannot write state; a pass blocked by another
  executor no longer clears a down state.
@hallelx2
hallelx2 merged commit 9f8abb6 into main Oct 8, 2026
1 of 5 checks passed
@hallelx2
hallelx2 deleted the halleluyaholudele/hal-2371-no-install branch October 8, 2026 01:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant