Skip to content

Conversation

@mayagbarnes
Copy link
Collaborator

@mayagbarnes mayagbarnes commented Dec 1, 2025

Describe your changes

Our current theme hashing only handles top level [theme] properties in calculating the theme hash to determine whether theme has been updated. This was sufficient prior to custom theming, but now section theme changes are silently ignored and a re-run does not trigger the expected update.

Issue Repro:

  • In [theme.sidebar] section of your config.toml - set primaryColor = "blue"
  • See blue primary color applied in app
  • Update primaryColor = "green"
  • A file change is detected, select Rerun in the app header
  • The primary color in sidebar is still blue

This PR implements comprehensive theme hashing that includes all theme properties - including new subsections (light, dark, etc.) so changes now properly trigger theme reprocessing.

Testing Plan

  • Unit Tests (JS and/or Python): ✅ Added
  • Manual Testing: ✅

@mayagbarnes mayagbarnes added security-assessment-completed Security assessment has been completed for PR change:refactor PR contains code refactoring without behavior change impact:users PR changes affect end users labels Dec 1, 2025
@snyk-io
Copy link
Contributor

snyk-io bot commented Dec 1, 2025

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
Contributor

github-actions bot commented Dec 1, 2025

✅ PR preview is ready!

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

@github-actions
Copy link
Contributor

github-actions bot commented Dec 1, 2025

📈 Frontend coverage change detected

The frontend unit test (vitest) coverage has increased by 0.0100%

  • Current PR: 85.9400% (12454 lines, 1751 missed)
  • Latest develop: 85.9300% (12445 lines, 1751 missed)

✅ Coverage change is within normal range.

📊 View detailed coverage comparison

Copy link
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

This PR fixes a critical bug where theme changes in nested configuration sections (like [theme.sidebar], [theme.light], [theme.dark]) were not triggering theme updates on app rerun. The previous implementation only hashed top-level theme properties, causing nested property changes to be silently ignored.

Key Changes:

  • Introduced comprehensive recursive theme hashing via sortThemeInputKeys utility that handles all nested objects and arrays
  • Simplified the App constructor to defer theme hash initialization until custom theme is received from server
  • Added extensive test coverage for both the utility function and the hash change detection behavior

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
frontend/lib/src/theme/utils.ts Added sortThemeInputKeys utility function to recursively sort theme object keys for consistent hashing
frontend/lib/src/theme/utils.test.ts Added comprehensive unit tests for sortThemeInputKeys covering nested objects, arrays, primitives, and edge cases
frontend/lib/src/index.ts Exported the new sortThemeInputKeys utility for use in the app package
frontend/app/src/App.tsx Updated createThemeHash to use recursive key sorting, removed premature hash calculation from constructor, removed unused toThemeInput import
frontend/app/src/App.test.tsx Added extensive tests verifying hash change detection for all theme subsections (sidebar, light, dark) and nested configurations; updated existing test to better describe its behavior

@mayagbarnes mayagbarnes marked this pull request as ready for review December 4, 2025 20:41
@lukasmasuch
Copy link
Collaborator

@cursor review

@lukasmasuch lukasmasuch added the ai-review If applied to PR or issue will run AI review workflow label Dec 10, 2025
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Dec 10, 2025
@github-actions
Copy link
Contributor

Summary

This PR fixes a bug in the theme hashing mechanism where changes to nested theme sections (light, dark, sidebar, etc.) were being silently ignored. The previous implementation only considered top-level properties when calculating the theme hash, meaning subsection changes wouldn't trigger theme reprocessing on rerun.

Key changes:

  1. Added a new sortThemeInputKeys utility function that recursively sorts all object keys for consistent hashing
  2. Modified createThemeHash in App.tsx to convert protobuf objects to plain JS objects and use recursive key sorting
  3. Changed themeHash initialization from a pre-computed value to an empty string ("") to ensure the first theme input is always processed
  4. Added comprehensive unit tests for the new functionality

Code Quality

Strengths

  1. Well-structured utility function: The sortThemeInputKeys function is clean, well-documented, and handles edge cases appropriately (null, undefined, arrays, nested objects).
export function sortThemeInputKeys(obj: unknown): unknown {
  if (obj === null || obj === undefined) {
    return obj
  }

  // Handle arrays by recursively sorting their elements
  if (Array.isArray(obj)) {
    return obj.map(item => sortThemeInputKeys(item))
  }

  // Handle objects (including nested theme sections)
  if (typeof obj === "object") {
    const sorted: Record<string, unknown> = {}
    Object.keys(obj)
      .sort()
      .forEach(key => {
        sorted[key] = sortThemeInputKeys((obj as Record<string, unknown>)[key])
      })
    return sorted
  }

  // Return primitives as-is
  return obj
}
  1. Clear comments: The changes include helpful comments explaining the purpose and rationale, especially for the themeHash initialization change.
      // Initialize themeHash to empty string to ensure the first processThemeInput
      // call always processes the theme (whether null or custom theme from server).
      // This prevents the bug where a cached custom theme isn't cleared when the
      // server sends null, because null and undefined both hash to the same value.
      themeHash: "",
  1. Proper protobuf handling: The use of JSON.parse(JSON.stringify(themeInput)) to convert protobuf objects to plain JS objects is a good approach to ensure only actual data is hashed, not protobuf metadata.

Minor Observations

  1. Type safety: The sortThemeInputKeys function uses unknown type which is appropriate, but the return type could potentially be more specific with generics if needed in the future.

Test Coverage

Unit Tests (Excellent)

The test coverage is comprehensive and follows TypeScript testing best practices:

sortThemeInputKeys tests (utils.test.ts):

  • ✅ Basic sorting of theme input keys
  • ✅ Deeply nested objects
  • ✅ Arrays of objects
  • ✅ Null and undefined values
  • ✅ Mixed types
  • ✅ Consistent hashing for same content with different key orders
  • ✅ Empty objects and arrays
  • ✅ Arrays of primitives (maintains order)
  • ✅ Complex nested theme config structures

App.test.tsx theme hash tests:

  • ✅ Tests for sidebar config modifications
  • ✅ Tests for light section config modifications
  • ✅ Tests for dark section config modifications
  • ✅ Tests for nested sidebar in light section
  • ✅ Tests for unchanged hash (no re-processing)
  • ✅ Tests for same content with different key order
  • ✅ Tests for null theme clearing cached themes on first session
  • ✅ Tests for theme updates when values actually differ

E2E Tests

No new E2E tests were added. Given that this is primarily a bug fix for an internal hashing mechanism, the existing theme E2E tests should continue to work. However, adding an E2E test that specifically validates theme subsection changes trigger proper reprocessing could be beneficial for regression prevention.

Suggestion (optional): Consider adding an E2E test that:

  1. Starts an app with a theme subsection (e.g., [theme.sidebar])
  2. Modifies the subsection value
  3. Reruns the app
  4. Verifies the new theme value is applied

Backwards Compatibility

Fully backwards compatible

  • No public API changes
  • The sortThemeInputKeys function is newly exported but doesn't break existing code
  • The behavior change (theme subsections now properly trigger updates) is a bug fix, not a breaking change
  • The initialization change from computed hash to empty string is transparent to users

Security & Risk

Security

No security concerns identified

  • The changes are purely frontend, dealing with theme configuration
  • No user input is processed without proper handling
  • The JSON.parse(JSON.stringify()) pattern is safe for the expected input types (theme configs)

Risk Assessment

Low risk - This is a targeted bug fix with:

  • Comprehensive test coverage
  • No changes to data flow or external APIs
  • Limited scope (theme hashing only)

Potential edge case to consider: The JSON.parse(JSON.stringify(themeInput)) approach will:

  • Remove undefined values (which become null in JSON)
  • Not handle circular references (not expected in theme configs)

This behavior is acceptable for theme configurations, which should only contain JSON-serializable values.

Recommendations

No blocking issues identified. The PR is well-implemented with good test coverage.

Optional improvements (non-blocking):

  1. Consider documenting the fix: The PR description is good, but a brief inline comment in createThemeHash explaining why JSON serialization is used to strip protobuf metadata could help future maintainers.

  2. E2E test consideration: While unit tests are comprehensive, an E2E test demonstrating the fix (theme subsection change triggers visual update) would provide additional confidence for this specific bug scenario.

Verdict

APPROVED: This is a well-implemented bug fix with comprehensive unit test coverage. The changes are targeted, backwards compatible, and pose minimal risk. The new sortThemeInputKeys utility is clean and properly tested. The fix addresses a real user-facing issue where theme subsection changes were silently ignored.


This is an automated AI review. Please verify the feedback and use your judgment.

Copy link
Collaborator

@lukasmasuch lukasmasuch left a comment

Choose a reason for hiding this comment

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

LGTM 👍

@mayagbarnes mayagbarnes merged commit 4b895a5 into develop Dec 10, 2025
48 checks passed
@mayagbarnes mayagbarnes deleted the theme-hashing-update branch December 10, 2025 19:45
sfc-gh-mbarnes pushed a commit that referenced this pull request Dec 15, 2025
Our current theme hashing only handles top level `[theme]` properties in calculating the theme hash to determine whether theme has been updated. This was sufficient prior to custom theming, but now section theme changes are silently ignored and a re-run does not trigger the expected update.

This PR implements comprehensive theme hashing that includes all theme
properties - including new subsections (`light`, `dark`, etc.) so
changes now properly trigger theme reprocessing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:refactor PR contains code refactoring without behavior change impact:users PR changes affect end users security-assessment-completed Security assessment has been completed for PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants