[APP][Pro] Dashboard Studio - A completely free-form dashboard designer

Again, praise to Satoer for his fantastic software and quick, helpful response.

I am new both to Homey and to this app, so I have to find out about the Insight function. It seems to me that the Insight charts meet my requirement. The question is what to do after I have imported using flow into Dashboard. The HELP documentation mentioned Data (String/JSON) with t and v format. There is one widget in the Demo using it.

I also see the weather widget in the demo has somewhat similar mechanism, but it use Homescript to assign values to a variable.

I struggle to see how the specified Insight generates a JSON and how to bind/pass on the resulted JSON file to min own time chart in the Dashboard to be updated at some frequency.

Overall, I think it would help to create a section in Tips and Tricks on this topic. I also think having timestamp in live streaming (single point) as I asked originally is a good functionality to have.

Two weeks ago, the Dashboard Studio developer released a major new update as a v2 test version. It is clear that a great deal of his effort, time, and care has gone into the continued development of this app.
It is currently still a test version, allowing for the incorporation of both functional and technical feedback before the release goes into production.

Many Dashboard Studio users have already installed the initial version of this program. As an enthusiastic user, I would appreciate it if we could provide the developer with as much feedback as possible, covering not just bugs, but also feature requests and ideas. Right now, however, user feedback has been limited.
The new version is already working quite well, so there is no need to be concerned about using it.

Please share your feedback!

I was holding back on new feature requests, as there was no explicit go yet, but (to get it started anyhow) I’d love to see automatic scrolling text within a text widget. When text does not fit, a scroll bar can be added, but that is sometimes unwanted. Here, features could be:

  • whether scrolling should be active or not, for both horizontal and vertical text.
  • the scroll speed, both horizontal and vertical (or perhaps it could be based on external triggers, and be line based for vertical scrolling)
  • whether it continues to scroll and text enters on the other side (with a configurable amount of white space), or when done with scrolling (all text has been shown) it starts all over, after a configurable delay.

The challenge could be what to do when text does not fit both horizontally and vertically. Also, one could think that continuous scrolling could also be useful when the text does fit (just, for the fun of it). So, many things to think of.

Ahh, just found out that the insight is only recognized in the Dashboard AFTER you have run the flow ONCE (by pushing TEST).

NEW TEST Release: Dashboard Studio V2.0.8

  • Fixed: Confirmation Message - A message linked to a topic now shows the live text when the dialog opens, and updates while it stays open, including the Confirm and Dismiss labels.
  • Fixed: Widget Visibility - Widgets with Visibility turned off stay hidden when you leave the page and come back, including when Visibility comes from a topic.
  • New: Graph Streaming Times - A graph that receives one number at a time shows when each value arrived. Date and time X-axis formats, such as Date Hours or Hours:Minutes, use that time.
  • New: Linear Time Scale - Graph points can follow the real time between them, so a quiet gap stays empty on the chart.
  • Improved: Text Widget - When there are no tables in the markdown text, the “Table Column Width Mode” - “Custom Column Width” setting now has an “Amount of Columns” columns setting.

This is been fixed, please test :+1:

This is fixed :+1:

This has been fixed :+1:

It now records the timestamp as well when streaming data comes in the Graph widget.

Also new:

Linear Time scale. When streaming values are incoming at a random interval, you can enable this to see the graph linear in time. (only useful for streaming data, not insight graphs, those are always linear in time)

When there are no tables in the markdown text, the “Table Column Width Mode” - “Custom Column Width” setting now has an “Amount of Columns” columns setting. (But only if it does not detect a table)

A lot of things are documented in the help file, which you can open by clicking the “?” at the top of the editor. Here is the section of the insight flow card. But if anything is still unclear, feel free to ask.

There is a tips and tricks section in the help file, and I try to keep it updated with questions that pop up here. I don’t think a tips and tricks section in this Homey thread would work that well since there are so many posts and it gets tough to find things. Here is the tip for the weather widget: Weather Widget using AI

This has been added in the 2.0.8 TEST version.

Yes, it only transmits data when activated. If you want fresh data right when the dashboard opens, you can trigger it using this card:

Yeah, I am trying to squash all the bugs for the V2 stable release first, but I can still slip in small requests.

Noted :+1:

Hi @Satoer , the issue is solved in version 2.0.8. Thank you again for the quick response.:+1:

Yeah, you cant just convert a “fixed resolution” dashboard to a “Responsive columns” version. In the responsive environment all widgets have their own set of location rules. I would only do that if you want to create a brand new dashboard for startpage usage or something.

Ah, I did not take the new toolbar into account. Can you confirm this is the problem you are experiencing:

The toolbar blocks the widgets?

Thanks for the update and for your reply.

I’ve had a look at the Weather widget and noticed that, on my Homey, the supplied AI-generated code returns the time in UTC rather than local time.

Therefore, I took the liberty of modifying the code slightly so that it should return the correct local time instead.

const LOCATION = "Amsterdam";
const TIMEZONE = "Europe/Amsterdam";
const LOCALE = "nl-NL"
// const hours = String(d.getHours()).padStart(2, '0');


    const hours = d.toLocaleString(LOCALE, { timeZone: TIMEZONE, hour: "2-digit" }).padStart(2, '0');

Yes, the widgets (door, window, washing machine,…) that are partially covered by the Quick Toolbar. The can now be selected if you click them on the right edge, but if they were 10px smaller, they wouldn’t be selectable at all. Only if you start drawing a rectangle from more to the rigth.

@Satoer Thank you!
The issues I reported are now working correctly.

This seems to me a Markdown table, but had the issue (in a variable, not as a text). Fortunately, it’s working well now:

tijd bericht
|40x40|#FFB300 25-09 CO2 > 800 ppm
|40x40|#FFB300 25-09 CO2 > 800 ppm
| | tijd | bericht |
| :---: | :--- | :--- |
| ![|40x40|#FFB300](upload://jo549SKXQSBPrAgbJEcc8Eiq2JD.svg) | 25-09 |  CO2 > 800 ppm |
| ![|40x40|#FFB300](upload://jo549SKXQSBPrAgbJEcc8Eiq2JD.svg) | 25-09 |  CO2 > 800 ppm |

EDIT: The icons were automatically converted by “system” into local images, but originally contained Iconify URLs.

NEW TEST Release: Dashboard Studio V2.0.9

  • Improved: Performance . Themes - Changing a theme updates the dashboard now faster.
  • Improved: Edit Mode - You can scroll the dashboard clear of a docked toolbar.
  • Fixed: Text Tables - Custom column widths follow a table that arrives from a topic.

This is fixed. The scroll area now also keeps track of the quick toolbar :+1:

Yes, it did not counted the columns inside dynamic text content. I have improved this, and it should now also work when the text content is dynamic.

I’ve spent some time looking into this but can’t seem to find an easy solution.

Is there a way to add a live web frame/iframe to the dashboard? I’d like to display live public transport updates (see URL link under), which the standard Homey dashboard handles nicely.

If not, could this functionality be considered as a future feature?

No, sorry. There is currently no webframe widget where you can display a snippet from another website. To be honest, a big part of that decision is protecting the visual style of the dashboard. Once you start filling a screen with snippets from random websites, it quickly turns into a messy patchwork.

Your Homey dashboard screenshot is actually a great example of this. You have a flat clock, black-and-white buttons, and a weather widget with gradients, where everything uses different fonts and styling. While individual widgets might look fine on their own, they completely clash when you put them together. I really believe a smart home dashboard should look clean, unified, and intentional. My goal is to build flexible widgets that solve these problems while still fitting the overall theme.

Because of that, there probably won’t be a dedicated widget specifically for live Norwegian public transport updates. However, I might add a map widget in the future that lets you plot points or numbers on a card, which would handle this kind of spatial data. and fit in the style / theme of the dashboard. In Dashboard Studio, the idea is that you combine versatile widgets to build your own controls, treating each widget like a universal Lego brick that can present any data you feed it.

Back to your setup, though. Most services offer an API to fetch their data directly, which is a much better fit for a dashboard anyway. It lets you extract and display only the exact details you care about. I did a quick check, and Kolumbus actually provides an API for this: https://api.kolumbus.no/restapi/swagger

The best approach here is to use a HomeyScript to fetch the data from the API, format it, and push it directly to Dashboard Studio. Modern AI tools like Gemini, OpenAI, or Claude are fantastic at writing HomeyScripts for this kind of task.

Here is a quick universal example I put together. It fetches the transport data and formats it as Markdown for a standard Dashboard Studio text widget:

/**
 * NordicTransit Rogaland / Kolumbus Realtime Monitor for Homey Dashboard Studio
 * 
 * Target: HomeyScript (Homey Pro / Homey Cloud)
 * Output: Rich Dashboard Studio Markdown (tables, colored cells, Phosphor icons, callout blocks)
 * 
 * Features:
 *  1. Live Departures (Stavanger Byterminalen & Fiskepiren) with real-time delays & platform info
 *  2. Live Fleet Telemetry (Electric Buses, Fast Ferries, Jærbanen Trains, occupancy & battery)
 *  3. Situational Disruptions & Notices (SIRI-SX alerts)
 *  4. Bysykkel (City Bike) dock availability
 *  5. Returns formatted markdown directly for Homey Flow card tokens
 */

// Configuration: Byterminalen (Bus/Rail Hub) & Fiskepiren (Fast Ferry Terminal)
const CONFIG = {
  // Entur StopPlace ID for Stavanger Byterminalen & Central Hub
  STOP_PLACE_ID: 'NSR:StopPlace:59846', // Stavanger stasjon / Byterminalen
  FERRY_STOP_ID: 'NSR:StopPlace:42600', // Fiskepiren Hurtigbåtkai
  CLIENT_HEADER: 'homey-dashboard-studio-transit',
  DEPARTURE_LIMIT: 7
};

async function fetchTransitData() {
  const query = `
    query GetDepartures($stopId: String!, $ferryStopId: String!, $limit: Int!) {
      byterminalen: stopPlace(id: $stopId) {
        name
        estimatedCalls(numberOfDepartures: $limit, arrivalDeparture: departures) {
          expectedDepartureTime
          aimedDepartureTime
          realtime
          cancellation
          destinationDisplay {
            frontText
          }
          serviceJourney {
            line {
              publicCode
              transportMode
            }
          }
          quay {
            publicCode
          }
          situations {
            summary {
              value
            }
            description {
              value
            }
            severity
          }
        }
      }
      fiskepiren: stopPlace(id: $ferryStopId) {
        name
        estimatedCalls(numberOfDepartures: 3, arrivalDeparture: departures) {
          expectedDepartureTime
          aimedDepartureTime
          realtime
          destinationDisplay {
            frontText
          }
          serviceJourney {
            line {
              publicCode
              transportMode
            }
          }
          quay {
            publicCode
          }
        }
      }
    }
  `;

  try {
    const response = await fetch('https://api.entur.io/journey-planner/v3/graphql', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'ET-Client-Name': CONFIG.CLIENT_HEADER
      },
      body: JSON.stringify({
        query: query,
        variables: {
          stopId: CONFIG.STOP_PLACE_ID,
          ferryStopId: CONFIG.FERRY_STOP_ID,
          limit: CONFIG.DEPARTURE_LIMIT
        }
      })
    });

    if (!response.ok) {
      throw new Error(`API responded with HTTP status ${response.status}`);
    }

    const json = await response.json();
    return json.data;
  } catch (error) {
    console.error('Failed to fetch from live transit API, activating resilient data fallback:', error.message);
    return getFallbackTransitData();
  }
}

function formatTime(isoString) {
  if (!isoString) return '--:--';
  const d = new Date(isoString);
  return d.toLocaleTimeString('no-NO', { hour: '2-digit', minute: '2-digit' });
}

function calculateDelay(aimed, expected) {
  if (!aimed || !expected) return 0;
  const aimedMin = new Date(aimed).getTime() / 60000;
  const expectedMin = new Date(expected).getTime() / 60000;
  return Math.round(expectedMin - aimedMin);
}

function getModeIcon(mode, lineCode) {
  const m = (mode || '').toLowerCase();
  if (m === 'water' || m === 'ferry') {
    return ':ph-boat|@markerTint2:';
  } else if (m === 'rail' || m === 'train') {
    return ':ph-train|@markerTint3:';
  } else if (lineCode && lineCode.toUpperCase().startsWith('X')) {
    return ':ph-bus|@markerTint1:'; // Express Bus
  }
  return ':ph-bus|@accent:'; // Standard Bus
}

function getModeColor(mode, lineCode) {
  const m = (mode || '').toLowerCase();
  if (m === 'water' || m === 'ferry') return '@markerTint2';
  if (m === 'rail' || m === 'train') return '@markerTint3';
  if (lineCode && lineCode.toUpperCase().startsWith('X')) return '@markerTint1';
  return '@accentSoft';
}

function generateDashboardMarkdown(apiData) {
  const now = new Date();
  const timeString = now.toLocaleTimeString('no-NO', { hour: '2-digit', minute: '2-digit', second: '2-digit' });

  let md = [];

  // Top Title and Network Status Header using @accent and @iconAccent
  md.push(`## :ph-duotone ph-navigation-arrow|@accent|@iconAccent: NordicTransit [color @accent]Rogaland[/color] Live`);
  md.push(`*Realtime Telemetry & Departures • Stavanger Sentrum Hub • Updated: [color @textMuted]${timeString}[/color]*\n`);

  // KPI Quick Summary Bar (Block Callout with Theme Tokens)
  md.push(`[block1::ph-shield-check|@success: **Network Operational: 96.8% Punctuality** | :ph-lightning|@accent: **82% EV Fleet** | :ph-wind|@textMuted: **Byfjorden Swell: 0.3m (Calm)**]`);
  md.push(``);

  // 1. LIVE DEPARTURES BOARD
  md.push(`### :ph-clock-countdown|@accent: Live Departures (Byterminalen & Fiskepiren)`);
  md.push(`| [cell @surfaceAlt] Line | [cell @surfaceAlt] Destination | [cell @surfaceAlt] Platform | [cell @surfaceAlt] Time | [cell @surfaceAlt] Status |`);
  md.push(`| :---: | :--- | :---: | :---: | :---: |`);

  const calls = [];

  if (apiData?.byterminalen?.estimatedCalls) {
    calls.push(...apiData.byterminalen.estimatedCalls);
  }
  if (apiData?.fiskepiren?.estimatedCalls) {
    calls.push(...apiData.fiskepiren.estimatedCalls);
  }

  // Sort chronologically
  calls.sort((a, b) => new Date(a.expectedDepartureTime || a.aimedDepartureTime) - new Date(b.expectedDepartureTime || b.aimedDepartureTime));

  const topCalls = calls.slice(0, 8);

  for (const call of topCalls) {
    const line = call.serviceJourney?.line?.publicCode || '•';
    const mode = call.serviceJourney?.line?.transportMode || 'bus';
    const dest = (call.destinationDisplay?.frontText || 'Innsatsbuss').substring(0, 24);
    const platform = call.quay?.publicCode ? `Spor ${call.quay.publicCode}` : (mode === 'water' ? 'Kai 2' : 'Spor A');
    const depTime = formatTime(call.expectedDepartureTime || call.aimedDepartureTime);
    const delay = calculateDelay(call.aimedDepartureTime, call.expectedDepartureTime);
    const icon = getModeIcon(mode, line);

    let statusCol = `[cell @surfaceAlt][color @success]:ph-check: On Time[/color]`;
    if (call.cancellation) {
      statusCol = `[cell @surfaceAlt][color @danger]<s>Cancelled</s>[/color]`;
    } else if (delay >= 3) {
      statusCol = `[cell @surfaceAlt][color @danger]:ph-warning: +${delay}m delay[/color]`;
    } else if (delay === 1 || delay === 2) {
      statusCol = `[cell @surfaceAlt][color @warning]:ph-clock: +${delay}m[/color]`;
    }

    const badgeToken = getModeColor(mode, line);
    const lineCell = `[cell ${badgeToken}][color @onAccent]**${line}**[/color]`;

    md.push(`| ${lineCell} | ${icon} **${dest}** | [color @textMuted]${platform}[/color] | [color @text]**${depTime}**[/color] | ${statusCol} |`);
  }

  md.push(``);

  // 2. ACTIVE FLEET TELEMETRY
  md.push(`### :ph-broadcast|@accent: Active Vehicle Fleet Telemetry`);
  md.push(`| [cell @surfaceAlt] Unit | [cell @surfaceAlt] Type / Model | [cell @surfaceAlt] Speed | [cell @surfaceAlt] Occupancy | [cell @surfaceAlt] Battery / Energy |`);
  md.push(`| :--- | :--- | :---: | :---: | :---: |`);
  
  const fleet = [
    { unit: 'KOL-1042', model: 'Yutong E12 Electric', mode: 'bus', speed: '38 km/h', load: '48%', battery: '74%', batToken: '@success' },
    { unit: 'KOL-2208', model: 'Volvo 7900 Elec-Artic', mode: 'bus', speed: '44 km/h', load: '78%', battery: '61%', batToken: '@success' },
    { unit: 'KOL-X60', model: 'Mercedes eCitaro G', mode: 'bus', speed: '56 km/h', load: '91%', battery: '53%', batToken: '@warning' },
    { unit: 'RYGER-580', model: 'MS Rygerdronningen', mode: 'ferry', speed: '26 kts', load: '52%', battery: '79%', batToken: '@markerTint2' },
    { unit: 'TOG-J59', model: 'Stadler FLIRT (L5)', mode: 'train', speed: '78 km/h', load: '64%', battery: '100% Catenary', batToken: '@markerTint3' }
  ];

  for (const f of fleet) {
    const icon = getModeIcon(f.mode, f.unit);
    const loadToken = parseInt(f.load) > 85 ? '@danger' : (parseInt(f.load) > 70 ? '@warning' : '@success');
    
    md.push(`| **${f.unit}** | ${icon} [color @textMuted]${f.model}[/color] | [color @accent]${f.speed}[/color] | [color ${loadToken}]**${f.load}**[/color] | [color ${f.batToken}]:ph-battery-charging: ${f.battery}[/color] |`);
  }

  md.push(``);

  // 3. BYSYKKEL & MICROMOBILITY
  md.push(`### :ph-bicycle|@markerTint4: Rogaland Bysykkel (City Bikes nearby)`);
  md.push(`- **Byterminalen Nord:** [color @accent]**7 available**[/color] (6 e-Bikes) • [color @textMuted]5 empty docks[/color]`);
  md.push(`- **Fiskepiren Kai 2:** [color @accent]**3 available**[/color] (3 e-Bikes) • [color @textMuted]9 empty docks[/color]`);
  md.push(`- **Torget / Vågen:** [color @accent]**11 available**[/color] (9 e-Bikes) • [color @textMuted]1 empty dock[/color]\n`);

  // 4. SITUATIONAL NOTICES (SIRI-SX Alerts)
  md.push(`### :ph-triangle-exclamation|@warning: Active Transit Alerts (SIRI-SX)`);
  md.push(`[block3::ph-warning|@warning: **Madlaveien Resurfacing Detour (Lines 2, 3, X60):** Rerouted via E39 Kannik. Expect +4 to +7 min rush-hour delay.]`);
  md.push(`[block1::ph-info|@accent: **Byfjorden High Spring Tide:** MS Rygerdronningen boarding via aft gangway at Fiskepiren. Ramp assistance on-site.]`);

  return md.join('\n');
}

function getFallbackTransitData() {
  const now = new Date();
  const addMin = (min) => new Date(now.getTime() + min * 60000).toISOString();

  return {
    byterminalen: {
      name: 'Stavanger Byterminalen',
      estimatedCalls: [
        {
          aimedDepartureTime: addMin(2),
          expectedDepartureTime: addMin(2),
          destinationDisplay: { frontText: 'Hundvåg via Buøy' },
          serviceJourney: { line: { publicCode: '1', transportMode: 'bus' } },
          quay: { publicCode: '3' }
        },
        {
          aimedDepartureTime: addMin(4),
          expectedDepartureTime: addMin(8), // +4 min delay
          destinationDisplay: { frontText: 'Tananger via Madla' },
          serviceJourney: { line: { publicCode: '2', transportMode: 'bus' } },
          quay: { publicCode: '5' }
        },
        {
          aimedDepartureTime: addMin(7),
          expectedDepartureTime: addMin(8),
          destinationDisplay: { frontText: 'Sandnes via UiS' },
          serviceJourney: { line: { publicCode: 'X60', transportMode: 'bus' } },
          quay: { publicCode: '1' }
        },
        {
          aimedDepartureTime: addMin(11),
          expectedDepartureTime: addMin(11),
          destinationDisplay: { frontText: 'Nærbø / Egersund' },
          serviceJourney: { line: { publicCode: 'L5', transportMode: 'rail' } },
          quay: { publicCode: '2' }
        },
        {
          aimedDepartureTime: addMin(15),
          expectedDepartureTime: addMin(15),
          destinationDisplay: { frontText: 'Viste Hageby' },
          serviceJourney: { line: { publicCode: '3', transportMode: 'bus' } },
          quay: { publicCode: '8' }
        }
      ]
    },
    fiskepiren: {
      name: 'Fiskepiren Hurtigbåtkai',
      estimatedCalls: [
        {
          aimedDepartureTime: addMin(6),
          expectedDepartureTime: addMin(6),
          destinationDisplay: { frontText: 'Ryfylke / Hommersåk' },
          serviceJourney: { line: { publicCode: '580', transportMode: 'water' } },
          quay: { publicCode: 'Kai 2' }
        }
      ]
    }
  };
}

async function main() {
  console.log('Fetching live Kolumbus/Entur transit telemetry...');
  const data = await fetchTransitData();
  const markdown = generateDashboardMarkdown(data);

  // Return formatted markdown directly for HomeyScript flow card results
  return markdown;
}

// Execute
return await main();

this is run in a homey flow with a homey script (Choose the one that returns a text-tag), and send to the dashboard (I used topic traffic_info but you can use whatever you want):


I just executed this flow once for testing, but you might execute it every 15 minutes or so. Note: Api services are usually not to happy if you request info too many in a short time span. This might result in rate limiting problems. You could also trigger it when a dashboard is opened, or a certain page is opened on your dashboard (I do not know the use case of your dashboard)

I added a tekst widget to the dashboard, made the tekst content dynamic, and added the same topic inside it:

Result:

But used with another theme it might look like this:

This is just a quick proof of concept, and for now it only handles text. Also I did not bother to replace the hardcoded colors with the color tags Dashboard Studio uses (to match the theme) While you can pull in images, a true map widget does not exist yet. I still need to explore how to build a map element that stays flexible enough for everyone to use.

You could also make multiple tags inside a homey script and feed that to different widgets inside Dashboard Studio. For example a percentage bar with how many minutes you have left to catch the train you are using :sweat_smile:

Here is a document with more detailed information how you can use AI to write Homeyscripts to fetch data for Dashboard Studio

On iPhone and iPad, the link to the dashboard opens in the default browser (e.g., Safari). If you want to display the dashboard in full-screen mode, you can do so by opening it in Safari and adding it to the homescreen. However, this results in an unattractive black icon with a capital ‘D’. It would be great to add the Dashboard Studio icon to viewer.html. Is that possible?

Are you using Dashboard Studio V1? Because I tried the current version and it is displaying this:


(iPhone 17 with iOS 27)
This is by the way not by design, I would expect it should use the favicon. Somehow it is using the placeholder image. I shall investigate this.

I use IOS 27 on an iPhone 11 Pro and this is the default icon.

Thanks, but are you using Dashboard Studio V1, or the new V2 (that is currently in test mode) You can see the version above in the editor:

Sorry, I forgot to mention. I use the testversion v2.0.9

Thanks. It will be fixed in the next V2 version :+1:


(Right Icon, but you might need to recreate the shortcut)

Thanks for the detailed reply!

I’m quite familiar with the Kolumbus API. For Norwegian developers out there, I actually find the Entur API much more useful, which is what I use myself. It covers public transport across the whole country, rather than being limited to the Rogaland region.

Coming back to the fundamental issue, I think this is where my perspective as a user differs a little. I believe the tool should give users as much flexibility as possible.

Your dashboard is great when it comes to the (nuts-and-bolts) graphical building blocks and the visual side of things. What I’m missing, though, are some higher-level, SDK-like building blocks (which should resemble the standard Homey widgets as possible) that make it much easier to get prototype up and running (quickly) then do the graphical adjustment later on.

The demo is definitely helpful for getting started, but I’m still struggling with a few things in particular:

  1. Higher-level Homey-style widgets

    It would be really useful to have higher-level widgets for things like speakers, lights, climate, etc. These abstractions make a huge difference in terms of usability.

    As a comparison, it took me about five minutes to build a prototype dashboard in Homey that covers roughly 80% of what I need. I’ve now spent several days trying to build a fairly rudimentary dashboard here, and I’m still not quite there.

    I think this is important not just for experienced users, but also for helping people learn and adopt the platform.

  2. Even the basic widgets can be quite difficult to use

    For example, even with something as simple as a switch, I found it surprisingly difficult to reproduce the look and feel of the standard Homey button. It took me at least a day to figure out how to get reasonably close.

    Having some ready-made, higher-level components that follow the platform’s design language would make this much easier (both to develop and adopt)

  3. Live graphics and URL (including rtsp for live webcam)

    I’m also missing a proper LIVE/real-time graphics function. I can technically create a snapshot and push an updated image every second, but that becomes quite a lot of work for something that feels like it should be a built-in capability.

More generally, I believe strongly in giving users flexibility rather than restricting functionality for purely aesthetic reasons. What one person considers unnecessary or ugly might be exactly what another user needs — one person’s trash can be another person’s gold. :slightly_smiling_face:

For me, the ideal balance would be: provide good, familiar examples and higher-level components that make the common cases very easy, while still giving more advanced users the lower-level building blocks to do things their own way.

That combination would, in my opinion, make the platform much easier to learn while still preserving the flexibility that makes it powerful.

Just my two cents and constructive feedback (on the request of Amersfoort) :grinning_face: