Skip to content
ChatfuelSDK

Your first change

Add a view to a module — where its id lives, which files change with it, and how to check it in the browser.

A module never touches window.location. The shell owns the address bar and hands each module its own slice through props — view, the path segment after the module id, and setView to move within it. So "add a screen" is not a route you register anywhere central. It's one more value the module accepts for view, and a component to render when it arrives.

Here is that change end to end, in contacts, adding a view called timeline. Every file below is under src/modules/contacts/ in the app the wizard wrote.

Name the view

The module's addresses are parsed and written in one pure file, lib/contactsParams.ts. Three lines at the top of it are the whole vocabulary:

src/modules/contacts/lib/contactsParams.ts
export type ContactsView = 'list' | 'fields' | 'audience';
export const VIEWS: readonly ContactsView[] = ['list', 'fields', 'audience'];
export const DEFAULT_VIEW: ContactsView = 'list';

Add 'timeline' to the type and to the array. Both, not one: the type is what the compiler walks, and the array is what the parser accepts off the address bar.

DEFAULT_VIEW stays list, and it is the one view with no segment of its own — /contacts is the list, and /contacts/timeline is yours.

Write the component

Views live in views/. The props are frozen: a view takes ContactsViewProps and nothing else, owns its own data, its own toolbar and its own live channel, and reports its count and busy state upward. The audience breakdown is a short one to start from:

src/modules/contacts/views/AudienceView.tsx
export function AudienceView({ team, catalog, refreshToken, onCount, onBusy }: ContactsViewProps) {

Copy that file to views/TimelineView.tsx, rename the function, and empty the body. Anything a view wants that is not in the contract is a sign the view should own it, not that the contract should grow.

Register it

One record in ContactsApp.tsx maps a view id to its component, and the workspace picks out of it with VIEWS[parsed.view]:

src/modules/contacts/ContactsApp.tsx
const VIEWS: Record<ContactsView, ComponentType<ContactsViewProps>> = {
  list: ListView,
  fields: FieldsView,
  audience: AudienceView,
};

Add timeline: TimelineView and import it. This record is keyed on the union, so if you skip this step the compiler says so by name.

Let people reach it

Two places, because there are two ways into a view. The header tabs, in components/ContactsHeader.tsx:

src/modules/contacts/components/ContactsHeader.tsx
const VIEW_TABS: { id: ContactsView; label: string }[] = [
  { id: 'list', label: 'Contacts' },
  { id: 'fields', label: 'Fields' },
  { id: 'audience', label: 'Audience' },
];

And the command palette, in lib/commands.ts, which builds its destination group from three records keyed on the union — VIEW_LABELS, VIEW_KEYWORDS and VIEW_SHORTCUT — plus its own array of the views to offer.

Those three records are compiler-checked, so npm run check names them for you. The plain arrays are not: VIEW_TABS above, the palette's list beside those records, and the one in step one.

Check it

npm run check
npm run dev

Open localhost:5173/contacts. The new tab is in the header; clicking it puts /contacts/timeline in the address bar and pushes a history entry, so the back button returns you to the list. Reload on /contacts/timeline and your view comes back. Press ⌘K and type the view's name — it should be in the destinations.

Forget the array in step one and nothing breaks loudly. An address the parser does not recognise falls back to the default view in silence — that rule exists so a stale link renders the list instead of a white screen — so the tab appears to do nothing and the URL rewrites itself back. If a new view will not open, the array is the first place to look.

Ask your agent instead

The wizard installed a skill per module: .claude/skills/chatfuel-contacts/ for Claude Code, .agents/skills/chatfuel-contacts/ for Codex CLI. Each one carries that module's structure and the traps in the API behind it, which is why this is a sentence rather than a file hunt. Open your agent in the project and type:

Add a "timeline" view to the contacts module: the view id, a component under views/,
the tab in the header and the command-palette entry. Then run npm run check.

Next: deploy, or what the wizard writes for the rest of the tree.

On this page