Adding a feature
This is the most practical question a new developer asks: where does my code go?
Decision tree
Follow this tree to find the right home for your code:
Is it a URL-routed page or a multi-tab screen?
├── Yes → views/<your-page>/
└── No → Is it a self-contained UI widget loaded async?
├── Yes → ui-features/<your-feature>/
└── No → Is it state management, API calls, or business logic (no UI)?
├── Yes → data/<your-domain>-data/
└── No → Is it app-wide infrastructure (i18n, analytics, gateway)?
├── Yes → platform-libs/<your-service>/
└── No → Is it a pure utility or reusable component with no app deps?
└── Yes → libs/<your-util>/
When in doubt, the answer is almost always ui-features/.
Creating a new ui-feature (most common case)
Let's say your ticket is: "Add a new Workspace Activity feed widget."
Step 1: Generate the package
npx nx generate @postman/app-generator:library \
--type=ui-feature \
--name=workspace-activity-feed \
--scope=collaboration
This creates:
ui-features/workspace-activity-feed/
├── src/
│ ├── components/
│ │ └── WorkspaceActivityFeed.tsx ← Main component
│ └── index.ts ← Public API (what others can import)
├── package.json
├── project.json ← Nx config + tags
└── tsconfig.json
Step 2: Build the component
// ui-features/workspace-activity-feed/src/components/WorkspaceActivityFeed.tsx
import React from 'react';
import { observer } from 'mobx-react-lite';
import { useWorkspaceActivityStore } from '@postman-app/workspace-activity-data';
import { Text, Spinner } from '@postman-app/aether-components';
import { useTranslation } from '@postman-app/i18n-sdk';
export const WorkspaceActivityFeed = observer(() => {
const { t } = useTranslation('workspace-activity');
const store = useWorkspaceActivityStore();
if (store.isLoading) return <Spinner />;
return (
<div>
<Text variant="heading">{t('activity.title')}</Text>
{store.activities.map(activity => (
<ActivityItem key={activity.id} activity={activity} />
))}
</div>
);
});
Step 3: Export from the package
// ui-features/workspace-activity-feed/src/index.ts
export { WorkspaceActivityFeed } from './components/WorkspaceActivityFeed';
Step 4: If you need data, create a data package
npx nx generate @postman/app-generator:library \
--type=data \
--name=workspace-activity-data \
--scope=collaboration
// data/workspace-activity-data/src/store.ts
import { makeAutoObservable } from 'mobx';
import { gatewayClient } from '@postman-app/gateway-service';
export class WorkspaceActivityStore {
activities = [];
isLoading = false;
constructor() {
makeAutoObservable(this);
}
async fetch(workspaceId: string) {
this.isLoading = true;
this.activities = await gatewayClient.get(`/workspaces/${workspaceId}/activity`);
this.isLoading = false;
}
}
Step 5: Load the feature from a view
// views/workspace-overview/src/components/WorkspaceOverviewContainer.tsx
import React, { lazy, Suspense } from 'react';
// Lazy load the ui-feature — it is NOT in the initial bundle
const WorkspaceActivityFeed = lazy(() =>
import('@postman-app/workspace-activity-feed').then(m => ({ default: m.WorkspaceActivityFeed }))
);
export function WorkspaceOverviewContainer() {
return (
<Suspense fallback={<div>Loading...</div>}>
<WorkspaceActivityFeed />
</Suspense>
);
}
Adding a new page (view)
If your feature is a brand new URL-addressable page:
npx nx generate @postman/app-generator:library \
--type=view \
--name=workspace-activity-page
Then register it in the router (in src/renderer/onboarding/src/features/ or the manifest system — check with your team for the current pattern).
Adding a translation string
All user-facing text must be translated. Never hardcode English strings.
-
Add the key to
locales/en-US/<namespace>.json:{"activity": {"title": "Recent activity","empty": "No activity yet"}} -
Use it in the component:
import { useTranslation } from '@postman-app/i18n-sdk';const { t } = useTranslation('workspace-activity');t('activity.title') // → "Recent activity"
Adding analytics
Use the analytics service to track user interactions:
import { analyticsService } from '@postman-app/analytics-service';
// Track an event when the user performs an action
analyticsService.track('activity_feed_viewed', {
workspaceId,
activityCount: store.activities.length,
});
Event names and schemas should be documented in the event registry (ask your squad's analytics lead).
Checklist before opening a PR
□ Code is in an Nx package — not in src/renderer/
□ Package generated with the Nx generator (not created manually)
□ Imports use @postman-app/* aliases (no cross-package relative imports)
□ All user-facing strings use t() from @postman-app/i18n-sdk
□ Unit tests written (co-located with the component)
□ nx lint <package> passes
□ nx test <package> passes
□ yarn typecheck passes
□ yarn validate-modules passes
□ No new circular dependencies (yarn knip)