Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .cursor/rules/patternfly-ux-capitalization.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
description:
PatternFly UX writing — sentence case and capitalization for user-visible UI copy (aligned with PatternFly guidelines).
globs:
- "**/*.tsx"
- "**/*.jsx"
alwaysApply: false
---

# PatternFly capitalization (UI copy)

Follow [PatternFly capitalization guidelines](https://www.patternfly.org/ux-writing/capitalization/) for **user-visible strings** (labels, headings, buttons, table text, empty states, errors, etc.).

## Defaults

- Use **sentence case**: capitalize only the **first word** of the phrase/sentence, plus **proper nouns**, **product names**, **acronyms**, and **initialisms** (e.g. OpenShift, OVN, API, DNS).
- **Buttons, navigation, page titles, headings**: sentence case unless an existing product term requires otherwise.
- Prefer **consistency** within the same surface (wizard step, table, modal).

## Feature vs generic wording

- Capitalize a name when it refers to a **specific product feature or UI location**; use **lowercase** when it is a **generic concept** (PatternFly’s *Compliance* / *Sources* examples apply the same idea).

## Breadcrumbs and user-defined names

- **Breadcrumbs**: match the capitalization of the **source page title** (even if mixed styles).
- **User- or system-defined names** (e.g. cluster names, resource names): **preserve the exact casing** the user or API used.

## Third-party products

- Spell and capitalize **external product names** as in **that vendor’s** documentation.

## Docs / component names in prose

- In **documentation-style** text about components: component names **lowercase** unless they start a sentence. **Code identifiers** follow normal language conventions.

## Scope note

- **`libs/ui-lib/lib/ocm/**`** does not use `t('ai:…')`; still apply these rules to **plain English** UI strings there. **`t('ai:…')`** strings under `libs/ui-lib` (outside `ocm`) follow **`translations.mdc`** for workflow; English source strings should still respect sentence case where it fits the string catalog.
5 changes: 2 additions & 3 deletions libs/locales/lib/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,6 @@
"ai:Cluster CIDR": "Cluster CIDR",
"ai:Cluster configured and ready for installation.": "Cluster configured and ready for installation.",
"ai:Cluster details": "Cluster details",
"ai:Cluster Details": "Cluster Details",
"ai:cluster doesn't contain the feature_usage field": "cluster does not contain the feature_usage field",
"ai:cluster doesn't contain the openshift_version field": "cluster does not contain the openshift_version field",
"ai:Cluster Events": "Cluster Events",
Expand Down Expand Up @@ -981,7 +980,7 @@
"ai:useAlerts must be used within AlertsContextProvider": "useAlerts must be used within AlertsContextProvider",
"ai:Used to describe hosts' physical location. Helps for quicker host selection during cluster creation.": "Used to describe hosts' physical location. Helps for quicker host selection during cluster creation.",
"ai:useFeatureSupportLevel must be used within FeatureSupportLevelContextProvider.": "useFeatureSupportLevel must be used within FeatureSupportLevelContextProvider.",
"ai:User-Managed Networking": "User-Managed Networking",
"ai:User-Managed networking": "User-Managed networking",
"ai:Username": "Username",
"ai:UUID": "UUID",
"ai:Valid hostname": "Valid hostname",
Expand Down Expand Up @@ -1014,7 +1013,7 @@
"ai:With BMC form": "With BMC form",
"ai:With Discovery ISO": "With Discovery ISO",
"ai:With iPXE": "With iPXE",
"ai:With User-Managed Networking, you'll need to provide and configure a load balancer for the API and ingress endpoints.": "With User-Managed Networking, you'll need to provide and configure a load balancer for the API and ingress endpoints.",
"ai:With User-Managed networking, you'll need to provide and configure a load balancer for the API and ingress endpoints.": "With User-Managed networking, you'll need to provide and configure a load balancer for the API and ingress endpoints.",
"ai:Worker_one": "Worker",
"ai:Worker_other": "Workers",
"ai:Worker Hosts Labels": "Worker Hosts Labels",
Expand Down
2 changes: 1 addition & 1 deletion libs/ui-lib-tests/cypress/support/variables/networking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Cypress.env(
'#form-input-clusterNetworks-0-hostPrefix-field-helper',
);
Cypress.env('serviceNetworkCidrFieldHelperId', '#form-input-serviceNetworks-0-cidr-field-helper');
Cypress.env('userManagedNetworkingRadioText', 'User-Managed Networking');
Cypress.env('userManagedNetworkingRadioText', 'User-Managed networking');
Cypress.env('openVirtualNetworkingRadioText', 'Open Virtual Networking');
Cypress.env('networkTypeToggleId', '#form-input-networkType-field');
Cypress.env('hostSubnetFieldId', '#form-input-hostSubnet-field');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ export const ManagedNetworkingControlGroup = ({
value={'userManaged'}
label={
<>
<span>{t('ai:User-Managed Networking')}</span>{' '}
<span>{t('ai:User-Managed networking')}</span>{' '}
<PopoverIcon
bodyContent={t(
"ai:With User-Managed Networking, you'll need to provide and configure a load balancer for the API and ingress endpoints.",
"ai:With User-Managed networking, you'll need to provide and configure a load balancer for the API and ingress endpoints.",
)}
/>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { Table, TableVariant, Tbody, Td, Tr } from '@patternfly/react-table';
import { genericTableRowKey, getDefaultCpuArchitecture } from '../../../../common';
import { getDiskEncryptionEnabledOnStatus } from '../../clusterDetail/ClusterProperties';
import { getDiskEncryptionEnabledOnStatus } from './utils';
import OpenShiftVersionDetail from '../../clusterDetail/OpenShiftVersionDetail';
import { useNewFeatureSupportLevel } from '../../../../common/components/newFeatureSupportLevels';
import { Cluster } from '@openshift-assisted/types/assisted-installer-service';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Title } from '@patternfly/react-core';
import { Table, TableVariant, Tbody, Td, Tr } from '@patternfly/react-table';
import React from 'react';
import { genericTableRowKey, isDualStack, NETWORK_TYPE_LABELS } from '../../../../common';
import { getManagementType, getStackTypeLabel } from '../../clusterDetail/ClusterProperties';
import { getManagementType, getStackTypeLabel } from './utils';
import { Cluster } from '@openshift-assisted/types/assisted-installer-service';

type ReviewTableRowsType = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { test, describe, expect } from 'vitest';
import { getStackTypeLabel } from './ClusterProperties';
import { getStackTypeLabel } from './utils';
import { Cluster } from '@openshift-assisted/types/assisted-installer-service';

const createCluster = (overrides: Partial<Cluster> = {}): Cluster =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import { isDualStack } from '../../../../common';
import { Cluster, DiskEncryption } from '@openshift-assisted/types/assisted-installer-service';

export const getManagementType = ({ userManagedNetworking }: Cluster): string =>
userManagedNetworking ? 'User-Managed networking' : 'Cluster-managed networking';

export const getStackTypeLabel = (cluster: Cluster): string =>
isDualStack(cluster) ? 'Dual-stack' : 'IPv4';

export const getDiskEncryptionEnabledOnStatus = (diskEncryption: DiskEncryption['enableOn']) => {
let diskEncryptionType = null;
switch (diskEncryption) {
case undefined:
case 'none':
break;
case 'all':
diskEncryptionType = (
<>
Enabled on control plane nodes
<br />
Enabled on workers
</>
);
break;
case 'masters':
diskEncryptionType = <>Enabled on control plane nodes</>;
break;
case 'workers':
diskEncryptionType = <>Enabled on workers</>;
break;
case 'arbiters':
diskEncryptionType = <>Enabled on arbiters</>;
break;
case 'masters,arbiters':
diskEncryptionType = <>Enabled on control plane nodes and arbiters</>;
break;
case 'masters,workers':
diskEncryptionType = <>Enabled on control plane nodes and workers</>;
break;
case 'arbiters,workers':
diskEncryptionType = <>Enabled on arbiters and workers</>;
break;
case 'masters,arbiters,workers':
diskEncryptionType = (
<>
Enabled on control plane nodes
<br />
Enabled on arbiters
<br />
Enabled on workers
</>
);
break;
default: {
const _exhaustive: never = diskEncryption;
return _exhaustive;
}
}
return diskEncryptionType;
};

This file was deleted.

Loading
Loading