Skip to content

Concepts

A few ideas underpin the whole framework. Understanding them makes every other page click into place.

The shell / user-surface boundary

The project has a hard line down the middle:

app/
├── settings.ts        ┐
├── layers.ts          │
├── symbology.ts       ├─ USER SURFACE (yours to edit)
├── popups.tsx         │
├── info.md            │
├── widgets/           ┘
├── src/               ─ SHELL (framework code; rarely touched)
│   ├── core/          │   state, layer loaders, popups, theme, registry
│   └── shell/         │   AppShell, TopBar, panels, MapView, …
├── public/            ─ static assets (logo, favicon)
└── index.html, vite.config.ts, …
  • You own the user surface. Everything you configure or author lives in the root app/ files and app/widgets/.
  • The shell reads your config and renders. It's framework code. When Conness GIS is eventually published as a package, the shell becomes the package and the user surface stays in your project unchanged.

Two TypeScript path aliases express this boundary in imports:

  • @/…app/src/… (the shell)
  • @user/…app/… (the user surface)

So shell code imports your config as @user/settings, @user/layers, etc.

The settings object

app/settings.ts exports one typed Settings object. It's the spine of your app — branding, theme, the map's initial view, basemaps, native map controls, the context menu, per-widget config blocks (search/measure/draw/bookmarks), and where each widget is placed.

export const settings: Settings = {
  branding: { title: 'My App', /* … */ },
  ui: { /* layout, popups, layer list, … */ },
  map: { style, center, zoom, /* … */ },
  basemaps: [ /* … */ ],
  layers,                 // imported from app/layers.ts
  topBarWidgets: [ /* … */ ],
  leftPanelWidgets: [ /* … */ ],
  floatingWidgets: [ /* … */ ],
}

The whole Configuration section documents this object field by field. The data layers are pulled out into app/layers.ts (and imported back in as settings.layers) just to keep settings.ts readable.

Widgets are auto-discovered

A widget is a self-contained folder under app/widgets/. The shell finds widgets automatically — it globs app/widgets/*/widget.config.ts at build time and reads each manifest. There's no central registry to edit; dropping in a folder is enough.

Each manifest declares which surfaces the widget can live on:

  • top-bar — an icon button in the top bar; its UI opens in a side panel
  • left-panel — an icon in the left rail; its UI slides out beside the map
  • floating — a control floating over a corner of the map

A folder whose name starts with _ (like _template) is skipped by discovery — that's how the starter template stays out of your app.

You place a widget by referencing its id in settings.ts:

topBarWidgets:    [{ widgetName: 'measure' }, { widgetName: 'draw' }],
leftPanelWidgets: [{ widgetName: 'layer-list' }, { widgetName: 'legend' }],
floatingWidgets:  [{ slot: 'top-left', widgets: [{ widgetName: 'search' }] }],

A widget can be placed on more than one surface. See the Widgets catalog and the Building a Custom Widget guide.

The resolve / merge order

Almost every setting is optional with a sensible default. Removing a key never errors — it falls back. Where a setting can be specified at more than one level, the framework merges them in a consistent order, most-specific wins:

kit default ← global setting ← per-item override

For example, a popup's trigger resolves as: the kit default ('click') ← settings.ui.popups.trigger ← the layer's own popup.trigger. The same pattern governs widget headers, layer-list actions, label styling, and more. Wherever you see "defaults to X", that's the bottom of one of these chains.

This means you can configure broadly (set it once globally) and override narrowly (tweak one layer or one widget) without repeating yourself.

The boot lifecycle (at a glance)

When the app loads:

  1. main.tsx applies the theme + UI CSS variables synchronously (no flash), then mounts <AppShell>.
  2. <AppShell> validates the layout config, picks the desktop or mobile shell, and renders the regions: top bar, side panels, and the map.
  3. <MapView> creates the MapLibre map, registers native controls, and — once the style loads — registers your declarative layers and popup handlers.
  4. Switching basemaps re-runs layer + popup registration (a new style wipes the old sources), so your data and toggles survive the switch.

That's the whole mental model. The Internals section goes deeper for contributors; the Configuration and Widgets sections are what you'll use day to day.