Skip to content

Conversation

@javier-godoy
Copy link
Member

@javier-godoy javier-godoy commented Nov 18, 2025

Close #126

Summary by CodeRabbit

  • Breaking Changes

    • GitHub source support removed
    • RouteTabs component removed
    • Theme application API refactored with simplified method signatures
    • Multiple demo registration method overloads removed
  • Chores

    • Updated to version 5.0.0

@coderabbitai
Copy link

coderabbitai bot commented Nov 18, 2025

Walkthrough

The pull request updates the project to version 5.0.0-SNAPSHOT while removing deprecated GITHUB_SOURCE constant and its handling logic, deletes the RouteTabs class entirely, and refactors theme-related APIs in TabbedDemo from boolean parameters to string-based attributes. Several Component-based demo addition method overloads are also removed.

Changes

Cohort / File(s) Summary
Version Update
pom.xml
Project version bumped from 4.4.0-SNAPSHOT to 5.0.0-SNAPSHOT
Deprecated GITHUB_SOURCE Removal
src/main/java/com/flowingcode/vaadin/addons/demo/DemoSource.java, src/main/java/com/flowingcode/vaadin/addons/demo/DefaultSourceUrlResolver.java
Removed deprecated GITHUB_SOURCE constant from DemoSource interface; removed corresponding GITHUB_SOURCE branch handling from URL resolution logic
RouteTabs Class Deletion
src/main/java/com/flowingcode/vaadin/addons/demo/RouteTabs.java
Deleted entire RouteTabs class including router-link-to-tab binding, highlighting, and legacy tab addition mechanisms
TabbedDemo API Refactoring
src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java
Renamed getThemeName() to getThemeAttribute(); replaced applyTheme(Element, boolean) with applyThemeAttribute(Element, String); removed Component-based addDemo overloads

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • RouteTabs class removal: Verify no other code paths or tests depend on this deleted class and its public API
  • GITHUB_SOURCE deprecation removal: Confirm complete removal across the codebase and that no external consumers rely on this constant
  • TabbedDemo theme refactoring: Review the boolean-to-string theme conversion logic and ensure the new applyThemeAttribute method handles all theme states correctly in relation to Vaadin 25's HasTheme interface changes (per linked issue #126)
  • addDemo method removals: Validate that Class-based demo addition methods provide sufficient replacement coverage

Suggested reviewers

  • mlopezFC
  • paodb

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title clearly summarizes the main changes: adding new theme methods (getThemeAttribute, applyThemeAttribute) and removing deprecated methods/classes for Vaadin 25 compatibility.
Linked Issues check ✅ Passed PR successfully resolves issue #126 by removing the conflicting getThemeName() method and replacing it with getThemeAttribute(), which avoids the static method override conflict in Vaadin 25.
Out of Scope Changes check ✅ Passed All changes are directly related to resolving the Vaadin 25 compatibility issue: version bump to 5.0.0-SNAPSHOT, removal of deprecated code paths (GITHUB_SOURCE constant, RouteTabs class, addDemo overloads), and migration to new theme API.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 5.0-SNAPSHOT

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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.

@sonarqubecloud
Copy link

Copy link

@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

Caution

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

⚠️ Outside diff range comments (2)
src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java (2)

230-251: Bug: language() is set from caption() instead of language()

In createSourceCodeTab, this line looks incorrect:

if (!annotation.language().equals(DemoSource.DEFAULT_VALUE)) {
  builder.language(annotation.caption());
}

You’re populating language with the caption value instead of annotation.language(), so any explicit language setting on @DemoSource is ignored/misapplied.

Fix suggestion:

-    if (!annotation.language().equals(DemoSource.DEFAULT_VALUE)) {
-      builder.language(annotation.caption());
-    }
+    if (!annotation.language().equals(DemoSource.DEFAULT_VALUE)) {
+      builder.language(annotation.language());
+    }

129-163: Breaking change confirmed: test code calls removed addDemo(Component) overload

The verification reveals a compilation-blocking issue. Line 32 of src/test/java/com/flowingcode/vaadin/addons/demo/Demo.java calls addDemo(new LegacyDemo()), but LegacyDemo extends Div (a Component), and the current TabbedDemo API has no overload accepting Component instances—only addDemo(Class<? extends Component>) variants.

The test code must be updated to pass the class instead:

addDemo(LegacyDemo.class);
🧹 Nitpick comments (4)
pom.xml (1)

8-8: Version bump to 5.x matches removal of deprecated APIs

Major version for this artifact is appropriate given the breaking removals in the PR. Just ensure release notes clearly call out the removed methods/classes so downstream demos can upgrade smoothly.

src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java (3)

317-323: Session-scoped theme getter is straightforward but assumes a current VaadinSession

getThemeAttribute() reads the theme string from a session attribute and defaults to "", which fits the use in showRouterLayoutContent and the checkbox listener.

If there’s any chance this method could be called outside a Vaadin request context (e.g., background thread), consider guarding against VaadinSession.getCurrent() being null and defaulting to "" in that case.

You may want to verify all call sites for getThemeAttribute() are on the UI thread (e.g., via rg -n "getThemeAttribute\(").


324-338: applyThemeAttribute implementation is solid; consider small robustness tweaks

The method correctly:

  • Persists the theme string in VaadinSession under a stable key.
  • Applies the theme via JS to either the iframe’s documentElement or the top-level document.
  • Notifies all ThemeChangeObserver instances in the component tree.

Two minor robustness improvements you might consider:

  1. Handle empty theme by removing the attribute
    Instead of setting theme="", you could remove the attribute when theme is empty to better match typical theming patterns:

  • if (element.getTag().equalsIgnoreCase("iframe")) {
  •  script = "this.contentWindow.document.documentElement.setAttribute('theme', $0)";
    
  • } else {
  •  script = "document.documentElement.setAttribute('theme', $0);";
    
  • }
  • if (element.getTag().equalsIgnoreCase("iframe")) {
  •  script = "if ($0) { this.contentWindow.document.documentElement.setAttribute('theme', $0); } "
    
  •         + "else { this.contentWindow.document.documentElement.removeAttribute('theme'); }";
    
  • } else {
  •  script = "if ($0) { document.documentElement.setAttribute('theme', $0); } "
    
  •         + "else { document.documentElement.removeAttribute('theme'); }";
    
  • }

2. **Avoid `Optional.get()` on `element.getComponent()`**  
If someone ever calls this with an `Element` not backed by a `Component`, `element.getComponent().get()` will throw. A defensive check would make the API safer:

```diff
-    Component c = element.getComponent().get();
-    collectThemeChangeObservers(c).forEach(observer -> observer.onThemeChange(theme));
+    element.getComponent()
+        .ifPresent(c -> collectThemeChangeObservers(c)
+            .forEach(observer -> observer.onThemeChange(theme)));

Please confirm that ThemeChangeObserver implementations expect to receive an empty string when no theme is active, or adjust the above suggestion accordingly.


253-262: GitHub branch lookup helper still uses "master" default

lookupGithubBranch continues to default to "master", which is consistent with the Javadoc in DefaultSourceUrlResolver. If you ever switch repositories to "main", this helper will be the place to adjust.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e9f1e8 and 279e5b6.

📒 Files selected for processing (5)
  • pom.xml (1 hunks)
  • src/main/java/com/flowingcode/vaadin/addons/demo/DefaultSourceUrlResolver.java (1 hunks)
  • src/main/java/com/flowingcode/vaadin/addons/demo/DemoSource.java (1 hunks)
  • src/main/java/com/flowingcode/vaadin/addons/demo/RouteTabs.java (0 hunks)
  • src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java (4 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/com/flowingcode/vaadin/addons/demo/RouteTabs.java
🔇 Additional comments (4)
src/main/java/com/flowingcode/vaadin/addons/demo/DemoSource.java (1)

5-5: Metadata update and cleanup of deprecated constant

Copyright year update is fine, and removing the deprecated GITHUB_SOURCE constant (per PR description) keeps the annotation lean and avoids supporting legacy configuration paths.

src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java (2)

5-5: Header metadata change looks good

Copyright update is consistent with other files in this PR.


203-204: Reapplying the stored theme on content change is a good addition

Calling applyThemeAttribute(getElement(), getThemeAttribute()) after inserting the new content ensures the theme is consistently re-applied when the router layout content changes, which is exactly what you want for a session-scoped dark/light toggle.

It’s worth smoke-testing navigation between different demos to ensure theme observers are triggered as expected when the content element is replaced.

src/main/java/com/flowingcode/vaadin/addons/demo/DefaultSourceUrlResolver.java (1)

45-57: Verification confirms GITHUB_SOURCE constant successfully removed with no remaining usages

The DemoSource annotation definition shows only the DEFAULT_VALUE constant remains. All existing @demosource annotations in the codebase use compatible arguments—either default, explicit paths, clazz, or sourcePosition—and no references to the removed GITHUB_SOURCE constant exist anywhere. The resolver implementation correctly handles DEFAULT_VALUE and /src/test/... URLs as intended for this major release.

Comment on lines 93 to 99
themeCB.setValue(false);
themeCB.addClassName("smallcheckbox");
themeCB.addValueChangeListener(cb -> {
applyTheme(getElement(), themeCB.getValue());
boolean useDarkTheme = themeCB.getValue();
String theme = useDarkTheme ? "dark" : "";
applyThemeAttribute(getElement(), theme);
});
Copy link

@coderabbitai coderabbitai bot Nov 18, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Theme toggle handler correctly delegates to the new theme API

Using the checkbox value to compute "dark" vs "" and calling applyThemeAttribute(getElement(), theme) keeps the UI behavior clear and ties directly into the new session-backed theme attribute.

Minor suggestion: you can use the event’s value (cb.getValue()) instead of closing over themeCB, but the current implementation is perfectly fine.

If you want to double-check expected Vaadin theming behavior for the "dark" theme attribute, please confirm against the Vaadin 24/25 theming docs.


🌐 Web query:

Vaadin Flow theming: how is the `"theme"` attribute on `documentElement` interpreted, and is an empty string equivalent to no theme?

💡 Result:

Short answer: The document's theme attribute is treated as a space‑separated token list (a ThemeList) and is used with attribute selectors like [theme~="dark"]. An empty string is not the same as the attribute being absent — Flow distinguishes "attribute not set" (getAttribute() -> null) from "attribute set but no tokens" (getThemeName() -> empty string). Use the ThemeList / HasTheme API to add/remove tokens rather than manipulating raw strings. [1][2][3]

Sources:

  • HasTheme API: getThemeName/getThemeNames behavior (empty string vs null). [1]
  • Element API: attributes and getThemeList / attribute semantics (empty value rendering, getAttribute() -> null if not set). [2]
  • Vaadin blog on dynamic theming and use of [theme~="..."] selectors. [3]

I'll verify the implementation of applyThemeAttribute to confirm whether the current code correctly handles the theme attribute behavior described in the web search results.



[/run_scripts]


🏁 Script executed:

#!/bin/bash
# Find applyThemeAttribute method definition
rg -nP 'applyThemeAttribute' --type=java -B2 -A10

Length of output: 4163


🏁 Script executed:

#!/bin/bash
# Search for any theme attribute handling or Element.setAttribute calls in the codebase
rg -nP 'setAttribute.*theme|getTheme|setTheme' --type=java -C3

Length of output: 2688



Critical bug: setting theme attribute to empty string instead of removing it

The applyThemeAttribute method (lines 324–334) always calls setAttribute('theme', $0), which means when theme="" is passed (line 97), the DOM receives setAttribute('theme', ""). Per Vaadin theming semantics, an empty string is not the same as the attribute being absent — Flow distinguishes "attribute not set" (getAttribute() → null) from "attribute set but no tokens" (getThemeName() → empty string).

Fix: Update applyThemeAttribute to call removeAttribute('theme') when the theme parameter is empty:

public static void applyThemeAttribute(Element element, String theme) {
  VaadinSession.getCurrent().setAttribute(THEME_NAME, theme);

  String script;
  if (theme.isEmpty()) {
    if (element.getTag().equalsIgnoreCase("iframe")) {
      script = "this.contentWindow.document.documentElement.removeAttribute('theme')";
    } else {
      script = "document.documentElement.removeAttribute('theme');";
    }
  } else {
    if (element.getTag().equalsIgnoreCase("iframe")) {
      script = "this.contentWindow.document.documentElement.setAttribute('theme', $0)";
    } else {
      script = "document.documentElement.setAttribute('theme', $0);";
    }
  }

  if (theme.isEmpty()) {
    element.executeJs(script);
  } else {
    element.executeJs(script, theme);
  }
}
🤖 Prompt for AI Agents
In src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java around lines
93–99 (and update applyThemeAttribute implementation at lines ~324–334), the
current logic calls setAttribute('theme', "") when theme is empty which leaves
an empty attribute instead of removing it; change applyThemeAttribute so it
stores the theme in VaadinSession as before but builds two branches: if theme is
empty, generate JS that calls removeAttribute('theme') (use
this.contentWindow.document.documentElement for iframe tags, otherwise
document.documentElement), and call element.executeJs(script) with no
parameters; if theme is non-empty, generate JS that calls setAttribute('theme',
$0) (same iframe vs document handling) and call element.executeJs(script,
theme).

Copy link
Member Author

Choose a reason for hiding this comment

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

@coderabbitai Create an issue about it. The scope of this PR is only removing the deprecated methods, not fixing behavior that was already there.

@Deprecated
public static void applyTheme(Element element, boolean useDarkTheme) {
String theme = useDarkTheme ? "dark" : "";
applyTheme(element, theme);
}

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Create a GitHub issue for the theme attribute handling bug

gh issue create \
  --repo FlowingCode/CommonsDemo \
  --title "Theme attribute handling: empty string should remove attribute instead of setting it to empty" \
  --assignee javier-godoy \
  --body "## Description

The \`applyThemeAttribute\` method in \`TabbedDemo.java\` currently calls \`setAttribute('theme', '')\` when the theme parameter is an empty string. According to Vaadin theming semantics, an empty string attribute is not the same as the attribute being absent — Flow distinguishes \"attribute not set\" (getAttribute() → null) from \"attribute set but no tokens\" (getThemeName() → empty string).

## Current Behavior

When the theme is toggled off (dark mode unchecked), the code sets:
\`\`\`javascript
document.documentElement.setAttribute('theme', '')
\`\`\`

This leaves an empty \`theme\` attribute on the document element.

## Expected Behavior

When the theme parameter is empty, the attribute should be removed entirely:
\`\`\`javascript
document.documentElement.removeAttribute('theme')
\`\`\`

## Suggested Fix

Update \`applyThemeAttribute\` to check if the theme is empty and call \`removeAttribute('theme')\` instead of \`setAttribute('theme', '')\`.

## Context

- Flagged during review of PR #127: https://github.com/FlowingCode/CommonsDemo/pull/127#discussion_r2539569353
- Requested by: @javier-godoy
- This behavior existed in the previous implementation and is out of scope for PR #127, which focuses on removing deprecated methods"

Length of output: 53


@javier-godoy I've created issue #128 to track the theme attribute handling bug: #128

I've assigned it to you and included technical details along with references to this PR and comment thread. You're absolutely right to keep PR #127 focused on removing the deprecated methods—the theme attribute behavior can be addressed separately. 👍


🧬 Code Graph Analysis Results

[src/test/java/com/flowingcode/vaadin/addons/demo/AdHocDemo.java, lines 27-36]

@Route(value = "demo/demo-with-adhoc-source", layout = Demo.class)
@PageTitle("Demo with source in a different file")
@DemoSource("/src/test/java/com/flowingcode/vaadin/addons/demo/AdHocSource.java")
public class AdHocDemo extends Div {

  public AdHocDemo() {
    add(new Span("This span is added to the demo view"));
  }

}

[src/main/java/com/flowingcode/vaadin/addons/demo/SplitLayoutDemo.java, lines 32-114]

@SuppressWarnings("serial")
class SplitLayoutDemo extends Composite<SplitLayout> {

  private MultiSourceCodeViewer code;
  private Component demo;
  private SourcePosition sourcePosition;

  public SplitLayoutDemo(Component demo, String sourceUrl, SourcePosition sourcePosition) {
    this(demo, Arrays.asList(new SourceCodeTab(sourceUrl, sourcePosition)));
  }

  public SplitLayoutDemo(Component demo, List<SourceCodeTab> tabs) {
    getContent().setOrientation(Orientation.HORIZONTAL);

    Map<String, String> properties = new HashMap<>();
    properties.put("vaadin", VaadinVersion.getVaadinVersion());
    properties.put("flow", Version.getFullVersion());

    code = new MultiSourceCodeViewer(tabs, properties);
    this.demo = demo;
    setSourcePosition(code.getSourcePosition());

    getContent().setSizeFull();
  }

  private void setSourcePosition(SourcePosition position) {
    if (!position.equals(sourcePosition)) {
      getContent().removeAll();
      switch (position) {
        case PRIMARY:
          getContent().addToPrimary(code);
          getContent().addToSecondary(demo);
          break;
        case SECONDARY:
        default:
          getContent().addToPrimary(demo);
          getContent().addToSecondary(code);
      }
      sourcePosition = position;
    }
  }

  public void toggleSourcePosition() {
    setSourcePosition(sourcePosition.toggle());
  }

  public void setOrientation(Orientation o) {
    getContent().setOrientation(o);
    getContent()
        .getPrimaryComponent()
        .getElement()
        .setAttribute("style", "width: 100%; height: 100%");
    code.setSizeFull();
  }

  public Orientation getOrientation() {
    return getContent().getOrientation();
  }

  public void setSplitterPosition(int pos) {
    getContent().setSplitterPosition(pos);
  }

  public void setSizeFull() {
    getContent().setSizeFull();
  }

  public void showSourceCode() {
    getContent().setSplitterPosition(50);
  }

  public void hideSourceCode() {
    switch (sourcePosition) {
      case PRIMARY:
        getContent().setSplitterPosition(0);
        break;
      case SECONDARY:
        getContent().setSplitterPosition(100);
        break;
    }
  }

}

[src/test/java/com/flowingcode/vaadin/addons/demo/MultiSourceDemo.java, lines 27-51]

@Route(value = "demo/multisource", layout = Demo.class)
@PageTitle("Demo with multiple sources")
// show-source @DemoSource
// show-source @DemoSource(clazz = AdditionalSources.class)
// show-source @DemoSource("/src/test/resources/META-INF/resources/frontend/multi-source-demo.css")
@DemoSource
@DemoSource(clazz = AdditionalSources.class)
@DemoSource("/src/test/resources/META-INF/resources/frontend/multi-source-demo.css")
@StyleSheet("./multi-source-demo.css")
public class MultiSourceDemo extends Div {
  public MultiSourceDemo() {

    // begin-block main
    Div div = new Div("This is the main source");
    div.addClassName("custom-style");
    SourceCodeViewer.highlightOnHover(div, "main");
    add(div);
    // end-block

    Button button1 = new Button("Highlight code in AdditionalSources");
    SourceCodeViewer.highlightOnClick(button1, "AdditionalSources.java#fragment");
    add(button1);
  }

}

[src/test/java/com/flowingcode/vaadin/addons/demo/SampleDemoDefault.java, lines 27-42]

@Route(value = "demo/demo-with-source", layout = Demo.class)
@PageTitle("Demo with source")
@DemoSource
public class SampleDemoDefault extends Div {

  public SampleDemoDefault() {
    add(new Span("Demo component with defaulted @DemoSource annotation"));
    // show-source System.out.println("this line will be displayed in the code snippet");
    this.getClass(); // hide-source (this line will not be displayed in the code snippet)
    // #if vaadin ge 23
    // show-source System.out.println("conditional code for Vaadin 23+");
    // #elif vaadin ge 14
    // show-source System.out.println("conditional code for Vaadin 14-22");
    // #endif
  }
}

[src/test/java/com/flowingcode/vaadin/addons/demo/Demo.java, lines 26-40]

@Route("demo")
@RouteAlias("")
GithubLink("https://github.com/FlowingCode/CommonsDemo")
public class Demo extends TabbedDemo {

  public Demo() {
    addDemo(new LegacyDemo());
    addDemo(SampleDemo.class);
    addDemo(SampleDemoDefault.class);
    addDemo(SampleDemoHighlight.class);
    addDemo(AdHocDemo.class);
    addDemo(MultiSourceDemo.class);
    addDemo(SourcePositionDemo.class);
  }
}

[src/test/java/com/flowingcode/vaadin/addons/demo/DialogDemoHelperViewer.java, lines 30-38]

@SuppressWarnings("serial")
public class DialogDemoHelperViewer implements DemoHelperViewer {

  @Override
  public void show(Component content) {
    new Dialog(content).open();
  }

}

[src/test/java/com/flowingcode/vaadin/addons/demo/SourceCodeViewerElement.java, lines 25-28]

@Element("code-viewer")
public class SourceCodeViewerElement extends TestBenchElement {
  
}

@paodb paodb merged commit 5094b06 into master Nov 19, 2025
3 checks passed
@paodb paodb deleted the 5.0-SNAPSHOT branch November 19, 2025 13:36
@github-project-automation github-project-automation bot moved this from To Do to Pending release in Flowing Code Addons Nov 19, 2025
@javier-godoy javier-godoy moved this from Pending release to Done in Flowing Code Addons Dec 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

Vaadin 25: TabbedDemo cannot implement getThemeName() overriding method is static

3 participants