Skip to content

Design system and constraints

Workspace UI pages run inside the Moltaro WebApp on the host’s own Vue, Pinia, Vuetify, vue-router, and vue-i18n instances. The design system is deliberately closed: an explicit import allowlist, a curated shared-component barrel, and a compiler that rejects any style able to escape the page. Workspace UI code is trusted workspace code compiled by Moltaro — the boundary is governance and review, not a hostile-code sandbox.

The build accepts imports only from:

  • vue, pinia, vue-router, vue-i18n;
  • approved vuetify/components and vuetify composable exports;
  • @moltaro/ui (the barrel below) and the @moltaro/workspace-ui host SDK (pages, components, and stores);
  • local project-relative paths and the generated @workspace-ui/* alias (project structure).

Everything else is a build error: Node built-ins, build-tool modules, WebApp internals (@/*), remote URL modules, and unapproved npm packages. The check covers static and supported dynamic import forms alike.

The framework modules are further restricted to specific named exports, so an allowed module does not mean every export is importable:

  • vue-router — only useRoute. Navigate with useNavigation(), not useRouter or RouterLink.
  • pinia — only defineStore and storeToRefs.
  • vue-i18n — only useI18n (see localization below).
  • vue — reactivity, lifecycle, and defineComponent are available, but rendering and app-creation APIs (h, render, createApp, Teleport, resolveDynamicComponent, cloneVNode, getCurrentInstance) are rejected; use templates and the host composables instead.

A disallowed named import fails the build with a specific WUI diagnostic that names the offending symbol.

ComponentPurpose
AppCardHeaderStandard card header with title, subtitle, eyebrow, and action row
AppEmptyStateIcon-plus-title empty or error state with an optional actions slot
ConfirmDialogConfirmation dialog before destructive or irreversible actions
DetailHeaderPanelRecord detail header: eyebrow, title, number label, busy state, actions
FormDialogDialog shell for short forms with the shared header and footer contract
MoltaroGridBaseShared AG Grid wrapper with Moltaro column and rendering conventions
OperationalPageToolbarToolbar for operational console pages
OperationalUtilityDrawerRight-side utility drawer for filters, sorting, and columns
PropertyItemRead-only label/value fact row for detail surfaces
UtilityDrawerSectionCollapsible content section inside a utility drawer
UtilityDrawerSectionTitleTitle row for a utility drawer section

The barrel also exports the TypeScript types these components use, such as MoltaroGridColumnDefinition and MoltaroGridRowClickedEvent. Every export is part of the host contract; other WebApp components are unreachable.

Each manifest page declares one host-owned Layout; a missing or unknown value is a build error.

  • Default — the normal document layout: the Moltaro navigation shell plus a container that grows with content and scrolls at document level. Use it for forms, detail pages, reports, and vertically flowing surfaces.
  • Fullscreen — the Data Explorer-style application layout: the same shell plus a root that fills the content viewport with no document-level scrolling; the page owns its internal scroll areas. It never hides Moltaro navigation and never calls the browser Fullscreen API.

Both layouts expose the same Vue/Vuetify context; responsive behavior stays page-owned via useDisplay(), breakpoint props, and media or container queries. A page can read its resolved layout but cannot replace the shell.

Pages may use <style scoped> blocks and static style/:style bindings: colors, typography, spacing, custom properties, flex/grid, media/container queries, animations, transitions, and pseudo-classes/elements — alongside Vuetify theme tokens, props, and utility classes. Inline style/:style is accepted only on native HTML elements; to style a Vuetify component use its props, classes, and theme tokens, or wrap it in a native element.

The compiler namespaces every style to its page: a deterministic opaque scope id is rendered as a data-workspace-ui-scope attribute, every compiled selector is rewritten to match only inside that scope, and keyframe names are namespaced. Vuetify overlays teleport to a page-owned overlay target carrying the same scope, so they never touch core Moltaro overlays.

Rejected at build time:

  • <style> without scoped, standalone or global CSS imports, :global, and selectors rooted at html, body, :root, #app, or shell elements;
  • @import, remote stylesheets or scripts, @font-face, and custom fonts;
  • v-html, runtime template compilation, dynamic <component> resolution, runtime style or stylesheet injection, and a second createApp;
  • direct host globals — window, document, localStorage, sessionStorage, globalThis, self, top, parent — use useDisplay() and container queries for responsive behavior, host composables for state, and the Moltaro/external API clients instead of raw fetch.

Moltaro owns the framework and toolchain versions. Builds run against an offline toolchain packaged with the installation; there are no arbitrary npm dependencies or third-party build plugins. After a Moltaro update a rebuild may be required, and a breaking host-contract change may require source edits — see Build, publish, and upgrade.

Each build also runs an ESLint quality gate based on eslint-config-vuetify, and lint errors fail the build. Author source to those conventions — attribute ordering, self-closing components, and import ordering — so a functionally correct page also passes the gate; every diagnostic carries the rule id that flagged it.

useMoltaroContext().theme exposes a computed { name, isDark }, and Vuetify tokens such as rgb(var(--v-theme-primary)) resolve against the active workspace theme, so scoped styles stay correct in light and dark.

Project source owns locale JSON for en, uk, de, pl, and es; the build requires matching key sets, messages register under a project namespace, and the active WebApp locale selects the page locale. Render text through useI18n() — call it with no arguments and destructure only t, te, and tm (the $t template global and the options/scope overloads are rejected). Read the active locale from useMoltaroContext().locale or useFormatting().locale, not from useI18n(). Keep Vuetify inputs labeled and page roots semantic, and give interactive elements stable data-testid values.

A trimmed excerpt of the Entity details page produced by the UI Studio generator:

<script setup lang="ts">
import { AppCardHeader, DetailHeaderPanel, PropertyItem } from '@moltaro/ui'
import { useOrderDetailsStore } from '@workspace-ui/stores/orderDetailsStore'
import { VCard, VCardText } from 'vuetify/components'
const store = useOrderDetailsStore()
</script>
<template>
<main data-testid="order-details-page">
<DetailHeaderPanel
:busy="store.loading"
eyebrow="Order"
:number-label="store.record?.Number"
:title="store.record?.DisplayName"
/>
<v-card v-for="group in groups" :key="group.key" border variant="flat">
<AppCardHeader density="compact" :title="group.title" />
<v-card-text>
<PropertyItem v-for="field in group.fields" :key="field.key"
:label="field.label" :value="displayValue(field)" />
</v-card-text>
</v-card>
</main>
</template>