Skip to content

fix(app): support runtime base paths for subpath deployments - #5969

Open
nutzlastfan wants to merge 4 commits into
OHIF:masterfrom
nutzlastfan:fix/runtime-base-path-subpath-deploy
Open

fix(app): support runtime base paths for subpath deployments#5969
nutzlastfan wants to merge 4 commits into
OHIF:masterfrom
nutzlastfan:fix/runtime-base-path-subpath-deploy

Conversation

@nutzlastfan

@nutzlastfan nutzlastfan commented Apr 20, 2026

Copy link
Copy Markdown

Context

This ports the runtime base-path work from a downstream OHIF 3.11 deployment into current upstream master.

The main issue is that a single build still assumes a fixed build-time PUBLIC_URL, which breaks chunk loading, dynamic extension imports, and PWA asset resolution when the viewer is mounted under a subpath instead of /.

Changes

  • add shared runtime public-path helpers in @ohif/core
  • set webpack public path at runtime before the app bootstraps
  • switch generated plugin imports to resolve non-HTTP assets through the runtime base path
  • make the PWA template emit relative asset paths and derive the base path in the HTML head
  • make manifest icon paths relative so they work under subpath deployments

Result

A single viewer build can resolve static assets and dynamic imports correctly when served from paths like /some-prefix/viewer/... instead of only from the site root.

Notes

  • This PR intentionally keeps the scope on runtime base-path handling.
  • I opened it as a draft because upstream may prefer a different bootstrap shape for the early base-path script.

Summary by CodeRabbit

  • New Features

    • Added dynamic runtime base-path resolution, enabling the app to properly determine its deployment location at runtime for improved flexibility in various hosting scenarios.
  • Bug Fixes

    • Fixed icon path resolution to use relative paths instead of absolute paths, ensuring proper asset loading in different deployment environments.

Greptile Summary

This PR introduces runtime base-path resolution so a single OHIF build can be served from any subpath (e.g. /some-prefix/viewer/) without a rebuild. It adds canonical helpers in @ohif/core, injects an inline <script> that sets <base href> before any head resources load, sets __webpack_public_path__ from the resolved path at bundle entry time, and makes manifest/chunk URLs relative.

  • platform/core/src/utils/publicUrl.ts — new canonical module providing normalizePublicUrl, toRouterBasename, getLocationBasePathFromPathname, resolveRuntimeBasePathFromWindow, and getPublicSubPath; logic is duplicated verbatim into both HTML templates and must be kept in sync manually.
  • writePluginImportsFile.js — generated runtime loader now routes non-HTTP extension import paths through getPublicSubPath; the startsWith('http') guard misses protocol-relative URLs (//cdn.example.com/ext.js), which getPublicSubPath then corrupts.
  • webpack.pwa.jsoutput.publicPath is set to '' for relative chunk URLs; historyApiFallback.index loses its leading slash, which may break SPA deep-link fallback during local development.

Confidence Score: 4/5

Safe to merge for standard deployments; the protocol-relative URL gap in the generated plugin loader should be fixed before any CDN-hosted extension relies on it.

The core runtime base-path logic is well-structured and the inline-script/webpack-entry timing is correct for the common case. One concrete defect exists in the generated getRuntimeImportPath helper: protocol-relative URLs (//host/path) are not excluded from getPublicSubPath, so any extension configured with such a URL would load from a mangled path instead of the CDN.

platform/app/.webpack/writePluginImportsFile.js — the generated getRuntimeImportPath guard needs || path.startsWith('//') to protect protocol-relative CDN URLs.

Important Files Changed

Filename Overview
platform/app/.webpack/writePluginImportsFile.js Injects getPublicSubPath into generated plugin imports and rewrites non-HTTP paths at runtime; getRuntimeImportPath misses protocol-relative URLs (//host/...), corrupting CDN-hosted extension imports.
platform/core/src/utils/publicUrl.ts New canonical public-URL utility; getLocationBasePathFromPathname has a known viewerIndex === 0 edge case (deployment at /viewer/) already flagged in prior review threads.
platform/app/src/setRuntimePublicPath.js Sets __webpack_public_path__ from the runtime base path before React renders; correctly imports from @ohif/core and runs before any dynamic imports in the app lifecycle.
platform/app/public/html-templates/index.html Inline <script> now derives and sets <base href> before other head resources load; logic is a verbatim copy of publicUrl.ts and must stay in sync manually (flagged in prior threads).
platform/app/.webpack/webpack.pwa.js Switches to empty publicPath for relative chunk URLs; historyApiFallback.index loses its leading slash which may break SPA deep-link fallback in dev-server.
platform/app/src/utils/publicUrl.ts Still imports directly from ../../../core/src/utils/publicUrl (raw TS source) instead of the @ohif/core package boundary, as flagged in prior review threads.
platform/app/public/manifest.json Icon paths changed from absolute to relative; browser resolves these against the manifest URL, so they work correctly under any subpath.
platform/core/src/utils/index.ts Re-exports the five new public-URL helpers from publicUrl.ts via both the named-export list and the default utils object.
platform/core/src/index.ts Surfaces the five new public-URL helpers from the top-level @ohif/core package entry point.
platform/app/src/index.js Adds import './setRuntimePublicPath' as the first side-effect import, ensuring __webpack_public_path__ is set before App renders.
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
platform/app/.webpack/writePluginImportsFile.js:68-71
Protocol-relative URLs (`//cdn.example.com/ext.js`) are valid extension import paths but are not guarded by `startsWith('http')`. They fall through to `getPublicSubPath`, which strips the leading `//` (via `replace(/^\/+/, '')`) and prepends the base path — e.g. `//cdn.example.com/ext.js` becomes `/myapp/cdn.example.com/ext.js`, silently breaking the import. The old `isAbsolutePath` check included `path.startsWith('/')`, which caught these; the new helper needs to explicitly handle the `//` case.

```suggestion
    'function getRuntimeImportPath(path) {',
    "  if (typeof path !== 'string' || path.startsWith('http') || path.startsWith('//')) return path;",
    '  return getPublicSubPath(path);',
    '}',
```

### Issue 2 of 2
platform/app/.webpack/webpack.pwa.js:181
With `publicPath` now set to `''`, webpack-dev-server's `historyApiFallback.index` should keep its leading slash. Without it, direct navigation to a deep route (e.g. `/viewer/study/123`) returns a 404 during local development because the fallback rewrite resolves relative to the request path rather than the server root.

```suggestion
        index: '/index.html',
```

Reviews (3): Last reviewed commit: "Merge master into runtime base path PR" | Re-trigger Greptile

@netlify

netlify Bot commented Apr 20, 2026

Copy link
Copy Markdown

Deploy Preview for ohif-dev ready!

Name Link
🔨 Latest commit d7b4934
🔍 Latest deploy log https://app.netlify.com/projects/ohif-dev/deploys/6a1c77e76af967000807a616
😎 Deploy Preview https://deploy-preview-5969--ohif-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@nutzlastfan
nutzlastfan marked this pull request as ready for review April 20, 2026 06:41
Comment thread platform/core/src/utils/publicUrl.ts Outdated
Comment thread platform/app/src/setRuntimePublicPath.js Outdated
Comment thread platform/app/public/html-templates/index.html
Comment thread platform/core/src/utils/publicUrl.ts
@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds runtime base path resolution to enable flexible app deployment. Core utilities normalize public URLs, infer paths from window.location.pathname, and resolve the base path from globals. HTML templates bootstrap this at page load, and the app integrates it into startup before React rendering. Icon paths are made relative to work with any deployment base.

Changes

Runtime Base Path Resolution

Layer / File(s) Summary
Public URL utility functions
platform/core/src/utils/publicUrl.ts
Introduces normalizePublicUrl, toRouterBasename, getLocationBasePathFromPathname, resolveRuntimeBasePathFromWindow, and getPublicSubPath to handle prefix normalization, router basename conversion, pathname-based inference, and runtime window-based path resolution.
Core library re-exports
platform/core/src/index.ts, platform/core/src/utils/index.ts
Wires public URL utilities through the core library's main export and utils module as both named exports and properties on the utils default export object.
Runtime bootstrap and initialization
platform/app/src/setRuntimePublicPath.js, platform/app/src/index.js
Establishes runtime resolution at app startup by importing utilities, computing the base path from window globals or pathname, assigning to window.__OHIF_BASE_PATH__ and window.PUBLIC_URL, initializing window.config.routerBasename, and configuring webpack's public path.
HTML template bootstrap scripts
platform/app/public/html-templates/index.html, platform/app/public/html-templates/rollbar.html
Adds client-side bootstrap scripts that compute and inject the normalized base path into the DOM via window globals and updates the <base> element's href, replacing previous static EJS-templated assignments.
App utils and asset references
platform/app/src/utils/publicUrl.ts, platform/app/public/manifest.json
Updates app utilities to use the shared public URL functions for computing publicUrl and routerBasename, and changes manifest icon paths from absolute (/assets/...) to relative (assets/...) for deployment-agnostic asset loading.

Sequence Diagram(s)

sequenceDiagram
  participant index.js as App Entry
  participant setRuntimePublicPath as Runtime Bootstrap
  participant Window as window globals
  participant Webpack as __webpack_public_path__
  
  index.js->>setRuntimePublicPath: import (side-effect)
  setRuntimePublicPath->>setRuntimePublicPath: resolve base path from window
  setRuntimePublicPath->>Window: assign __OHIF_BASE_PATH__
  setRuntimePublicPath->>Window: assign PUBLIC_URL
  setRuntimePublicPath->>Window: initialize config.routerBasename
  setRuntimePublicPath->>Webpack: set asset loading path
  setRuntimePublicPath->>index.js: bootstrap complete
  index.js->>index.js: continue with config load and React render
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A rabbit hops through paths anytime,
Runtime base paths, no build-time chime!
Window globals guide the way,
Normalize, resolve, and play!
Hop from here to anywhere today. 🌍

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Linked issue #39 concerns an unrecognized DICOM tag error in dimseservice.js during C-FIND operations, unrelated to runtime base-path handling. The PR makes no changes to DICOM tag parsing, dimseservice, or C-FIND operations. Remove issue #39 if incorrectly linked; verify the correct issue(s) addressing subpath deployment or base-path resolution should be linked instead.
Description check ⚠️ Warning PR description is comprehensive with clear Context, Changes, Result, and Notes sections; author also flags draft status and known issues, though some checklist items remain incomplete. Complete the mandatory checklist items: confirm PR title follows semantic-release format, document code with function/inline comments, update public documentation if needed, and specify tested environment (OS, Node version, Browser).
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(app): support runtime base paths for subpath deployments' accurately summarizes the main change: enabling single builds to work under subpaths via runtime base-path resolution.
Out of Scope Changes check ✅ Passed All changes are tightly focused on runtime base-path resolution: utilities for path normalization, webpack public-path bootstrap, manifest/HTML template updates, and PWA asset handling. No unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@platform/app/public/html-templates/rollbar.html`:
- Around line 63-88: The getLocationBasePathFromPathname function currently
finds viewerIndex via lastIndexOf('/viewer') which matches substrings inside
segment names (e.g. '/viewer-prod' or '/viewers/123'); change the detection to
require a full path segment boundary (match '/viewer' followed by '/' or
end-of-string) when computing viewerIndex, then use that bounded index to call
normalizePublicUrl as before; update the logic in
getLocationBasePathFromPathname (and keep it in sync with
platform/core/src/utils/publicUrl.ts) so only the exact '/viewer' segment is
treated as the viewer route.

In `@platform/core/src/utils/publicUrl.ts`:
- Around line 39-43: The code uses lowerPath.lastIndexOf('/viewer')
(viewerIndex) and then truncates using locationPath.substring, which incorrectly
matches prefixes inside longer segments like '/viewers'; update the check to
only accept '/viewer' as a distinct path segment by verifying the character
immediately after the match is either absent (end of string) or a '/' before
returning normalizePublicUrl(locationPath.substring(0, viewerIndex)); apply the
identical boundary-safe check to the duplicated occurrences in
platform/app/public/html-templates/index.html and
platform/app/public/html-templates/rollbar.html so
getLocationBasePathFromPathname no longer truncates on '/viewers' or other
longer segment names.
🪄 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 Plus

Run ID: b6798276-46e8-4d72-8246-05911bc2139c

📥 Commits

Reviewing files that changed from the base of the PR and between 0885468 and d7b4934.

⛔ Files ignored due to path filters (2)
  • platform/app/.webpack/webpack.pwa.js is excluded by !**/.webpack/**
  • platform/app/.webpack/writePluginImportsFile.js is excluded by !**/.webpack/**
📒 Files selected for processing (9)
  • platform/app/public/html-templates/index.html
  • platform/app/public/html-templates/rollbar.html
  • platform/app/public/manifest.json
  • platform/app/src/index.js
  • platform/app/src/setRuntimePublicPath.js
  • platform/app/src/utils/publicUrl.ts
  • platform/core/src/index.ts
  • platform/core/src/utils/index.ts
  • platform/core/src/utils/publicUrl.ts

Comment on lines +63 to +88
function getLocationBasePathFromPathname(pathname) {
var locationPath = typeof pathname === 'string' && pathname.length > 0 ? pathname : '/';
var lowerPath = locationPath.toLowerCase();
var viewerIndex = lowerPath.lastIndexOf('/viewer');

if (viewerIndex >= 0) {
return normalizePublicUrl(locationPath.substring(0, viewerIndex));
}

if (lowerPath.endsWith('/index.html')) {
return normalizePublicUrl(
locationPath.substring(0, locationPath.length - '/index.html'.length)
);
}

if (locationPath === '/') {
return '/';
}

var lastSegment = locationPath.substring(locationPath.lastIndexOf('/') + 1);
if (lastSegment.indexOf('.') !== -1) {
return normalizePublicUrl(locationPath.substring(0, locationPath.lastIndexOf('/')));
}

return normalizePublicUrl(locationPath);
}

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 | ⚡ Quick win

/viewer is matched as a substring, not a path segment.

lastIndexOf('/viewer') matches longer segments too (e.g. /viewers/123 or a deployment prefix like /viewer-prod/), causing the base path to be truncated incorrectly. For /viewer-prod/ this yields / instead of /viewer-prod/, breaking asset resolution for that deployment. Constrain the match to a full segment boundary. The same heuristic lives in platform/core/src/utils/publicUrl.ts; keep both in sync.

🐛 Proposed fix
           var viewerIndex = lowerPath.lastIndexOf('/viewer');

-          if (viewerIndex >= 0) {
+          var afterViewer = lowerPath.charAt(viewerIndex + '/viewer'.length);
+          if (viewerIndex >= 0 && (afterViewer === '' || afterViewer === '/')) {
             return normalizePublicUrl(locationPath.substring(0, viewerIndex));
           }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@platform/app/public/html-templates/rollbar.html` around lines 63 - 88, The
getLocationBasePathFromPathname function currently finds viewerIndex via
lastIndexOf('/viewer') which matches substrings inside segment names (e.g.
'/viewer-prod' or '/viewers/123'); change the detection to require a full path
segment boundary (match '/viewer' followed by '/' or end-of-string) when
computing viewerIndex, then use that bounded index to call normalizePublicUrl as
before; update the logic in getLocationBasePathFromPathname (and keep it in sync
with platform/core/src/utils/publicUrl.ts) so only the exact '/viewer' segment
is treated as the viewer route.

Comment on lines +39 to +43
const viewerIndex = lowerPath.lastIndexOf('/viewer');

if (viewerIndex >= 0) {
return normalizePublicUrl(locationPath.substring(0, viewerIndex));
}

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 | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP --type=ts -C2 "path:\s*['\"\`].*viewer" -g '!**/*.test.*'

Repository: OHIF/Viewers

Length of output: 393


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the exact implementation in publicUrl.ts
echo "---- publicUrl.ts (lines around viewer match) ----"
sed -n '1,120p' platform/core/src/utils/publicUrl.ts | nl -ba | sed -n '30,90p'

# 2) Inspect the duplicated logic in index.html template
echo "---- index.html template (viewer match) ----"
# Print around the line range mentioned in the review comment; fallback to finding the pattern if lines shifted.
sed -n '1,180p' platform/app/public/html-templates/index.html | nl -ba | sed -n '50,130p'

echo "---- find occurrences of lastIndexOf('/viewer') in repo ----"
rg -n "lastIndexOf\('/viewer'\)" -S . || true

echo "---- find occurrences of '/viewer' usage in routing ----"
# Look for path definitions and also raw '/viewer' strings.
rg -n --type=ts --type=tsx -g '!**/*.test.*' "path\s*:\s*['\"`].*\/viewer" . || true
rg -n --type=ts --type=tsx -g '!**/*.test.*' "['\"`]\/viewer['\"`]" . || true

echo "---- find how 'viewer' is used in route paths (broader) ----"
rg -n --type=ts --type=tsx -g '!**/*.test.*' "path\s*:\s*['\"`].*viewer" . || true

Repository: OHIF/Viewers

Length of output: 168


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- publicUrl.ts (lines around viewer match) ----"
cat -n platform/core/src/utils/publicUrl.ts | sed -n '25,80p'

echo "---- index.html template (viewer match) ----"
cat -n platform/app/public/html-templates/index.html | sed -n '40,140p'

echo "---- find occurrences of lastIndexOf('/viewer') in repo ----"
rg -n "lastIndexOf\('/viewer'\)" -S . || true

Repository: OHIF/Viewers

Length of output: 6508


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- find occurrences of '/viewer' in path/router config (TS/TSX only) ----"
rg -n --type=ts --type=tsx -g '!**/*.test.*' "path\s*:\s*['\"`].*\/viewer" . || true

echo "---- find literal '/viewer' strings (TS/TSX only) ----"
rg -n --type=ts --type=tsx -g '!**/*.test.*' "['\"`]/viewer['\"`]" . || true

Repository: OHIF/Viewers

Length of output: 219


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- rollbar.html (viewerIndex logic) ----"
cat -n platform/app/public/html-templates/rollbar.html | sed -n '50,95p'

echo "---- TS/TSX: literal \"/viewer\" occurrences (likely route strings) ----"
rg -n --type=ts --type=tsx -g '!**/*.test.*' "/viewer" -S . || true

echo "---- TS/TSX: literal \"/viewers\" occurrences ----"
rg -n --type=ts --type=tsx -g '!**/*.test.*' "/viewers" -S . || true

echo "---- TS/TSX: literal \"viewer-\" occurrences ----"
rg -n --type=ts --type=tsx -g '!**/*.test.*' "viewer-" -S . || true

Repository: OHIF/Viewers

Length of output: 2190


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Also check non-TS templates/config for /viewer segment usage.
echo "---- non-TS: occurrences of \"/viewer\" (templates/config/etc) ----"
rg -n --glob '!**/*.ts' --glob '!**/*.tsx' -g '!**/*.test.*' "/viewer" -S . || true

Repository: OHIF/Viewers

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- usages of getLocationBasePathFromPathname ----"
rg -n --glob '*.ts' --glob '*.tsx' -g '!**/*.test.*' "getLocationBasePathFromPathname" . || true

echo "---- usages of resolveRuntimeBasePathFromWindow / __OHIF_BASE_PATH__ ----"
rg -n --glob '*.ts' --glob '*.tsx' -g '!**/*.test.*' "__OHIF_BASE_PATH__|resolveRuntimeBasePathFromWindow" . || true

echo "---- literal \"/viewer\" occurrences in TS/TSX ----"
rg -n --glob '*.ts' --glob '*.tsx' -g '!**/*.test.*' "\"/viewer\"|'/viewer'|`/viewer`|/viewer" . || true

echo "---- literal \"/viewers\" occurrences in TS/TSX ----"
rg -n --glob '*.ts' --glob '*.tsx' -g '!**/*.test.*' "\"/viewers\"|'/viewers'|`/viewers`|/viewers" . || true

echo "---- literal \"viewer\" route segment patterns (path: '...viewer...') in TS/TSX (limited output) ----"
rg -n --glob '*.ts' --glob '*.tsx' -g '!**/*.test.*' "path\s*:\s*['\"`].*viewer" . | head -n 50 || true

Repository: OHIF/Viewers

Length of output: 50570


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- occurrences of lastIndexOf('/viewer') in templates/html ----"
rg -n "lastIndexOf\('/viewer'\)" platform/app/public/html-templates -S || true

echo "---- viewerIndex logic context in rollbar.html ----"
cat -n platform/app/public/html-templates/rollbar.html | sed -n '55,90p'

Repository: OHIF/Viewers

Length of output: 1820


🏁 Script executed:

#!/bin/bash
set -euo pipefail

RG_BASE="platform"

echo "---- router/path definitions containing viewer (ts/tsx) ----"
rg -n --glob '*.ts' --glob '*.tsx' -g '!**/*.test.*' \
  "path\s*:\s*['\"].*viewer" "$RG_BASE" -S --max-count 200 || true

echo "---- literal /viewer and /viewers in platform (ts/tsx) ----"
rg -n --glob '*.ts' --glob '*.tsx' -g '!**/*.test.*' \
  "/viewer|/viewers" "$RG_BASE" -S --max-count 200 || true

echo "---- literal viewer- in platform (ts/tsx) ----"
rg -n --glob '*.ts' --glob '*.tsx' -g '!**/*.test.*' \
  "viewer-" "$RG_BASE" -S --max-count 200 || true

echo "---- any literal '/viewer' in non-code templates/static config (excluding node_modules) ----"
rg -n --glob '*.html' --glob '*.js' --glob '*.json' --glob '*.ts' --glob '*.tsx' -g '!**/node_modules/**' \
  "['\"]/viewer['\"]" "$RG_BASE" -S --max-count 200 || true

Repository: OHIF/Viewers

Length of output: 1263


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Route-like strings in configs (excluding tests)
echo "---- path: 'viewer*' (route-like) ----"
rg -n --glob '*.ts' --glob '*.tsx' -g '!**/*.test.*' \
  "path\s*:\s*['\"]viewer[^'\"]*['\"]" . -S --max-count 200 || true

echo "---- modePath: 'viewer*' ----"
rg -n --glob '*.ts' --glob '*.tsx' -g '!**/*.test.*' \
  "modePath\s*:\s*['\"]viewer[^'\"]*['\"]" . -S --max-count 200 || true

echo "---- route basename 'viewer*' in any string ----"
rg -n --glob '*.ts' --glob '*.tsx' -g '!**/*.test.*' \
  "['\"][^'\"]*\/viewer[^'\"]*['\"]" . -S --max-count 200 || true

echo "---- any literal '/viewers' (route-like) ----"
rg -n --glob '*.ts' --glob '*.tsx' -g '!**/*.test.*' \
  "/viewers" . -S --max-count 200 || true

Repository: OHIF/Viewers

Length of output: 992


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- literal '/viewers' across repo (exclude node_modules) ----"
rg -n "/viewers" -S . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' -g '!**/.next/**' --max-count 200 || true

echo "---- literal '/viewer-' across repo (exclude node_modules) ----"
rg -n "/viewer-" -S . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' -g '!**/.next/**' --max-count 200 || true

echo "---- literal '/viewer' across repo (exclude node_modules) ----"
rg -n "/viewer" -S . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' -g '!**/.next/**' --max-count 200 || true

Repository: OHIF/Viewers

Length of output: 50370


Fix segment matching in getLocationBasePathFromPathname (avoid truncating on /viewers etc.)
lowerPath.lastIndexOf('/viewer') is substring-based: it will match the /viewer prefix inside longer segments like /viewers/..., truncating the computed base path incorrectly. Apply the same boundary-safe fix in platform/core/src/utils/publicUrl.ts and both duplicated copies in:

  • platform/app/public/html-templates/index.html
  • platform/app/public/html-templates/rollbar.html

Current route/path strings found in code include viewer/dicomlocal and viewer-cs3d, but boundary-safe matching avoids fragile dependence on “no other viewer* segment names” staying true.

🐛 Proposed segment-boundary check
-  const viewerIndex = lowerPath.lastIndexOf('/viewer');
-
-  if (viewerIndex >= 0) {
-    return normalizePublicUrl(locationPath.substring(0, viewerIndex));
-  }
+  const viewerMatch = lowerPath.match(/\/viewer(?=$|[/?#])/);
+  if (viewerMatch && viewerMatch.index !== undefined) {
+    return normalizePublicUrl(locationPath.substring(0, viewerMatch.index));
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const viewerIndex = lowerPath.lastIndexOf('/viewer');
if (viewerIndex >= 0) {
return normalizePublicUrl(locationPath.substring(0, viewerIndex));
}
const viewerMatch = lowerPath.match(/\/viewer(?=$|[/?#])/);
if (viewerMatch && viewerMatch.index !== undefined) {
return normalizePublicUrl(locationPath.substring(0, viewerMatch.index));
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@platform/core/src/utils/publicUrl.ts` around lines 39 - 43, The code uses
lowerPath.lastIndexOf('/viewer') (viewerIndex) and then truncates using
locationPath.substring, which incorrectly matches prefixes inside longer
segments like '/viewers'; update the check to only accept '/viewer' as a
distinct path segment by verifying the character immediately after the match is
either absent (end of string) or a '/' before returning
normalizePublicUrl(locationPath.substring(0, viewerIndex)); apply the identical
boundary-safe check to the duplicated occurrences in
platform/app/public/html-templates/index.html and
platform/app/public/html-templates/rollbar.html so
getLocationBasePathFromPathname no longer truncates on '/viewers' or other
longer segment names.

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