Skip to content

Conversation

@JSMike
Copy link
Contributor

@JSMike JSMike commented Jan 28, 2026

Closes #33548

What I did

Updated url.ts and ref.ts to use base path instead of full pathname. This solved a logic error when finding relative files that had assumed the pathname didn't end with /

PR based on #33624
I've added @ia319 as co-author to the commit.

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

Caution

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

Basic test:

  1. Run a sandbox for template: yarn start
  2. Open Storybook in your browser
  3. Change URL to: http://localhost:6006/index.html
  4. Expect same result as when initially opening storybook.

Advanced with refs:

  1. Create two different sandboxes:
  2. Run a sandbox for template: yarn task --task sandbox --start-from auto --template react-vite/default-ts
    1. Run a sandbox for template: yarn task --task sandbox --start-from auto --template lit-vite/default-ts
  3. navigate to ../storybook-sandboxes/react-vite-default-ts and run npm run build-storybook
  4. run npx http-server . --cors --port 8080
  5. in a separate terminal, navigate to ../storybook-sandboxes/lit-vite-default-ts
  6. modify .storybook/main.ts, add to the config:
  "refs": {
    "react": {
      "title": "React Components",
      "url": "http://localhost:8080/storybook-static/"
    }
  },
  1. run npm run build-storybook
  2. run npx http-server . --cors --port 8081
  3. open browser to http://localhost:8081/storybook-static/
  4. confirm page loads with composed storybook
  5. select globe icon next to 'React Components' (extra actions button) and select view external storybook (page should load as expected with index.html in path)
  6. open browser to http://localhost:8081/storybook-static/index.html
  7. confirm page loads with composed storybook
  8. select globe icon next to 'React Components' (extra actions button) and select view external storybook (page should load as expected with index.html in path)

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>

Summary by CodeRabbit

  • Bug Fixes
    • Improved URL comparison logic to more consistently match reference URLs across different path structures.

✏️ Tip: You can customize this high-level summary in your review settings.

@JSMike JSMike force-pushed the 33548-fix-base-path branch from 47bfeed to 92069d1 Compare January 28, 2026 11:16
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 28, 2026

📝 Walkthrough

Walkthrough

Changes URL canonicalization logic in refs manager by replacing path segment removal method: instead of stripping '/iframe.html' and trailing slashes, the code now uses regex pattern /[^/]*$ to remove the last path segment for equality checks between localFull and sourceFull URLs.

Changes

Cohort / File(s) Summary
URL Canonicalization Logic
code/core/src/manager-api/modules/refs.ts
Modified URL stripping pattern from removing '/iframe.html' and trailing slashes to regex-based removal of final path segment

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • #33647: Modifies the same URL canonicalization behavior for iframe/preview URL construction
✨ Finishing touches
  • 📝 Generate docstrings

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
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: 2

🤖 Fix all issues with AI agents
In `@code/core/src/manager-api/modules/refs.ts`:
- Around line 84-85: The normalization currently builds localFull/sourceFull
from localOrigin+localPathname and then strips the last path segment with
.replace(/\/[^\/]*$/,''), which collapses a trailing slash form
(https://host/storybook/) to https://host/storybook but turns
https://host/storybook into https://host; change the logic in the localFull and
sourceFull construction to normalize by removing only a trailing slash (e.g.,
replace(/\/$/,'')) or, better, use the URL API to join origin+pathname and then
trim a single trailing slash so both https://host/storybook and
https://host/storybook/ normalize to the same value; update the expressions that
compute localFull and sourceFull (and any callers that rely on them)
accordingly.

Comment on lines +84 to +85
const localFull = `${localOrigin + localPathname}`.replace(/\/[^\/]*$/, '');
const sourceFull = `${sourceOrigin + sourcePathname}`.replace(/\/[^\/]*$/, '');
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n code/core/src/manager-api/modules/refs.ts | head -100

Repository: storybookjs/storybook

Length of output: 3877


🏁 Script executed:

# Check where getSourceType is used
rg "getSourceType" code/core/src/manager-api/modules/refs.ts -B 2 -A 2

# Search for tests of this function
fd "refs" code/core --type f -name "*.test.ts" -o -name "*.spec.ts" | head -5

Repository: storybookjs/storybook

Length of output: 529


🏁 Script executed:

# Find test files for refs
fd "refs" code/core -name "*test*" -o -name "*spec*"

# Search for usage of getSourceType
rg "getSourceType" code/core --type ts --type tsx

Repository: storybookjs/storybook

Length of output: 323


🏁 Script executed:

# Find test files for refs module
find code/core -name "*refs*test*" -o -name "*refs*spec*"

# Search for getSourceType usage with correct rg syntax
rg "getSourceType" code/core

Repository: storybookjs/storybook

Length of output: 1135


🏁 Script executed:

cat code/core/src/manager-api/tests/refs.test.ts

Repository: storybookjs/storybook

Length of output: 34831


🏁 Script executed:

# Let me trace through the regex behavior with concrete examples
python3 << 'EOF'
import re

# Test the regex behavior
regex = r'/[^/]*$'

test_cases = [
    'https://storybook.js.org/storybook/iframe.html',
    'https://storybook.js.org/storybook/',
    'https://storybook.js.org/storybook',
    'https://external.com/storybook/',
    'https://external.com/storybook',
]

for url in test_cases:
    result = re.sub(regex, '', url)
    print(f"{url:50} → {result}")
EOF

Repository: storybookjs/storybook

Length of output: 151


🏁 Script executed:

# Let's manually verify the regex behavior with simple shell commands
echo "Testing regex replacement /\/[^\/]*$/:"
echo ""
echo "Input: https://storybook.js.org/storybook/iframe.html"
echo "https://storybook.js.org/storybook/iframe.html" | sed 's/\/[^\/]*$//'
echo ""
echo "Input: https://storybook.js.org/storybook/"
echo "https://storybook.js.org/storybook/" | sed 's/\/[^\/]*$//'
echo ""
echo "Input: https://storybook.js.org/storybook"
echo "https://storybook.js.org/storybook" | sed 's/\/[^\/]*$//'
echo ""
echo "Input: https://external.com/storybook/"
echo "https://external.com/storybook/" | sed 's/\/[^\/]*$//'

Repository: storybookjs/storybook

Length of output: 411


🏁 Script executed:

# Check if tests cover the scenario: location with trailing slash vs ref without trailing slash
grep -n "https://storybook.js.org/storybook'" code/core/src/manager-api/tests/refs.test.ts | head -20

Repository: storybookjs/storybook

Length of output: 166


🏁 Script executed:

# Check the context around those line numbers to understand what's being tested
sed -n '140,170p' code/core/src/manager-api/tests/refs.test.ts

Repository: storybookjs/storybook

Length of output: 1352


🏁 Script executed:

# Let me trace the exact scenario the review comment mentions
# Location: /storybook/ (trailing slash)
# Source ref: /storybook (no trailing slash, no filename)

echo "Scenario from review comment:"
echo "Manager location: https://storybook.js.org/storybook/ (with trailing slash)"
echo "Ref source: https://storybook.js.org/storybook (no trailing slash, no filename)"
echo ""
echo "After regex /\/[^\/]*$/:"
echo "localFull: https://storybook.js.org/storybook/ →" $(echo 'https://storybook.js.org/storybook/' | sed 's/\/[^\/]*$//')
echo "sourceFull: https://storybook.js.org/storybook →" $(echo 'https://storybook.js.org/storybook' | sed 's/\/[^\/]*$//')
echo ""
echo "Result: These DO NOT MATCH, so getSourceType() returns 'external' (INCORRECT)"
echo ""
echo "This edge case is NOT covered in the test suite."

Repository: storybookjs/storybook

Length of output: 563


The base-path normalization logic has a trailing-slash edge case that could cause refs to be misclassified.

The regex at lines 84–85 removes the last path segment including its slash. This means:

  • https://host/storybook/ normalizes to https://host/storybook
  • https://host/storybook normalizes to https://host

If a ref is configured without a trailing slash (e.g., https://host/storybook) but the manager runs at a path with a trailing slash (e.g., https://host/storybook/), they will normalize to different values and the ref will be incorrectly classified as external instead of local. This scenario is not covered by the current test suite.

🤖 Prompt for AI Agents
In `@code/core/src/manager-api/modules/refs.ts` around lines 84 - 85, The
normalization currently builds localFull/sourceFull from
localOrigin+localPathname and then strips the last path segment with
.replace(/\/[^\/]*$/,''), which collapses a trailing slash form
(https://host/storybook/) to https://host/storybook but turns
https://host/storybook into https://host; change the logic in the localFull and
sourceFull construction to normalize by removing only a trailing slash (e.g.,
replace(/\/$/,'')) or, better, use the URL API to join origin+pathname and then
trim a single trailing slash so both https://host/storybook and
https://host/storybook/ normalize to the same value; update the expressions that
compute localFull and sourceFull (and any callers that rely on them)
accordingly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tested with refs with both trailing and non-trailing and both worked, the ref adds /index.html to the link to external storybook properly in both cases.

}

const pathname = location.pathname || '/';
const pathname = location.pathname?.replace(/[^\/]*$/, '') || '/';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the file structure and read the relevant section
cat -n code/core/src/manager-api/modules/url.ts | head -280 | tail -50

Repository: storybookjs/storybook

Length of output: 2462


🏁 Script executed:

# Let's also check the full context around line 250
cat -n code/core/src/manager-api/modules/url.ts | sed -n '240,270p'

Repository: storybookjs/storybook

Length of output: 1518


🏁 Script executed:

# Search for how managerBase and previewBase are computed
grep -n "managerBase\|previewBase" code/core/src/manager-api/modules/url.ts

Repository: storybookjs/storybook

Length of output: 519


🏁 Script executed:

# Look for tests related to url module
fd -e "test\|spec" -p "url" code/

Repository: storybookjs/storybook

Length of output: 47


🏁 Script executed:

# Search for tests related to getStoryHrefs or url.ts
find code -name "*.test.*" -o -name "*.spec.*" | xargs grep -l "getStoryHrefs\|url" 2>/dev/null | head -20

Repository: storybookjs/storybook

Length of output: 1078


🏁 Script executed:

# Look at git history for this specific line/function
cd code/core/src/manager-api/modules && git log -p --all -S "replace(/\[^\/\]\*\$/," -- url.ts | head -150

Repository: storybookjs/storybook

Length of output: 47


🏁 Script executed:

# Check if there are any comments or documentation about pathname handling
grep -n -B5 -A5 "pathname" code/core/src/manager-api/modules/url.ts | head -50

Repository: storybookjs/storybook

Length of output: 799


🏁 Script executed:

# Search for any existing issue discussions or comments about trailing slashes
grep -rn "trailing slash\|bare.*path\|directory.*path" code/core/src/manager-api/ 2>/dev/null

Repository: storybookjs/storybook

Length of output: 47


🏁 Script executed:

# Let's examine the url.test.js file to see what tests exist
cat -n code/core/src/manager-api/tests/url.test.js

Repository: storybookjs/storybook

Length of output: 19528


🏁 Script executed:

# Let's also search for any references to pathname without trailing slash
grep -rn "pathname" code/core/src/manager-api/modules/url.ts

Repository: storybookjs/storybook

Length of output: 311


🏁 Script executed:

# Check if there are integration tests or other references to getStoryHrefs
grep -rn "getStoryHrefs" code --include="*.test.*" --include="*.spec.*"

Repository: storybookjs/storybook

Length of output: 1901


Preserve directory-like paths without file extensions.

Line 250 strips the last path segment unconditionally, collapsing /storybook to / or /index.html to /. This affects both managerBase and previewBase. To handle paths correctly, distinguish between actual files (with extensions) and directory-like paths—preserve directory paths by either keeping them intact or ensuring a trailing slash, while only stripping file segments.

💡 Suggested fix
-      const pathname = location.pathname?.replace(/[^\/]*$/, '') || '/';
+      const rawPathname = location.pathname || '/';
+      const pathname = rawPathname.endsWith('/')
+        ? rawPathname
+        : /\.[^/]+$/.test(rawPathname)
+          ? rawPathname.replace(/[^/]*$/, '')
+          : `${rawPathname}/`;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this feedback is also assuming that a directory wouldn't have a period. ie: /my-storybook/1.0, from my experience the trailing slash has always been required and caused other file path issues if you navigate to a path not ending in /

@JSMike JSMike force-pushed the 33548-fix-base-path branch 2 times, most recently from 6273404 to a61919a Compare January 28, 2026 11:44
@ghengeveld
Copy link
Member

Thanks for contributing! Looks like you took a very similar approach to the one I took here. Sorry I hadn't seen this PR when I fixed my own mess, otherwise I would've attributed you.

@JSMike
Copy link
Contributor Author

JSMike commented Jan 28, 2026

no worries @ghengeveld, I can fixup my PR, this still has changes to refs.ts, yours only updated url.ts

Co-authored-by: ia319 <linglingmeng876@gmail.com>
@JSMike JSMike force-pushed the 33548-fix-base-path branch from a61919a to 8ea6033 Compare January 28, 2026 23:39
@JSMike
Copy link
Contributor Author

JSMike commented Jan 29, 2026

@ghengeveld I've updated and synced with next. That being said, I made this change based off of the other PR that I referenced, I think the change you applied was the more relevant change for the issue, this appears to aligning the logic. Can confirm with @ia319

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.

[Bug]: Built SB renders with wrong path and no addons if visiting /index.html directly

2 participants