Skip to main content

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.

  1. Add the key to locales/en-US/<namespace>.json:

    {
    "activity": {
    "title": "Recent activity",
    "empty": "No activity yet"
    }
    }
  2. 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)