Skip to content

Add page visibility parameter to st.Page#13905

Merged
lukasmasuch merged 11 commits intodevelopfrom
lukasmasuch/page-visibility
Feb 13, 2026
Merged

Add page visibility parameter to st.Page#13905
lukasmasuch merged 11 commits intodevelopfrom
lukasmasuch/page-visibility

Conversation

@lukasmasuch
Copy link
Copy Markdown
Collaborator

@lukasmasuch lukasmasuch commented Feb 11, 2026

Describe your changes

Implements the visibility parameter for st.Page to allow pages to be hidden from navigation menus while remaining accessible via direct URL, st.page_link, and st.switch_page. This addresses issues #10738 and #9195.

Key changes:

  • Backend: Added visibility parameter with validation (visible/hidden)
  • Frontend: Filtering of hidden pages in SidebarNav and TopNav components
  • Tests: Comprehensive E2E and unit tests for the new feature
  • Code improvements: Simplified shouldShowNavigation logic and removed unused parameters

Github Issues

Testing Plan

  • Unit Tests: 26 Navigation utility tests + TopNav hidden page tests + SidebarNav section tests pass
  • E2E Tests: 4 new E2E tests cover st.switch_page, sections with all hidden pages, and both nav positions
  • All existing tests pass: 66 Python tests + 160 frontend tests
  • All checks pass: format, lint, type checking

The feature is fully backward compatible - visibility defaults to "visible" maintaining existing behavior.

Implement the `visibility` parameter for `st.Page` to allow hiding pages from navigation while remaining accessible via direct URL, st.page_link, and st.switch_page. Includes:

- Backend: Page visibility parameter with validation
- Frontend: Hidden page filtering in SidebarNav and TopNav
- E2E tests: st.switch_page, sections with all hidden pages
- Unit tests: TopNav hidden page tests, section visibility tests
- Code cleanup: Removed unused navSections parameter from shouldShowNavigation
Copilot AI review requested due to automatic review settings February 11, 2026 01:17
@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Feb 11, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 11, 2026

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-13905/streamlit-1.54.0-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-13905.streamlit.app (☁️ Deploy here if not accessible)

@lukasmasuch lukasmasuch changed the title feat: Add page visibility parameter to st.Page Add page visibility parameter to st.Page Feb 11, 2026
@lukasmasuch lukasmasuch changed the title Add page visibility parameter to st.Page Add page visibility parameter to st.Page Feb 11, 2026
@lukasmasuch lukasmasuch added security-assessment-completed change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users labels Feb 11, 2026
@lukasmasuch lukasmasuch changed the title Add page visibility parameter to st.Page [WIP] Add page visibility parameter to st.Page Feb 11, 2026
@lukasmasuch lukasmasuch marked this pull request as draft February 11, 2026 01:18
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a visibility parameter to st.Page to support “hidden” pages that remain routable and linkable (URL, st.page_link, st.switch_page) while being excluded from rendered navigation UI.

Changes:

  • Backend: Add visibility to st.Page/StreamlitPage with validation and forward it via protobuf (AppPage.is_hidden).
  • Frontend: Filter hidden pages out of SidebarNav/TopNav rendering and update nav visibility logic to consider only visible pages.
  • Tests/specs: Add unit + E2E coverage for hidden-page routing and navigation rendering behavior, plus product/tech specs.

Reviewed changes

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

Show a summary per file
File Description
specs/0009-page-visibility/tech-spec.md Technical design for backend/proto/frontend changes and edge cases.
specs/0009-page-visibility/product-spec.md Product/API spec describing the new visibility parameter and behaviors.
proto/streamlit/proto/AppPage.proto Adds is_hidden field to wire hidden-page state to the frontend.
lib/streamlit/navigation/page.py Introduces visibility parameter, validates it, and stores _visibility.
lib/streamlit/commands/navigation.py Populates AppPage.is_hidden based on StreamlitPage._visibility.
lib/tests/streamlit/navigation/page_test.py Adds unit tests for default/hidden/visible/invalid visibility.
lib/tests/streamlit/commands/navigation_test.py Adds unit tests ensuring is_hidden is set correctly in navigation messages and routing still works.
frontend/app/src/components/Navigation/utils.ts Updates shouldShowNavigation to count only visible pages; adds filterVisiblePages; simplifies section handling.
frontend/app/src/components/Navigation/utils.test.ts Expands tests for hidden-page behavior and adds tests for filterVisiblePages.
frontend/app/src/components/Navigation/SidebarNav.tsx Filters hidden pages for display and updates visible page counting logic.
frontend/app/src/components/Navigation/SidebarNav.test.tsx Adds tests asserting hidden pages/sections don’t render and visible counts drive “View more”.
frontend/app/src/components/Navigation/TopNav.tsx Filters hidden pages for display and ensures section data is built from visible pages only.
frontend/app/src/components/Navigation/TopNav.test.tsx Adds tests asserting hidden pages/sections don’t render in top nav.
frontend/app/src/components/AppView/AppView.tsx Updates shouldShowNavigation call signature usage.
frontend/app/src/components/AppView/AppView.test.tsx Updates tests for the new shouldShowNavigation(appPages) signature.
frontend/app/src/components/Sidebar/Sidebar.tsx Removes unused navSections usage and updates shouldShowNavigation call.
frontend/vitest.setup.ts Adds a localStorage mock fallback and adjusts global assignments typing.
frontend/app/tsconfig.json Includes vitest.setup.ts in the app TS project includes.
e2e_playwright/st_page_visibility.py New E2E app exercising hidden pages, sections, and top/sidebar nav positions.
e2e_playwright/st_page_visibility_test.py New E2E tests validating hidden pages aren’t displayed but remain accessible via URL/link/switch_page.

Fix mypy error by adding explicit type annotation allowing both
list and dict types for the pages variable.
- Use toBeVisible() instead of toBeInTheDocument() for RTL assertions
- Add try/catch guard for localStorage access in vitest setup
- Reuse filterVisiblePages() to avoid duplicate filtering logic
- Use to_have_count(0) for more precise E2E assertions
- Add is_hidden field to proto compatibility test
- Fix E2E tests to use stTopNavLink instead of non-existent stTopNav
When pages are in sections, TopNav renders them inside section dropdowns
(stTopNavSection) rather than as individual links (stTopNavLink). Updated
test to check for the section dropdown correctly.
@lukasmasuch
Copy link
Copy Markdown
Collaborator Author

@cursor review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

- Fix sidebar visibility check to use shouldShowNavigation() instead of
  appPages.length > 1, ensuring hidden pages are excluded from the count
- Use StreamlitValueError for invalid visibility parameter validation
- Move E2E tests to multipage_apps_v2 directory
- Add explicit test that sidebar doesn't mount when only 1 visible page
@lukasmasuch lukasmasuch changed the title [WIP] Add page visibility parameter to st.Page Add page visibility parameter to st.Page Feb 11, 2026
@lukasmasuch lukasmasuch marked this pull request as ready for review February 11, 2026 08:23
@github-actions
Copy link
Copy Markdown
Contributor

Consolidated Code Review: Add page visibility parameter to st.Page (#13905)

Summary

This PR introduces a visibility parameter to st.Page that accepts "visible" (default) or "hidden". Hidden pages are excluded from sidebar and top navigation UI while remaining accessible via direct URL, st.page_link, and st.switch_page. The implementation spans all layers: protobuf (is_hidden bool field), Python backend (parameter validation and serialization), and React frontend (filtering and navigation visibility logic). A shouldShowNavigation refactor simplifies the function signature by removing the navSections parameter.

Code Quality

Reviewer consensus: High quality, no defects identified.

Both reviewers agreed the implementation is clean, well-structured, and follows existing codebase patterns throughout all layers:

  • Backend: Literal["visible", "hidden"] typing, early validation with StreamlitValueError, private _visibility attribute — all consistent with established conventions.
  • Frontend: filterVisiblePages is a clean pure utility. useMemo is correctly applied for visiblePages in both SidebarNav and TopNav. The filtering happens before grouping/processing, ensuring downstream logic operates only on visible pages.
  • Refactor: The shouldShowNavigation simplification (removing navSections parameter) is a clear improvement. The new version counts visible pages directly, which is both simpler and more correct — especially for the appPages.length > 1shouldShowNavigation(appPages) change in AppView.tsx, which now properly accounts for hidden pages when deciding sidebar visibility.

One reviewer noted shouldShowNavigation is called multiple times per render in AppView with the same appPages, but correctly assessed this as acceptable given it's a lightweight filter + length check on context-provided data.

Test Coverage

Reviewer consensus: Comprehensive and well-structured.

Both reviewers confirmed strong, multi-layered test coverage:

  • Python unit tests: Visibility parameter validation (default, explicit, invalid), proto serialization, hidden pages in sections, hidden page as default, all-hidden scenario, navigation by hash.
  • Frontend unit tests: filterVisiblePages (all visible, filtered, all hidden, empty, undefined isHidden), shouldShowNavigation with hidden pages, SidebarNav and TopNav rendering with hidden pages, section hiding when all pages hidden.
  • E2E tests: 10 new tests covering sidebar nav, top nav, URL access, page_link, switch_page, single-visible-page behavior, and section header hiding.
  • Proto compatibility test updated for the new is_hidden field.

Agreed gap: Both reviewers independently identified the same minor omission — no typing test was added in lib/tests/streamlit/typing/navigation_types.py for the visibility parameter. This is non-blocking but would improve type contract enforcement.

Backwards Compatibility

Reviewer consensus: Fully backwards compatible.

  • visibility defaults to "visible", preserving all existing behavior.
  • Proto3 is_hidden (field 7) is additive; defaults to false, so clients unaware of the field treat pages as visible.
  • The shouldShowNavigation signature change is internal-only; all callers are updated.
  • The behavioral change where a sidebar now correctly hides when all pages are hidden (rather than showing empty nav) is intentional and correct.

Security & Risk

Reviewer consensus: No security concerns, low regression risk.

  • visibility is purely a UI display feature — no access control implications. Hidden pages remain fully accessible.
  • Validation via StreamlitValueError prevents unexpected values.
  • Risk is well-contained and mitigated by comprehensive test coverage.

Accessibility

Reviewer consensus: No accessibility regressions.

The changes filter existing nav items rather than introducing new interaction patterns. Navigation components continue using existing accessible patterns (SidebarNavLink, semantic list items, etc.).

Recommendations

  1. Add a typing test (raised by both reviewers): Add assertions in lib/tests/streamlit/typing/navigation_types.py for Page with the visibility parameter to lock down the Literal["visible", "hidden"] type contract at type-check time.

  2. Consider a public visibility property (raised by one reviewer): Currently _visibility is only accessible via the private attribute. For consistency with existing public properties (title, icon, url_path), a public read-only visibility property on StreamlitPage could be useful.

Both recommendations are minor and non-blocking.

Verdict

APPROVED

Both reviewers independently approved this PR with no blocking issues. The implementation is clean, well-tested, backwards compatible, and follows existing codebase patterns. Test coverage is comprehensive across all layers.


Consolidated review by opus-4.6-thinking. Based on reviews from 2/2 expected models.


📋 Review by `gpt-5.3-codex-high`

Summary

This PR introduces a visibility parameter on st.Page ("visible"/"hidden") and wires it through backend navigation protobufs to frontend rendering. Hidden pages are excluded from sidebar/top navigation UI while remaining routable via direct URL, st.page_link, and st.switch_page. The frontend navigation visibility logic is also simplified to depend on visible-page count, which resolves empty-sidebar behavior when only hidden pages remain.

Code Quality

The implementation is clean and consistent across backend (lib/streamlit/navigation/page.py, lib/streamlit/commands/navigation.py), protocol (proto/streamlit/proto/AppPage.proto), and frontend (frontend/app/src/components/Navigation/*, frontend/app/src/components/AppView/AppView.tsx, frontend/app/src/components/Sidebar/Sidebar.tsx).

I did not identify blocking code-quality defects or logic bugs in the changed code paths.

Test Coverage

Coverage is strong and appropriately layered:

  • Python unit tests validate default/explicit/invalid visibility handling and navigation message semantics (lib/tests/streamlit/navigation/page_test.py, lib/tests/streamlit/commands/navigation_test.py).
  • Proto compatibility checks were updated for the new field (lib/tests/streamlit/proto_compatibility_test.py).
  • Frontend unit tests cover filtering behavior, section handling, and nav visibility conditions (frontend/app/src/components/Navigation/utils.test.ts, frontend/app/src/components/Navigation/SidebarNav.test.tsx, frontend/app/src/components/Navigation/TopNav.test.tsx, frontend/app/src/components/AppView/AppView.test.tsx).
  • E2E tests cover sidebar/top nav visibility, direct URL routing, st.page_link, st.switch_page, single-visible-page behavior, and hidden-only sections (e2e_playwright/multipage_apps_v2/mpa_v2_page_visibility_test.py).

Overall, test coverage for this feature is adequate.

Backwards Compatibility

Backwards compatibility looks good:

  • visibility defaults to "visible", preserving existing behavior for all current st.Page(...) calls.
  • The protobuf change is additive (is_hidden = 7), which is wire-compatible.
  • Frontend filtering treats missing isHidden as visible, so mixed-version behavior remains safe.

Security & Risk

No security issues identified. Regression risk appears low-to-moderate and mostly constrained to navigation rendering logic; that risk is mitigated by broad unit + e2e coverage.

Accessibility

No new accessibility regressions were identified in this diff. The changes mainly filter existing nav items rather than introducing new interaction patterns or non-semantic controls.

Recommendations

  1. Consider adding a typing assertion for the new Page(..., visibility=...) API in lib/tests/streamlit/typing/navigation_types.py to lock down the literal type contract at type-check time (non-blocking).

Verdict

APPROVED: The feature is implemented coherently across backend/frontend/proto with strong test coverage and no blocking issues found.


This is an automated AI review by gpt-5.3-codex-high. Please verify the feedback and use your judgment.

📋 Review by `opus-4.6-thinking`

Summary

This PR adds a visibility parameter to st.Page that allows pages to be hidden from navigation menus while remaining accessible via direct URL, st.page_link, and st.switch_page. The implementation spans all layers of the stack:

  • Protobuf: New is_hidden bool field (field number 7) added to AppPage message.
  • Backend: visibility parameter added to st.Page with validation ("visible" or "hidden"), serialized to the is_hidden proto field.
  • Frontend: filterVisiblePages utility function filters hidden pages from SidebarNav and TopNav components. shouldShowNavigation was simplified to count only visible pages (removing the now-unnecessary navSections parameter).
  • Tests: Comprehensive unit tests (Python, TypeScript) and E2E tests covering sidebar nav, top nav, sections, URL access, page_link, switch_page, and edge cases.

Code Quality

The code is well-structured and follows existing patterns throughout the codebase.

Backend (Python):

  • visibility parameter uses Literal["visible", "hidden"] with proper type annotations — consistent with the codebase style.
  • Validation uses StreamlitValueError appropriately, matching the existing error pattern.
  • Visibility is validated early in __init__ before the potential early return (when ctx is None), which is correct.
  • The _visibility attribute is private (prefixed with _), following the convention for internal state in StreamlitPage.
  • Docstring follows numpydoc style and is clear.

Frontend (TypeScript):

  • filterVisiblePages is a clean, pure utility function.
  • The shouldShowNavigation simplification (removing navSections parameter) is a good refactor — the old function was overly complex and the sections parameter wasn't needed for the visibility check. The new version counts visible pages directly.
  • useMemo is correctly used for visiblePages in both SidebarNav and TopNav, ensuring referential stability.
  • In SidebarNav, filtering happens before grouping/processing, which is the correct approach — hidden pages are excluded early so all downstream logic (collapse threshold, section rendering) operates on visible pages only.

Minor observations:

  1. In AppView.tsx (line 164), the sidebar visibility check was changed from appPages.length > 1 to shouldShowNavigation(appPages). This is an improvement — it now consistently uses the same function and accounts for hidden pages when deciding whether to show the sidebar. However, this means shouldShowNavigation is called multiple times in AppView (lines 164, 283, 317) per render with the same appPages. Since appPages comes from context and shouldShowNavigation is a lightweight filter + length check, this is acceptable and not a performance concern.

Test Coverage

Test coverage is thorough and well-structured:

Python unit tests (6 new tests in navigation_test.py, 4 in page_test.py):

  • Proto message serialization with is_hidden field
  • All-hidden pages scenario
  • Hidden page as default page
  • Hidden pages in sections
  • Hidden page navigation by hash
  • Visibility parameter validation (default, set to hidden, set to visible, invalid value)

Frontend unit tests (26 new tests in utils.test.ts, plus tests in SidebarNav.test.tsx and TopNav.test.tsx):

  • filterVisiblePages — all visible, filtered, all hidden, empty, undefined isHidden
  • shouldShowNavigation — with hidden pages across various configurations
  • SidebarNav — hidden pages not rendered, count reflects visible pages, "View more" based on visible count, hidden page as current page, sections with hidden pages, section hiding when all pages hidden
  • TopNav — hidden pages not rendered, sections, section hiding

E2E tests (10 new tests):

  • Sidebar and top nav hidden page filtering
  • URL access, page_link access, switch_page access for hidden pages
  • Single visible page hides navigation (both sidebar and top nav)
  • Section headers hidden when all pages in section are hidden (both sidebar and top nav)

Proto compatibility test updated with is_hidden field.

One gap: No typing test was added in lib/tests/streamlit/typing/navigation_types.py for the new visibility parameter on st.Page. While not critical, it would be consistent with the existing typing test patterns. The existing typing tests for Page don't currently test keyword parameters beyond position and expanded for navigation, so this is a minor omission.

Backwards Compatibility

This change is fully backwards compatible:

  • Python API: visibility defaults to "visible", preserving existing behavior. No existing parameters changed.
  • Protobuf: New field is_hidden (field 7) is additive. Proto3 bool fields default to false, so existing clients that don't know about this field will treat all pages as visible — correct behavior.
  • Frontend: shouldShowNavigation signature change (removed navSections parameter) is an internal function, not a public API. All callers within the codebase have been updated.
  • appPages.length > 1shouldShowNavigation(appPages): In AppView.tsx, the sidebar visibility logic was updated. The old check (appPages.length > 1) did not account for hidden pages; the new check correctly filters visible pages first. This could cause a behavioral difference if an app had 2+ total pages but only 1 visible — the sidebar would now correctly hide rather than show an empty nav. This is the intended behavior.

Security & Risk

No security concerns identified:

  • The visibility parameter only controls UI rendering — hidden pages remain fully accessible via URL, st.page_link, and st.switch_page. There is no access control implication, which is appropriate and clearly documented.
  • No user input is passed to exec or eval beyond what already exists.
  • The StreamlitValueError validation prevents unexpected values.

Regression risk is low: The changes are well-contained. The main risk area is the shouldShowNavigation refactor affecting sidebar visibility, but this is well-tested and the new logic is actually more correct than the old appPages.length > 1 check.

Accessibility

No accessibility concerns:

  • Hidden pages are purely a navigation menu display feature — they don't affect the DOM structure of the pages themselves.
  • The navigation components continue to use their existing accessible patterns (SidebarNavLink, semantic list items, etc.).
  • No new interactive elements are introduced that would need ARIA attributes.

Recommendations

  1. Consider adding a typing test: Add a test in lib/tests/streamlit/typing/navigation_types.py for Page with the visibility parameter to ensure the Literal["visible", "hidden"] type is properly enforced by mypy. For example:

    page_hidden = Page("page1.py", visibility="hidden")
    page_visible = Page("page1.py", visibility="visible")
  2. Consider exposing visibility as a public read-only property: Currently _visibility is only accessible via the private _visibility attribute. For consistency with the existing public properties (title, icon, url_path), consider adding a public visibility property to StreamlitPage. This would be useful for users who need to inspect a page's visibility programmatically.

Both recommendations are minor and non-blocking for merge.

Verdict

APPROVED: This is a clean, well-tested feature addition that follows existing patterns throughout the codebase. The implementation is correct, backwards compatible, and covers all necessary layers (proto, backend, frontend, tests). The code quality is high, and test coverage is comprehensive.


This is an automated AI review by opus-4.6-thinking.

- Add public read-only `visibility` property to StreamlitPage for
  consistency with existing public properties (title, icon, url_path)
- Add typing tests in navigation_types.py to lock down the
  Literal["visible", "hidden"] type contract at type-check time
- Update unit tests to use the public property instead of private attr
- Simplify redundant conditional check in Navigation utils
- Remove redundant test_visibility_can_be_set_to_visible test
- Consolidate filterVisiblePages tests using it.each
Copy link
Copy Markdown
Collaborator

@sfc-gh-bnisco sfc-gh-bnisco left a comment

Choose a reason for hiding this comment

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

One question inline, but LGTM after


Object.entries(navSections).forEach(([header, pages]) => {
// Only include non-empty section headers
if (header && header !== "" && header !== "undefined") {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

question: Are we sure this logic for "undefined" isn't needed?

If the logic only cared about truthiness, then this code would just read if (header). It seems like a code smell that the previous logic checked against the string "undefined", and it makes me wonder if it was intentional.

Copy link
Copy Markdown
Collaborator Author

@lukasmasuch lukasmasuch Feb 13, 2026

Choose a reason for hiding this comment

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

I looked into this again and also asked Codex and Claude. I believe this was accidentally added, since there isn't any hint for "undefined" as a literal value in the codebase

Codex Response

You were right to question it. I investigated the full path and the current if (header !== "") is the correct logic.

  • The old check (header && header !== "" && header !== "undefined") came from older TopNav code that grouped by raw sectionHeader, which could create a literal "undefined" key when sectionHeader was missing.
  • That old flow was refactored. Now missing headers are normalized earlier in groupPagesBySection via page.sectionHeader || "", so missing values become "" (not "undefined").
  • Backend/protobuf also treats section_header as a string field (default empty string), so runtime missing values should map to empty string semantics.
  • Keeping header !== "undefined" would actually hide a valid user-defined section literally named "undefined".

I added explicit tests and a clarifying comment:

  • frontend/app/src/components/Navigation/utils.ts:81
  • frontend/app/src/components/Navigation/utils.test.ts:277
  • frontend/app/src/components/Navigation/utils.test.ts:290
  • frontend/app/src/components/Navigation/utils.test.ts:410

Validation I ran:

  • cd frontend/app && yarn test src/components/Navigation/utils.test.ts (pass, 29 tests)
  • cd frontend/app && yarn test src/components/Navigation/TopNav.test.tsx (pass, 4 tests)
  • cd frontend/app && yarn test src/components/Navigation/SidebarNav.test.tsx (fails in this environment due existing localStorage test harness issue: window.localStorage.clear is not a function, not related to this guard change)

So for this specific review comment: yes, the simplification is safe, and the new tests lock that behavior in.

Claude Response

Investigation Summary

What Changed

The condition was simplified from:

if (header && header !== "" && header !== "undefined") {

to:

if (header !== "") {

Why the "undefined" Check Was Unnecessary

  1. The data flow prevents it: In groupPagesBySection (line 44), the code uses:

    groupBy(appPages, page => page.sectionHeader || "")

    This normalizes undefined, null, and "" all to the empty string "" - not the literal string "undefined".

  2. Lodash's groupBy doesn't do string coercion: It uses the callback's return value directly. The || "" fallback handles the undefined case, so the literal string "undefined" can only appear if a user explicitly names their section that way.

  3. Tests explicitly verify this behavior (lines 277-310 in utils.test.ts):

    • normalizes missing section headers to the empty section key - confirms undefined/null become "", not "undefined"
    • preserves a literal "undefined" section name - confirms a user-provided "undefined" section should be valid

Why the Original Code Existed

Looking at the git history:

  • Commit 029b3e4e3d by @nico-bellante introduced the check in both hasNonEmptySections and processNavigationStructure
  • Commit e9379ccdb5 (also by Nico) removed it from hasNonEmptySections but missed processNavigationStructure
  • This was likely defensive coding or a misunderstanding that was partially corrected later

The Change is Safe

The simplified check is actually more correct because:

  1. It no longer filters out user-defined sections named "undefined"
  2. The existing test keeps sections named "undefined" when mixed with empty sections (line 410-436) now correctly verifies this behavior

The previous code was actually a bug - it would have incorrectly hidden any section a user deliberately named "undefined".

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Simplified it to if (header)

Address PR review comment about the "undefined" string check:
- Simplify `if (header !== "")` to `if (header)` for idiomatic JS
- Add tests verifying that JS undefined/null normalize to "" key
- Add tests verifying literal "undefined" section name is preserved
- Add clarifying comment explaining the behavior
@lukasmasuch lukasmasuch enabled auto-merge (squash) February 13, 2026 11:14
@lukasmasuch lukasmasuch merged commit c6507e0 into develop Feb 13, 2026
43 checks passed
@lukasmasuch lukasmasuch deleted the lukasmasuch/page-visibility branch February 13, 2026 11:30
lukasmasuch added a commit that referenced this pull request Feb 20, 2026
## Describe your changes

Implements the `visibility` parameter for `st.Page` to allow pages to be
hidden from navigation menus while remaining accessible via direct URL,
`st.page_link`, and `st.switch_page`. This addresses issues #10738 and
#9195.

- [Product
spec](https://github.com/streamlit/streamlit/blob/5d331ef2a46f5f2053f909ac73f974f31ab3e10b/specs/0009-page-visibility/product-spec.md)
- [Tech
spec](https://github.com/streamlit/streamlit/blob/5d331ef2a46f5f2053f909ac73f974f31ab3e10b/specs/0009-page-visibility/tech-spec.md)

**Key changes:**
- Backend: Added `visibility` parameter with validation (visible/hidden)
- Frontend: Filtering of hidden pages in SidebarNav and TopNav
components
- Tests: Comprehensive E2E and unit tests for the new feature
- Code improvements: Simplified `shouldShowNavigation` logic and removed
unused parameters

## Testing Plan

- Unit Tests: 26 Navigation utility tests + TopNav hidden page tests +
SidebarNav section tests pass
- E2E Tests: 4 new E2E tests cover st.switch_page, sections with all
hidden pages, and both nav positions
- All existing tests pass: 66 Python tests + 160 frontend tests
- All checks pass: format, lint, type checking

The feature is fully backward compatible - `visibility` defaults to
"visible" maintaining existing behavior.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a page without showing it in the navigation menu

4 participants