i99dash docs
Concepts

Categories + tags

How catalog navigation works — a closed 10-bucket category enum (primary nav) plus optional free-form tags (search/filter only).

The head-unit's mini-app store needs to render predictable buckets, not free-form chaos. So the manifest has two distinct fields:

FieldAuthorityCardinalityPurpose
categoryClosed enum (10 slugs)exactly 1 per appPrimary navigation — drives the chip rail
tags[]Free-form0–8 per appSearch / refinement (no UI surface in v1)

A category is mandatory at publish time. Tags are optional — schema accepts them now so future search can lean on them, but the v1 head-unit chip rail filters on category alone.

The 10 categories

SlugWhat goes here
navigationMaps, traffic, parking finder
mediaMusic, podcasts, audiobooks
vehicleCar status, diagnostics, climate, EV-charge planner
productivityCalendar, notes, todos
communicationMessaging, voice, call
entertainmentGames, trivia
servicesFuel, charging, food, payments
lifestyleWeather, news, sports
developerAdmin tools, diagnostics, internal
otherFallback — only if nothing above fits

Pick the closest match. Reaching for other regularly is a signal the enum needs a new slug — open an issue.

Why a closed enum

Free-form categories cause a predictable UX failure: 30 apps that all call themselves "productivity" plus 3 that say "Productivity" plus 5 that say "tools" plus 2 that say "utility". The store ends up with chip-rail soup, fuzzy search becomes load-bearing, and "can't find apps" becomes a support ticket.

Closing the enum forces the conversation up-front. Adding a new category becomes a SDK + backend lockstep PR (the slug list lives in category-slugs.json, vendored across both repos with a CI drift check) — intentional friction so the list doesn't sprawl.

How the chip rail consumes this

The head-unit fetches GET /api/v1/mini-apps/categories once on store-tab open (5-minute ETag cache) and renders a chip per slug, plus an "All" chip at the head. Slugs aren't hard-coded into the Flutter binary — adding a category is a backend deploy, not a Flutter release.

Filtering happens client-side over the already-fetched catalog until the production catalog hits 200 apps; past that threshold the chip-tap re-fetches with ?category=<slug> (server-side filter, JSONB extract). See the SDK plan for the migration trigger.

Tags

Optional, free-form, lowercase + hyphens, ≤ 24 chars, ≤ 8 entries:

{
  "category": "media",
  "tags": ["offline", "lossless", "kid-safe"]
}

Use them when a single category undersells the app (a "navigation" app that's also "ev-routing-aware" — the category puts it in the right chip; the tag surfaces it in a future search).

No tag-filter UI in v1. Schema accepts them so a future search feature can lean on them, but the head-unit chip rail filters by category alone today.

Out of scope for v1. Editorial sections are a separate concern from self-declared categorization — they need a curation surface (admin UI), an app_sections table, and ranking signals. When the store grows enough to warrant human curation, that ships as its own effort. For v1, the store renders categorical sections ("Music apps", "Navigation apps") which are just ?category=... filters under the hood.

On this page