# Theme resolution

## View lookup

When a view is requested, the theme manager checks the active theme first and falls back to the core view:

```
public/themes/{active}/views/{name}.php   ← used when present
app/views/{name}.php                      ← fallback
```

A theme therefore overrides only the views it ships. A missing override is normal, not an error.

## Path safety

The view name is resolved **inside** the theme root. Traversal sequences cannot escape it, so a crafted view name cannot read an arbitrary file from the server.

## Token resolution

Tokens are resolved in layers, later winning over earlier:

1. Core defaults
2. The theme manifest's declared tokens
3. The active theme profile stored in the database
4. Legacy appearance settings mapped forward for upgraded installations

The result is normalised and validated — colours, font stacks, dimensions, weights and the contrast rule — then compiled into a `<style data-theme-tokens>` block of `:root` custom properties.

An invalid token set is rejected rather than partially applied; the previous appearance stays live.

## Layout presets

Presets are normalised against an allow-list and rendered as attributes on `<body>`:

```html
<body class="page-home theme-default"
      data-home-layout="community"
      data-question-layout="classic"
      data-topic-layout="discussion"
      data-header-layout="standard"
      data-density="comfortable"
      data-sidebar-position="end">
```

Stylesheets select on those attributes. A preset changes composition and classes; it never swaps a template. Full template replacement stays a developer-theme capability.

## Profiles and preview

Applying a profile snapshots a version first, so rollback is always available. Preview is bound to the administrator's session — the public default does not change while an admin previews.

The active or default profile cannot be deleted. When no profile exists yet, the live tokens are adopted as the first one, so the panel always has a current appearance to work from.

## Asset URLs

Theme assets are addressed through the URL helper with a cache-busting version parameter, so a subdirectory install and a cache-busted upgrade both work without editing templates.

## Failure behaviour

- A missing theme falls back to the default theme.
- A missing view falls back to the core view.
- A theme that declares an incompatible Core version is reported in the panel rather than half-loaded.
