Web Mount Point

Build browser-accessible interfaces for staff dashboards, operator desks, and public forms using the TelemetryOS web mount point.

Web Mount Point

The web mount point is a browser-accessible interface that runs outside the TelemetryOS device player and Studio admin portal. Unlike the render mount point (which displays on physical devices) and the settings mount point (which runs inside the Studio admin sidebar), the web mount point is accessed directly via URL in any standard browser — phones, tablets, and desktops.

Overview

  • Accessible from any browser via a direct URL
  • Used for operator desks, staff dashboards, and public-facing forms
  • Requires useSpaRouting: true in telemetry.config.json
  • Uses the application name as the URL base path
  • Store access: use application scope by default; shared(namespace) available for multi-app data sharing

When to Use

Use CaseExample
Operator desksStaff calling queue numbers from a phone or tablet
Staff dashboardsManagement consoles for monitoring operations
Public signup formsCustomer-facing registration or check-in
Public status boardsReal-time status displays accessible via shared link

Configuration

Add the web mount point and enable SPA routing in telemetry.config.json:

{
  "name": "queue-manager",
  "version": "1.0.0",
  "useSpaRouting": true,
  "mountPoints": {
    "render": "/render",
    "settings": "/settings",
    "web": "/queue-manager"
  },
  "devServer": {
    "runCommand": "vite --port 3000",
    "url": "http://localhost:3000"
  }
}

Web Path

The web mount point path must match the application name. For an application named queue-manager, the web path is /queue-manager.

useSpaRouting

Setting useSpaRouting to true is required when using the web mount point. This tells the platform to route all sub-paths back to the application so that client-side routing (React Router) handles navigation. Without it, refreshing or navigating directly to a sub-route like /queue-manager/l/Downtown returns a 404.

Store Scope Access

The web mount point runs in a standard browser with no device or instance context. Only two storage scopes are available:

ScopeAvailableWhy
application✅ YesAccount-wide data (entity lists, global config)
shared(namespace)✅ YesEntity-scoped shared data via dynamic namespaces
instance❌ NoNo device/instance context in browser
device❌ NoNo physical device context
❗️

Attempting to use store().instance or store().device in a web view will fail. Use only store().application and store().shared(namespace).

Default to Application Scope

Use application scope as your default in web views. Data written to application scope is readable by all mount points within the same app — Settings, Render, and Web — making it the natural choice for most web view data. Entity lists, configuration, and any data that your Render and Settings views also need should go here.

import { store } from '@telemetryos/sdk';

// Web view writes to application scope
await store().application.set('menuItems', updatedItems);

// Render view on the device can read the same data
const items = await store().application.get('menuItems');

When to Use Shared Scope

Use shared(namespace) only when you need multiple different applications to share the same data. If your Web, Settings, and Render views are all part of the same application, application scope is sufficient and simpler.

The shared(namespace) scope is designed for scenarios where separately developed applications need to coordinate — for example, a weather app publishing temperature data that a separate energy dashboard app consumes.

Entity from URL, Not Instance Store

In the render mount point, the current entity (e.g., which location to display) is typically stored in instance scope. The web mount point has no instance scope, so the entity comes from URL parameters instead:

import { useParams } from 'react-router';
import { store } from '@telemetryos/sdk';

function WebEntityView() {
  const { locationName } = useParams();
  const decodedLocation = decodeURIComponent(locationName || '');

  // Use application scope for data within the same app
  const items = await store().application.get('menuItems');

  // Only use shared scope if coordinating with other applications
  const ns = `location::${decodedLocation.toLowerCase().replace(/\s+/g, '-')}`;
  const data = await store().shared(ns).get('counters');
}

When using shared namespaces, the namespace helper function should be shared between all views that access the same data to ensure they derive the same namespace string from a given entity name.

Routing

Web views use React Router with createBrowserRouter for client-side navigation.

App Name as Base Path

All web routes live under the application name. For queue-manager, all routes start with /queue-manager.

Entity-Driven URL Hierarchy

Structure URLs to mirror the data hierarchy. Each level of the URL corresponds to an entity selection:

/queue-manager                                    → Root (list entities)
/queue-manager/l/:locationName                    → Entity detail
/queue-manager/l/:locationName/c/:counterId       → Deep detail

React Router Setup

Define all mount point routes in a single router. Render and settings use their standard paths; web routes use the app name prefix:

import { createBrowserRouter, RouterProvider } from 'react-router';
import { Render } from './views/Render';
import { Settings } from './views/Settings';
import { WebLocations, WebCounters, WebCounter } from './views/Web';

const router = createBrowserRouter([
  { path: '/render', Component: Render },
  { path: '/settings', Component: Settings },
  { path: '/queue-manager', Component: WebLocations },
  { path: '/queue-manager/l/:locationName', Component: WebCounters },
  { path: '/queue-manager/l/:locationName/c/:counterId', Component: WebCounter },
]);

export function App() {
  return <RouterProvider router={router} />;
}

URL Encoding

Always encode entity names in URLs and decode them when reading parameters. Entity names may contain spaces, special characters, or unicode:

// Building a link
<Link to={`/queue-manager/l/${encodeURIComponent(locationName)}`}>
  {locationName}
</Link>

// Reading a parameter
const { locationName } = useParams();
const decoded = decodeURIComponent(locationName || '');

Styling

The html:has(.web) Pattern

The TelemetryOS base template sets font-size: 1vmax on the <html> element for viewport-relative scaling on device displays. This scaling is not appropriate for web views, which run in standard browsers and should use standard font sizing.

Override the base scaling by applying a .web class to the root element of your web view components, then targeting it with html:has():

html:has(.web) {
  font-size: 16px;
}
export function WebLocations() {
  return (
    <div className="web">
      {/* Web view content */}
    </div>
  );
}

Every web view component should include className="web" on its outermost element. This ensures the html:has(.web) selector activates whenever any web view is rendered.

Design Guidelines

Web views use standard CSS without the viewport-relative scaling used by render views. Design for responsive layouts across phones, tablets, and desktops. Do not use useUiScaleToSetRem() or other SDK scaling utilities — these are for render mount points only.

Communication with Render and Settings

All three mount points share data through the application storage scope. This is the simplest and most common pattern:

Settings (admin portal)
  → Writes configuration to application scope

Web (browser)
  → Reads/writes data via application scope

Render (device)
  → Subscribes to application scope for real-time display

Settings manages the configuration, Web provides interactive access for staff or the public, and Render displays the output on physical devices. All three access the same underlying data through the application scope.

For advanced multi-app architectures where separately developed applications need to share data, use shared(namespace) to coordinate across application boundaries.

Next Steps


What’s Next