šŸ”¹ Browser_mod - turn your browser into a controllable device, and a media_player

Hello, I have one last question. I actually got the Card Mod to work that you pasted. I had something indented wrong, or I don’t even know what I did wrong the first time.

The vertical align center works, but almost every time I click the pop up , it does a quick jump, like you can see it loading up at the top by default, and then quickly adjusting to the center.

If this should be fixed in 2025.11, then no worries. I’ll be patient. But in case I’m just coding something wrong, and I’m still going to have the problem in the future, I wanted to see if someone could double check my code.

Thanks

      size: classic
      card_mod:
        style:
          ha-dialog:
            $: |
              @media (max-width: 900px), (max-height: 1000px) {
                .mdc-dialog .mdc-dialog__surface {
                  min-height: 10% !important;
                }
              }
            .: |
              ha-dialog {
                --mdc-dialog-min-height: 10px !important;
                --ha-dialog-border-radius: 30px !important;
                --vertical-align-dialog: center !important;                
              }

Great. Yaml and CSS can drive you crazy some time.

Anything that can be applied by a CSS var can go into styles and will be available straight away and does not rely on card_mod. For a Browser Mod popup you only need card_mod when you need to directly style the shadow DOM, like we are doing for the min-height workaround. Below I have used the new popup_styles format for all style (could have used classic as well).

...
popup_styles:
  - style: all
    styles: |-
      ha-dialog {
        --mdc-dialog-min-height: 10px !important;
        --ha-dialog-border-radius: 30px !important;
        --vertical-align-dialog: center !important;                
      }
card_mod:
  style:
    ha-dialog $: |
      @media (max-width: 450px), (max-height: 500px) {
        .mdc-dialog .mdc-dialog__surface {
          min-height: 10%;
        }
      }

:mega: UPDATE: classic mode - Frontend PR closed :man_shrugging:

Just got some bad news that the planned PR to fix classic style from working has been closed. See Reintroduce dialog size vars for theming by timmo001 Ā· Pull Request #27372 Ā· home-assistant/frontend Ā· GitHub for my response. If you want to make sure classic style can be used without card_mod acrobats, please make your voice known on the PR. Thank you.

UPDATE 2. New Frontend PR created and merged. I have tested and confirm classic will return in 2025.11

2 Likes

This is probably gonna be one of my dumber questions I’ve asked, but which update is the latest update on your last post, bead news up top or the good news down bottom lol

Also I noticed browser mod just released an update today, but I’m assuming if that had any affect, it would have been mentioned

Sorry. I made that clear now. I tend to update posts so as to not hit the 3 post consecutive limit in a thread.

As per UPDATE 2 this will be fixed by Home Assistant 2025.11 with no update required to Browser Mod. Good news! This fix has been included in 2025.10.2 (Frontend 20251001.2).

2.6.2 has no effect to classic mode. A fix to camera and a few new features paving the way for 2.7.0.

1 Like

:mega: As per edit above, the fix for classic Browser Mod popups has been delivered in 2025.10.2 (Frontend 20251001.2).

I note that on my desktop I needed to refresh cache after updating. May also be required on mobile. This is due to this being a theme/style change which will be wrong if Browser/Device is using a cached page.

1 Like

Not sure if this has already been addressed: Browser_mod stops working when computer/browser cache reset. This is becoming a massive headache for me. Due to other work I have to clear my browser cache often (been automated to clear everything I restart my browser). As such the registering of Browser_mod keeps disappearing every time I come into my browser. Is there a way to make this a little less, um how do you say ā€˜flaky’

Greg

P.S. This is an AWSESOME bit of code. I really love working with it and finding all the neat things I can do with it.

1 Like

This is the nature of clearing caches. Browser Mod needs to store the Browser ID somewhere. A few thoughts: 1) Use a different Browser profile for Home Assistant, isolating HA session so it does not get affected by your other work. 2) Place a button with action browser_mod.change_browser_id (no parameters) which pops up a quick dialog to change your Browser ID with current known Brower IDs in a dropdown. Since you know when you clear cache, your next step would be to log back into HA and set your Browser ID.

1 Like

I try to somehow reset the displayed timespan for multiple energy date pickers (energy-date-selection) on a custom energy dashboard.

I have a date dicker and few energy cards (see: Energy cards - Home Assistant) normally displaying hourly values of the current day.

below, I have a second date picker which is normally set to the current year and a few of the cards showing monthly values of the current year.

The date pickers and the corresponding energy cards have the same ā€˜collection_key’ set as described here to stay in sync:

Now sometimes, I change the time range on the daily charts to weely or montly.
But I would like to reset them after a specific time, so I see the dashboard in its initial value when opening it later.

The energy date pickers save their state in the current browser in the localStorage.
And that’s what I want to reset.

I tried using the Browser_mod → Default action → Global like this:

action: browser_mod.sequence
sequence:
  - action: browser_mod.javascript
    data:
      code: |
        localStorage.setItem('energy-default-period-_energy_custom_daily', 'today');
        localStorage.setItem('energy-default-period-_energy_custom_yearly', 'this_year');

But this isn’t working sadly.
I was afraid, that it wouldn’t apply fast enough, so I see the charts / picker flickering and updating two times. But in fact nothing happens at all.
Any idea what might be wrong?

It does not look like it is that simple. The energy date picker both sets the localStorage item but also the range of the date selector, which updates the date range of the queried data to the energy data collection. The localStorage item only affects on first load of the energy data collection. When energy data collection returns data, this updates the range of the energy date picker. The energy data collection is untied form a dashboard hence why the dashboard and energy date selector remembers the range.

I can’t see any easy way to achieve what you want.

1 Like

Thank you for your hints. :slight_smile:

I’ve already noticed that the energy date picker isn’t using these localStorage keys while on the page.
But I wasn’t aware that this selection is untied from the current dashboard, so leaving and reentering the dashboard in the same tab / browser-session will also not trigger a reload.

So this solution got quite a bit larger than I hoped for, but it’s working. :partying_face:

It is important to use specific energy collection keys for all the pickers you want to control this way and not use the default.
That way this solution is working independently from other date pickers on your dashboards.

It will call itself recursively every 10 seconds and check if you just left the dashboard with the date pickers and reset them in this case.
Otherwise it will check if you’re currently on the dashboard with the date pickers and will then reset the date pickers only if there are no touch or mouse movements in the last 5 minutes.

action: browser_mod.sequence
data:
  sequence:
    - service: browser_mod.javascript
      data:
        code: |
          // IIFE to restrict scope
          (function () {
            'use strict';

            const DAILY_DATE_PICKER_KEY = 'energy-default-period-_energy_custom_daily';
            const YEARLY_DATE_PICKER_KEY = 'energy-default-period-_energy_custom_yearly';
            const ENERGY_PATH_PART = 'dashboard-custom-energy/0';
            const ENERGY_DASHBOARD_INACTIVITY_LIMIT = 300_000;

            // Function to change date range for a specific energy date picker and it's cards
            async function setEnergyRange(key, start, end) {
              const hass = await window.hassConnection;
              if (!hass || !hass.conn) {
                console.error('BROWSER MOD DEFAULT ACTION ERROR: HASS CONNECTION NOT READY.');
                return;
              }
              const collection = hass.conn[key];
              if (!collection) {
                console.error('BROWSER MOD DEFAULT ACTION ERROR: ENERGY DATE PICKER COLLECTION NOT FOUND');
                return;
              }
              collection.setPeriod(start, end);
              collection.refresh();
            }

            // Local Storage handling
            const applyLocalStorageDefaults = () => {
              if (localStorage.getItem(DAILY_DATE_PICKER_KEY) !== 'today') {
                localStorage.setItem(DAILY_DATE_PICKER_KEY, 'today');
              }
              if (localStorage.getItem(YEARLY_DATE_PICKER_KEY) !== 'this_year') {
                localStorage.setItem(YEARLY_DATE_PICKER_KEY, 'this_year');
              }
            };

            // Time helpers
            const startOfToday = () => { const d = new Date(); d.setHours(0,0,0,0); return d; };
            const endOfToday   = () => { const d = new Date(); d.setHours(23,59,59,999); return d; };
            const startOfYear  = () => { const n = new Date(); return new Date(n.getFullYear(), 0, 1, 0,0,0,0); };
            const endOfYear    = () => { const n = new Date(); return new Date(n.getFullYear(),11,31,23,59,59,999); };

            // Dashboard presence & user activity
            const currentlyOnEnergyView = () => window.location.pathname.includes(ENERGY_PATH_PART);

            let lastActivity = Date.now();
            let lastResetAt = 0;
            let wasOnEnergyView = currentlyOnEnergyView();

            const bump = () => { lastActivity = Date.now(); };
            window.addEventListener('mousemove', bump, { passive: true });
            window.addEventListener('touchstart', bump, { passive: true });
            window.addEventListener('touchmove', bump, { passive: true });

            // Call every 10 seconds
            const tick = async () => {
              applyLocalStorageDefaults();

              const onEnergyView = currentlyOnEnergyView();

              // Don't check user activity when on other dashboards
              if (onEnergyView) {
                const inactiveMs = Date.now() - lastActivity;
                if (inactiveMs >= ENERGY_DASHBOARD_INACTIVITY_LIMIT && lastActivity > lastResetAt) {
                  try {
                    await setEnergyRange('_energy_custom_daily',  startOfToday(), endOfToday());
                    await setEnergyRange('_energy_custom_yearly', startOfYear(),  endOfYear());
                    lastResetAt = Date.now(); // Only once per inactivity period
                  } catch (e) {
                    console.error('BROWSER MOD DEFAULT ACTION ERROR: INACTIVITY ENERGY RESET FAILED:', e);
                  }
                }
              }

              // Just left the dashboard with the energy pickers
              if (wasOnEnergyView && !onEnergyView) {
                try {
                  await setEnergyRange('_energy_custom_daily',  startOfToday(), endOfToday());
                  await setEnergyRange('_energy_custom_yearly', startOfYear(),  endOfYear());
                } catch (e) {
                  console.error('BROWSER MOD DEFAULT ACTION ERROR: LEAVE-DASHBOARD ENERGY RESET FAILED:', e);
                }
              }

              wasOnEnergyView = onEnergyView;
            };

            applyLocalStorageDefaults();
            tick();
            setInterval(tick, 10_000);
          })();

Looking good. A few suggestions since this is using Browser Mod.

  1. Take advantange of the widow.browser_mod.connection which will always be available when Default action is called.
  2. Take advantage of browser-mod-activity event on window.browser_mod.
1 Like

:mega: Browser Mod 2.7.0-beta.1 now available for testing :mega:

Browser Mod 2.7.0-beta.1 is now available for testing. Sorry for the long delay between the teaser and now, it has taken that long between other projects to find time to do the all important documentation.

New features

NOTE: All Tile/Badge actions on the main entity are supported. Supporting browser_entities.xxx in Tile/Badge visibility conditions, other actions etc is not yet supported.

2 Likes

Today I noticed that opening a more info pop-up on desktop opens it on my mobile, too.
By browsing the Internet, I found an association with the Browser Mod. However, all mentions were about calling the Browser mod popup service.

In my case, it happens when opening the HA built-in more info, for example, from the Developer Tools.

Could you guide me on how to fix that?

When you say built in from the developer tools, can you explain more of the steps you are taking. If you are using an action it is likely browser_mod.more_info and if so it works the same as browser_mod.popup in that if you leave target blank it will send the action to all Browsers.

It seems I got confused and overreacted a bit, mentioning Dev Tools without any real reason. After reading your explanation, I realized I was using browser_mod.more_info incorrectly (I forgot about the fire-dom-event). I must have copied that snippet from somewhere without noticing.

All fixed.
Thank you.

1 Like

Yes, it can be confusing as you can’t use Developer Tools to debug Browser calls as they are always Server calls in Developer Tools.

Hello, I am using browser_mod.popup to display my camera stream using this code, works perfectly until browser_mod updates, after such update the popup doesn’t popup anymore, and I always need to figure out what happens / how to repair.
Here is my code :

  - data:
      title: CamƩra ExtƩrieuresxxx
      dismissable: false
      content:
        camera_view: live
        type: picture-glance
        entities: []
        camera_image: camera.dahua_rue
        aspect_ratio: "1"
      timeout: 300000
      size: fullscreen
      browser_id:
        - Lenovo-tablette
      right_button: Fermer
    action: browser_mod.popup

… the ā€œbrowser_idā€ is I think what causes the issue … it is my tablet ā€œLenovo-tabletteā€, and it is appearing to be ok in the Browser Mod page :

… the browser_mod updated couple of days ago, and again, my popup is not working anymore. Any idea what I need to look at on priority ? What I need to do ?
Many thanks !!

If your Browser ID changes you can just change it back using the dropdown, which will update the Browser ID as well as toggle register.

Just updating Browser Mod itself should not change any Browser IDs. However whenever you clear cache of the device you will lose Browser ID as it is stored in localStorage of the Browser which is cleared when you clear caches. When you update Browser Mod you don’t need to clear cache, just click Reload on any popup that notifies of the client code being behind server. If you are clearning caches for other reasons, then you will always lose Browser ID and have to reset.