| description | Authoring guardrails for building ANY new frontend UI in the OpenObserve web app (web/) — new views, pages, panels, dialogs, feature components, or edits to existing ones. Enforce six house rules the moment you write Vue/template markup: (1) use OPageHeader for every page/module header, (2) build UI from O2 library components in web/src/lib — never bare HTML controls when an O2 equivalent exists, (3) NEVER write px — always rem, including inside Tailwind class arbitrary values ([320px] is banned; 1rem = 16px, so px/16 = rem and px/4 = the Tailwind step). px is allowed ONLY where it is the genuinely correct unit — hairlines, shadow/ring widths, query conditions, IntersectionObserver rootMargin, zero inside calc()/clamp(), user-facing copy, canvas/email consumers — and there it MUST carry an eslint-disable-next-line local/no-hardcoded-px with a `-- <reason>` at the site, never a side-file exemption (in a <style> block the directive goes inside the block as a CSS comment). The local/no-hardcoded-px rule runs in CI, so an unannotated px fails the build and cannot reach main; and corner radius uses only the two-tier scale rounded-default (4px controls) / rounded-surface (12px surfaces) / rounded-full — never rounded-[..] or the retired rounded-sm/md/lg/xl, (4) no scoped-CSS blocks and no inline style="", (5) never hardcode colors/sizes and never reach a token by raw var() in a component — use the modern registered --color-* design tokens through their utility class (bg-x/text-x), register a new --color-* token if one is missing; the legacy --o2-* token vocabulary is BANNED (never write var(--o2-*), never define one, never add a .body--dark block — migrate any --o2-* you touch to its --color-* equivalent); all of this is CI-enforced and fails the build, (6) no hardcoded user-facing text — every label, title, placeholder, and message comes from i18n (useI18nTyped t(), never useI18n from vue-i18n, which is banned) with keys added to web/src/locales/languages/en-US.json; text-carrying props/fields are typed I18nText and i18n keys stored as data are typed I18nKey, and the ONLY opt-out for a genuinely non-translatable string is raw() — never an eslint-disable. It also settles the recurring structural decisions: use OTable for any tabular data, follow the view → service → Vuex/local-ref layering for fetching list data, choose the right form container (ConfirmDialog vs ODialog vs ODrawer vs a full in-page view) by the weight of the interaction, and build every validated form with OForm + a colocated Zod schema (single-source-of-truth name-bound fields, no v-model/ref mirrors, automatic submit/loading, correct field-array keys). Trigger this whenever the user asks to create, add, build, scaffold, lay out, validate, or restyle any screen, component, header, table, list, dialog, drawer, form, field, or panel in the web frontend, or asks where a form/table/fetch should live, how to validate a form, how to add a keyboard shortcut, how to build a new reusable/common O2 component when nothing existing fits (create one in web/src/lib instead of assembling divs and classes), whether something belongs in a dialog or a drawer, or where a new page should be listed in navigation — the left-rail menu, a Settings/IAM sub-menu, or a hover-flyout — how to register its route, and how to gate it for cloud / enterprise / RBAC — even if they don't mention these rules by name. If you are about to type <template>, a page title, a hex color, a px value, or <style scoped>, this skill applies. |