@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.
Files changed (79) hide show
  1. package/AGENTS.md +102 -0
  2. package/README.md +73 -0
  3. package/components/index.json +1270 -0
  4. package/components/recipes/README.md +41 -0
  5. package/components/recipes/recipes.json +922 -0
  6. package/components/registry/README.md +44 -0
  7. package/components/registry/_schema.json +47 -0
  8. package/components/registry/button.json +368 -0
  9. package/components/registry/checkbox.json +177 -0
  10. package/components/registry/data-viz-tooltips.json +409 -0
  11. package/components/registry/date-time-pickers.json +296 -0
  12. package/components/registry/drawer.json +222 -0
  13. package/components/registry/dropdown-picker.json +388 -0
  14. package/components/registry/filters.json +155 -0
  15. package/components/registry/form-item.json +281 -0
  16. package/components/registry/input.json +277 -0
  17. package/components/registry/link.json +186 -0
  18. package/components/registry/loose-tags.json +196 -0
  19. package/components/registry/menu.json +145 -0
  20. package/components/registry/modal.json +265 -0
  21. package/components/registry/navigation.json +425 -0
  22. package/components/registry/popover.json +216 -0
  23. package/components/registry/radio.json +238 -0
  24. package/components/registry/scheduler.json +188 -0
  25. package/components/registry/select.json +247 -0
  26. package/components/registry/severity.json +179 -0
  27. package/components/registry/switch.json +177 -0
  28. package/components/registry/table.json +275 -0
  29. package/components/registry/tabs.json +264 -0
  30. package/components/registry/tag.json +345 -0
  31. package/components/registry/tags-list.json +115 -0
  32. package/components/registry/toolbars.json +240 -0
  33. package/components/registry/tooltip.json +175 -0
  34. package/components/specs/README.md +72 -0
  35. package/components/specs/button.md +230 -0
  36. package/components/specs/checkbox.md +162 -0
  37. package/components/specs/data-viz-tooltips.md +93 -0
  38. package/components/specs/date-time-pickers.md +161 -0
  39. package/components/specs/drawer.md +162 -0
  40. package/components/specs/dropdown-picker.md +161 -0
  41. package/components/specs/filters.md +118 -0
  42. package/components/specs/form-item.md +130 -0
  43. package/components/specs/input.md +130 -0
  44. package/components/specs/link.md +131 -0
  45. package/components/specs/loose-tags.md +139 -0
  46. package/components/specs/menu.md +88 -0
  47. package/components/specs/modal.md +176 -0
  48. package/components/specs/navigation.md +181 -0
  49. package/components/specs/popover.md +118 -0
  50. package/components/specs/radio.md +144 -0
  51. package/components/specs/scheduler.md +133 -0
  52. package/components/specs/select.md +118 -0
  53. package/components/specs/switch.md +124 -0
  54. package/components/specs/table.md +115 -0
  55. package/components/specs/tabs.md +136 -0
  56. package/components/specs/tag.md +196 -0
  57. package/components/specs/tags-list.md +105 -0
  58. package/components/specs/toolbars.md +108 -0
  59. package/components/specs/tooltip.md +112 -0
  60. package/foundation/README.md +39 -0
  61. package/foundation/layout-shells.md +67 -0
  62. package/foundation/page-templates.md +69 -0
  63. package/foundation/panel-behaviours.md +61 -0
  64. package/foundation/screen-regions.md +62 -0
  65. package/index.js +75 -0
  66. package/layout/grid.json +34 -0
  67. package/layout/layouts.json +310 -0
  68. package/llms.txt +60 -0
  69. package/package.json +42 -0
  70. package/spec.manifest.json +407 -0
  71. package/tokens/README.md +125 -0
  72. package/tokens/component.json +34 -0
  73. package/tokens/kit-accents.json +14 -0
  74. package/tokens/primitive.json +130 -0
  75. package/tokens/purpose-map.json +67 -0
  76. package/tokens/semantic.dark.json +90 -0
  77. package/tokens/semantic.light.json +90 -0
  78. package/tokens/structural.json +35 -0
  79. 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.