@mtdt/observeops-ds-spec 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +102 -0
- package/README.md +73 -0
- package/components/index.json +1270 -0
- package/components/recipes/README.md +41 -0
- package/components/recipes/recipes.json +922 -0
- package/components/registry/README.md +44 -0
- package/components/registry/_schema.json +47 -0
- package/components/registry/button.json +368 -0
- package/components/registry/checkbox.json +177 -0
- package/components/registry/data-viz-tooltips.json +409 -0
- package/components/registry/date-time-pickers.json +296 -0
- package/components/registry/drawer.json +222 -0
- package/components/registry/dropdown-picker.json +388 -0
- package/components/registry/filters.json +155 -0
- package/components/registry/form-item.json +281 -0
- package/components/registry/input.json +277 -0
- package/components/registry/link.json +186 -0
- package/components/registry/loose-tags.json +196 -0
- package/components/registry/menu.json +145 -0
- package/components/registry/modal.json +265 -0
- package/components/registry/navigation.json +425 -0
- package/components/registry/popover.json +216 -0
- package/components/registry/radio.json +238 -0
- package/components/registry/scheduler.json +188 -0
- package/components/registry/select.json +247 -0
- package/components/registry/severity.json +179 -0
- package/components/registry/switch.json +177 -0
- package/components/registry/table.json +275 -0
- package/components/registry/tabs.json +264 -0
- package/components/registry/tag.json +345 -0
- package/components/registry/tags-list.json +115 -0
- package/components/registry/toolbars.json +240 -0
- package/components/registry/tooltip.json +175 -0
- package/components/specs/README.md +72 -0
- package/components/specs/button.md +230 -0
- package/components/specs/checkbox.md +162 -0
- package/components/specs/data-viz-tooltips.md +93 -0
- package/components/specs/date-time-pickers.md +161 -0
- package/components/specs/drawer.md +162 -0
- package/components/specs/dropdown-picker.md +161 -0
- package/components/specs/filters.md +118 -0
- package/components/specs/form-item.md +130 -0
- package/components/specs/input.md +130 -0
- package/components/specs/link.md +131 -0
- package/components/specs/loose-tags.md +139 -0
- package/components/specs/menu.md +88 -0
- package/components/specs/modal.md +176 -0
- package/components/specs/navigation.md +181 -0
- package/components/specs/popover.md +118 -0
- package/components/specs/radio.md +144 -0
- package/components/specs/scheduler.md +133 -0
- package/components/specs/select.md +118 -0
- package/components/specs/switch.md +124 -0
- package/components/specs/table.md +115 -0
- package/components/specs/tabs.md +136 -0
- package/components/specs/tag.md +196 -0
- package/components/specs/tags-list.md +105 -0
- package/components/specs/toolbars.md +108 -0
- package/components/specs/tooltip.md +112 -0
- package/foundation/README.md +39 -0
- package/foundation/layout-shells.md +67 -0
- package/foundation/page-templates.md +69 -0
- package/foundation/panel-behaviours.md +61 -0
- package/foundation/screen-regions.md +62 -0
- package/index.js +75 -0
- package/layout/grid.json +34 -0
- package/layout/layouts.json +310 -0
- package/llms.txt +60 -0
- package/package.json +42 -0
- package/spec.manifest.json +407 -0
- package/tokens/README.md +125 -0
- package/tokens/component.json +34 -0
- package/tokens/kit-accents.json +14 -0
- package/tokens/primitive.json +130 -0
- package/tokens/purpose-map.json +67 -0
- package/tokens/semantic.dark.json +90 -0
- package/tokens/semantic.light.json +90 -0
- package/tokens/structural.json +35 -0
- package/tokens/variables.json +2018 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Tag (`MTag`) — Spec, Findings & Solutions
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Tier** | Atom |
|
|
6
|
+
| **Maturity** | 🟡 Stable-but-flawed (heavily used as a plain tag; the `variant` feature is broken) |
|
|
7
|
+
| **Source** | `@motadata/ui` kit → `ui/components/Tags/Tag.vue` (wraps Ant `a-tag`). Not overridden/excluded. |
|
|
8
|
+
| **Storybook** | `Atoms/Tag` (Examples · Usage · Accessibility · Changelog) |
|
|
9
|
+
| **Registry** | [`../registry/tag.json`](../registry/tag.json) |
|
|
10
|
+
| **Figma** | TODO |
|
|
11
|
+
|
|
12
|
+
## Usage (product analytics)
|
|
13
|
+
|
|
14
|
+
- **`<MTag>` used 150× across 87 files** — almost always a **plain** tag (no variant).
|
|
15
|
+
- **`variant` used ~3× total** (primary/error/default ×1 each) — effectively unused.
|
|
16
|
+
- `<MStatusTag>` (the separate status component) used 30×.
|
|
17
|
+
|
|
18
|
+
## Overview
|
|
19
|
+
|
|
20
|
+
A compact label for categorization, metadata, or removable selections. Wraps `a-tag`;
|
|
21
|
+
maps `variant` → a color via `ColorPalette` (`ui/style/colorPalette.js`) or takes a raw
|
|
22
|
+
`color`. **In practice it's used as a plain or removable label, not a colored variant.**
|
|
23
|
+
|
|
24
|
+
## Anatomy
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
┌───────────────┐
|
|
28
|
+
│ Label [×] │ ← container (.ant-tag) · label (slot) · remove "×" (when closable)
|
|
29
|
+
└───────────────┘
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Options
|
|
33
|
+
|
|
34
|
+
- **Colored / status tags → CSS classes (the working mechanism):** apply a class, not the
|
|
35
|
+
`variant` prop — colored text on a tinted background, legible in both themes:
|
|
36
|
+
`tag-red` (75×) · `tag-green` (57×) · `tag-yellow` (15×) · `tag-orange` (9×) ·
|
|
37
|
+
`tag-purple` (1×) · `tag-unknown` (2×). Plus state classes `.new` / `.provision` /
|
|
38
|
+
`.unprovision` / `.used-count-pill` (48×). Example: `<MTag class="tag-red" :closable="false">`.
|
|
39
|
+
(`tag-primary` (49×) is actually a neutral chip, not brand-blue.)
|
|
40
|
+
- **`variant` prop (ColorPalette keys) — avoid:** `default · primary · success · error ·
|
|
41
|
+
warning · orange · neutral-lighter`; **colored ones are broken/illegible (F2)**. Use the
|
|
42
|
+
classes above instead.
|
|
43
|
+
- **Modifiers:** `closable` (default **true**), `rounded`, `confirmable` (confirm-before-
|
|
44
|
+
remove), `visible`, custom `color`.
|
|
45
|
+
|
|
46
|
+
## Behaviors
|
|
47
|
+
|
|
48
|
+
- **Removable:** with `closable`, a "×" emits `close`. `confirmable` wraps it in a confirm.
|
|
49
|
+
- **`closable` defaults to true** → every tag shows a "×" unless `:closable="false"` (F5).
|
|
50
|
+
|
|
51
|
+
## Content & writing
|
|
52
|
+
|
|
53
|
+
Short noun/label; for removable chips the label is the selected value.
|
|
54
|
+
|
|
55
|
+
## Accessibility
|
|
56
|
+
|
|
57
|
+
- A plain tag is a `<span>` label — fine.
|
|
58
|
+
- Default tag contrast (neutral text on a neutral chip) is legible in both themes; bg
|
|
59
|
+
themes (`#e3e8f2` → `#2b394f`).
|
|
60
|
+
- ⚠️ **The remove "×" is not keyboard-accessible** (F4).
|
|
61
|
+
|
|
62
|
+
## Props / API
|
|
63
|
+
|
|
64
|
+
`closable` (default true) · `color` · `variant` · `rounded` · `confirmable` · `visible`.
|
|
65
|
+
Emits `close`; slots: default (label), `confirm-title`. No `model`.
|
|
66
|
+
Machine spec: [`../registry/tag.json`](../registry/tag.json).
|
|
67
|
+
|
|
68
|
+
## Design tokens used
|
|
69
|
+
|
|
70
|
+
`--tag-bg` / `--tag-color` (themed) for the plain tag. Variant colors come from
|
|
71
|
+
`ColorPalette` **static hex** (not tokens) — see F1/F6.
|
|
72
|
+
|
|
73
|
+
## Findings & Inconsistencies
|
|
74
|
+
|
|
75
|
+
### F2 — Colored variants are illegible (effectively broken) · High · Open
|
|
76
|
+
|
|
77
|
+
Measured: every colored `variant` (primary/success/error/warning) renders the **same
|
|
78
|
+
neutral chip background** (`--tag-bg`) with **white text** → in light theme that's white
|
|
79
|
+
on `#e3e8f2` (contrast ~1.2:1, unreadable). The product forces the tag bg to `--tag-bg`
|
|
80
|
+
regardless of `color`, so the variant only changes text color. This is why `variant` is
|
|
81
|
+
used only ~3×. **Use the `tag-*` CSS classes instead** (see Options — they're legible and
|
|
82
|
+
heavily used: `tag-red` 75×, `tag-green` 57×, …), or `MStatusTag` for status. Longer term,
|
|
83
|
+
either fix the `variant` override to produce a legible bg+text pair (token-based) or remove
|
|
84
|
+
the `variant` color feature in favor of the classes.
|
|
85
|
+
|
|
86
|
+
### F4 — Remove "×" is not keyboard-accessible · High · Open *(a11y)*
|
|
87
|
+
|
|
88
|
+
The close control is an `<a @click>` with **no `href`/`role`/`aria-label`/`tabindex`** —
|
|
89
|
+
confirmed not focusable. Keyboard/AT users can't remove tags, and the control is unlabeled.
|
|
90
|
+
**Solution:** use `<button type="button" aria-label="Remove {label}">` + a focus ring
|
|
91
|
+
(see [SF-001](../../findings/SF-001-focus-visible.md)).
|
|
92
|
+
|
|
93
|
+
### F1 — Variant set is small + static; no info/neutral · Medium · Open
|
|
94
|
+
|
|
95
|
+
Valid variants = ColorPalette keys only; `info`/`neutral` silently do nothing. Colors are
|
|
96
|
+
static hex (`success #89c540`, `error #f04e3e`, …) that **differ from the product's
|
|
97
|
+
severity tokens** (`#14b053`, `#ec5b5b`). **Solution:** document the real set; align to
|
|
98
|
+
tokens if variants are revived.
|
|
99
|
+
|
|
100
|
+
### F5 — `closable` defaults to true · Medium · Open
|
|
101
|
+
|
|
102
|
+
Display tags get an unexpected "×" unless `:closable="false"`. **Solution:** consider
|
|
103
|
+
defaulting `closable` to false; for now, document it loudly.
|
|
104
|
+
|
|
105
|
+
### F6 — Close "×" optically off (baseline layout, not flex) · Low · Open *(polish)*
|
|
106
|
+
|
|
107
|
+
`.ant-tag` uses `line-height: 22px` + inline/baseline layout (no flex). The thin Font-
|
|
108
|
+
Awesome `times` glyph (`vertical-align: -1.65px`) aligns by baseline, so the × *reads*
|
|
109
|
+
slightly off the label even though it's geometrically centered (**measured offset ~1px**).
|
|
110
|
+
Product-side, not Storybook. **Solution (optional polish):** `.ant-tag { display:
|
|
111
|
+
inline-flex; align-items: center; gap: 4px; }` in `tags.less` for optically-clean alignment
|
|
112
|
+
and controlled spacing.
|
|
113
|
+
|
|
114
|
+
### F3 — `neutral-ligher` class typo · Low · Open
|
|
115
|
+
|
|
116
|
+
`Tag.vue` applies the misspelled class `neutral-ligher` (missing "t") for the
|
|
117
|
+
`neutral-lighter` variant, so the themed `.neutral-lighter` LESS override never matches.
|
|
118
|
+
**Solution:** fix the typo to `neutral-lighter`.
|
|
119
|
+
|
|
120
|
+
### F7 — `MStatusTag` label inversion + silent fallback · Low · Open
|
|
121
|
+
|
|
122
|
+
`_base-status-tag.vue` `textMap` flips two labels: `poweredoff` renders **"Up"** and
|
|
123
|
+
`poweredon` renders **"Down"** (colours still follow `TAG_MAP`: poweredoff = red,
|
|
124
|
+
poweredon = green) — surprising for a reader. Also, a `status` not in `TAG_MAP` gets **no
|
|
125
|
+
colour class** and renders as a plain rounded tag (silent). **Solution:** document the
|
|
126
|
+
inversion explicitly; add an `unknown`/default colour mapping so unmapped statuses are
|
|
127
|
+
visibly neutral rather than silently plain.
|
|
128
|
+
|
|
129
|
+
## Do / Don't
|
|
130
|
+
|
|
131
|
+
### Do
|
|
132
|
+
|
|
133
|
+
- Use a plain tag for labels; `:closable="true"` for removable chips; `:rounded` for pills.
|
|
134
|
+
- Use `MStatusTag` for status/severity.
|
|
135
|
+
|
|
136
|
+
### Don't
|
|
137
|
+
|
|
138
|
+
- Don't use the color `variant`s (broken/illegible — F2).
|
|
139
|
+
- Don't assume `info`/`neutral` exist (F1); don't forget `closable` defaults to true (F5).
|
|
140
|
+
|
|
141
|
+
## Tag family (related components & treatments)
|
|
142
|
+
|
|
143
|
+
"Tag" is a **family**, not just `MTag`. Each member is classified by *what it is*
|
|
144
|
+
(decision [D12](../../decisions/DECISIONS.md) — own entry vs. variant vs. internal part):
|
|
145
|
+
|
|
146
|
+
| Member | Source | Role | Classification → home |
|
|
147
|
+
| --- | --- | --- | --- |
|
|
148
|
+
| `MTag` | `ui/components/Tags/Tag.vue` | base tag (+ `tag-*` colour classes, state classes) | **base** → this sheet |
|
|
149
|
+
| `tag-*` colour classes | `src/design/tags.less` | colour restyle of MTag | **variant** → Tag (ColoredTags story) |
|
|
150
|
+
| `MStatusTag` | `_base-status-tag.vue` | status/severity tag — maps status→`tag-*` (30×) | **variant** → Tag (Status story + section below) |
|
|
151
|
+
| **`SelectedItemPills`** | `dropdown-trigger/selected-item-pills.vue` | **teal `ant-tag-has-color` pills for picker selections** — truncated, with a **`+N` overflow popover** (default `maxItems: 1`); teal from `--main-tags-*` in `input.less`. | **internal part** → [DropdownPicker](./dropdown-picker.md) ✅ (preview story under Tag) |
|
|
152
|
+
| `MultipleTrigger` / `SingleTrigger` | `dropdown-trigger/` | picker triggers that render `SelectedItemPills` | **internal part** → [DropdownPicker](./dropdown-picker.md) ✅ |
|
|
153
|
+
| `LooseTags` | `loose-tags.vue` | free-form **tag input** (type to create tags) | **own entry** → [Molecules / Forms](./loose-tags.md) ✅ |
|
|
154
|
+
| `TagsList` | `tags-list.vue` | read-only tag overflow (count + popover) — **dead code, 0 usages** | documented **badged unused** → [Molecules / TagsList](./tags-list.md) ✅ (prefer `SelectedItemPills`) |
|
|
155
|
+
|
|
156
|
+
App-specific tag classes also exist: `filter-alert-tag` (7×), `os-tag`, `nav-beta-tag`,
|
|
157
|
+
plus the `main-tags` teal treatment. The **teal `key:value` + `+N` pills** seen in pickers
|
|
158
|
+
are `SelectedItemPills` — shown live in Storybook as **Atoms / Tag / Examples → "Selected
|
|
159
|
+
pills (picker, +N overflow)"** (rendered with `:tags="true"`, which adds the
|
|
160
|
+
`.loose-tags-input` ancestor that activates the teal `--main-tags-*` styling). Full
|
|
161
|
+
behavioural coverage (triggers, search, clear) comes with the DropdownPicker spec.
|
|
162
|
+
|
|
163
|
+
### Status / severity tag — `MStatusTag` (variant of Tag)
|
|
164
|
+
|
|
165
|
+
`_base-status-tag.vue` (registered globally as **`MStatusTag`**, used **30× / 28 files**).
|
|
166
|
+
It is **not a separate component** in the DS — it's a *semantic variant of Tag*: it renders
|
|
167
|
+
`<MTag rounded :closable="false" :class="tag-*">` and maps a **status string** to a
|
|
168
|
+
**colour class plus a capitalized label**. Shown live as **Atoms / Tag / Examples →
|
|
169
|
+
"Status / severity"**.
|
|
170
|
+
|
|
171
|
+
- **API:** `status` (String|Number) · `forcePrimary` (Boolean). Renders nothing when
|
|
172
|
+
`status` is falsy.
|
|
173
|
+
- **Mapping (`TAG_MAP`, ~90 keys):** green = `up`/`on`/`active`/`success`/`healthy`/…;
|
|
174
|
+
red = `down`/`off`/`critical`/`failed`/`disconnected`/…; yellow = `paused`/`queued`/
|
|
175
|
+
`halted`/`starting`; orange = `suspended`/`fair`; purple = `unreachable`; neutral
|
|
176
|
+
(`tag-primary`) = `maintenance`/`connected`/`standby`. Verified colours match the
|
|
177
|
+
severity palette (`up` → `#14b053`, `down` → `#ec5b5b`, …).
|
|
178
|
+
- **`forcePrimary`** overrides any status to the neutral `tag-primary` chip.
|
|
179
|
+
- **Fallback:** a status **not** in the map gets no colour class → renders as a plain
|
|
180
|
+
rounded tag (silent — easy to miss). Consider a documented default/`unknown` mapping.
|
|
181
|
+
- **Quirk (finding F7, low):** `textMap` inverts two labels — `poweredoff` shows **"Up"**
|
|
182
|
+
and `poweredon` shows **"Down"** (the *colour* still follows `TAG_MAP`: poweredoff = red,
|
|
183
|
+
poweredon = green). Intentional domain inversion, but surprising; document loudly.
|
|
184
|
+
- **Alignment:** `MStatusTag` carries `inline-flex items-center`, which is exactly the
|
|
185
|
+
alignment plain `MTag` lacks (F6). This now renders in Storybook since the Tailwind
|
|
186
|
+
pipeline was enabled — see [SF-002](../../findings/SF-002-storybook-tailwind.md) (Fixed).
|
|
187
|
+
|
|
188
|
+
## Changelog
|
|
189
|
+
|
|
190
|
+
- **2026-06-05** — Added (placeholder variants).
|
|
191
|
+
- **2026-06-06** — Full deep-dive: real variant set (ColorPalette) found; colored variants
|
|
192
|
+
measured **illegible** (F2); examples refocused on plain/removable usage; findings F1–F5
|
|
193
|
+
(with the SF-001 link); Option B pages.
|
|
194
|
+
- **2026-06-11** — Back-catalog re-audit (prop-value distribution sweep): added a **State
|
|
195
|
+
classes** story rendering `used-count-pill` (48×), `new`, `provision`, `unprovision` —
|
|
196
|
+
previously documented in text but not shown.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# TagsList — Spec, Findings & Solutions
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Tier** | Molecule (read-only display) |
|
|
6
|
+
| **Maturity** | ⚠️ **Unused (dead code)** — exists but has **0 usages** in the product |
|
|
7
|
+
| **Source** | `src/components/tags-list.vue` (composes `MTag` + `MPopover`) |
|
|
8
|
+
| **Storybook** | `Molecules/TagsList` (Examples · Usage · Changelog) |
|
|
9
|
+
| **Registry** | [`../registry/tags-list.json`](../registry/tags-list.json) |
|
|
10
|
+
| **Family** | Tag family — the read-only "overflow" display (see [`tag.md`](./tag.md); [D12](../../decisions/DECISIONS.md)) |
|
|
11
|
+
| **Figma** | N/A (dead code — do not build) |
|
|
12
|
+
|
|
13
|
+
> ⚠️ **This component is unused.** Documented at the owner's request for a complete
|
|
14
|
+
> catalog. The **live** equivalent of this pattern is **`SelectedItemPills`** (the picker
|
|
15
|
+
> "+N" pills). Prefer that. See finding **F1**.
|
|
16
|
+
|
|
17
|
+
## Usage (product analytics)
|
|
18
|
+
|
|
19
|
+
- **`<TagsList>` used 0× / 0 files.** Not imported anywhere. (`name: 'TagsList'`; the only
|
|
20
|
+
"TagsList" matches in the codebase are unrelated data fields like `_objectTagsList`.)
|
|
21
|
+
|
|
22
|
+
## Overview
|
|
23
|
+
|
|
24
|
+
A read-only **tag overflow** display. Given an array of strings:
|
|
25
|
+
|
|
26
|
+
- **≤ `maxLength`** (default **2**) → render each tag inline as a neutral `tag-primary`
|
|
27
|
+
chip (rounded, non-closable).
|
|
28
|
+
- **> `maxLength`** → render a single chip showing the **count**, with a hover **popover**
|
|
29
|
+
listing every item.
|
|
30
|
+
|
|
31
|
+
Note it collapses to **just the count** (e.g. "5") — it never shows "first N + remaining",
|
|
32
|
+
unlike `SelectedItemPills` which shows the first item(s) then a `+N`.
|
|
33
|
+
|
|
34
|
+
## Anatomy
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
≤ maxLength: ⌜web-server⌟ ⌜database⌟ ← inline neutral chips
|
|
38
|
+
> maxLength: ⌜ 5 ⌟ ──hover──▶ ┌──────────┐ ← count chip + popover list
|
|
39
|
+
│ web-server│
|
|
40
|
+
│ database │
|
|
41
|
+
│ … │
|
|
42
|
+
└──────────┘
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Options / API
|
|
46
|
+
|
|
47
|
+
- **`value`** (Array | Object) — the tags to display.
|
|
48
|
+
- **`maxLength`** (Number, default **2**) — inline threshold before collapsing to a count.
|
|
49
|
+
|
|
50
|
+
No events, no slots, no v-model. Pure presentational.
|
|
51
|
+
|
|
52
|
+
## Behaviors
|
|
53
|
+
|
|
54
|
+
- Renders nothing when `value` is empty.
|
|
55
|
+
- Hover (not click) opens the count popover (`trigger="hover"`, `placement="bottomRight"`).
|
|
56
|
+
|
|
57
|
+
## Design tokens used
|
|
58
|
+
|
|
59
|
+
`--tag-bg` / `--tag-color` (the neutral `tag-primary` chip, themed) · `--border-color`
|
|
60
|
+
(popover row dividers). Chip corner is `border-radius: 10px` (local `.application-item`).
|
|
61
|
+
|
|
62
|
+
## Accessibility
|
|
63
|
+
|
|
64
|
+
- Read-only text chips (`<span>`) — fine for contrast in both themes.
|
|
65
|
+
- The overflow affordance is **hover-only** — the count chip isn't focusable/click-openable,
|
|
66
|
+
so keyboard/touch users can't reveal the full list. (Moot while unused; would matter if
|
|
67
|
+
revived — prefer `SelectedItemPills`, which has the same caveat tracked there.)
|
|
68
|
+
|
|
69
|
+
## Findings & Inconsistencies
|
|
70
|
+
|
|
71
|
+
### F1 — Dead code (unused component) · Medium · Open
|
|
72
|
+
|
|
73
|
+
`tags-list.vue` is **never imported or rendered**. It duplicates a pattern already provided
|
|
74
|
+
(better) by `SelectedItemPills` (first-N + `+N` overflow, used in pickers/`LooseTags`).
|
|
75
|
+
**Solution:** **remove** `src/components/tags-list.vue` (dead-code cleanup), or — if a
|
|
76
|
+
count-only overflow display is genuinely wanted — fold it into `SelectedItemPills` behind a
|
|
77
|
+
prop instead of maintaining a second component.
|
|
78
|
+
|
|
79
|
+
### F2 — Count-only overflow (pattern inconsistency) · Low · Open
|
|
80
|
+
|
|
81
|
+
It collapses to **only a count** with no preview of any tag, whereas the product's live
|
|
82
|
+
pattern (`SelectedItemPills`) shows the first item(s) + `+N`. Two different overflow idioms
|
|
83
|
+
for the same concept. **Solution:** standardize on the `SelectedItemPills` idiom.
|
|
84
|
+
|
|
85
|
+
## Do / Don't
|
|
86
|
+
|
|
87
|
+
### Do
|
|
88
|
+
|
|
89
|
+
- For tag overflow in the product, use **`SelectedItemPills`** (live, themed, `+N`), not this.
|
|
90
|
+
|
|
91
|
+
### Don't
|
|
92
|
+
|
|
93
|
+
- Don't adopt `TagsList` in new code — it's unused/dead. Don't build it in Figma.
|
|
94
|
+
|
|
95
|
+
## Related
|
|
96
|
+
|
|
97
|
+
`SelectedItemPills` (the live overflow equivalent) · `MTag` · `MPopover` · `LooseTags`.
|
|
98
|
+
See the Tag family table in [`tag.md`](./tag.md).
|
|
99
|
+
|
|
100
|
+
## Changelog
|
|
101
|
+
|
|
102
|
+
- **2026-06-07** — Added to the catalog (badged **unused**) at the owner's request. Verified
|
|
103
|
+
render (inline chips ≤ maxLength; count chip + hover popover above it). Logged dead-code
|
|
104
|
+
finding F1 + pattern inconsistency F2; recommended removal / consolidation onto
|
|
105
|
+
`SelectedItemPills`.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Toolbars — Spec, Findings & Solutions
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Tier** | Organism |
|
|
6
|
+
| **Maturity** | 🟢 Stable |
|
|
7
|
+
| **Source** | `_base-page-header.vue` (`FlotoPageHeader`) · `widgets/views/components/widget-title.vue` (`WidgetTitle`) · `_base-bulk-action-bar.vue` (`BulkActionBar`) · grid toolbar composition · `column-selector.vue`. |
|
|
8
|
+
| **Storybook** | Organisms/Toolbars |
|
|
9
|
+
| **Registry** | [`registry/toolbars.json`](../registry/toolbars.json) |
|
|
10
|
+
| **Family** | [Toolbars](../family-map.md) |
|
|
11
|
+
|
|
12
|
+
## Why this is one family
|
|
13
|
+
|
|
14
|
+
A **toolbar** is an organism that *arranges* molecules/atoms (title · search · filters · actions).
|
|
15
|
+
The product has **several** distinct toolbars — catalogued here as **variants** of one family. They
|
|
16
|
+
**compose** the **Filters** molecules; they don't own them.
|
|
17
|
+
|
|
18
|
+
## The variants
|
|
19
|
+
|
|
20
|
+
| Variant | Source | Usage | What it is |
|
|
21
|
+
| --- | --- | --- | --- |
|
|
22
|
+
| **App header** | `layout/header.vue` | global | the app-shell **top bar** (logo · search · notifications · user) |
|
|
23
|
+
| **Page header** | `FlotoPageHeader` | **54×** | back + title + right-side actions |
|
|
24
|
+
| **Widget header** | `widget-title` | **58×** | title + time-range pill + kebab of widget actions |
|
|
25
|
+
| **Bulk action bar** | `_base-bulk-action-bar` | — | a floating N-selected toolbar |
|
|
26
|
+
| **Grid toolbar** | composition | — | search + filter + columns + Add over a grid |
|
|
27
|
+
| **Column chooser** | `column-selector` | 174× | the eye-button column show/hide dropdown |
|
|
28
|
+
|
|
29
|
+
### Page header (54×)
|
|
30
|
+
|
|
31
|
+
- **Left:** optional `back-button` + **title** (`--primary-alt`, font-500, with an optional count).
|
|
32
|
+
- **Right:** the default slot — actions (search, export, primary **Add**).
|
|
33
|
+
- **Props:** `title` · `back-link` · `main-header` · `use-divider`. **Slots:** `back-button` ·
|
|
34
|
+
`before/after-title` · `title` · `title-append` · default · `additional-rows`.
|
|
35
|
+
|
|
36
|
+
### Widget header (58×)
|
|
37
|
+
|
|
38
|
+
- **Title** (ellipsis, font-500) + a **disabled `TimeRangePicker` pill** (the widget's window) + a
|
|
39
|
+
**kebab** (`ellipsis-v` → `FlotoGridActions`): **Edit · Clone · Full Screen · Share · Export as CSV**.
|
|
40
|
+
- Slot: `flip-toggle`; emits `exit-fullscreen`.
|
|
41
|
+
|
|
42
|
+
### Bulk action bar
|
|
43
|
+
|
|
44
|
+
- A floating `role="toolbar"` shown when `selectedCount >= minSelection`: a **clear checkbox** +
|
|
45
|
+
**"N items selected"**, inline **primary** actions + **danger** actions (`--secondary-red`), and a
|
|
46
|
+
**"More"** (`ellipsis-v`) overflow. Emits `clear` / `selected`.
|
|
47
|
+
|
|
48
|
+
### Grid toolbar / Column chooser
|
|
49
|
+
|
|
50
|
+
- **Grid toolbar** — search + **filter** (opens the Filters molecules) + **columns** + **Add**.
|
|
51
|
+
- **Column chooser** (174×) — an eye-button dropdown of column checkboxes + Reset. *Product is
|
|
52
|
+
show/hide only*; reordering is by **dragging grid headers** (Table → Basic). The grip drag here is a
|
|
53
|
+
**DS enhancement**.
|
|
54
|
+
|
|
55
|
+
## Checked & scoped out (sweep recheck)
|
|
56
|
+
|
|
57
|
+
A census of every header / toolbar / action-bar / `role="toolbar"` confirmed the 6 archetypes above.
|
|
58
|
+
The rest are **variants, compositions, or other families** (not missed):
|
|
59
|
+
|
|
60
|
+
- **`FlotoGridActions`** (**88×**) — the **row action kebab** (Edit/Clone/Delete ⋮). The *action-menu*
|
|
61
|
+
archetype, catalogued under **Popover** — and it's the kebab **composed into** the Widget header and
|
|
62
|
+
the Bulk action bar's "More."
|
|
63
|
+
- **Detail headers** (RUM drill-down ×7, `guide-header`) — **Page-header variants** for detail views.
|
|
64
|
+
- **`bulk-provision-toolbar`** — a domain **variant of the Bulk action bar**.
|
|
65
|
+
- **Action footer** (`result-footer`, modal/drawer footers) — covered by **Modal / Drawer**.
|
|
66
|
+
- **`overlay-controls`** (metric-explorer) — a feature-specific control strip, not reusable.
|
|
67
|
+
- **`rule-group-header` / `permission-section-header`** — in-content **section labels**, not toolbars.
|
|
68
|
+
- **`FlotoNavBar` / left menu / breadcrumb / tabs** — **navigation** archetypes (a future *Navigation*
|
|
69
|
+
family), not toolbars.
|
|
70
|
+
|
|
71
|
+
## Accessibility
|
|
72
|
+
|
|
73
|
+
- The **Bulk action bar** uses `role="toolbar"` + `aria-label`. **Verify** focus-visible ring
|
|
74
|
+
(**SF-001**) and `aria-label`s on icon-only buttons (export, kebab, More, ×).
|
|
75
|
+
|
|
76
|
+
## Design tokens used
|
|
77
|
+
|
|
78
|
+
`--primary` · `--primary-alt` (title) · `--border-color` · `--page-background-color` ·
|
|
79
|
+
`--page-text-color` · `--neutral-light` · `--secondary-red` (danger) · `--common-widget-bg` ·
|
|
80
|
+
`--timerange-background-color`.
|
|
81
|
+
|
|
82
|
+
## Findings & Inconsistencies
|
|
83
|
+
|
|
84
|
+
| # | Severity | Status | Finding |
|
|
85
|
+
| --- | --- | --- | --- |
|
|
86
|
+
| F1 | Low | Noted | Reproductions (the live Page/Widget headers use the router / widget store). |
|
|
87
|
+
| F2 | Low (a11y) | Open | Verify focus-visible ring + icon-button `aria-label`s (SF-001). |
|
|
88
|
+
| N1 | Info | — | **Taxonomy (2026-06-16):** consolidated the product's toolbars into one family; the former "Grid Toolbar" entry was folded in (Grid toolbar + Column chooser variants), and its filter molecules moved to **Filters**. |
|
|
89
|
+
|
|
90
|
+
## Do / Don't
|
|
91
|
+
|
|
92
|
+
- **Do** use the Page header for list/detail pages; compose toolbars from **Filters** + search +
|
|
93
|
+
actions; use the Bulk action bar for multi-select; put destructive bulk actions in `--secondary-red`.
|
|
94
|
+
- **Don't** duplicate a filter inside a toolbar (compose the molecule); don't make a per-screen toolbar
|
|
95
|
+
component; don't rely on the column chooser to reorder columns (that's grid-header drag).
|
|
96
|
+
|
|
97
|
+
## Related components
|
|
98
|
+
|
|
99
|
+
**Filters** (composed into toolbars) · **Table** (grid toolbar sits above it) · `FlotoDropdownPicker` ·
|
|
100
|
+
`MPopover` (kebab menus) · **Date & Time Pickers** (the widget time-range pill).
|
|
101
|
+
|
|
102
|
+
## Changelog
|
|
103
|
+
|
|
104
|
+
- **2026-06-16** — Added — the **Toolbars** family (organisms, by variant): **Page header** (54×),
|
|
105
|
+
**Widget header** (58×), **Bulk action bar**, **Grid toolbar**, **Column chooser**. Surfaced the two
|
|
106
|
+
**missing** big toolbars (Page header, Widget header) during the taxonomy reorg, and **folded in** the
|
|
107
|
+
former "Grid Toolbar" entry (its filter molecules moved to **Filters**). Reproductions; verified
|
|
108
|
+
render + the Widget-header kebab opens, no console errors. Findings F1 (reproduction), F2 (a11y).
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Tooltip (`MTooltip`) — Spec, Findings & Solutions
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Tier** | Molecule |
|
|
6
|
+
| **Maturity** | 🟢 Stable |
|
|
7
|
+
| **Source** | `src/components/_base-tooltip.vue` (Floto override — the kit `MTooltip` is **excluded** in the preview/app and this ships instead). Renders via **VTippy** (vue-tippy). |
|
|
8
|
+
| **Storybook** | Molecules/Tooltip |
|
|
9
|
+
| **Registry** | [`registry/tooltip.json`](../registry/tooltip.json) |
|
|
10
|
+
| **Family** | [Popover / Tooltip](../family-map.md) |
|
|
11
|
+
|
|
12
|
+
## Usage analytics
|
|
13
|
+
|
|
14
|
+
- **94×** across the app. The dominant pattern is an **info-circle icon trigger + a short text
|
|
15
|
+
hint**.
|
|
16
|
+
- **Placement (VTippy names):** `top-start` (default) **73×** · `top` 11× · `left` 6× ·
|
|
17
|
+
`bottomRight` 2× · `topLeft`/`right` 1×. The default dominates.
|
|
18
|
+
- Overlay-class variants: `chart-like-tooltip` (2×, chart-surface background), `noc-dashboard-tooltip` (1×).
|
|
19
|
+
- **Sibling tooltip mechanisms (intentional):** native **`title=""`** attribute (~59× — browser
|
|
20
|
+
tooltip for plain/truncated text), the **`v-tooltip` directive** (1×, negligible), the **data-viz
|
|
21
|
+
tooltips** (Highcharts `TooltipBuilder` chart tooltip + sparkline + `heatmap-tooltip.vue` — see
|
|
22
|
+
**Molecules/Data-Viz Tooltips**), and **graph/topology tooltips** (vis-network/d3 edge & node).
|
|
23
|
+
**The product has 7 distinct tooltip kinds** — full taxonomy on the Data-Viz Tooltips Usage page.
|
|
24
|
+
|
|
25
|
+
## Overview
|
|
26
|
+
|
|
27
|
+
A **transient, non-interactive label** revealed on **hover/focus**, anchored to a trigger. Note
|
|
28
|
+
the project **overrides the kit tooltip**: `main.js`/preview exclude `MTooltip` from the kit and
|
|
29
|
+
register the Floto `_base-tooltip.vue`, which renders through **VTippy** (vue-tippy) rather than
|
|
30
|
+
Ant `a-tooltip`. Use it for brief hints; for anything the user clicks or types into, use
|
|
31
|
+
**`MPopover`**.
|
|
32
|
+
|
|
33
|
+
## Anatomy
|
|
34
|
+
|
|
35
|
+
- **Trigger** (`trigger` slot) — the anchored element (commonly an `info-circle` `MIcon`).
|
|
36
|
+
- **Bubble** — the floating content (default slot), **no arrow** (`:arrow="false"`), animation
|
|
37
|
+
`shift-toward`, appended to the closest `.__panel` or `document.body`.
|
|
38
|
+
|
|
39
|
+
## Options (props)
|
|
40
|
+
|
|
41
|
+
| Prop | Default | Notes |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| `placement` | `top-start` | VTippy placement (`top`/`bottom`/`left`/`right` + `-start`/`-end`) |
|
|
44
|
+
| `disabled` | `false` | short-circuits — renders the trigger inline, no tooltip |
|
|
45
|
+
| `trigger` | `hover focus` | **declared but not wired to VTippy (F1)** |
|
|
46
|
+
| `overlayClassName` | — | e.g. `chart-like-tooltip` |
|
|
47
|
+
| `getPopupContainer` | — | defaults to closest `.__panel` or `document.body` |
|
|
48
|
+
|
|
49
|
+
## Behaviors
|
|
50
|
+
|
|
51
|
+
- Shows on **hover/focus**; `interactive:true` lets the pointer move onto the bubble without it
|
|
52
|
+
closing. `lazy:true` defers creation until first trigger.
|
|
53
|
+
- `disabled` is the supported on/off switch (not the `trigger` prop).
|
|
54
|
+
|
|
55
|
+
## Content & writing
|
|
56
|
+
|
|
57
|
+
- Brief, plain hints — a phrase or one sentence. No interactive controls.
|
|
58
|
+
|
|
59
|
+
## Accessibility
|
|
60
|
+
|
|
61
|
+
- **Keyboard/SR:** the bubble content is **not** associated with the trigger via
|
|
62
|
+
`aria-describedby`, and icon-only triggers have **no accessible name** — screen-reader users
|
|
63
|
+
may miss the hint (**F3**). Recommended: add `aria-label` to icon triggers and wire
|
|
64
|
+
`aria-describedby` to the bubble id.
|
|
65
|
+
- **Focus ring:** removed on the overlay (`:focus { outline:none }` in `_base-popper.less`) — see
|
|
66
|
+
[SF-001](../../findings/SF-001-focus-visible.md) (**F2**).
|
|
67
|
+
- Tooltips appear on **focus** too (good), but the missing aria wiring limits SR value.
|
|
68
|
+
|
|
69
|
+
## Props / API
|
|
70
|
+
|
|
71
|
+
See the table above and [`registry/tooltip.json`](../registry/tooltip.json).
|
|
72
|
+
|
|
73
|
+
## Design tokens used
|
|
74
|
+
|
|
75
|
+
`--tooltip-background-color` · `--tooltip-text-color` · `--tooltip-box-shadow` · `--border-color`
|
|
76
|
+
· `--chart-tooltip-background` (chart variant).
|
|
77
|
+
|
|
78
|
+
## Findings & Inconsistencies
|
|
79
|
+
|
|
80
|
+
| # | Severity | Status | Finding |
|
|
81
|
+
| --- | --- | --- | --- |
|
|
82
|
+
| F1 | Low | Open | `trigger` prop declared (default `hover focus`) but **not passed to VTippy** — changing it does nothing. |
|
|
83
|
+
| F2 | Medium (a11y) | Open | No focus ring on the overlay — links to **SF-001**. |
|
|
84
|
+
| F3 | Medium (a11y) | Open | Tooltip content not linked via `aria-describedby`; icon-only triggers lack an accessible name. |
|
|
85
|
+
|
|
86
|
+
## Recommended solutions
|
|
87
|
+
|
|
88
|
+
- **F1:** either bind `:trigger="trigger"` on `<VTippy>` or remove the dead prop.
|
|
89
|
+
- **F2:** adopt the `:focus-visible` ring from SF-001.
|
|
90
|
+
- **F3:** add `aria-label` to icon triggers; generate a bubble id and set `aria-describedby` on the
|
|
91
|
+
trigger when the tooltip is open.
|
|
92
|
+
|
|
93
|
+
## Do / Don't
|
|
94
|
+
|
|
95
|
+
- **Do** keep content brief and non-interactive; use the info-circle idiom; use `:disabled` when
|
|
96
|
+
the hint is redundant.
|
|
97
|
+
- **Don't** put buttons/links/inputs in a tooltip (it dismisses on mouse-out) — use **Popover**;
|
|
98
|
+
don't rely on the `trigger` prop to change activation (F1).
|
|
99
|
+
|
|
100
|
+
## Related components
|
|
101
|
+
|
|
102
|
+
`MPopover` (interactive panel) · `FlotoDropdownPicker` (select) · `MModal` (dialog).
|
|
103
|
+
|
|
104
|
+
## Changelog
|
|
105
|
+
|
|
106
|
+
- **2026-06-11 (nth-level audit)** — Documented the **three intentional tooltip kinds** (native
|
|
107
|
+
`title=` ~59×, `MTooltip` 94×, graph/canvas tooltips), the real placement distribution
|
|
108
|
+
(`top-start` 73× default · `top` 11× · `left` 6×), and the VTippy-vs-Ant placement-name split.
|
|
109
|
+
- **2026-06-11** — Added (decision-grade Usage). Deep-dive of `_base-tooltip.vue` (94×, VTippy
|
|
110
|
+
override). Stories: Basic (info idiom) · Placements · Disabled · Rich content · Playground —
|
|
111
|
+
verified the bubble shows on hover with the hint text, no console errors. Findings F1 (dead
|
|
112
|
+
`trigger` prop), F2 (focus → SF-001), F3 (aria wiring).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Foundations — product coverage sweep
|
|
2
|
+
|
|
3
|
+
A page-by-page sweep of the ObserveOps product (2026-06-18) validating the four **Foundations/Layout**
|
|
4
|
+
elements against what the product actually does. Six parallel passes covered: inventory/monitors ·
|
|
5
|
+
alert/dashboard/NOC · the explorers (log/apm/rum/flow/trap/metric) · topology/ncm/netroute/slo/reports/audit ·
|
|
6
|
+
all ~23 settings submodules · auth/public/system pages + global chrome + the layout switcher.
|
|
7
|
+
|
|
8
|
+
Each file lists the **variants**, **where they're used in the product** (modules / pages / flows, with
|
|
9
|
+
files), a **coverage verdict**, **notable findings** (including things we'd missed or mis-filed), and
|
|
10
|
+
**best practices**.
|
|
11
|
+
|
|
12
|
+
| File | Element | Storybook |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| [layout-shells.md](./layout-shells.md) | The outer page frame (route shells) + content layouts | Foundations/Layout/Layout shells · App shell |
|
|
15
|
+
| [page-templates.md](./page-templates.md) | The canonical page types | Foundations/Layout/Page templates |
|
|
16
|
+
| [panel-behaviours.md](./panel-behaviours.md) | How panels open / move / resize | Foundations/Layout/Panel behaviours |
|
|
17
|
+
| [screen-regions.md](./screen-regions.md) | Regions inside the content panel + content layouts | Foundations/Layout/Screen regions |
|
|
18
|
+
|
|
19
|
+
> **Status (2026-06-18):** every addition below has been **folded into the live Storybook** —
|
|
20
|
+
> Layout shells reclassified (4 route + 2 content), the **Graph / Canvas** page template added, **Modal /
|
|
21
|
+
> Expandable rows / Bulk-action bar / Popover / Full-screen** added to Panel behaviours, and **three-pane /
|
|
22
|
+
> chart-over-grid** added to Screen regions. Machine spec `layout/layouts.json` updated to match.
|
|
23
|
+
|
|
24
|
+
## Headline findings
|
|
25
|
+
|
|
26
|
+
1. **Route shells are 4, not 5.** The switcher in `src/app.vue` resolves **Layout** (default) /
|
|
27
|
+
**LoginLayout** / **EmptyLayout** / **PublicLayout** from `route.meta.layout`. **`MonitorHierarchyLayout`
|
|
28
|
+
has 0 route overrides** — it's a two-pane *content layout component* used inside `Layout`, not a route
|
|
29
|
+
shell. Re-filed accordingly.
|
|
30
|
+
2. **A 7th page archetype exists — the Topology graph canvas** (Cytoscape: pan/zoom/minimap/keyboard/node-drag).
|
|
31
|
+
It isn't a Dashboard or an Explorer; document it as **Graph / Canvas view**.
|
|
32
|
+
3. **Panel behaviours are broader than the catalogued 5.** Beyond Drawer/Collapsible/Resizable/Affix/Dashboard-tiles,
|
|
33
|
+
the product leans heavily on **Modal** (37 files), **Expandable rows** (9), **Popover** (20+), a fixed
|
|
34
|
+
**Bulk-action bar**, **resizable table columns**, and the full-screen **OmniBox** overlay.
|
|
35
|
+
4. **More content layouts than four** — add **three-pane** (metric-explorer: saved-views + picker + chart)
|
|
36
|
+
and **chart-over-grid** (trap-viewer, log dashboard) to single / two-pane / master-detail / dashboard-grid.
|
|
37
|
+
5. **Two wizard shapes** — multi-route stateful (report builder, network discovery) vs single-view stepped
|
|
38
|
+
(integration setup). Plus a **stateless utility-tool form** family (settings/utility) and **system/status
|
|
39
|
+
message** pages.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Layout shells — product coverage
|
|
2
|
+
|
|
3
|
+
The outer page frame. Source: `src/views/layouts/`. The shell is chosen by the switcher in
|
|
4
|
+
`src/app.vue` (watch on `$route`): if not logged in → **LoginLayout**; otherwise default **Layout**;
|
|
5
|
+
then the first `route.matched[].meta.layout` override wins.
|
|
6
|
+
|
|
7
|
+
## The variants
|
|
8
|
+
|
|
9
|
+
### Route shells (4 — selected by `meta.layout`)
|
|
10
|
+
|
|
11
|
+
| Shell | File | Chrome | Selected when |
|
|
12
|
+
| --- | --- | --- | --- |
|
|
13
|
+
| **Layout** (default) | `views/layouts/main.vue` | NavBar (left) + Header (top) + scrolling content + overlay layer (OmniBox, notifications, portals, DB/socket) | every authenticated route with no override |
|
|
14
|
+
| **LoginLayout** | `views/layouts/login-layout.vue` | bare — centered, no chrome, no socket | `meta.layout: 'LoginLayout'` **and** the not-logged-in fallback |
|
|
15
|
+
| **EmptyLayout** | `views/layouts/empty-layout.vue` | no nav/header, padded; keeps socket + local DB | `meta.layout: 'EmptyLayout'` |
|
|
16
|
+
| **PublicLayout** | `views/layouts/public-layout.vue` | no chrome; socket with `isMotadataUpdatingOrRestoring` | `meta.layout: 'PublicLayout'`, `public: true` |
|
|
17
|
+
|
|
18
|
+
Observed route overrides (excluding chart-layout props): **LoginLayout ×3 · PublicLayout ×2 · EmptyLayout ×1**;
|
|
19
|
+
everything else inherits the default **Layout**.
|
|
20
|
+
|
|
21
|
+
### Content layouts (rendered *inside* `Layout` — not route shells)
|
|
22
|
+
|
|
23
|
+
| Layout | File | What |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| **MonitorHierarchyLayout** | `views/layouts/monitor-hierarchy-layout.vue` | two-pane: collapsible/affixable left **hierarchy tree** + right content. **0 route overrides; used as a component in 4 files.** |
|
|
26
|
+
| **Settings two-pane** | `modules/settings/views/main.vue` | `splitpanes` (library) draggable split: left **menu** (15%, `MCollapse`+`MMenu`) + right `RouterView` (85%); hidden via `meta.hideSettingsMenu` |
|
|
27
|
+
|
|
28
|
+
## Where it's used in the product
|
|
29
|
+
|
|
30
|
+
| Shell | Pages / flows (files) |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| **Layout** | All of inventory, alert, dashboard, topology, ncm, netroute, slo, reports, audit, the explorers, and settings. The product's default. |
|
|
33
|
+
| **LoginLayout** | `auth/views/login.vue`, `auth/views/reset-password.vue`, the forgot-password modal (in login), and the `views/errors/_disk-space-full.vue` page. |
|
|
34
|
+
| **EmptyLayout** | `/reports/export/:id` — the print-safe report renderer (`views/print/report-renderer.vue`). Chrome-free so PDF/print output is clean. |
|
|
35
|
+
| **PublicLayout** | `/upgrade` and `/restore` system pages (`views/upgrade-restore/upgrade-restore-progress.vue`) — full-screen maintenance progress. |
|
|
36
|
+
| **MonitorHierarchyLayout** (content) | `modules/log/views/main.vue` (log explorer tree), inventory monitor hierarchy, topology (tree + graph), + 1 more. |
|
|
37
|
+
| **Settings two-pane** (content) | `modules/settings/views/main.vue` — wraps every settings submodule. |
|
|
38
|
+
|
|
39
|
+
## Coverage verdict
|
|
40
|
+
|
|
41
|
+
- **All four route shells are catalogued and fully covered.** No 5th/6th route shell exists.
|
|
42
|
+
- **Correction:** the Storybook "Layout shells" page and `layout/layouts.json` list **MonitorHierarchyLayout**
|
|
43
|
+
among the five shells. It is **not** a route shell (0 `meta.layout` overrides) — it's a two-pane **content
|
|
44
|
+
layout** component. The doc should present **4 route shells + 2 content layouts** (MonitorHierarchyLayout,
|
|
45
|
+
Settings two-pane). Functionally still covered; the *classification* is the fix.
|
|
46
|
+
|
|
47
|
+
## Notable / different findings
|
|
48
|
+
|
|
49
|
+
- **The switcher is auth-aware**, not purely meta-driven: unauthenticated always forces LoginLayout
|
|
50
|
+
regardless of the target route's meta (`app.vue` lines ~63–76).
|
|
51
|
+
- **Settings two-pane uses the `splitpanes` library** (genuinely drag-resizable), distinct from the
|
|
52
|
+
CSS/transition sidebars elsewhere — the only first-class resizable split in product chrome besides topology.
|
|
53
|
+
- **`MonitorHierarchyLayout` sidebar can be affix + collapsible simultaneously** (`affixed-sidebar` →
|
|
54
|
+
absolutely-positioned overlay that doesn't reflow the grid) — a hybrid not captured by "sticky" or
|
|
55
|
+
"collapsible" alone.
|
|
56
|
+
- **`hideSettingsMenu` route meta** full-screens settings create/edit forms by collapsing the left menu —
|
|
57
|
+
a per-route content-layout toggle.
|
|
58
|
+
|
|
59
|
+
## Best practices
|
|
60
|
+
|
|
61
|
+
- Don't override the shell unless required — **Layout** is right for almost every authenticated page.
|
|
62
|
+
- **Auth screens must use LoginLayout** (no socket/DB boot). **Print/export → EmptyLayout.** **System
|
|
63
|
+
maintenance → PublicLayout.**
|
|
64
|
+
- Reach for **MonitorHierarchyLayout** when a module needs a persistent left tree + detail; use the provided
|
|
65
|
+
context (`hideHierarchy()` / `showHierarchy()`) to toggle it.
|
|
66
|
+
- For configuration modules, follow the **Settings two-pane** pattern (left menu + content, `hideSettingsMenu`
|
|
67
|
+
on full-screen forms) rather than inventing a new sidebar.
|