-
Notifications
You must be signed in to change notification settings - Fork 2
Frontend Structure Guidelines
Paulo Gomes da Cruz Junior edited this page May 6, 2026
·
1 revision
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/.
- Always use
<script setup lang="ts">syntax. Class-based components and the Options API are not permitted. - Define props with
defineProps<T>()and defaults withwithDefaults(). 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 usesetup()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) { ... }
})| 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 |
- Pages live in
src/pages/with aPagesuffix. - 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.
- All composables must be named with the
useprefix (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
useFetchTooruseFetchfromsrc/composables/useFetch.ts. Do not callaxiosdirectly 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.
-
src/services/ForestClientService.tscontains 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 insrc/services/. - Export all service functions individually (named exports). No default class export.
- All TypeScript interfaces describing API request/response shapes live in
src/dto/. - DTOs must use
interface(nottype 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.tsis for types shared across at least two distinct pages or composables. Page-exclusive types live in ashared.tsinside the page directory.
- Validator functions must follow the composable-validator pattern: they accept a
configuration argument and return a function
(value: T) => stringthat returns an empty string on success and an error message on failure. - All validators live in
src/helpers/validators/. -
GlobalValidators.tsis 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");
}- Use
useEventBus<T>(channel)from@vueuse/corefor 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 |
- Do not add new channels without documenting them in this table and updating Frontend Architecture Overview.
- Never access
ForestClientUserSessiondirectly in component templates. Access it via$sessionglobal property or inject via composable. - Never store the raw JWT token in
localStorage— it is managed by AWS Amplify viaCookieStorage. - 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 likeauthorities.includes("admin").
- Feature flags are read from
featureFlagsexported inCoreConstants.ts. - The
$featuresglobal 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
featureFlagsor$features. - Flags not registered in
VITE_FEATURE_FLAGSevaluate toundefined(falsy).
-
strictmode is not explicitly set intsconfig.base.json; however,@typescript-eslint/no-explicit-anyis off by project choice (Carbon Web Components require extensiveanyusage for shadow DOM interop). Minimiseanyto shadow-DOM or third-party interop cases only. -
@typescript-eslint/explicit-module-boundary-typesis error-level. All exported functions must have explicit return types. - Use TypeScript
interfacefor object shapes. Usetypefor unions, intersections, and aliases to primitives. - Do not use
@ts-ignoreexcept at Carbon/icon import call sites where no type declarations exist (already established pattern in the codebase).
- 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-shadowdirective (defined inCustomDirectives.ts) to injectpartattributes onto Shadow DOM nodes, then target via::part()in scoped CSS. - Do not use
vue/no-v-htmlbypass (<!-- eslint-disable-next-line -->) except on explicitly trusted, DOMPurify-sanitised content rendered viav-dompurify-html. - Never use
v-html. Always usev-dompurify-htmlfor server-provided HTML.
- Use the
v-maskedcustom directive for Carbon Web Component text inputs that requirevue-the-maskpatterns. - Do not use
vue-the-mask'sv-maskdirective directly oncds-text-input; it cannot penetrate the Shadow DOM. Usev-maskedexclusively. - Custom token definitions (
N,U) are registered inCustomDirectives.ts.
- Every route must declare a
metaobject with at minimum:requireAuth,visibleTo,format,style,headersStyle,sideMenu,profile. -
visibleTomust be either a provider array (["idir", "bceidbusiness", "bcsc"]) or a role object ({ idirRoles: string[] }). Never omit it. -
redirectTois required on all authenticated routes. - Feature-gated routes must set
meta.featureFlagged = '<flag-key>'. - Do not add routes without updating Frontend Structure.
- Unit tests use Vitest with
@vue/test-utils. Test files must use the.unit.test.tssuffix and co-locate with the source file, or live undertests/. - Component tests use Cypress Component Testing. Test files use
.cy.tssuffix. - 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.
- ESLint is configured in
eslint.config.mjsusing the flat config format. - All
.vue,.ts, and.tsxfiles are linted on every CI run. -
prettierenforces formatting. Runnpm run formatlocally before committing. - The following rules are active at error level:
@typescript-eslint/explicit-module-boundary-typesvue/no-v-htmlvue/require-prop-types-
import/extensions(no extension for.js/.ts, required for.vue)
CLIENT application (The Ministry of Forests' client management system)
Frontend
General