Skip to content

feat: add configurable BASE_PATH for subpath deployments#687

Open
rjmayott wants to merge 6 commits intositeboon:mainfrom
rjmayott:pr/base-path-support
Open

feat: add configurable BASE_PATH for subpath deployments#687
rjmayott wants to merge 6 commits intositeboon:mainfrom
rjmayott:pr/base-path-support

Conversation

@rjmayott
Copy link
Copy Markdown

@rjmayott rjmayott commented Apr 22, 2026

Summary

  • Add support for serving CloudCLI from a configurable subpath (e.g. /s/myproject/) via BASE_PATH environment variable
  • Uses runtime injection (window.__CLOUDCLI_BASE_PATH__) rather than build-time config, so a single build works at any subpath
  • When BASE_PATH is not set, behavior is identical to current — zero impact on existing deployments

Note: I saw #626 after writing this implementation. This PR takes a different approach — runtime injection vs build-time — which avoids requiring a rebuild per subpath. Happy to collaborate or revise if the maintainers prefer a different direction.

Details

Frontend:

  • New src/utils/basePath.ts exports BASE_PATH and assetUrl() helper
  • authenticatedFetch auto-prepends BASE_PATH for root-relative URLs (covers 50+ callers across 20 files without touching each one)
  • Bare fetch() calls (auth endpoints, health check) and EventSource URLs manually prefixed
  • WebSocket URLs (/ws, /shell) include BASE_PATH
  • All component image src attributes use assetUrl()
  • Router basename uses window.__ROUTER_BASENAME__ (already existed in App.tsx, just needed the value injected)

Server (server/index.js):

  • Reads BASE_PATH from env, strips trailing slash
  • Injects window.__CLOUDCLI_BASE_PATH__ and window.__ROUTER_BASENAME__ into index.html via <script> tag
  • Serves manifest.json dynamically with base-path-prefixed start_url, scope, and icon src paths
  • Strips BASE_PATH from WebSocket pathname before routing
  • Sets index: false on express.static so the SPA catch-all handles index.html (required for injection)

Service Worker (public/sw.js):

  • Derives base path from self.location.pathname at runtime
  • All cached URLs, path checks, notification icons, and navigation URLs use the derived base path

Build (vite.config.js):

  • Reads VITE_BASE_PATH env for Vite's base option (default /)
  • For subpath deployments, build with VITE_BASE_PATH=./ for relative asset paths

Usage

# Build with relative asset paths (one build works at any subpath)
VITE_BASE_PATH=./ npm run build

# Start with a base path
BASE_PATH=/s/myproject npm run server

Reverse proxy example (Caddy):

:443 {
    handle /s/myproject/* {
        uri strip_prefix /s/myproject
        reverse_proxy 127.0.0.1:3001
    }
}

Test plan

  • Run without BASE_PATH — app works at http://localhost:3001/ (regression test passed)
  • Set BASE_PATH=/s/test, run behind Caddy reverse proxy — full UI loads at /s/test/
  • Registration, login, onboarding flow works through proxy
  • WebSocket connections (chat + shell) connect at /s/test/ws and /s/test/shell
  • All logo icons (Claude, Cursor, Codex, Gemini) render correctly
  • manifest.json returns prefixed paths
  • Service worker registers at correct path
  • API calls route through /s/test/api/...
  • Build passes (npm run build)

Summary by CodeRabbit

  • New Features

    • Full subpath deployment support: build-time base path and runtime-injected base path; service worker, web manifest, cached assets, API and WebSocket endpoints, notifications, and UI asset links now respect BASE_PATH so the app works under a URL subpath.
  • Documentation

    • Added "Subpath Deployment (BASE_PATH)" docs with build/runtime configuration examples and reverse-proxy guidance.

Enable CloudCLI to be served from a subpath (e.g. /s/myproject/)
instead of only root (/). Useful for running multiple instances
behind a single reverse proxy.

When BASE_PATH is not set, behavior is identical to current.

Changes:
- New src/utils/basePath.ts helper (BASE_PATH constant + assetUrl)
- authenticatedFetch auto-prepends BASE_PATH for root-relative URLs
- Bare fetch() calls (auth endpoints, health) manually prefixed
- WebSocket URLs (/ws, /shell) include BASE_PATH
- Component image src attributes use assetUrl()
- EventSource and hardcoded href paths prefixed
- server/index.js: read BASE_PATH env, inject window globals into
  index.html, serve manifest.json dynamically with prefixed paths,
  strip BASE_PATH in WebSocket routing, disable express.static
  index serving so SPA catch-all handles injection
- public/sw.js: derive base path from SW location at runtime
- index.html + main.jsx: SW registration uses injected global
- vite.config.js: base option from VITE_BASE_PATH env
- README: document BASE_PATH usage with Caddy example

Key design decisions:
- Runtime injection (window.__CLOUDCLI_BASE_PATH__) rather than
  build-time, so one build works at any subpath
- VITE_BASE_PATH=./ at build time for relative asset paths
- BASE_PATH env on server for runtime path injection
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 22, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds subpath deployment support: build-time Vite base, runtime base-path injection into served HTML, server-side URL rewriting and manifest rewriting, service-worker and client registration updated to respect base path, and client helpers to resolve API, WebSocket, SSE, and asset URLs via the configured base path.

Changes

Cohort / File(s) Summary
Configuration & Docs
vite.config.js, README.md
Vite base set from VITE_BASE_PATH; README documents subpath deployment, build/server env vars, and reverse-proxy example.
Server & HTML injection
server/index.js
Adds normalized BASE_PATH; middleware strips prefix from incoming req.url; serves rewritten /manifest.json; disables static index; injects window.__CLOUDCLI_BASE_PATH__ and window.__ROUTER_BASENAME__ into served HTML; normalizes WS pathname matching.
Service worker & registration
public/sw.js, index.html, src/main.jsx
SW derives BASE_PATH from its pathname and prefixes cached/notification/navigation URLs; client registers SW using injected window.__CLOUDCLI_BASE_PATH__ (e.g., ${BASE_PATH}/sw.js).
Runtime base-path helpers
src/utils/basePath.ts
New module exporting BASE_PATH (from `window.CLOUDCLI_BASE_PATH
Asset path updates
src/components/.../SetupForm.tsx, .../ClaudeLogo.tsx, .../CodexLogo.tsx, .../CursorLogo.tsx, .../GeminiLogo.tsx, src/components/settings/.../ApiKeysSection.tsx
Replaced root-relative asset/href strings with assetUrl(...) so icons/docs link respect BASE_PATH.
API, SSE, health-check & WebSocket URLs
src/utils/api.js, src/components/project-creation-wizard/data/workspaceApi.ts, src/components/shell/utils/socket.ts, src/contexts/WebSocketContext.tsx, src/hooks/useVersionCheck.ts
Centralized BASE_PATH usage: authenticatedFetch now prepends BASE_PATH for root-relative URLs; SSE/EventSource, health-check, and WebSocket URL builders include BASE_PATH.
Static/SPA behavior
server/index.js, index.html
Prevent static auto-indexing of dist; SPA fallback serves injected HTML so router basename and asset resolution work under a subpath.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant Server
    participant SW as ServiceWorker
    participant API as API/WebSocket

    Server->>Browser: Serve HTML with window.__CLOUDCLI_BASE_PATH__
    Browser->>Browser: Read BASE_PATH from window
    Browser->>SW: Register Service Worker at ${BASE_PATH}/sw.js
    activate SW
    SW->>SW: Derive BASE_PATH from its script pathname
    deactivate SW

    Browser->>API: Request ${BASE_PATH}/api/... or connect to ${BASE_PATH}/ws
    activate API
    Server->>Server: Strip BASE_PATH for internal routing and manifest rewriting
    API-->>Browser: Response / WebSocket handshake
    deactivate API

    Browser->>SW: Fetch ${BASE_PATH}/assets/...
    activate SW
    SW->>SW: Match/cache requests under ${BASE_PATH}/assets or manifest
    SW-->>Browser: Serve cached or network asset
    deactivate SW
Loading

Possibly Related PRs

Suggested Reviewers

  • blackmammoth
  • viper151

Poem

🐇 I hopped along a path both neat and true,
BASE_PATH in paw, I nudged each asset through.
SW, server, client — all learned the same tune,
Subpaths hum softly under sun and moon. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.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 accurately describes the main objective of the changeset—adding configurable BASE_PATH support for subpath deployments—which is reflected across all modified files and the comprehensive PR objectives.
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

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
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
public/sw.js (1)

27-48: ⚠️ Potential issue | 🟡 Minor

Use parsed path checks instead of substring matching in the service worker.

event.request.url.includes(...) can match query strings or unrelated/cross-origin URLs. Parse the URL and compare origin/pathname so only this app’s scoped routes are skipped or cached.

🛡️ Proposed route matching cleanup
 self.addEventListener('fetch', event => {
-  const url = event.request.url;
+  const requestUrl = new URL(event.request.url);
+
+  if (requestUrl.origin !== self.location.origin) {
+    return;
+  }
+
+  const pathname = requestUrl.pathname;

   // Never intercept API requests or WebSocket upgrades
-  if (url.includes(`${BASE_PATH}/api/`) || url.includes(`${BASE_PATH}/ws`)) {
+  if (
+    pathname.startsWith(`${BASE_PATH}/api/`) ||
+    pathname === `${BASE_PATH}/ws` ||
+    pathname.startsWith(`${BASE_PATH}/ws/`)
+  ) {
     return;
   }
@@
   // Hashed assets (JS/CSS in /assets/) — cache-first since filenames change per build
-  if (url.includes(`${BASE_PATH}/assets/`)) {
+  if (pathname.startsWith(`${BASE_PATH}/assets/`)) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@public/sw.js` around lines 27 - 48, The service worker currently uses
substring matching on event.request.url which can produce false positives;
instead parse the request URL with new URL(event.request.url) and compare its
origin to self.location.origin (or self.origin) and its pathname to exact scoped
routes using startsWith comparisons against BASE_PATH + '/api/' and BASE_PATH +
'/ws' (or exact equality for the ws endpoint), and for WebSocket upgrades also
check the Upgrade header on event.request; update the early-return logic around
event.request.url, the navigation handling (event.request.mode === 'navigate')
and the assets branch (url.includes(`${BASE_PATH}/assets/`)) to use the parsed
urlObj.pathname and origin checks before calling event.respondWith or returning.
src/utils/api.js (1)

104-108: ⚠️ Potential issue | 🟠 Major

Avoid putting the auth token in the generated URL for EventSource.

The searchConversationsUrl function is used exclusively with EventSource (in src/components/sidebar/hooks/useSidebarController.ts:215), which passes the full URL—including the token query parameter—through browser history, proxy logs, analytics, and Referer headers. Replace the stored auth token with a short-lived scoped token that cannot be misused if leaked, since EventSource doesn't support custom headers like authenticatedFetch.

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

In `@src/utils/api.js` around lines 104 - 108, The current searchConversationsUrl
helper (used by EventSource) exposes the persistent auth token in the URL;
change it so it does NOT embed the stored auth-token. Update
searchConversationsUrl to only build the base URL/params (q, limit) and remove
the token logic, then implement a short-lived, scoped token flow: create a
server endpoint (e.g., POST /api/sse-token) that mints a one-time/short-lived
token tied to the current session and allowed scopes, call that endpoint from
the client (from the code that constructs the EventSource in
useSidebarController) to fetch the ephemeral token, and append that ephemeral
token to the returned URL before constructing EventSource; alternatively switch
server to accept the session cookie for SSE so no token is put in the URL.
Ensure references: searchConversationsUrl and the EventSource constructor usage
in useSidebarController are updated accordingly.
🧹 Nitpick comments (1)
src/utils/basePath.ts (1)

14-16: Normalize assetUrl inputs in the shared helper.

Current call sites pass leading-slash paths, but this helper is now the shared API; central normalization prevents future callers from producing /baseicons/foo.svg or /base//icons/foo.svg.

♻️ Proposed hardening
-export const BASE_PATH = window.__CLOUDCLI_BASE_PATH__ || '';
+const normalizeBasePath = (value?: string): string => {
+  if (!value || value === '/') {
+    return '';
+  }
+
+  return `/${value}`.replace(/\/+/g, '/').replace(/\/$/, '');
+};
+
+export const BASE_PATH = normalizeBasePath(window.__CLOUDCLI_BASE_PATH__);

-export const assetUrl = (path: string): string => `${BASE_PATH}${path}`;
+export const assetUrl = (path: string): string => {
+  const normalizedPath = path.startsWith('/') ? path : `/${path}`;
+  return `${BASE_PATH}${normalizedPath}`;
+};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/basePath.ts` around lines 14 - 16, assetUrl currently concatenates
BASE_PATH and path verbatim which yields duplicate or missing slashes; update
assetUrl to normalize both parts: if BASE_PATH is falsy return path unchanged to
preserve existing behavior, otherwise remove any trailing slash from BASE_PATH
and any leading slashes from path and join them with a single '/' (referencing
BASE_PATH and assetUrl to locate the change).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 123-130: The fenced code block in README.md containing the
Caddyfile example is missing a language identifier; update the opening triple
backticks for that snippet to include a language specifier (use "caddy" or
"caddyfile") so markdown linters and syntax highlighters recognize the block
(the block that begins with the lines showing ":443 {", "handle /s/myproject/*
{", and "uri strip_prefix /s/myproject"); simply add the specifier immediately
after the opening ``` and save the file.

In `@server/index.js`:
- Around line 1371-1374: The code currently strips BASE_PATH from pathname using
startsWith, which incorrectly rewrites paths like "/apple/ws"; change the
condition so BASE_PATH is removed only when pathname equals BASE_PATH or when it
is followed by a path-separator. Specifically, update the block that checks
BASE_PATH and pathname (the variables and expressions
pathname.startsWith(BASE_PATH) and pathname.slice(BASE_PATH.length)) to only
slice when pathname === BASE_PATH or pathname.startsWith(BASE_PATH + '/'),
otherwise leave pathname unchanged.
- Around line 2218-2225: The code injects BASE_PATH directly into an inline
script (see BASE_PATH, injection, html.replace) which allows unsafe characters
to break out of the script; fix by safely serializing and escaping the value
before embedding: replace direct string interpolation with a serialized version
(e.g., JSON.stringify or an equivalent serializer) and additionally escape any
'<' characters in the serialized output before constructing injection, then use
that escaped string in the injection variable so html.replace inserts a safe,
quoted JS value.
- Around line 16-17: The app currently defines BASE_PATH but still mounts routes
at root; update route mounting to serve API/static/manifest/sw under the subpath
by prefixing route mounts with BASE_PATH (e.g., use app.use(path.join(BASE_PATH,
'/api'), apiRouter), app.use(path.join(BASE_PATH, '/assets'),
express.static(...)), and app.get(path.join(BASE_PATH, '/manifest.json'), ...)
and app.get(path.join(BASE_PATH, '/sw.js'), ...)). Alternatively add an early
middleware that rewrites incoming requests that start with BASE_PATH by
stripping the prefix and passing the modified url to the existing handlers;
ensure BASE_PATH is normalized (no trailing slash) and used wherever
app.use/app.get or static middleware are registered so direct subpath
deployments route correctly.

In `@src/utils/api.js`:
- Around line 5-7: The code in authenticatedFetch currently calls
rawUrl.startsWith unconditionally which will throw for non-string inputs
(Request, URL objects); change the URL construction to first check typeof rawUrl
=== 'string' and only then use startsWith to prepend BASE_PATH (e.g. const url =
(typeof rawUrl === 'string' && rawUrl.startsWith('/')) ? `${BASE_PATH}${rawUrl}`
: rawUrl), so non-string inputs are returned/used untouched and the function
safely handles Request/URL objects.

---

Outside diff comments:
In `@public/sw.js`:
- Around line 27-48: The service worker currently uses substring matching on
event.request.url which can produce false positives; instead parse the request
URL with new URL(event.request.url) and compare its origin to
self.location.origin (or self.origin) and its pathname to exact scoped routes
using startsWith comparisons against BASE_PATH + '/api/' and BASE_PATH + '/ws'
(or exact equality for the ws endpoint), and for WebSocket upgrades also check
the Upgrade header on event.request; update the early-return logic around
event.request.url, the navigation handling (event.request.mode === 'navigate')
and the assets branch (url.includes(`${BASE_PATH}/assets/`)) to use the parsed
urlObj.pathname and origin checks before calling event.respondWith or returning.

In `@src/utils/api.js`:
- Around line 104-108: The current searchConversationsUrl helper (used by
EventSource) exposes the persistent auth token in the URL; change it so it does
NOT embed the stored auth-token. Update searchConversationsUrl to only build the
base URL/params (q, limit) and remove the token logic, then implement a
short-lived, scoped token flow: create a server endpoint (e.g., POST
/api/sse-token) that mints a one-time/short-lived token tied to the current
session and allowed scopes, call that endpoint from the client (from the code
that constructs the EventSource in useSidebarController) to fetch the ephemeral
token, and append that ephemeral token to the returned URL before constructing
EventSource; alternatively switch server to accept the session cookie for SSE so
no token is put in the URL. Ensure references: searchConversationsUrl and the
EventSource constructor usage in useSidebarController are updated accordingly.

---

Nitpick comments:
In `@src/utils/basePath.ts`:
- Around line 14-16: assetUrl currently concatenates BASE_PATH and path verbatim
which yields duplicate or missing slashes; update assetUrl to normalize both
parts: if BASE_PATH is falsy return path unchanged to preserve existing
behavior, otherwise remove any trailing slash from BASE_PATH and any leading
slashes from path and join them with a single '/' (referencing BASE_PATH and
assetUrl to locate the change).
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4741c193-53b7-4661-9765-41038786e5f1

📥 Commits

Reviewing files that changed from the base of the PR and between f6200e3 and a545183.

📒 Files selected for processing (18)
  • README.md
  • index.html
  • public/sw.js
  • server/index.js
  • src/components/auth/view/SetupForm.tsx
  • src/components/llm-logo-provider/ClaudeLogo.tsx
  • src/components/llm-logo-provider/CodexLogo.tsx
  • src/components/llm-logo-provider/CursorLogo.tsx
  • src/components/llm-logo-provider/GeminiLogo.tsx
  • src/components/project-creation-wizard/data/workspaceApi.ts
  • src/components/settings/view/tabs/api-settings/sections/ApiKeysSection.tsx
  • src/components/shell/utils/socket.ts
  • src/contexts/WebSocketContext.tsx
  • src/hooks/useVersionCheck.ts
  • src/main.jsx
  • src/utils/api.js
  • src/utils/basePath.ts
  • vite.config.js

Comment thread README.md Outdated
Comment thread server/index.js
Comment thread server/index.js
Comment thread src/utils/api.js Outdated
- Fix greedy BASE_PATH stripping in WS routing (e.g. /apple/ws)
- Escape BASE_PATH injection to prevent XSS via script breakout
- Type-check rawUrl in authenticatedFetch for Request/URL objects
- Add caddyfile language to README code block
Copy link
Copy Markdown
Contributor

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

🧹 Nitpick comments (1)
src/utils/api.js (1)

40-52: Optional: collapse the dual BASE_PATH patterns.

The auth block mixes two styles: auth.status/login/register hand-prefix ${BASE_PATH}/api/... on raw fetch, while auth.user/logout rely on authenticatedFetch's auto-prefix with a root-relative path. Both work (unauthenticated calls via authenticatedFetch just skip the Authorization header when no token is present), but the inconsistency is easy to get wrong in future edits — e.g., a contributor adding another pre-auth endpoint could forget the ${BASE_PATH} prefix and silently break subpath deployments.

Consider routing all of them through authenticatedFetch (or a shared helper) so there's one source of truth for BASE_PATH handling.

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

In `@src/utils/api.js` around lines 40 - 52, The auth methods (status, login,
register) mix direct fetch with `${BASE_PATH}` while user/logout use
authenticatedFetch, so consolidate them to use authenticatedFetch (or a small
wrapper) to centralize BASE_PATH handling; update status, login, and register to
call authenticatedFetch('/api/auth/status') and
authenticatedFetch('/api/auth/login', { method: 'POST', headers:
{'Content-Type':'application/json'}, body: JSON.stringify(...) }) and similarly
for register, keeping user and logout unchanged, and ensure authenticatedFetch
continues to skip Authorization when no token so unauthenticated flows still
work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/utils/api.js`:
- Around line 40-52: The auth methods (status, login, register) mix direct fetch
with `${BASE_PATH}` while user/logout use authenticatedFetch, so consolidate
them to use authenticatedFetch (or a small wrapper) to centralize BASE_PATH
handling; update status, login, and register to call
authenticatedFetch('/api/auth/status') and authenticatedFetch('/api/auth/login',
{ method: 'POST', headers: {'Content-Type':'application/json'}, body:
JSON.stringify(...) }) and similarly for register, keeping user and logout
unchanged, and ensure authenticatedFetch continues to skip Authorization when no
token so unauthenticated flows still work.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6852e6f3-f6f4-41c1-9bd1-4ad30a843687

📥 Commits

Reviewing files that changed from the base of the PR and between a545183 and 4118ed7.

📒 Files selected for processing (3)
  • README.md
  • server/index.js
  • src/utils/api.js
✅ Files skipped from review due to trivial changes (1)
  • README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/index.js

Add early middleware to strip BASE_PATH prefix from incoming HTTP
requests. This allows direct subpath deployments without a reverse
proxy, while remaining compatible with proxy setups that already
strip the prefix.
Copy link
Copy Markdown
Contributor

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

🧹 Nitpick comments (1)
server/index.js (1)

343-350: Icon URL prefixing works for current manifest but should handle edge cases.

The manifest currently contains only root-relative paths (/icons/...), so BASE_PATH + icon.src is safe. However, if the manifest format changes to include relative paths, absolute URLs, or data URLs, this concatenation would fail. Consider defensive URL handling:

Proposed safer URL prefixing
         if (BASE_PATH) {
             manifest.start_url = BASE_PATH + '/';
             manifest.scope = BASE_PATH + '/';
+            const prefixManifestPath = (src) => {
+                if (!src || /^(?:[a-z][a-z\d+\-.]*:|\/\/)/i.test(src)) {
+                    return src;
+                }
+                const normalizedSrc = src.startsWith('/')
+                    ? src
+                    : `/${src.replace(/^\.\//, '')}`;
+                return `${BASE_PATH}${normalizedSrc}`;
+            };
             if (manifest.icons) {
                 manifest.icons = manifest.icons.map(icon => ({
                     ...icon,
-                    src: BASE_PATH + icon.src,
+                    src: prefixManifestPath(icon.src),
                 }));
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.js` around lines 343 - 350, The manifest icon URL concatenation
is brittle; update the manifest.icons mapping (where manifest.icons is
transformed) to handle edge cases by skipping prefixing for absolute URLs
(starting with "http://", "https://", "//") and data URLs ("data:"), properly
joining root-relative ("/...") and relative paths by resolving them against
BASE_PATH (use a URL constructor or path-join behavior) and ensure
manifest.start_url and manifest.scope remain BASE_PATH + '/'; modify the mapping
used in the manifest.icons block to detect these cases and only prefix BASE_PATH
when appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/index.js`:
- Around line 16-17: The BASE_PATH constant currently strips trailing slashes
but doesn't ensure a leading slash, so a value like "app" won't match "/app"
requests; update the BASE_PATH normalization (the BASE_PATH definition) to trim
surrounding slashes and then, if non-empty, prefix a single leading slash (or
otherwise set it to an empty string) so manifest generation and request matching
use "/app" style paths consistently.

---

Nitpick comments:
In `@server/index.js`:
- Around line 343-350: The manifest icon URL concatenation is brittle; update
the manifest.icons mapping (where manifest.icons is transformed) to handle edge
cases by skipping prefixing for absolute URLs (starting with "http://",
"https://", "//") and data URLs ("data:"), properly joining root-relative
("/...") and relative paths by resolving them against BASE_PATH (use a URL
constructor or path-join behavior) and ensure manifest.start_url and
manifest.scope remain BASE_PATH + '/'; modify the mapping used in the
manifest.icons block to detect these cases and only prefix BASE_PATH when
appropriate.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1d0d3761-09f9-4b87-9ce5-64788504f013

📥 Commits

Reviewing files that changed from the base of the PR and between 4118ed7 and 11de2b0.

📒 Files selected for processing (1)
  • server/index.js

Comment thread server/index.js Outdated
BASE_PATH=app now correctly becomes /app. Strips leading and
trailing slashes then prepends one, so request matching and
manifest generation work consistently.
Copy link
Copy Markdown
Contributor

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/index.js`:
- Around line 216-219: In the app.use middleware that compares req.url to
BASE_PATH, parse out the pathname (or use Express's req.path) so you compare
only the path portion (not the query string), and when you strip the BASE_PATH
from req.url (in the block around BASE_PATH and
req.url.slice(BASE_PATH.length)), preserve and reattach the original query
string (if any) to the new req.url; update the logic in the middleware that
references req.url/BASE_PATH so it uses the pathname for matching and
reconstructs req.url = slicedPath + (queryString || '/') or similar to keep
queries intact.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: de6972c0-263c-4b5f-b0ca-83f34ac541b8

📥 Commits

Reviewing files that changed from the base of the PR and between 11de2b0 and ca87502.

📒 Files selected for processing (1)
  • server/index.js

Comment thread server/index.js
Copy link
Copy Markdown
Contributor

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

♻️ Duplicate comments (1)
server/index.js (1)

217-218: ⚠️ Potential issue | 🟡 Minor

Preserve the leading slash after stripping a bare BASE_PATH with query params.

/app?next=/projects now matches, but req.url.slice(BASE_PATH.length) becomes ?next=/projects; Express should receive /?next=/projects for the root route plus query. This is the same query-string edge case previously flagged, but the current patch only fixed the match condition.

🐛 Proposed fix
-        if (req.url === BASE_PATH || req.url.startsWith(`${BASE_PATH}/`) || req.url.startsWith(`${BASE_PATH}?`)) {
-            req.url = req.url.slice(BASE_PATH.length) || '/';
+        if (req.url === BASE_PATH || req.url.startsWith(`${BASE_PATH}/`) || req.url.startsWith(`${BASE_PATH}?`)) {
+            const strippedUrl = req.url.slice(BASE_PATH.length);
+            req.url = strippedUrl
+                ? (strippedUrl.startsWith('/') ? strippedUrl : `/${strippedUrl}`)
+                : '/';
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.js` around lines 217 - 218, When stripping BASE_PATH from
req.url (the check in the route matching block), ensure the resulting path
preserves a leading slash when the original was root with query params: after
computing newUrl = req.url.slice(BASE_PATH.length) || '/', if newUrl starts with
'?' prepend '/' so Express receives '/?query' rather than '?query'. Update the
logic around req.url = req.url.slice(BASE_PATH.length) || '/' to handle the case
where the slice begins with '?' and set req.url to `"/" + newUrl` in that case.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@server/index.js`:
- Around line 217-218: When stripping BASE_PATH from req.url (the check in the
route matching block), ensure the resulting path preserves a leading slash when
the original was root with query params: after computing newUrl =
req.url.slice(BASE_PATH.length) || '/', if newUrl starts with '?' prepend '/' so
Express receives '/?query' rather than '?query'. Update the logic around req.url
= req.url.slice(BASE_PATH.length) || '/' to handle the case where the slice
begins with '?' and set req.url to `"/" + newUrl` in that case.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a42f4c28-3874-47ff-b241-784db1a00fc8

📥 Commits

Reviewing files that changed from the base of the PR and between ca87502 and 6c688cb.

📒 Files selected for processing (1)
  • server/index.js

…ponse

Flip from blacklist (skip known routes, catch everything) to whitelist
(only handle /assets/ and navigation). Routes like /session/<id> were
not skipped, causing caches.match() to return undefined on cache miss,
which respondWith() rejects as invalid Response.

Now only assets get cache-first and navigation gets offline fallback.
Everything else passes through to the network untouched.
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