ui/docs/ARCHITECTURE.md
This document describes the architecture of the Meshery UI as it exists during and after the multi-phase restructure tracked in meshery/meshery#18656. It is the contributor's map: a short reference for deciding where new code goes, which UI kit and tokens to use, and when a file is too big. For the full long-form spec — the inventory of what exists today, the migration mechanics, ESLint rules, and the phased PR roadmap — read ../restructure-plan.md.
This is a living document. The target architecture is being landed incrementally over five phases; the principles and directory model below are already binding for new code, even where the existing tree has not yet been reshaped.
The Meshery UI is fundamentally sound — 87% TypeScript, RTK Query for data, and @sistent/sistent already imported in 276 files — but it has accreted three overlapping styling systems, two parallel icon libraries, two parallel workspace concepts, and eight files over 1,000 lines. The restructure is not a rewrite. It is a disciplined consolidation that:
@sistent/sistent as the single source of truth for components, tokens, and theme.@mui/* imports from application code.theme.palette.*.components/ folders into ~15 discoverable domains.See the parent epic #18656 for the full motivation.
These are load-bearing. Every rule below answers a single question: what does this mean when I write a new component?
Import every component you need from @sistent/sistent — never from @mui/material, @mui/icons-material, @material-ui/*, or @rjsf/mui. Hooks and styling utilities (styled, useTheme, alpha, palette accessors) come from @/theme, the project-local front door that re-exports the supported Sistent surface; see THEMING.md for the full theming contract. If Sistent is missing something you need, file an upstream PR to Sistent rather than reaching past it. The single exception is RJSF: @rjsf/mui is allowed only inside Sistent's wrapper (ui/components/shared/FormFields/) and is invisible to the rest of the app.
// Good
import { Button, Dialog } from '@sistent/sistent';
import { useTheme, styled } from '@/theme';
// Forbidden in application code
import Button from '@mui/material/Button';
No hex, rgb, or named color string belongs in any .tsx or .ts file outside ui/theme/. Colors come from theme.palette.*, spacing from theme.spacing(), and breakpoints from theme.breakpoints.*. If Sistent's palette is missing a token your design calls for, open an upstream PR to Sistent — do not invent a local override.
// Good
const Banner = styled('div')(({ theme }) => ({
color: theme.palette.error.main,
padding: theme.spacing(2),
}));
// Forbidden
const Banner = styled('div')({
color: '#F91313',
padding: '16px',
});
styled() over style={{}}Inline style props are reserved for genuinely dynamic values that cannot be expressed in a styled component — e.g. transform: translate(x, y) for a draggable element. Colors, spacing, and typography are never dynamic enough to justify inline. Reach for styled() (re-exported from @/theme) first, and only fall back to style={{}} when the value changes per render in a way the CSS engine can't express.
Every domain — Designs, Workspaces, Connections, Environments, Performance, Dashboard, and the rest — lives in exactly one folder under ui/components/. Cross-cutting primitives (Modal, Card, Stepper, EmptyState, …) live under ui/components/shared/. If you find yourself building a second Workspaces-ish component, stop: extend the canonical one or move it to shared/.
Files have a budget:
Splits should be by concern (extract a hook, lift a sub-component into its own file, move types into a sibling *.types.ts), not by arbitrary line count. The budget exists to make the codebase navigable, not to encourage shallow file shuffling.
A folder containing only index.tsx is hard to navigate in an IDE; name files for what they contain. Generic names like style.tsx, utils.tsx, helpers.tsx, and components.tsx are also forbidden. Prefer Header.tsx, Header.styled.ts, Header.types.ts, useHeaderState.ts — names that survive a file-search.
# Good
components/layout/Header/
Header.tsx
Header.styled.ts
Header.types.ts
useHeaderState.ts
# Bad
components/layout/Header/
index.tsx
styles.tsx
utils.tsx
The post-restructure ui/ layout. Areas marked NEW or CONSOLIDATED are introduced by the restructure; areas marked keep as-is are already in good shape.
ui/
├── pages/ # Next.js routes — shape unchanged, only imports
│ ├── _app.tsx # Slimmed to provider stack only, < 150 lines
│ ├── _document.tsx
│ ├── index.tsx
│ ├── designs/ # was: configuration/designs
│ ├── filters/ # was: configuration/filters.tsx (promoted)
│ ├── catalog/ # was: configuration/catalog.tsx (promoted)
│ ├── performance/
│ ├── workspaces/ # was: management/workspaces
│ ├── environments/ # was: management/environments
│ ├── connections/ # was: management/connections
│ ├── adapters/ # was: management/adapter
│ ├── settings/
│ ├── user/
│ └── extensions/
│
├── components/
│ ├── shared/ # NEW — cross-cutting primitives
│ │ ├── Modal/ # 1 canonical Modal + Confirm/Info/Export flavors
│ │ ├── Card/ # ResourceCard for Patterns/Filters/Workspaces
│ │ ├── Stepper/ # Single Stepper used by Deploy/URL/CSV/Relationship
│ │ ├── EmptyState/ # 1 canonical EmptyState with variants
│ │ ├── LoadingState/ # Replaces LoadingComponents/
│ │ ├── ErrorBoundary/
│ │ ├── PageHeader/ # Replaces inline page headers
│ │ ├── DataTable/ # Wraps @sistent/mui-datatables
│ │ ├── FormFields/ # RJSF custom widgets
│ │ ├── CodeEditor/
│ │ └── Markdown/
│ │
│ ├── layout/ # NEW — app chrome
│ │ ├── AppShell/ # StyledRoot + StyledMainContent + StyledDrawer
│ │ ├── Header/ # was: Header.tsx + Header.styles.tsx + HeaderMenu.tsx
│ │ ├── Navigator/ # was: Navigator.tsx (1142-line giant, split)
│ │ ├── Footer/
│ │ └── NotificationCenter/
│ │
│ ├── designs/ # CONSOLIDATED — Patterns + Configurator
│ │ ├── list/ # was: MesheryPatterns/
│ │ ├── builder/ # was: configuratorComponents/MeshModel/
│ │ ├── lifecycle/ # was: DesignLifeCycle/
│ │ ├── validation/
│ │ └── deployment/
│ │
│ ├── filters/ # was: MesheryFilters/ (1376-line Filters.tsx split)
│ │
│ ├── workspaces/ # CONSOLIDATED — SpacesSwitcher + Lifecycle/Workspaces
│ │ ├── list/
│ │ ├── switcher/
│ │ ├── content/
│ │ └── share/
│ │
│ ├── environments/ # was: Lifecycle/Environments/
│ ├── connections/ # ConnectionTable split (was 1291 lines)
│ ├── adapters/ # was: MeshAdapterConfigComponent + MesheryAdapterPlayComponent
│ ├── registry/ # was: Settings/Registry/ (lifted out of Settings)
│ │
│ ├── performance/ # Performance/index.tsx split by concern
│ │ ├── profiles/
│ │ ├── results/
│ │ ├── dashboard/
│ │ └── charts/
│ │
│ ├── dashboard/ # was: Dashboard/ — flattened
│ │ ├── widgets/
│ │ ├── charts/
│ │ └── resources/
│ │
│ ├── settings/ # Slimmed — Registry and Performance moved out
│ ├── user/ # was: UserPreferences/
│ ├── telemetry/ # kept — grafana + prometheus
│ ├── extensions/ # kept
│ └── subscription/ # kept — GraphQL subscription wiring
│
├── theme/ # RENAMED & COLLAPSED from themes/
│ ├── index.ts # Re-exports from @sistent/sistent — the ONLY source for theme & styling
│ ├── SistentProvider.tsx # was: SistentWrapper.tsx + provider plumbing
│ ├── snackbar.tsx # ThemeResponsiveSnackbar (uses theme.palette.*)
│ └── rjsf.ts # Sistent-backed RJSF theme — no Material UI
│
├── store/ # keep as-is — RTK store + slices
├── rtk-query/ # keep as-is — RTK Query endpoints
├── machines/ # keep as-is — XState machines
│
├── lib/ # Third-party integrations & network helpers
├── utils/ # Pure functions only — no JSX
│ ├── hooks/ # Generic hooks (useDebounce, etc.)
│ ├── context/ # React contexts
│ ├── format/ # camelCase, PascalCase, kebabCase helpers
│ └── k8s/ # k8s-utils.ts, multi-ctx.ts
│
├── assets/ # Static only — never contains .tsx components
│ ├── images/
│ ├── gifs/
│ ├── icons/ # SVG components, the one icon home
│ └── schemas/
│
├── graphql/ # MOVED from components/graphql/ — not components
│ ├── queries/
│ ├── mutations/
│ └── subscriptions/
│
├── tests/
└── playwright/
pages/ — Next.js route entry points. Pages are thin: they assemble components and wire data. No business logic, no styled components beyond layout glue.components/shared/ — Cross-cutting primitives that any domain may use. If two domain folders both need it, it belongs here. The canonical home for Modal, Card, Stepper, EmptyState, LoadingState, PageHeader, DataTable, FormFields, CodeEditor, and Markdown.components/layout/ — Application chrome — the Shell, Header, Navigator, Footer, and NotificationCenter. These are mounted once per app, not per route.components/<domain>/ — One folder per product domain (Designs, Workspaces, Connections, Environments, Performance, Dashboard, Adapters, Registry, Settings, User, Telemetry, Extensions, Subscription). Each domain owns its own list, detail, and form views.theme/ — The single entrypoint for theme utilities. Always import styling utilities (styled, useTheme, alpha, lighten, darken, and palette accessors) from @/theme, rather than importing them directly from @sistent/sistent or @mui/* in application code. UI components themselves still come from @sistent/sistent per principle 1. See THEMING.md for the full theming guide.utils/ — Pure functions only. No JSX, no React components. Hooks live in utils/hooks/. Generic helpers (format/, k8s/, etc.) live in subfolders by category.assets/ — Static-only, plus typed SVG icons. Icons, images, GIFs, and schemas. The one allowed .tsx flavor is the typed SVG components under assets/icons/; any other .tsx file in assets/ is a bug.graphql/ — All GraphQL queries, mutations, and subscriptions. These are not components — they live at the top level, not under components/.store/, rtk-query/, machines/ — Already clean. Untouched by the restructure.ui/themes/ — renamed to ui/theme/ and shrunk to a Sistent re-export.ui/constants/colors.ts — deleted; callers move to theme.palette.*.ui/components/icons/ and ui/assets/new-icons/ — merged into ui/assets/icons/ with consistent naming.ui/components/hooks/ — merged into ui/utils/hooks/.ui/components/General/ — split into shared/ (primitives) and layout/ (chrome).ui/components/shapes/ — merged into ui/assets/icons/shapes/.*.style(s).tsx files — colocated inside their component folder with unique names (Header.styled.ts, not styles.tsx).A quick decision guide. When in doubt, follow the rule below; when the rule disagrees with the existing code, the rule wins for new code and the existing code is a candidate for a follow-up cleanup.
components/shared/Modal/ — never write a new one. If you need a variant, add it inside shared/Modal/ (e.g. a new flavor alongside Confirm/Info/Export).components/shared/Card/. If your card has domain-specific content, compose it inside your domain folder using the shared Card primitive.shared/ first.components/designs/deployment/.components/layout/.@/theme, not from @mui/*, not from @sistent/sistent directly in application code. The @/theme indirection keeps the codebase forward-compatible as Sistent evolves.theme.palette.* inside a styled() callback or via useTheme(). No hex, no rgb, no named-color strings.theme.spacing(n). No raw '8px', no '1rem'.theme.breakpoints.up('md') etc. inside styled callbacks.style={{ color: ... }}? Refactor to a styled() component first. Inline style is reserved for values that genuinely change per render (drag transforms, dynamic positioning).useDebounce, useLocalStorage)? It lives in utils/hooks/, not components/hooks/.useDesignLifecycle)? It lives in its domain folder: components/designs/lifecycle/useDesignLifecycle.ts.utils/format/.utils/context/ for cross-cutting contexts; otherwise colocate with its consumer.@sistent/sistent first. If absent, add a typed SVG component to ui/assets/icons/ and export it from ui/assets/icons/index.ts. Do not import from @mui/icons-material.ui/assets/, never under ui/components/.ui/graphql/, not under ui/components/graphql/. Components import from ui/graphql/.DesignBuilder.tsx, not index.tsx (unless index.tsx is a folder's public re-export alongside other named files).<Component>.styled.ts, not styles.tsx.<Component>.types.ts.To keep this document honest and the restructure scope contained, the following are out of scope for both this doc and the restructure itself:
@sistent/sistent needs a new release, that is a separate coordinated upgrade, not part of this restructure.If your change touches any of the above, it belongs in a separate issue.
The restructure is split into five phases, each shipping as small PRs rather than one monolith:
ARCHITECTURE.md and THEMING.md). Adds the ui/theme/index.ts re-export but deletes nothing. No runtime behavior change.theme.palette.*. Delete themes/app.ts, themes/index.ts, constants/colors.ts.@mui/* imports with Sistent. Drop @mui/* and @rjsf/mui from package.json direct deps.git mv PR at a time so history is preserved.This document is part of Phase 1. The directory model it describes is the target; the current tree is in the middle of being moved into it. When existing code disagrees with this document, the document wins for new code, and the existing code is a follow-up cleanup candidate.
../restructure-plan.md — the full long-form restructure plan, including the current-state inventory, ESLint rules, deduplication tables, and the per-file split plan for the giant files../THEMING.md — the theming guide, covering tokens, palette accessors, dark mode, and the @/theme entrypoint. (Landing in parallel with this document.)