This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git

commit ea29dc2d1d9aee47ba1dec68df534db410dc5ed2
Author: Wu Sheng <[email protected]>
AuthorDate: Thu May 21 22:15:32 2026 +0800

    rbac+docs: grant overview/ttl/config reads to maintainer; doc refresh
    
    Add overview:read to viewer + maintainer defaults, and ttl:read / 
config:read
    to the maintainer + operator baselines, so the read-only Data Retention and
    OAP Configuration pages are reachable by monitoring roles. Document those 
two
    operate pages and refresh the docs tree (menu, customization, components,
    access-control, setup) for accuracy.
---
 apps/bff/src/config/schema.ts            |   2 +
 apps/ui/src/shell/AppSidebar.vue         |   2 +-
 docs/README.md                           |  17 ++-
 docs/access-control/admin-pages.md       |   6 +-
 docs/access-control/audit-log.md         |  28 +++--
 docs/access-control/rbac.md              |  16 +--
 docs/components/charts.md                | 151 ++++++--------------------
 docs/components/dashboard-widgets.md     |  27 +----
 docs/customization/adding-a-new-layer.md |   4 +-
 docs/customization/layer-templates.md    |  42 ++++----
 docs/customization/menu-structure.md     | 177 ++++++++++---------------------
 docs/customization/overview-templates.md |  56 ++--------
 docs/design-target.md                    |   4 +-
 docs/menu.yml                            |  79 +++++++-------
 docs/operate/cluster-metadata.md         |  12 ---
 docs/operate/data-retention.md           |  28 +++++
 docs/operate/inspect.md                  |   2 +-
 docs/operate/oap-configuration.md        |  24 +++++
 docs/setup/audit.md                      |   2 +-
 docs/setup/auth.md                       |   2 +-
 docs/setup/debug-log.md                  |   2 +-
 docs/setup/files.md                      |   2 +-
 docs/setup/oap.md                        |   2 +-
 docs/setup/overview.md                   |  51 +++++++--
 docs/setup/rbac.md                       |  13 +--
 docs/setup/server.md                     |   2 +-
 docs/setup/session.md                    |   2 +-
 horizon.example.yaml                     |   4 +
 28 files changed, 316 insertions(+), 443 deletions(-)

diff --git a/apps/bff/src/config/schema.ts b/apps/bff/src/config/schema.ts
index 9ac009a..0adbdd5 100644
--- a/apps/bff/src/config/schema.ts
+++ b/apps/bff/src/config/schema.ts
@@ -176,6 +176,7 @@ const rbacSchema = z
           'logs:read',
           'topology:read',
           'profile:read',
+          'overview:read',
         ],
         // Viewer baseline plus the platform-monitoring reads (cluster
         // health + OAP internals). Maintainer's whole job is watching
@@ -187,6 +188,7 @@ const rbacSchema = z
           'logs:read',
           'topology:read',
           'profile:read',
+          'overview:read',
           'cluster:read',
           'inspect:read',
           'ttl:read',
diff --git a/apps/ui/src/shell/AppSidebar.vue b/apps/ui/src/shell/AppSidebar.vue
index 030c117..5f862fe 100644
--- a/apps/ui/src/shell/AppSidebar.vue
+++ b/apps/ui/src/shell/AppSidebar.vue
@@ -354,7 +354,7 @@ watch(
     </RouterLink>
 
     <nav ref="navRef" class="sw-nav">
-      <!-- Overviews are gated by `overview:read` (operator / admin). -->
+      <!-- Overviews are gated by `overview:read`. -->
       <template v-if="auth.hasVerb('overview:read')">
         <div class="sw-nav-section sw-nav-section--icon">
           <Icon :name="sectionIcon('Overviews')" />
diff --git a/docs/README.md b/docs/README.md
index f098e10..cd2e4d5 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -6,17 +6,16 @@ The sidebar on the left of this site is the canonical entry 
point — every sect
 
 ## How it's organized
 
-- **Design Target** — what Horizon UI is built for and how it differs from 
booster-ui.
-- **Compatibility** — which OAP version + modules + ports Horizon needs, and 
what each pane of the Cluster Status page actually probes.
-- **Setup** — quick start, the container image, and a field-by-field reference 
for `horizon.yaml`.
-- **Access Control** — local + LDAP auth, break-glass admin, the role / verb 
model, the audit log, and the admin pages.
-- **Customization** — how the sidebar is composed from OAP layers, how to 
author layer dashboard and overview templates, and the end-to-end recipe for 
adding a new layer.
-- **Components** — field-by-field reference for every widget primitive and the 
wrapped chart components.
-- **Operate** — Cluster Status & Metadata, and the Inspect page (metric 
catalog + entity enumerator).
+- **Setup** — quick start, container deployment, and the `horizon.yaml` 
settings operators usually need.
+- **Compatibility** — OAP version, network ports, required modules, and 
cluster-status checks.
+- **Operate** — Cluster Status, Data Retention, OAP Configuration, and Metrics 
Inspect.
+- **Access Control** — local users, LDAP login, break-glass access, roles, 
audit log, and admin pages.
+- **Customization** — layer menus, dashboard templates, overview templates, 
and adding a layer.
+- **Reference** — design target and widget reference for template authors.
 
 ## Quick orientation
 
-Horizon runs as two pieces: a Vue single-page app (the UI) and a Backend For 
Frontend (the BFF). The UI **only** talks to the BFF; the BFF is the single 
place that talks to OAP — query GraphQL, admin REST, and Zipkin. Every OAP-side 
requirement is enforced once, in the BFF, not scattered through the UI.
+Horizon runs as a browser UI plus a Horizon server process. The browser talks 
only to the Horizon server; the server talks to OAP for query, admin, and 
Zipkin traffic. That keeps OAP connectivity, credentials, and compatibility 
checks in one place.
 
 ## Where to start, by role
 
@@ -26,7 +25,7 @@ Horizon runs as two pieces: a Vue single-page app (the UI) 
and a Backend For Fro
 | Wiring up LDAP / configuring roles | Access Control → LDAP Backend, then 
RBAC. |
 | Customizing per-layer dashboards | Customization → Layer Dashboard 
Templates. |
 | Building a "war room" overview | Customization → Overview Templates. |
-| Diagnosing a "module disabled" warning | Compatibility → Required OAP 
Modules and Cluster Status Check Sequence. |
+| Diagnosing a "module disabled" warning | Compatibility → Required OAP 
Modules, then Operate → Cluster Status. |
 
 ## Live demo
 
diff --git a/docs/access-control/admin-pages.md 
b/docs/access-control/admin-pages.md
index f54c799..350c131 100644
--- a/docs/access-control/admin-pages.md
+++ b/docs/access-control/admin-pages.md
@@ -22,7 +22,7 @@ After successful login, the UI redirects to the page the user 
was bounced from (
 ## Auth Status
 
 **Path:** `/admin/auth-status`
-**Verb:** `auth:read` (maintainer, admin)
+**Verb:** `auth:read` (admin by default; grant explicitly for delegated 
security admins)
 
 Auto-refreshes every 30 seconds. The single pane for "is my auth wiring 
correct?" Shows:
 
@@ -111,7 +111,9 @@ Read-only. To change roles, edit `rbac.roles` in 
`horizon.yaml`; hot-reload appl
 | `/admin/auth-status` | `auth:read` | (none built-in; assign explicitly) |
 | `/admin/users` | `user:read` | (none built-in; assign explicitly) |
 | `/admin/roles` | `role:read` | (none built-in; assign explicitly) |
-| `/admin/inspect` | `inspect:read` | maintainer, operator, admin |
+| `/operate/inspect` | `inspect:read` | maintainer, operator, admin |
+| `/operate/ttl` | `ttl:read` | maintainer, operator, admin |
+| `/operate/config` | `config:read` | maintainer, operator, admin |
 
 `auth:read`, `user:read`, `role:read` are **not** in any default role. Either 
grant them via a custom role:
 
diff --git a/docs/access-control/audit-log.md b/docs/access-control/audit-log.md
index 6a008ec..125ab14 100644
--- a/docs/access-control/audit-log.md
+++ b/docs/access-control/audit-log.md
@@ -2,23 +2,21 @@
 
 The audit log records sensitive operations as JSON Lines, one event per line, 
append-only. Configure the path via `audit.file` in `horizon.yaml` (see [Setup 
→ audit](../setup/audit.md)).
 
-## Event schema
+## Event Fields
 
 Each event has these fields:
 
-```ts
-interface AuditEvent {
-  ts: string;                        // ISO-8601 timestamp
-  actor: string | null;              // username; null for system events
-  action: string;                    // e.g. 'auth.login', 'rule.addOrUpdate'
-  verb?: string;                     // RBAC verb checked, if any
-  target?: string;                   // resource id / name
-  outcome: string;                   // 'success' | 'failure' | 'break-glass' 
| HTTP status | OAP status
-  details?: Record<string, unknown>; // free-form context
-  fromIp?: string;                   // requester IP
-  sessionId?: string;
-}
-```
+| Field | Meaning |
+|---|---|
+| `ts` | ISO-8601 timestamp. |
+| `actor` | Username, or `null` for system events. |
+| `action` | Operation name, such as `auth.login` or `rule.addOrUpdate`. |
+| `verb` | RBAC verb checked, when applicable. |
+| `target` | Resource id or name, when applicable. |
+| `outcome` | `success`, `failure`, `break-glass`, or the upstream HTTP/OAP 
status. |
+| `details` | Extra context for the operation. |
+| `fromIp` | Requester IP. |
+| `sessionId` | Session id, when a session exists. |
 
 One event per line, `\n`-terminated. Use `jq -c` to filter:
 
@@ -28,7 +26,7 @@ tail -f horizon-audit.jsonl | jq -c 'select(.action | 
startswith("auth."))'
 
 ## Recorded actions
 
-The recorded set evolves with the codebase. As of the current build:
+The recorded set can grow between releases. In 0.5.0:
 
 | Action | Outcome values | Notes |
 |---|---|---|
diff --git a/docs/access-control/rbac.md b/docs/access-control/rbac.md
index c246390..00e8963 100644
--- a/docs/access-control/rbac.md
+++ b/docs/access-control/rbac.md
@@ -13,7 +13,7 @@ Sessions capture the **role list** at login time, and the 
verbs they grant are r
 
 ## Verb vocabulary
 
-Twenty-eight verbs grouped into areas:
+Known verbs are grouped into areas:
 
 ### Data reads (the public catalog)
 
@@ -25,12 +25,13 @@ Twenty-eight verbs grouped into areas:
 | `logs:read` | Logs tab on any layer, log detail page. |
 | `topology:read` | Topology tab, topology widgets on overviews. |
 | `profile:read` | Profiling tab (results read-only). |
+| `overview:read` | Public overview dashboards. |
 
 ### Operate — dashboards, rules, diagnostics
 
 | Verb | Gates |
 |---|---|
-| `overview:read` / `overview:write` | Overview templates admin page 
(`/admin/overview-templates`): list / edit. |
+| `overview:write` | Overview templates admin page 
(`/admin/overview-templates`): edit. |
 | `dashboard:read` / `dashboard:write` | Layer dashboard templates admin page: 
list / edit. |
 | `alarm-setup:read` / `alarm-setup:write` | Alarm Setup page: list / edit. |
 | `alarm-rule:read` / `alarm-rule:write` | Alarm Rule catalog: list / edit. |
@@ -48,7 +49,9 @@ Twenty-eight verbs grouped into areas:
 | Verb | Gates |
 |---|---|
 | `cluster:read` | Cluster Status page (`/operate/cluster`). |
-| `inspect:read` | Inspect page (`/admin/inspect`). |
+| `ttl:read` | Data Retention page (`/operate/ttl`). |
+| `config:read` | OAP Configuration page (`/operate/config`). |
+| `inspect:read` | Metrics Inspect page (`/operate/inspect`). |
 
 ### Admin surface
 
@@ -59,7 +62,7 @@ Twenty-eight verbs grouped into areas:
 | `role:read` | Roles & Permissions admin page (`/admin/roles`). |
 | `role:write` | Reserved. |
 | `auth:read` | Auth Status admin page (`/admin/auth-status`) + LDAP probe. |
-| `auditRead` | Reserved (audit log not yet exposed via API). |
+| `audit:read` | Reserved (audit log not yet exposed via API). |
 
 ### Special
 
@@ -90,7 +93,7 @@ Default definitions (used when `rbac.roles` is not 
overridden):
 Read-only data catalog. Deliberately limited — does not include `*:read` so a 
viewer cannot peek at rule definitions, live-debug sessions, setup screens, or 
platform internals.
 
 ```
-metrics:read, alarms:read, traces:read, logs:read, topology:read, profile:read
+metrics:read, alarms:read, traces:read, logs:read, topology:read, 
profile:read, overview:read
 ```
 
 ### `maintainer`
@@ -98,7 +101,7 @@ metrics:read, alarms:read, traces:read, logs:read, 
topology:read, profile:read
 Viewer + platform monitoring.
 
 ```
-viewer baseline + cluster:read, inspect:read
+viewer baseline + cluster:read, ttl:read, config:read, inspect:read
 ```
 
 ### `operator`
@@ -177,6 +180,7 @@ roles:
     - traces:read
     - logs:read
     - topology:read
+    - overview:read
     - inspect:read       # so they can browse the catalog
 landingByRole:
   on-call: /alarms       # land on the alarm board
diff --git a/docs/components/charts.md b/docs/components/charts.md
index 6403c6c..84b7fb2 100644
--- a/docs/components/charts.md
+++ b/docs/components/charts.md
@@ -1,145 +1,60 @@
 # Charts
 
-Horizon renders metrics through a small set of chart kinds. This page 
describes each kind, the inputs it accepts, and how it behaves. For the 
dashboard/overview widget types that select these charts, see [Dashboard 
Widgets](dashboard-widgets.md).
+Charts are the visual forms used by dashboard and overview widgets. Most users 
choose a widget type rather than a chart directly; this page helps template 
authors understand what each chart is good for.
 
-## Time chart
+## Time Chart
 
-**Used by:** `line` dashboard widget; ad-hoc embeds in feature pages.
+Used by `line` dashboard widgets.
 
-**Renders:** Multi-series line chart.
+Best for metrics that change over time: throughput, latency, error rate, queue 
depth, JVM memory, CPU, and similar series.
 
-### Props
+Behavior:
 
-| Prop | Type | Default | Notes |
-|---|---|---|---|
-| `series` | `Series[]` | required | One per line. |
-| `height` | number | 180 | Fixed pixel height. |
-| `unit` | string | — | Optional unit suffix in tooltips. |
-| `accent` | string | `var(--sw-accent)` | CSS var or hex for the first 
series. Subsequent series cycle through the palette. |
-| `format` | `int` \| `decimal` \| `compact` | — | Axis and tooltip number 
formatting. |
+- Supports one or more lines.
+- Shows a legend when there is more than one series.
+- Supports a second y-axis for mixed units, such as throughput and latency.
+- Shares hover position with other time charts on the same page, so operators 
can compare the same moment across panels.
 
-### `Series`
+Use `card` instead when the MQE expression returns a single scalar.
 
-```ts
-interface Series {
-  label: string;
-  data: Array<number | null>;
-  yAxisIndex?: number;   // 0 = left (default), 1 = right
-  unit?: string;
-}
-```
+## Top List
 
-### Behavior
+Used by `top` dashboard widgets.
 
-- Dual y-axis appears when any series has `yAxisIndex: 1`.
-- Legend visible iff `series.length > 1`.
-- Smooth lines with circle point markers.
-- Tooltip is positioned so it does not clip near grid edges.
-- **Synced crosshairs**: hovering broadcasts to peer time charts on the same 
page so they highlight the same time.
-- Data-only updates animate smoothly; structure changes (series count, label 
set) do a full replace.
+Best for ranked lists: slow endpoints, high-traffic services, worst error 
rates, busiest instances.
 
-## Top list
+Behavior:
 
-**Used by:** `top` dashboard widget.
+- Shows rank, name, value, and a proportional background bar.
+- Supports tabs when the widget has multiple ranking expressions.
+- Rows can navigate to an entity page when the result carries an entity 
reference.
 
-**Renders:** Sorted list with optional tab switcher.
+Use `line` instead when the expression returns a time series.
 
-### Props
+## Alarms Timeline
 
-| Prop | Type | Default | Notes |
-|---|---|---|---|
-| `items` | `ReadonlyArray<DashboardTopItem>` | — | Single list mode. |
-| `groups` | `ReadonlyArray<TopGroup>` | — | Multi-list mode (mutually 
exclusive with `items`). |
-| `unit` | string | — | Widget-level unit suffix. |
-| `color` | string | `var(--sw-accent)` | Bar color. |
+Used on the Alarms page.
 
-### `TopGroup`
+Best for triage during an incident. It buckets firing and recovered alarms 
over time and lets the operator select a time range for the alarm table below.
 
-```ts
-interface TopGroup {
-  label: string;
-  expression?: string;     // surfaced in tab tooltip
-  unit?: string;           // per-tab unit override
-  items: DashboardTopItem[];
-}
-```
+Behavior:
 
-### `DashboardTopItem`
-
-```ts
-interface DashboardTopItem {
-  name: string;
-  value: number | null;
-}
-```
-
-### Layout
-
-- Rank column (18 px) | name (flex) | value (auto).
-- Background fill bar normalized to the maximum value (per tab in multi-list 
mode).
-- Tabs shown when `groups.length > 1`.
-
-## Alarms timeline
-
-**Used by:** Alarms page (full timeline above the alarm table).
-
-**Renders:** Per-minute stacked bar chart of firing + recovered alarms, with 
brush selection.
-
-### Props
-
-| Prop | Type | Default | Notes |
-|---|---|---|---|
-| `alarms` | `AlarmMessage[]` | required | Alarm messages to bucket. |
-| `startTime` | number | required | Window start (ms). |
-| `endTime` | number | required | Window end (ms). |
-| `height` | number | 110 | Pixel height. |
-| `selectedRange` | `{ startTime, endTime } \| null` | null | Current brush 
selection. |
-
-### Emits
-
-| Event | Payload | When |
-|---|---|---|
-| `select-time-range` | `{ startTime, endTime }` | Brush completed or pin flag 
clicked. |
-| `clear-selection` | — | Empty area click or parent clears selection. |
-
-### Behavior
-
-- Two stacked series per minute bucket: firing (red), recovered (green).
-- Pin flags on non-zero buckets with count labels.
-- Brush (`lineX`) for range selection. Snaps to minute boundaries.
-- Click on non-zero point → selects that single minute. Click on zero → clears 
selection.
+- Shows firing and recovered alarms as stacked bars.
+- Clicking a busy minute narrows the alarm list to that minute.
+- Dragging a range narrows the alarm list to the selected window.
 
 ## Sparkline
 
-**Used by:** Inline tiles, sidebar mini-charts, layer service-list picker 
(when a column carries a trend).
-
-**Renders:** Tiny inline trend line — lightweight enough to render dozens per 
page.
-
-### Props
-
-| Prop | Type | Default | Notes |
-|---|---|---|---|
-| `values` | `Array<number \| null>` | required | Data points. `null` = gap. |
-| `width` | number | 56 | Internal coord width. |
-| `height` | number | 14 | Internal coord height. |
-| `color` | string | `var(--sw-accent)` | Line color. |
-| `stroke` | number | 1.25 | Line width (px). |
-| `fluid` | boolean | false | Stretch to container width. |
-| `crosshairBucket` | number \| null | null | Shared hover index (for synced 
sparklines). |
-
-### Emits
+Used in compact places such as tiles, sidebars, and picker rows.
 
-| Event | Payload | When |
-|---|---|---|
-| `bucket-hover` | bucket index | Pointer over the chart. |
-| `bucket-leave` | — | Pointer leaves the chart. |
+Best for small trend hints where a full chart would be too heavy.
 
-### Behavior
+Behavior:
 
-- Fallback single dot when fewer than 2 finite samples.
-- Gap bridging on `null` entries (line breaks).
-- No interactivity beyond hover broadcasting.
+- Renders a tiny trend line.
+- Shows a single dot when there is only one usable sample.
+- Shares hover position with related sparklines when the page supports it.
 
-## Theming
+## Colors
 
-Chart colors follow the active design theme. Per-chart accents default to the 
theme accent and update live when the theme is switched — no reload needed. Hex 
color strings are accepted for one-off cases (e.g. severity colors); prefer the 
theme accent for anything that should follow theming.
+Charts follow the active Horizon theme. Use the layer accent or the theme 
accent for normal metrics, and reserve explicit colors for semantic states such 
as severity or error.
diff --git a/docs/components/dashboard-widgets.md 
b/docs/components/dashboard-widgets.md
index ad4b8cb..ba9f88d 100644
--- a/docs/components/dashboard-widgets.md
+++ b/docs/components/dashboard-widgets.md
@@ -14,29 +14,12 @@ Five widget types render on per-layer dashboards. Each 
`widget.type` you set in
 
 ## Common widget shape
 
-```ts
-interface DashboardWidget {
-  id: string;
-  title: string;
-  tip?: string;
-  type: 'card' | 'line' | 'top' | 'record' | 'table';
-  expressions: string[];
-  expressionLabels?: string[];
-  expressionUnits?: string[];
-  expressionAxes?: number[];
-  unit?: string;
-  format?: 'int' | 'decimal' | 'compact';
-  tableHeaders?: [string, string];
-  showTableValues?: boolean;
-  span?: number;
-  rowSpan?: number;
-  visibleWhen?: string;
-  layerScope?: boolean;
-}
-```
-
 | Field | Notes |
 |---|---|
+| `id` | Unique widget id within the dashboard. |
+| `title` | Widget title shown in the card header. |
+| `tip` | Optional hover hint. |
+| `type` | One of `card`, `line`, `top`, `record`, or `table`. |
 | `expressions[]` | MQE expressions. `card` typically uses one; `line` 
one-per-series; `top` one-per-tab; `table` one labeled `latest(…)` metric. |
 | `expressionLabels[]` | Used by `top` for tab labels and by `line` for legend 
names. |
 | `expressionUnits[]` | Per-expression unit override (mixed-unit charts). |
@@ -258,7 +241,7 @@ The predicate is evaluated on every data refresh; the 
widget disappears (rather
 | `top_n(...)` returning labeled list | `top` |
 | Record-shaped output (slow SQL, slow gRPC) | `record` |
 
-The widget editor (planned) will warn on type / MQE mismatches. The schema 
does not enforce — author carefully.
+The widget editor helps catch common type / MQE mismatches, but it does not 
replace testing against a live OAP window. After changing a widget, preview it 
with data before publishing.
 
 ## Per-scope widget sets
 
diff --git a/docs/customization/adding-a-new-layer.md 
b/docs/customization/adding-a-new-layer.md
index aa6200a..f600edc 100644
--- a/docs/customization/adding-a-new-layer.md
+++ b/docs/customization/adding-a-new-layer.md
@@ -94,10 +94,10 @@ Each iteration is template + BFF restart. The schema is 
validated at startup; a
 
 Once the template stabilizes:
 
-- Open `/admin/layer-templates`, find the layer, click edit, save (no actual 
changes needed — the act of saving promotes it to admin-managed).
+- Open `/admin/layer-dashboards`, find the layer, click edit, then save 
locally.
 - Subsequent edits go through the admin UI; you no longer need to rebuild and 
restart the BFF for cosmetic changes.
 
-The bundled file remains in the BFF as the fallback; admin-managed copies 
override it.
+The local bundled file remains the fallback. After you publish, the OAP-stored 
template becomes the runtime copy every Horizon instance reads.
 
 ### 10. Add an overview entry (optional)
 
diff --git a/docs/customization/layer-templates.md 
b/docs/customization/layer-templates.md
index 7d61248..3dc7ed6 100644
--- a/docs/customization/layer-templates.md
+++ b/docs/customization/layer-templates.md
@@ -220,29 +220,23 @@ A layer without an explicit `instance` widget set will 
reuse `service` widgets o
 
 ### Dashboard widget fields
 
-```ts
-interface DashboardWidget {
-  id: string;
-  title: string;
-  tip?: string;
-  type: 'card' | 'line' | 'top' | 'record';
-  expressions: string[];
-  expressionLabels?: string[];      // tab labels for 'top'
-  expressionUnits?: string[];       // per-expression unit override
-  expressionAxes?: number[];        // 0 = left, 1 = right (dual y-axis)
-  unit?: string;                    // widget-level unit suffix
-  format?: 'int' | 'decimal' | 'compact';
-  span?: number;                    // 12-col span; default 4
-  rowSpan?: number;                 // row count; default 1
-  visibleWhen?: string;             // visibility predicate
-  layerScope?: boolean;             // evaluate against layer rather than 
selected service
-  // legacy 24-col coordinates (back-compat with old templates):
-  x?: number; y?: number; w?: number; h?: number;
-}
-```
-
 | Field | Notes |
 |---|---|
+| `id` | Unique widget id within the dashboard. |
+| `title` | Widget title shown in the card header. |
+| `tip` | Optional hover hint. |
+| `type` | Widget kind, usually `card`, `line`, `top`, `record`, or `table`. |
+| `expressions[]` | MQE expressions to run. |
+| `expressionLabels[]` | Tab labels for `top`, legend labels for `line`. |
+| `expressionUnits[]` | Per-expression unit override. |
+| `expressionAxes[]` | `0` for left axis, `1` for right axis on dual-axis line 
charts. |
+| `unit` | Widget-level unit suffix. |
+| `format` | `int`, `decimal`, or `compact`. |
+| `span` | 12-column width. Default 4. |
+| `rowSpan` | Row count. Default 1. |
+| `visibleWhen` | Visibility predicate. |
+| `layerScope` | Evaluate against the whole layer rather than the selected 
service. |
+| `x`, `y`, `w`, `h` | Legacy coordinates kept for old templates. Prefer 
`span` and `rowSpan`. |
 | `type` | `card` for single scalar (MQE collapses to one number); `line` for 
time-series; `top` for sorted list; `record` for tabular records (slow SQL, 
slow statements). |
 | `expressions[]` | Array of MQE expressions. `card` typically uses one; 
`line` uses one per series; `top` may use multiple (each becomes a tab). |
 | `expressionLabels[]` | Used by `top` to label each tab. |
@@ -325,9 +319,9 @@ Service-name parsing rule. Extracts a cluster (or other 
token) from the OAP-repo
 
 When set, the layer's service list groups by `cluster`. Without it, services 
are listed flat.
 
-## Admin editor — edit locally, publish on your terms
+## Admin Editor
 
-Layer templates are editable at runtime via `/admin/layer-templates` (verb 
`dashboard:write`). The editor shows the JSON tree with per-field type-aware 
controls; changes are validated against the same schema as the bundled files.
+Layer templates are editable at runtime via **Dashboard setup → Layer 
dashboards** (`/admin/layer-dashboards`, verb `dashboard:write`). The editor 
shows layer-specific controls for service, instance, endpoint, topology, trace, 
log, and profiling views.
 
 The save/publish model has two steps:
 
@@ -357,7 +351,7 @@ Read the bundled JSON for the closest layer to yours before 
authoring a new temp
 
 ## Hot reload
 
-Template changes (bundled or admin-edited) take effect on the next `/api/menu` 
or `/api/layer/:key/dashboard/config` request. Browsers see the new shape on 
the next page navigation. No BFF restart needed.
+Template changes made in the admin editor take effect on the next menu or 
dashboard refresh. Bundled file changes made outside Horizon require a BFF 
restart.
 
 ## Common patterns
 
diff --git a/docs/customization/menu-structure.md 
b/docs/customization/menu-structure.md
index 9d606bf..7986ee5 100644
--- a/docs/customization/menu-structure.md
+++ b/docs/customization/menu-structure.md
@@ -1,150 +1,83 @@
-# Menu Structure
-
-The Horizon sidebar is **composed from active OAP layers**, not hand-written. 
There is no "edit sidebar items" page — what shows up in the sidebar is a 
function of:
-
-1. Which layers OAP currently exposes via `listLayers`.
-2. The bundled per-layer JSON templates 
(`apps/bff/src/bundled_templates/layers/<key>.json`).
-3. Per-user preferences (landing order, visibility toggles) held in the setup 
store.
-
-This page documents how those three combine into the live sidebar.
-
-## Data flow
-
-1. **OAP discovery.** Layers reported by `listLayers` are "active".
-2. **Template merge.** For each active layer, the bundled 
`bundled_templates/layers/<key>.json` (or defaults if absent) is merged with 
OAP-provided data, contributing template cosmetics: `alias`, `color`, `group`, 
`visibility`, `caps`, `slots`, `header`, `overview`, `log`, `traces`, `naming`, 
`documentLink`.
-3. **Counts.** Each layer carries a service count from `listServices(layer)`. 
The count is `-1` if OAP is unreachable.
-4. **Sidebar render.** The sidebar shows the layer list, ordered per the 
user's landing-order preference.
-
-## The `MenuResponse` shape
-
-```ts
-interface MenuResponse {
-  layers: LayerDef[];
-  generatedAt: number;
-  oap: { reachable: boolean; queryUrl: string; error?: string };
-}
-
-interface LayerDef {
-  key: string;                          // OAP layer enum (UPPER_SNAKE)
-  name: string;                         // Display name from OAP
-  color: string;                        // Sidebar accent (hex or CSS var)
-  serviceCount: number;                 // From listServices; -1 if OAP 
unreachable
-  active: boolean;                      // True iff returned by listLayers
-  group?: string;                       // Sidebar grouping label
-  visibility?: 'public' | 'operate';    // Section placement
-  normal?: boolean | null;              // Affects MQE scope (per OAP)
-  level: number | null;                 // From listLayerLevels (sort hint)
-  documentLink?: string;                // External docs URL
-  slots: LayerSlots;                    // Entity term overrides
-  caps: LayerCaps;                      // Feature toggles
-  header?: LayerHeaderConfig;           // Service-list picker columns
-  overview?: LayerOverviewConfig;       // Overview tile config
-  log?: LogConfig;                      // Logs tab scope
-  traces?: { source?: 'native' | 'zipkin' | 'both' };
-  naming?: ServiceNamingRule;
-}
-```
-
-## Sidebar sections
-
-The sidebar has two main sections + the static Operate group:
-
-### Layers
+# Menu and Layers
 
-Active, public layers (`visibility: 'public'`, `serviceCount > 0`). Sorted by:
+Horizon's sidebar follows the data OAP reports. You do not hand-build a menu 
tree in Horizon; you make OAP expose layers, then use templates and user 
preferences to control how those layers appear.
 
-1. Per-user `landing.priority` from the setup store.
-2. Falls back to `level` from `listLayerLevels` when no user priority is set.
+## What Controls the Sidebar
 
-A layer with `serviceCount === 0` is hidden from the Layers section but still 
available for the admin's setup screen ("enable this layer when services 
appear").
+| Source | What it controls |
+|---|---|
+| OAP layers | Whether a layer exists and whether it has services. |
+| Layer templates | Display name, color, group, visible tabs, service-list 
columns, trace/log behavior, and dashboard widgets. |
+| User preference | Personal ordering of visible layers on the landing page 
and sidebar. |
+| RBAC | Whether operate, dashboard setup, and admin pages are visible for the 
signed-in user. |
 
-### Operate (per-layer, optional)
+The result is intentionally reactive: when OAP starts reporting data for a 
layer, Horizon shows it; when a user lacks a permission, Horizon hides the page 
link.
 
-Layers with `visibility: 'operate'` go under the Operate group instead of 
Layers. Used for self-observability layers (e.g., the OAP cluster's own metrics 
layer).
+## Main Sidebar Areas
 
-### Operate (static)
+| Area | What appears there |
+|---|---|
+| Overviews | Public overview dashboards, when the user has `overview:read`. |
+| Alarms | The active alarm board, when the user has `alarms:read`. |
+| Layers | Active public OAP layers with at least one service. |
+| Platform monitoring | Cluster Status, Data Retention, and OAP Configuration. 
|
+| Operate | Alerting rules, DSL Management, Live Debugger, Capture History, 
and Metrics Inspect. |
+| Dashboard setup | Overview templates, Layer dashboards, Alert page setup, 
and Global defaults. |
+| Admin | Users, Auth status, and Roles & permissions. |
 
-These items are always present (RBAC permitting):
+Only rows the current user can open are shown.
 
-- Cluster Status (`cluster:read`)
-- Inspect (`inspect:read`)
-- DSL Management (`rule:read`)
-- Live Debugger (`live-debug:read`)
-- Alarm Setup (`alarm-setup:read`)
-- Alarm Rules (`alarm-rule:read`)
+## Layer Visibility
 
-These are not layer-derived; they are first-class Horizon features.
+A layer appears under **Layers** when all of these are true:
 
-### Admin (RBAC permitting)
+1. OAP reports the layer.
+2. OAP reports at least one service in that layer.
+3. The layer template uses public visibility.
 
-- Auth Status, Users, Roles & Permissions (each verb-gated; see [Admin 
Pages](../access-control/admin-pages.md)).
-- Overview Templates editor (`overview:write`).
-- Layer Templates editor (`dashboard:write`).
+If a layer is meant for SkyWalking self-observability rather than application 
observability, set its template visibility to `operate`; Horizon places it 
under the Operate area instead of the main Layers list.
 
-## Per-layer composition
+## First Tab for a Layer
 
-When a user clicks a layer in the sidebar, the first enabled sub-route is 
picked from this priority order:
+When a user clicks a layer, Horizon opens the first enabled tab in this order:
 
-```
-service → instance → endpoint → topology → trace → logs → profiling
+```text
+service -> instance -> endpoint -> topology -> trace -> logs -> profiling
 ```
 
-The enablement comes from the template's `components` flags (mapped onto 
`caps` in the menu response):
-
-```json
-{
-  "key": "GENERAL",
-  "components": {
-    "service": true,
-    "instance": true,
-    "endpoint": true,
-    "topology": true,
-    "trace": true,
-    "logs": false,
-    "profiling": true
-  }
-}
-```
-
-A layer with `components.service: false` and only `topology: true` will land 
directly on the Topology tab when clicked.
+Disable unsupported tabs in the layer template. For example, a layer without 
traces should turn the trace tab off so users do not land on an empty page.
 
-## Customization surface
+## Common Changes
 
-| Want to | Edit |
+| Goal | Where to change it |
 |---|---|
-| Rename a layer in the sidebar | `alias` in 
`bundled_templates/layers/<key>.json` |
-| Change a layer's color | `color` |
-| Group several layers under one collapsible header | `group` (same string on 
multiple layers) |
-| Move a layer into the Operate section | `visibility: operate` |
-| Hide a tab on a layer | flip the corresponding `components.*` flag |
-| Change which sub-route is the landing tab | reorder via `components` flags 
(the leftmost enabled wins, per priority above) |
-| Add an external doc link | `documentLink` |
-| Re-order layers in the sidebar | per-user via the landing-order control 
(setup store) |
-| Add a brand-new layer | OAP-side first (must show up in `listLayers`), then 
add a template — see [Adding a New Layer](adding-a-new-layer.md) |
-
-The menu is **never user-editable as a tree** in the UI. Customization is 
always via:
+| Rename a layer | Layer template `alias`. |
+| Change a layer color | Layer template `color`. |
+| Group related layers | Same layer template `group` value on each layer. |
+| Move a layer to Operate | Layer template `visibility: operate`. |
+| Hide a tab | Layer template `components`. |
+| Change layer order | User layer-order preference. |
+| Add a new layer | Add it in OAP first, then add a Horizon layer template. |
 
-- Templates (for cosmetics + feature toggles), or
-- The setup store (for per-user ordering), or
-- OAP itself (for layer existence).
+Use **Dashboard setup → Layer dashboards** for normal template edits. Save 
locally to preview, then sync to OAP when you want the change published for 
everyone.
 
-## What "active" means
+## When OAP Is Unreachable
 
-A layer is `active: true` when OAP returns it from `listLayers`. An inactive 
layer can still appear in the menu response (so the admin can enable it via the 
setup page) but is **not shown in the sidebar**. Once OAP starts reporting it 
(e.g., once data arrives for that layer), the sidebar shows it on the next 
`/api/menu` refresh.
+If OAP is unreachable, Horizon keeps the last known sidebar shape in memory 
and shows an OAP-unreachable banner. Service counts may show as unknown until 
OAP is reachable again.
 
-This means: **stand up your OAP receivers first, install/configure them to 
ingest data for the layer you want, then refresh Horizon**. The sidebar is 
purely reactive to OAP state.
+This avoids the worst failure mode during a short OAP outage: an empty sidebar 
that makes operators think configuration disappeared.
 
-## When OAP is unreachable
+## Troubleshooting
 
-`/api/menu` returns `oap.reachable: false` and `serviceCount: -1` for every 
layer. The sidebar still renders the last-known shape (the BFF caches the most 
recent successful response in memory) with an "OAP unreachable" banner. This 
avoids the UX collapse of a fully empty sidebar during a brief OAP blip.
-
-## Polling cadence
-
-- The UI fetches `/api/menu` on mount and on tab focus (return-to-tab triggers 
a refresh).
-- The BFF does not cache `/api/menu` responses cross-request — every call 
re-queries OAP. For very large layer counts this can be tuned; file an issue if 
you see latency.
+| Symptom | Check |
+|---|---|
+| Layer missing | Confirm OAP reports the layer and at least one service. |
+| Layer appears in Operate, not Layers | Check template visibility. |
+| Expected tab missing | Check the layer template components. |
+| User cannot see an admin page | Check their role grants in Roles & 
permissions. |
 
 ## Related
 
-- [Layer Dashboard Templates](layer-templates.md) — the JSON shape that backs 
each layer.
-- [Overview Templates](overview-templates.md) — the war-room overviews 
(separate from the sidebar).
-- [Adding a New Layer](adding-a-new-layer.md) — end-to-end recipe.
+- [Layer Dashboard Templates](layer-templates.md)
+- [Overview Templates](overview-templates.md)
+- [Add a Layer](adding-a-new-layer.md)
diff --git a/docs/customization/overview-templates.md 
b/docs/customization/overview-templates.md
index 1fdadd5..e3c4fb3 100644
--- a/docs/customization/overview-templates.md
+++ b/docs/customization/overview-templates.md
@@ -70,51 +70,23 @@ The 72 px row height is tuned for KPI tile content; widgets 
that need more verti
 
 ## Widget shape (common fields)
 
-```ts
-interface OverviewWidget {
-  id: string;
-  title: string;
-  tip?: string;
-  layer?: string;
-  type: 'metric' | 'topology' | 'section-break' | 'kpi-tile' | 'alarms' | 
'metric-composite';
-  span?: number;            // 1–12
-  rowSpan?: number;         // 1–8
-  // type-specific fields below
-  mqe?: string;             // metric
-  unit?: string;            // metric
-  aggregation?: 'sum' | 'avg'; // metric
-  cols?: number;            // section-break
-  kpis?: OverviewKpi[];     // kpi-tile, metric-composite
-  showCount?: boolean;      // kpi-tile
-  limit?: number;           // alarms
-}
-```
-
 | Field | Notes |
 |---|---|
 | `id` | Unique within the dashboard. |
 | `title` | Card title (not used by `section-break` — uses `title` as the 
section header). |
 | `tip` | Optional one-line hover hint next to the title. |
 | `layer` | Layer key (UPPER_SNAKE). Used to scope MQE evaluation. Optional 
for `section-break` and `alarms` (alarms can scope server-side if the layer is 
set). |
+| `type` | One of `metric`, `topology`, `section-break`, `kpi-tile`, `alarms`, 
or `metric-composite`. |
 | `span` | Column span. Defaults vary per widget type. |
 | `rowSpan` | Row span. Defaults vary per widget type. |
+| `mqe`, `unit`, `aggregation` | Metric-specific fields. |
+| `cols` | Section-break column count for following widgets. |
+| `kpis`, `showCount`, `limit` | Type-specific fields described below. |
 
 ## `OverviewKpi`
 
 Used by `kpi-tile` and `metric-composite`:
 
-```ts
-interface OverviewKpi {
-  label: string;
-  mqe?: string;
-  unit?: string;
-  aggregation?: 'sum' | 'avg';
-  style?: 'number' | 'progress-bar';
-  max?: number;
-  source?: 'mqe' | 'service-count';
-}
-```
-
 | Field | Notes |
 |---|---|
 | `label` | Row label. |
@@ -232,9 +204,9 @@ Following widgets render in a **6-column** grid (rather 
than 12) until the next
 
 Read-only — Horizon does not support acknowledge / close / silence operations. 
Alarm recovery is backend-automatic.
 
-## Admin editor
+## Admin Editor
 
-Overview templates are editable at runtime via `/admin/overview-templates` 
(verb `overview:write`). The editor:
+Overview templates are editable at runtime via **Dashboard setup → Overview 
templates** (`/admin/overview-templates`, verb `overview:write`). The editor:
 
 - Lists all bundled overviews + any added ones, with widget count and editable 
flag.
 - For each overview, shows the widget array with per-widget controls.
@@ -252,22 +224,16 @@ Overview templates are editable at runtime via 
`/admin/overview-templates` (verb
 - **Add / remove widgets** with the type picker.
 - **Preview** renders the in-progress template against live OAP data.
 
-Changes go through `POST /api/admin/overview-templates/:id`, validated 
server-side before being written.
+The save/publish model has two steps:
 
-## HTTP API
-
-| Method | Path | Verb | Notes |
-|---|---|---|---|
-| GET | `/api/admin/overview-templates` | `overview:read` | List all 
overviews. |
-| GET | `/api/admin/overview-templates/:id` | `overview:read` | Full config. |
-| POST | `/api/admin/overview-templates/:id` | `overview:write` | Replace 
config. Validated, cache invalidated on write. |
-| DELETE | `/api/admin/overview-templates/:id` | `overview:write` | Remove 
overview. |
+1. **Save locally.** The edit is written to the local bundled copy and renders 
immediately for preview. OAP is not changed yet.
+2. **Publish.** **Sync all to OAP** pushes diverged overview templates to OAP 
behind a confirmation that lists the affected templates.
 
-The view route `/overview/:id` calls `GET /api/overview/:id/data` (verb 
`metrics:read`) which evaluates the widgets server-side and returns the 
resolved value set.
+If the local copy differs from OAP, Horizon shows the template as 
**diverged**. Use **Show diff** to compare local and remote, **Keep my local 
edits** to preview, or **Use live** to discard the local copy and render the 
OAP version.
 
 ## Hot reload
 
-Bundled file changes require a BFF restart (templates are loaded at startup). 
Admin-API edits go through the cache and apply on the next data fetch — no 
restart needed.
+Admin editor changes apply on the next overview refresh. Bundled file changes 
made outside Horizon require a BFF restart.
 
 ## Common patterns
 
diff --git a/docs/design-target.md b/docs/design-target.md
index 48be514..49fbf83 100644
--- a/docs/design-target.md
+++ b/docs/design-target.md
@@ -48,7 +48,7 @@ The per-layer drill-down presents a single service through 
every data type SkyWa
 | Logs | log records | per-layer scope (service / instance / endpoint) |
 | Profiling | trace / eBPF / async profiler | scope-aware widget set |
 
-The renderer is template-driven (see [Customization → Layer Dashboard 
Templates](customization/layer-templates.md)). New layers do not require new 
Vue files; they require a new JSON template.
+The renderer is template-driven (see [Customization → Layer Dashboard 
Templates](customization/layer-templates.md)). New layers require a JSON 
template, not a custom UI build.
 
 ### Customization is the whole key
 
@@ -61,7 +61,7 @@ Every visual decision a site operator wants to make is 
template-driven:
 - **Overview content** — overview template JSON; type-aware admin editor edits 
each widget's only-relevant fields.
 - **Authentication / authorization** — `horizon.yaml` + `/admin/auth-status` 
page.
 
-There is no "add a custom Vue component" extension point — adding rendering 
primitives is a code change. Adding **content** is always configuration.
+There is no custom UI plugin extension point. Adding a new rendering primitive 
requires a Horizon release; adding **content** is configuration.
 
 ### Density beats whitespace
 
diff --git a/docs/menu.yml b/docs/menu.yml
index f21d5df..9cef5dc 100644
--- a/docs/menu.yml
+++ b/docs/menu.yml
@@ -14,57 +14,65 @@
 # limitations under the License.
 
 catalog:
-  - name: "Horizon UI Overview"
+  - name: "Overview"
     path: "/readme"
 
-  - name: "Design Target"
-    path: "/design-target"
-
-  - name: "Compatibility"
-    catalog:
-      - name: "OAP Version"
-        path: "/compatibility/oap-version"
-      - name: "Required OAP Modules"
-        path: "/compatibility/required-modules"
-      - name: "Network Ports"
-        path: "/compatibility/ports"
-      - name: "Cluster Status Check Sequence"
-        path: "/compatibility/cluster-status"
-
   - name: "Setup"
     catalog:
       - name: "Quick Start"
         path: "/setup/overview"
       - name: "Container Image"
         path: "/setup/container-image"
-      - name: "horizon.yaml Reference"
+      - name: "Configuration File"
         path: "/setup/horizon-yaml"
-      - name: "server"
+      - name: "Server Listener"
         path: "/setup/server"
-      - name: "oap"
+      - name: "OAP Connection"
         path: "/setup/oap"
-      - name: "auth"
+      - name: "Authentication"
         path: "/setup/auth"
-      - name: "rbac"
+      - name: "Access Control"
         path: "/setup/rbac"
-      - name: "session"
+      - name: "Sessions"
         path: "/setup/session"
-      - name: "audit"
+      - name: "Audit Log File"
         path: "/setup/audit"
-      - name: "setup / alarms files"
+      - name: "Runtime State Files"
         path: "/setup/files"
-      - name: "debugLog"
+      - name: "Wire Debug Log"
         path: "/setup/debug-log"
 
+  - name: "Compatibility"
+    catalog:
+      - name: "OAP Version"
+        path: "/compatibility/oap-version"
+      - name: "Network Ports"
+        path: "/compatibility/ports"
+      - name: "Required OAP Modules"
+        path: "/compatibility/required-modules"
+      - name: "Cluster Status Checks"
+        path: "/compatibility/cluster-status"
+
+  - name: "Operate"
+    catalog:
+      - name: "Cluster Status"
+        path: "/operate/cluster-metadata"
+      - name: "Data Retention"
+        path: "/operate/data-retention"
+      - name: "OAP Configuration"
+        path: "/operate/oap-configuration"
+      - name: "Metrics Inspect"
+        path: "/operate/inspect"
+
   - name: "Access Control"
     catalog:
-      - name: "Local Backend"
+      - name: "Local Users"
         path: "/access-control/local-backend"
-      - name: "LDAP Backend"
+      - name: "LDAP Login"
         path: "/access-control/ldap-backend"
       - name: "Break-Glass Access"
         path: "/access-control/break-glass"
-      - name: "RBAC: Roles & Verbs"
+      - name: "Roles and Permissions"
         path: "/access-control/rbac"
       - name: "Audit Log"
         path: "/access-control/audit-log"
@@ -73,27 +81,20 @@ catalog:
 
   - name: "Customization"
     catalog:
-      - name: "Menu Structure"
+      - name: "Menu and Layers"
         path: "/customization/menu-structure"
       - name: "Layer Dashboard Templates"
         path: "/customization/layer-templates"
       - name: "Overview Templates"
         path: "/customization/overview-templates"
-      - name: "Adding a New Layer"
+      - name: "Add a Layer"
         path: "/customization/adding-a-new-layer"
 
-  - name: "Components"
+  - name: "Reference"
     catalog:
+      - name: "Design Target"
+        path: "/design-target"
       - name: "Overview Widgets"
         path: "/components/overview-widgets"
       - name: "Dashboard Widgets"
         path: "/components/dashboard-widgets"
-      - name: "Charts"
-        path: "/components/charts"
-
-  - name: "Operate"
-    catalog:
-      - name: "Cluster Status & Metadata"
-        path: "/operate/cluster-metadata"
-      - name: "Inspect"
-        path: "/operate/inspect"
diff --git a/docs/operate/cluster-metadata.md b/docs/operate/cluster-metadata.md
index edffd08..4cf0a66 100644
--- a/docs/operate/cluster-metadata.md
+++ b/docs/operate/cluster-metadata.md
@@ -62,18 +62,6 @@ This is **informational only**. A 3-node OAP cluster behind 
one DNS name should
 
 The cluster-members section is **not** required for Horizon to function; it is 
a sanity check that the operator's expectation matches reality.
 
-## Coming soon strip
-
-The page documents upcoming additions inline. These are not implemented today; 
the strip is there so operators know what's on the roadmap and what is and 
isn't currently surfaced:
-
-- **Per-node module activity matrix** — module × provider × node grid. 
Requires per-node admin calls (currently the dump is consumed cluster-wide).
-- **Storage backend health** — BanyanDB / Elasticsearch / JDBC: connection 
pool, index lag, throughput.
-- **Receiver activity** — gRPC / HTTP / Kafka / OTLP: throughput, queue depth.
-- **Effective-configuration tree** — two-node diff of merged config (advanced 
troubleshooting).
-- **TTL & retention grid** — hot / warm / cold storage timeline per metric 
scope.
-
-When these land, this page is where they will surface; the data flow will 
follow the same pattern as today's panes (BFF preflight call → cached → polled).
-
 ## Reading the page during an incident
 
 1. **Both panes green?** Backend is fine; the problem is elsewhere (network 
from browser, BFF process, OAP-side data ingestion).
diff --git a/docs/operate/data-retention.md b/docs/operate/data-retention.md
new file mode 100644
index 0000000..563bc85
--- /dev/null
+++ b/docs/operate/data-retention.md
@@ -0,0 +1,28 @@
+# Data Retention
+
+Path: `/operate/ttl`. Verb: `ttl:read` (granted by maintainer, operator, 
admin).
+
+The Data Retention page shows how long the connected OAP keeps records and 
metrics. It is read-only; change retention in OAP configuration, then refresh 
Horizon.
+
+## What You See
+
+The page has two sections:
+
+| Section | What it means |
+|---|---|
+| Records | Event-style data such as traces, Zipkin traces, logs, and browser 
error logs. |
+| Metrics | Aggregated metric tiers: minute, hour, day, and metadata when the 
OAP backend exposes it. |
+
+Values are shown in days. A cold value means BanyanDB cold-stage retention. 
`no cold stage` means the connected OAP has no cold storage stage for that data 
class.
+
+## Requirements
+
+- OAP query port reachable from Horizon.
+- The logged-in user has `ttl:read`.
+- OAP supports `getRecordsTTL` and `getMetricsTTL`.
+
+If the page reports OAP unreachable, check `oap.queryUrl` and the network path 
to the query port.
+
+## During Operations
+
+Use this page before changing dashboard time windows, alert retention, or 
storage sizing. If a user expects old traces or metrics and Horizon cannot find 
them, compare the requested time range with this page first.
diff --git a/docs/operate/inspect.md b/docs/operate/inspect.md
index 0f843f1..5c9dd4d 100644
--- a/docs/operate/inspect.md
+++ b/docs/operate/inspect.md
@@ -1,6 +1,6 @@
 # Inspect
 
-Path: `/admin/inspect`. Verb: `inspect:read` (granted by maintainer, operator, 
admin).
+Path: `/operate/inspect`. Verb: `inspect:read` (granted by maintainer, 
operator, admin).
 
 The Inspect page lets the operator browse OAP's live metric catalog and 
enumerate the entities (services, instances, endpoints, processes, …) that have 
data for a given metric. It is built on OAP's **Inspect API**, which is 
**v11-only** — the page does not render on v10.
 
diff --git a/docs/operate/oap-configuration.md 
b/docs/operate/oap-configuration.md
new file mode 100644
index 0000000..6919d5f
--- /dev/null
+++ b/docs/operate/oap-configuration.md
@@ -0,0 +1,24 @@
+# OAP Configuration
+
+Path: `/operate/config`. Verb: `config:read` (granted by maintainer, operator, 
admin).
+
+The OAP Configuration page shows the connected OAP's resolved runtime 
configuration. It is read-only and intended for support and incident triage.
+
+## What You See
+
+Horizon reads OAP's admin-port config dump and groups keys by module. Use the 
filter box to find a module, selector, or value.
+
+Secret values are masked by OAP before Horizon displays them. Masked values 
appear as `******`.
+
+## Requirements
+
+- OAP 11.x.
+- `SW_ADMIN_SERVER=default` on OAP.
+- The OAP admin port, usually `17128`, reachable from Horizon.
+- The logged-in user has `config:read`.
+
+If the page reports the admin host is unreachable, check the OAP admin-server 
module and the Service, firewall, or load balancer exposing the admin port.
+
+## During Operations
+
+Use this page to confirm what OAP actually started with, especially after a 
config change, deployment rollback, or module enablement change. For module 
health, start with [Cluster Status](cluster-metadata.md); for the complete 
key/value view, use this page.
diff --git a/docs/setup/audit.md b/docs/setup/audit.md
index f927621..1097bdd 100644
--- a/docs/setup/audit.md
+++ b/docs/setup/audit.md
@@ -1,4 +1,4 @@
-# audit
+# Audit Log File
 
 Audit log file path. The format and event schema are documented in [Access 
Control → Audit Log](../access-control/audit-log.md); this page is the 
`horizon.yaml` shape.
 
diff --git a/docs/setup/auth.md b/docs/setup/auth.md
index f21a3aa..eefb7e9 100644
--- a/docs/setup/auth.md
+++ b/docs/setup/auth.md
@@ -1,4 +1,4 @@
-# auth
+# Authentication
 
 Authentication backend selection. Detailed per-backend configuration lives 
under [Access Control](../access-control/local-backend.md); this page is the 
`horizon.yaml` shape.
 
diff --git a/docs/setup/debug-log.md b/docs/setup/debug-log.md
index bd52578..376d83f 100644
--- a/docs/setup/debug-log.md
+++ b/docs/setup/debug-log.md
@@ -1,4 +1,4 @@
-# debugLog
+# Wire Debug Log
 
 Wire-level HTTP request/response log for troubleshooting OAP communication. 
**Off by default.** Very verbose — only use when actively debugging.
 
diff --git a/docs/setup/files.md b/docs/setup/files.md
index b6374fa..cb98dcc 100644
--- a/docs/setup/files.md
+++ b/docs/setup/files.md
@@ -1,4 +1,4 @@
-# setup and alarms
+# Runtime State Files
 
 State files for user-configured settings. Both write JSON, both are managed by 
the BFF, neither needs hand-editing.
 
diff --git a/docs/setup/oap.md b/docs/setup/oap.md
index c12c500..9fe789e 100644
--- a/docs/setup/oap.md
+++ b/docs/setup/oap.md
@@ -1,4 +1,4 @@
-# oap
+# OAP Connection
 
 Connectivity to the upstream Apache SkyWalking OAP cluster. Required for 
everything except the login page.
 
diff --git a/docs/setup/overview.md b/docs/setup/overview.md
index 0b2a1ab..9abdae4 100644
--- a/docs/setup/overview.md
+++ b/docs/setup/overview.md
@@ -1,18 +1,26 @@
 # Setup Quick Start
 
-This page is the smallest possible path from "no Horizon" to "Horizon in front 
of a running OAP". For per-section field reference, see the rest of the 
**Setup** chapter. For a containerized deployment, see [Container 
Image](container-image.md) instead — it covers image tags, env vars, volume 
mounts, and a Kubernetes example.
+This page is the shortest path from "no Horizon" to "Horizon in front of a 
running OAP". It uses the released binary layout first. For containerized 
deployments, use [Container Image](container-image.md).
 
 ## Prerequisites
 
 - Apache SkyWalking **OAP 11.x** (native). OAP 10.x runs the data-plane stack 
(dashboards, traces, logs, topology, alarms, profiling) but the entire admin 
port — Inspect, DSL Management, Live Debugger, Alarm Rule editor, Cluster 
Status → Admin pane, and OAP UI-template sync — is v11-only. See [Compatibility 
→ OAP Version](../compatibility/oap-version.md) for the feature-vs-version 
matrix.
 - Network reachability from the Horizon BFF to the OAP query port (`:12800`) 
and admin port (`:17128`). See [Network Ports](../compatibility/ports.md).
-- Node.js 20+, pnpm 10+ (for source builds; pinned via Corepack). A pre-built 
artifact only needs Node.js.
+- Node.js 20+ for the binary tarball. Source builds also need pnpm 10+.
 
 ## Five-step start
 
-### 1. Place `horizon.yaml` next to the BFF
+### 1. Unpack Horizon
 
-Copy `horizon.example.yaml` (in the repo root) to `horizon.yaml` in your 
working directory. The BFF looks for `./horizon.yaml` by default; override with 
`HORIZON_CONFIG=/path/to/file`.
+Unpack the binary tarball and copy the example config:
+
+```sh
+tar -xzf apache-skywalking-horizon-ui-0.5.0-bin.tar.gz
+cd apache-skywalking-horizon-ui-0.5.0-bin
+cp horizon.example.yaml horizon.yaml
+```
+
+The binary is self-contained: `server.js`, `node_modules/`, `static/`, and 
bundled templates are already present. There is no `pnpm install` step.
 
 ### 2. Point Horizon at OAP
 
@@ -36,11 +44,10 @@ oap:
 
 ### 3. Add at least one local user
 
-With no users configured, the BFF boots so the login page can show the 
setup-required state, but no login can succeed. Generate an argon2 hash:
+With no users configured, Horizon starts but no login can succeed. Generate an 
Argon2id hash with the source checkout helper or any Argon2id-capable password 
tool:
 
 ```sh
 pnpm --filter bff cli:hash
-# prompts for the password, prints the hash
 ```
 
 Paste the hash into `auth.local.users`:
@@ -59,13 +66,13 @@ For LDAP setup instead, see [Access Control → LDAP 
Backend](../access-control/
 
 ### 4. Start the BFF
 
+From inside the unpacked binary directory:
+
 ```sh
-pnpm --filter bff dev
-# or, for a built artifact:
-node apps/bff/dist/server.js
+HORIZON_CONFIG=./horizon.yaml HORIZON_STATIC_DIR=./static node server.js
 ```
 
-The BFF defaults to `127.0.0.1:8081`. For production, bind to `0.0.0.0` and 
put TLS termination in front:
+Horizon defaults to `127.0.0.1:8081`. For production, bind to `0.0.0.0` and 
put TLS termination in front:
 
 ```yaml
 server:
@@ -84,6 +91,30 @@ Browse to `http://<bff-host>:8081/`. Log in with the user 
you created. The first
 
 If either pane is red or yellow, see [Cluster Status Check 
Sequence](../compatibility/cluster-status.md) for triage.
 
+## Container start
+
+For Docker or Kubernetes, mount the same `horizon.yaml` and `/data` state 
volume:
+
+```sh
+docker run -d --name horizon \
+  -p 8081:8081 \
+  -v "$PWD/horizon.yaml:/app/horizon.yaml:ro" \
+  -v horizon-state:/data \
+  ghcr.io/apache/skywalking-horizon-ui:0.5.0
+```
+
+See [Container Image](container-image.md) for image tags, Kubernetes YAML, log 
handling, and probes.
+
+## Source build
+
+Use source builds when you are developing Horizon itself:
+
+```sh
+pnpm install
+pnpm package
+HORIZON_CONFIG=./horizon.yaml HORIZON_STATIC_DIR=./dist/static node 
dist/server.js
+```
+
 ## Production checklist
 
 - [ ] `server.host: 0.0.0.0` and TLS terminator in front.
diff --git a/docs/setup/rbac.md b/docs/setup/rbac.md
index 8ea0423..963e83b 100644
--- a/docs/setup/rbac.md
+++ b/docs/setup/rbac.md
@@ -1,4 +1,4 @@
-# rbac
+# Access Control Configuration
 
 Role-Based Access Control. Defines the role → verb grants and the post-login 
landing route per role. Full behavior reference (verb vocabulary, grant 
matching, where each verb gates) is in [Access Control → RBAC: Roles & 
Verbs](../access-control/rbac.md); this page is the `horizon.yaml` shape.
 
@@ -8,8 +8,8 @@ Role-Based Access Control. Defines the role → verb grants and 
the post-login l
 rbac:
   enabled: true
   roles:
-    viewer:     [metrics:read, alarms:read, traces:read, logs:read, 
topology:read, profile:read]
-    maintainer: [metrics:read, alarms:read, traces:read, logs:read, 
topology:read, profile:read, cluster:read, inspect:read]
+    viewer:     [metrics:read, alarms:read, traces:read, logs:read, 
topology:read, profile:read, overview:read]
+    maintainer: [metrics:read, alarms:read, traces:read, logs:read, 
topology:read, profile:read, overview:read, cluster:read, ttl:read, 
config:read, inspect:read]
     operator:   [metrics:read, ..., rule:*, live-debug:*, profile:enable]
     admin:      ["*"]
   landingByRole:
@@ -31,9 +31,9 @@ rbac:
 
 | Role | Purpose | Grants |
 |---|---|---|
-| `viewer` | Read-only data catalog. | `metrics:read`, `alarms:read`, 
`traces:read`, `logs:read`, `topology:read`, `profile:read`. Deliberately not 
`*:read` so the viewer cannot see rule definitions, live-debug sessions, setup 
screens, or platform internals. |
-| `maintainer` | Viewer + platform monitoring. | viewer baseline + 
`cluster:read`, `inspect:read`. |
-| `operator` | Configures observability. | maintainer baseline + 
`overview:read/write`, `setup:read/write`, `dashboard:read/write`, 
`alarm-setup:read/write`, `alarm-rule:read/write`, `rule:*` (including 
`rule:write:structural`, `rule:delete`, `rule:debug`), `live-debug:*`, 
`profile:enable`. |
+| `viewer` | Read-only data catalog and public overviews. | `metrics:read`, 
`alarms:read`, `traces:read`, `logs:read`, `topology:read`, `profile:read`, 
`overview:read`. Deliberately not `*:read` so the viewer cannot see rule 
definitions, live-debug sessions, setup screens, or platform internals. |
+| `maintainer` | Viewer + platform monitoring. | viewer baseline + 
`cluster:read`, `ttl:read`, `config:read`, `inspect:read`. |
+| `operator` | Configures observability. | maintainer baseline + 
`overview:write`, `setup:read/write`, `dashboard:read/write`, 
`alarm-setup:read/write`, `alarm-rule:read/write`, `rule:*` (including 
`rule:write:structural`, `rule:delete`, `rule:debug`), `live-debug:*`, 
`profile:enable`. |
 | `admin` | Unrestricted. | `*`. |
 
 ## Verb grammar
@@ -88,6 +88,7 @@ rbac:
       - traces:read
       - logs:read
       - topology:read
+      - overview:read
       - inspect:read                  # so they can poke at the catalog
       - live-debug:read               # but not write
   landingByRole:
diff --git a/docs/setup/server.md b/docs/setup/server.md
index 5023aa6..dbc271d 100644
--- a/docs/setup/server.md
+++ b/docs/setup/server.md
@@ -1,4 +1,4 @@
-# server
+# Server Listener
 
 HTTP listener for the Horizon BFF. Also serves the built UI as static assets 
when `staticDir` is set.
 
diff --git a/docs/setup/session.md b/docs/setup/session.md
index 34cd1c8..74064f6 100644
--- a/docs/setup/session.md
+++ b/docs/setup/session.md
@@ -1,4 +1,4 @@
-# session
+# Sessions
 
 HTTP session cookie configuration.
 
diff --git a/horizon.example.yaml b/horizon.example.yaml
index 224e90e..8accd30 100644
--- a/horizon.example.yaml
+++ b/horizon.example.yaml
@@ -144,6 +144,8 @@ rbac:
       - profile:read
       - overview:read
       - cluster:read
+      - ttl:read
+      - config:read
       - inspect:read
 
     # Configures observability: dashboards, alarm rules, DSL/OAL,
@@ -157,6 +159,8 @@ rbac:
       - topology:read
       - profile:read
       - cluster:read
+      - ttl:read
+      - config:read
       - inspect:read
       - overview:read
       - overview:write

Reply via email to