Skip to content

Frontend Structure Guidelines

Paulo Gomes da Cruz Junior edited this page May 6, 2026 · 1 revision

Frontend Structure Guidelines — nr-forest-client

Overview

These guidelines define the conventions, patterns, and rules for the nr-forest-client frontend. All contributors must follow these rules when adding or modifying code in frontend/src/.


Vue 3 Composition API

  • Always use <script setup lang="ts"> syntax. Class-based components and the Options API are not permitted.
  • Define props with defineProps<T>() and defaults with withDefaults(). Never use the runtime array-style prop declaration.
  • Emit events with defineEmits<T>() typed explicitly.
  • Keep component logic in the <script setup> block. Do not use setup() in the Options API style.
  • Extract reusable reactive logic into composables in src/composables/ rather than duplicating it across components.
// ✅ Correct
<script setup lang="ts">
const props = withDefaults(defineProps<{ label: string; disabled?: boolean }>(), {
  disabled: false,
});
</script>

// ❌ Incorrect
export default defineComponent({
  props: ['label', 'disabled'],
  setup(props) { ... }
})

File Naming

Artifact Convention Example
Pages PascalCase + Page suffix SearchPage.vue
Components PascalCase + Component suffix MainHeaderComponent.vue
Wizard steps PascalCase + WizardStep suffix ContactsWizardStep.vue
Composables camelCase + use prefix useFetch.ts
DTOs PascalCase + Dto suffix CommonTypesDto.ts
Helpers PascalCase descriptive name DataConverters.ts
Validators PascalCase + Validations suffix BCeIDFormValidations.ts
Constants/env camelCase exports, values in UPPER_SNAKE_CASE VITE_BACKEND_URL
Test files (unit) Same name as source + .unit.test.ts useFetch.unit.test.ts
Test files (E2E) Descriptive kebab + .cy.ts unauthorizedAccess.cy.ts

Folder Structure

  • Pages live in src/pages/ with a Page suffix.
  • Wizard step components that belong exclusively to one page live in a subdirectory named after the parent page's identifier (e.g., src/pages/bceidform/).
  • Sub-views for a single page live in a subdirectory matching the page slug (e.g., src/pages/client-details/).
  • Reusable atomic form-field components belong in src/components/forms/.
  • Reusable composite sections (multi-field groups) belong in src/components/grouping/.
  • Layout or utility components that do not fit either sub-folder live directly under src/components/.
  • Do not create new top-level directories under src/ without updating Frontend Structure and this guidelines page.

Composables

  • All composables must be named with the use prefix (useFetch, useFocus).
  • Composables must be stateless singletons or return isolated reactive state per call site. Do not use module-level mutable state inside composables unless it is intentionally a shared singleton (e.g., useEventBus).
  • HTTP data-fetching composables must use useFetchTo or useFetch from src/composables/useFetch.ts. Do not call axios directly inside components.
  • A composable that wraps an external VueUse primitive must re-export only the relevant reactive values — do not leak the raw VueUse internals into the caller.

Service Layer

  • src/services/ForestClientService.ts contains pure utility functions only — no HTTP calls, no reactive state, no Vue imports beyond type declarations.
  • Functions that make HTTP requests belong in composables (useFetch*), not in src/services/.
  • Export all service functions individually (named exports). No default class export.

Data Transfer Objects (DTOs)

  • All TypeScript interfaces describing API request/response shapes live in src/dto/.
  • DTOs must use interface (not type alias) for object shapes.
  • Enums used exclusively within DTOs are defined in the same DTO file.
  • Do not place component-local types in DTO files. Component-local types belong in the component file or a co-located types.ts.
  • CommonTypesDto.ts is for types shared across at least two distinct pages or composables. Page-exclusive types live in a shared.ts inside the page directory.

Validators

  • Validator functions must follow the composable-validator pattern: they accept a configuration argument and return a function (value: T) => string that returns an empty string on success and an error message on failure.
  • All validators live in src/helpers/validators/.
  • GlobalValidators.ts is for validators used in more than one form. Form-specific validators live in their own file (e.g., BCeIDFormValidations.ts).
  • Do not throw inside validators — return the error message string.
// ✅ Correct
export const isNotEmpty = (message = "This field is required") =>
  (value: string): string => (value?.trim().length > 0 ? "" : message);

// ❌ Incorrect
export function validateName(value: string) {
  if (!value) throw new Error("Required");
}

Event Bus

  • Use useEventBus<T>(channel) from @vueuse/core for cross-component communication that cannot be handled by props/emits.
  • Register canonical channel names as string constants, not inline literals, to prevent typos.
  • Registered channels in this project:
Channel Payload Type Purpose
error-notification ValidationMessageType | undefined API error → global toast
submission-error-notification ValidationMessageType[] Form validation errors
modal-notification ModalNotification Open global confirm/delete modal
toast-notification ModalNotification Open global toast
overlay-event { isVisible, message, showLoading } Loading overlay control

Authentication & Session

  • Never access ForestClientUserSession directly in component templates. Access it via $session global property or inject via composable.
  • Never store the raw JWT token in localStorage — it is managed by AWS Amplify via CookieStorage.
  • Authentication provider names are lowercase string identifiers: idir, bceidbusiness, bcsc.
  • Role checks must use ForestClientUserSession.loadAuthorities() and compare against the known role constants. No inline string comparisons like authorities.includes("admin").

Feature Flags

  • Feature flags are read from featureFlags exported in CoreConstants.ts.
  • The $features global property provides template access: v-if="$features['flag-name']".
  • Route-level flag enforcement uses meta.featureFlagged = 'flag-name'; the navigation guard redirects if the flag is falsy.
  • Do not hard-code flag values. Every flag read must go through featureFlags or $features.
  • Flags not registered in VITE_FEATURE_FLAGS evaluate to undefined (falsy).

TypeScript

  • strict mode is not explicitly set in tsconfig.base.json; however, @typescript-eslint/no-explicit-any is off by project choice (Carbon Web Components require extensive any usage for shadow DOM interop). Minimise any to shadow-DOM or third-party interop cases only.
  • @typescript-eslint/explicit-module-boundary-types is error-level. All exported functions must have explicit return types.
  • Use TypeScript interface for object shapes. Use type for unions, intersections, and aliases to primitives.
  • Do not use @ts-ignore except at Carbon/icon import call sites where no type declarations exist (already established pattern in the codebase).

Carbon Design System

  • All Carbon components are Web Components (cds-* prefix). Import them at the top of <script setup> from the @carbon/web-components/es/components/ path.
  • To apply CSS to Carbon Web Component internals, use the v-shadow directive (defined in CustomDirectives.ts) to inject part attributes onto Shadow DOM nodes, then target via ::part() in scoped CSS.
  • Do not use vue/no-v-html bypass (<!-- eslint-disable-next-line -->) except on explicitly trusted, DOMPurify-sanitised content rendered via v-dompurify-html.
  • Never use v-html. Always use v-dompurify-html for server-provided HTML.

Input Masking

  • Use the v-masked custom directive for Carbon Web Component text inputs that require vue-the-mask patterns.
  • Do not use vue-the-mask's v-mask directive directly on cds-text-input; it cannot penetrate the Shadow DOM. Use v-masked exclusively.
  • Custom token definitions (N, U) are registered in CustomDirectives.ts.

Routing

  • Every route must declare a meta object with at minimum: requireAuth, visibleTo, format, style, headersStyle, sideMenu, profile.
  • visibleTo must be either a provider array (["idir", "bceidbusiness", "bcsc"]) or a role object ({ idirRoles: string[] }). Never omit it.
  • redirectTo is required on all authenticated routes.
  • Feature-gated routes must set meta.featureFlagged = '<flag-key>'.
  • Do not add routes without updating Frontend Structure.

Testing

  • Unit tests use Vitest with @vue/test-utils. Test files must use the .unit.test.ts suffix and co-locate with the source file, or live under tests/.
  • Component tests use Cypress Component Testing. Test files use .cy.ts suffix.
  • E2E tests use Cypress and reside in cypress/e2e/.
  • API stubs for tests use WireMock mapping files in stub/mappings/.
  • Coverage threshold is enforced at 40% lines (nyc.check-coverage: true).
  • Do not modify coverage thresholds downward without a documented architectural decision.

Linting & Formatting

  • ESLint is configured in eslint.config.mjs using the flat config format.
  • All .vue, .ts, and .tsx files are linted on every CI run.
  • prettier enforces formatting. Run npm run format locally before committing.
  • The following rules are active at error level:
    • @typescript-eslint/explicit-module-boundary-types
    • vue/no-v-html
    • vue/require-prop-types
    • import/extensions (no extension for .js/.ts, required for .vue)

See Also

CLIENT application (The Ministry of Forests' client management system)

Architecture

Frontend

General

Development Conventions

Changelogs

Clone this wiki locally