scaffold lists Pluma's recipes, shows one, or copies one recipe's React or
Ember source into your app. The copied file is yours from then on: it imports
Pluma from @customerio/pluma-components/react or /ember like the rest of
your app, and nothing ties it back to the recipe.
Use scaffold when a recipe matches the page or block you are building, to
start from working Pluma code instead of a blank file. If you don't know which
recipe fits, run plan first:
it picks one from a description of the screen. scaffold writes a new file
only. It never updates a file it copied earlier, so to pick up a later version
of a recipe, compare it with --show and carry the changes over by hand.
--list lists every recipe. --show <recipe-id> shows one before you copy it:
its type, title, description, components, frameworks, the third-party packages
each framework's source imports, and the source as scaffold would write it,
for both frameworks or only the one --framework names.
The third form copies the recipe into <dest-dir>, creating the directory if
needed. The file is named after the component: <kebab-name>.tsx for React,
<kebab-name>.gts for Ember. After copying, scaffold lists the third-party
packages the file imports (for example @tanstack/table-core), so you can add
any your app doesn't already depend on. Pluma and the framework's own packages
are left out. An import of a file the recipe doesn't ship is printed as a note,
so you can bring that file over or replace the import.
List recipes with their type (page or block) and description.
--show <id>
Show one recipe's metadata, the packages its sources import, and its source. Also --show=<id>.
--json
With --list or --show, print machine-readable JSON.
--framework <fw>
Which source to copy: react or ember. With --show, which source to show (both when omitted). Also --framework=<fw>.
--name <Name>
Rename the component, the props or signature type named after it, and the file (<kebab-name>.tsx or <kebab-name>.gts). Defaults to the recipe's own name. Also --name=<Name>.
--dry, -d
Print the file that would be written, without writing it.
-h, --help
Show help.
scaffold never overwrites a file: if the target exists, it stops with exit
status 2 and changes nothing. It also stops with exit status 2 when --name
would clash with a name the recipe already imports or declares, such as
--name EmptyState next to Pluma's EmptyState.
Page recipes: choose-a-type [react, ember] Use this page as the first step of a creation flow, when people pick what kind of thing to create before setting it up, such as choosing how a new campaign starts. Each type is a card with an icon, a title and a short description, the most common type starts out chosen, and Continue moves on to the setup for that type. Use a setup wizard for the steps that follow, and radios inside a form instead when the choice is one field among many. dashboard-overview [react, ember] Use this page as the landing view that summarizes how a workspace is performing over a recent period, such as its messaging over the last 30 days. It leads with a row of headline metrics compared with the previous period, follows with charts of the daily trends, and ends with a table of the latest activity. Use it when people need to check performance at a glance before drilling into a specific campaign or report. editor-with-preview [react, ember] Use this page when people write or edit content that they need to see rendered as they work, such as an in-app message or the body of an email. The fields sit beside a live preview of the current values, and the page header keeps Discard changes, Save draft and Publish in reach. Use a settings form instead when there is nothing to preview. list-with-drawer-detail [react, ember] Use this page for a collection, such as a workspace's segments, where people often need a quick look at one item without losing their place in the list. Selecting an item's name opens a drawer beside the list with that item's details and an action to edit it. Use it when the details fit in a short summary; when people need the item's full history or tabs, link to a detail page instead. list-with-filters [react, ember] Use this page for the index of a collection people browse and narrow down, such as a workspace's campaigns, with the action that creates a new one in the header. People search by name, add filters to refine the list, and page through the results in a table. Use it when the collection is too large to scan in one screen. object-detail [react, ember] Use this page for a single record, such as one person, segment or campaign, when people need to see what it is, what state it is in and what has happened to it. The header names the record, reports its status, holds the actions that apply to the whole record and links to its other views as tabs. The body puts the record's history in the main column and a summary of its attributes beside it. segment-builder [react, ember] Use this page when people define a group of people by rules on their attributes and activity, such as everyone on the Growth plan who signed up in the last 30 days. They name the group, build the conditions everyone in it has to match, and see how many people match as they edit. Use a list with filters instead when people only narrow a list for the moment and don't save the rules. settings-form [react, ember] Use this page when people edit the configuration of a whole workspace, account or integration and save it in one go, such as a workspace's name, timezone and default sender. It names the screen and its scope in the header and keeps the fields in a narrow column, with Cancel and Save changes at the end of the form. Add a panel per group of related settings as the page grows. setup-wizard [react, ember] Use this page to walk people through creating or connecting something that takes several ordered steps of input, such as adding a sending domain, one step at a time. A stepper shows where they are and takes them back to finished steps, Back and Continue move between steps, and a final review step lists every value before the action that creates it. Use a single form instead when the fields do not depend on each other.Block recipes: empty-state-cta [react, ember] Use this block when a list or panel has nothing to show yet and one action would fill it, such as creating the first campaign. It explains what is missing and puts the action that creates it front and center. Keep it in place of the content it stands in for, inside the same panel. form-section [react, ember] Use this block for one titled group of related fields, such as sender details, inside a longer form or settings page. Stack several of them to break a long form into sections people can scan, each with a short description of what its fields control. Mark the fields people can leave empty as optional and add help text where a field needs it. panel-with-description-list [react, ember] Use this block to summarize one object, such as a campaign, segment or person, as a short list of labeled facts on its detail page. Each row pairs a label with its value, so people can scan status, configuration and dates at a glance. Put the action that changes these facts, such as editing them, in the panel header.Copy one with: pluma-cli scaffold <recipe-id> <dest-dir> --framework <react|ember>
empty-state-cta (block): Empty state with call to action Use this block when a list or panel has nothing to show yet and one action would fill it, such as creating the first campaign. It explains what is missing and puts the action that creates it front and center. Keep it in place of the content it stands in for, inside the same panel.Components: button, empty-state, panelFrameworks: react, emberPackages: react: none ember: nonereact source (empty-state-cta.tsx):import { Button, EmptyState, EmptyStateActions, EmptyStateDescription, EmptyStateTitle, Panel, PanelSection,} from '@customerio/pluma-components/react';export interface EmptyStateCtaProps { /** Called when the primary action is activated. */ onCreate: () => void;}export function EmptyStateCta({ onCreate }: EmptyStateCtaProps) { return ( <Panel> <PanelSection> <EmptyState> <EmptyStateTitle icon="campaigns">No campaigns yet</EmptyStateTitle> <EmptyStateDescription> Campaigns send messages to people when they match a trigger you choose. Create one to start reaching your audience. </EmptyStateDescription> <EmptyStateActions> <Button variant="primary" icon="add" onClick={onCreate}> Create campaign </Button> </EmptyStateActions> </EmptyState> </PanelSection> </Panel> );}Copy it with: pluma-cli scaffold empty-state-cta <dest-dir> --framework <react|ember>
[dry] Would write src/campaigns/no-campaigns.tsx:import { Button, EmptyState, EmptyStateActions, EmptyStateDescription, EmptyStateTitle, Panel, PanelSection,} from '@customerio/pluma-components/react';export interface NoCampaignsProps { /** Called when the primary action is activated. */ onCreate: () => void;}export function NoCampaigns({ onCreate }: NoCampaignsProps) { return ( <Panel> <PanelSection> <EmptyState> <EmptyStateTitle icon="campaigns">No campaigns yet</EmptyStateTitle> <EmptyStateDescription> Campaigns send messages to people when they match a trigger you choose. Create one to start reaching your audience. </EmptyStateDescription> <EmptyStateActions> <Button variant="primary" icon="add" onClick={onCreate}> Create campaign </Button> </EmptyStateActions> </EmptyState> </PanelSection> </Panel> );}
The recipe id is misspelled, or the recipe isn't in the version of
pluma-cli you ran. The command exits with status 2 and lists every recipe id
it knows. With --show, it points you at pluma-cli scaffold --list instead.
src/campaigns/no-campaigns.tsx already exists. Refusing to overwrite it; pass another --name or <dest-dir>.
scaffold never overwrites a file, so it stops with exit status 2 and writes
nothing. Pick another --name or <dest-dir>, or move the existing file out
of the way first.
--name EmptyState would declare EmptyState, which the recipe already imports or declares. Pass another --name.
The name you passed would collide with something the recipe already imports
or declares, here Pluma's own EmptyState. The command exits with status 2.
Pass a name specific to your screen, such as NoCampaigns. A name that isn't
PascalCase stops the same way, with
--name must be a PascalCase identifier, such as NoCampaigns.
Copying a recipe needs --framework react or --framework ember; with
--show the flag is optional, but must be one of the two when given. The
command exits with status 2. A recipe without a source for the framework you
asked for stops with Recipe "<id>" has no <framework> source.