A metadata-driven Salesforce demo environment that produces deployable artifacts. Author once, demo to the client, ship the same metadata to a real SFDC org on sign-off.
What this is
Browser-only Salesforce simulator. No org, no login, no API call.
Authored as JSON metadata that maps 1:1 to Salesforce Metadata API shapes (schema.json → CustomObject XML, layouts/<Obj>.json → FlexiPage, etc.).
Engagement-scoped: each engagements/<slug>/ is a self-contained POC.
Runs from any static host: python3 -m http.server, GitHub Pages, Claude Design, etc. No npm build step.
Why we build it this way
Discovery-to-deploy in one workspace. Same artifacts that drive the demo become user stories and metadata-API XML on deploy.
Claude is the primary author. The structure is optimized for LLM tooling — file-per-concept, predictable shapes, lint rules that catch drift.
Client sign-off becomes a deployment. When the client signs off on what they see in the demo, "what they see" is metadata that already maps to a real SFDC project.
Why metadata-defined artifacts matter
Discovery → metadata-API XML. Every JSON file maps to a real SFDC metadata type with a 1:1 converter pass.
User stories from layouts. A layout file is a user story — "On the Account record, in the Account Info section, the user can edit Name, Type, Phone…" Claude can lift these as Jira/Linear tickets.
Sign-off becomes a deployable manifest. When the client signs off in the demo, the JSON they signed off on is the source of truth the implementation pod builds from.
No drift between "what we showed" and "what we built". This is the core promise — and the reason we accept the constraint of authoring everything as metadata up front.
The layers
Engagement metadata engagements/<slug>/
Manifest, schema, seed, layouts, optional component bundles. Self-contained per engagement.
Runtime runtime/
Generic React app. Reads engagement metadata at boot and renders Lightning-style record pages, list views, tabs, related lists, custom components.
Canonical SF describe + layout templates, refreshed via scripts/build_metadata_source.py and scripts/convert_layouts.py.
What works
Schema & data model
All standard scalar field types — Text, Number, Currency, Percent, Date, DateTime, Email, Phone, URL, Checkbox, Picklist, MultiselectPicklist, LongTextArea, Lookup, MasterDetail, AutoNumber.
Formulas — written in real SFDC formula syntax. IF, AND, OR, CASE, arithmetic, date math, picklist comparisons.
Rollups — SUM / MIN / MAX / COUNT / AVG with optional filter clauses. Recompute automatically on child mutations.
Validation rules — declared per object, blocking save when expression evaluates true.
Cascade delete — deleteConstraint: 'Cascade' | 'Restrict' | 'SetNull' on Lookup/MasterDetail; runtime honors at delete time.
Polymorphic lookups — Task.WhatId, Activity.WhoId, etc., narrowed via a <Field>Type__c sidecar picklist.
Record Types — salesProcess (allowed Stage / Status values), layout-per-RT, typeBinding for auto-Type-default at create.
Address compounds — BillingAddress/ShippingAddress expand to 5 sub-inputs on edit, concatenate to multi-line on display.
Audit fields — CreatedBy/Date, LastModifiedBy/Date, SystemModstamp stamped by the runtime, locked read-only everywhere.
UI & record pages
Lightning-style record pages — Highlights, Header Actions, Path, tabs, sections, related lists, custom-component embeds.
Tabs with mixed content — fields, related lists, and component embeds interleave inside the same tab.
Inline edit — pencil or double-click swaps any cell to its native field input. Address compounds expand inline (5 inputs).
List views — pin/unpin per object, column sort by header click, search-this-list, select-fields-to-display dual-list picker, gear menu, refresh, per-row action caret (View / Edit / Clone / Delete).
App Launcher — mini flyout + full modal, switches between Lightning Apps.
Permissions / personas — switch users via the avatar menu; perms gate read/edit/delete on objects and fields. Test "as Sales Rep" / "as CSR" scenarios in one session.
Data lifecycle
Seed-driven hydration — seed.json populates the in-memory org on every load.
Edit persistence within session — mutations live in memory + localStorage. Refresh keeps changes.
Reset — Demo Controls → "Reset Database" wipes local mutations and rehydrates from seed.
Seed bumps — bumping manifest.seedVersion invalidates persisted state automatically.
Automations
Triggers — engagement-declared automation handlers (automations.json) fire on create/update/delete with standard before / after / record-set semantics.
Notification model — Notification__c (or per-engagement equivalent) for in-app alerts.
Setup tooling
Heads up — Setup writes are PROTOTYPE-ONLY under v2. Setup edits persist to
engagements/<slug>/, which is generated output: the next
compose regenerates it and the edit is gone. Authoring truth lives in
client/base/engagement/ + client/variants/<id>/ (see the
poc-v2 skill). Rule of thumb:
Exploring a layout idea visually? Setup is great — sketch it live, then fold the keeper back into authoring source (tools/composer/decompose.py) and recompose.
Making a change you want to keep? Edit the authoring source (or ask Claude) and run compose — never rely on a Setup edit surviving.
Browsing / verifying? The explorers are read-only and always safe.
Setup is the sketchpad. The composer is the printing press. Only the source ships.
Engagement Info — edit displayName / client / industry / description.
App & Nav — declare multiple Lightning Apps, edit each app's nav menu (sobj list + custom workspace tabs).
Page Layouts editor — per-object visual editor with tab strip, block list (Section / Related List / Component), drag-to-reorder field chips, View / Edit Layout variant toggle, Highlights mini-card, all-fields palette.
Schema Explorer — read-only browser of every object/field/relationship in the engagement.
Schema Picker — engagement-agnostic. Pick objects from the SF metadata source; scaffolds manifest + schema + seed + layout copies into engagements/<slug>/ (generated output — prototype-only; a real new engagement starts from tools/composer/new_engagement.py).
Optional components
Two installable bundles today (FSL Mobile, Dispatch Console). Each ships its own .jsx, a component.json declaring required objects + manifestPatch, and integrates via the Schema Picker's optional components panel.
Demo menu (avatar icon, top-right)
Click your avatar in the top-right of the runtime to open the demo menu. It exposes:
Switch user — change persona on the fly. Permission gates re-apply immediately, so "as Sales Rep" / "as CSR" scenarios swap without a re-login.
Automations panel — view every registered automation handler, read what each one does (description pulled from the handler source), sort by object, and selectively disable any of them at runtime. Useful when demoing "what changes if this trigger doesn't fire?"
Notification controls — toggle whether system-generated notifications fire to your own user. Some demo scenarios want you to receive the ping yourself; others want the recipient to be a different persona. Configurable per session.
Reset Database — wipe local mutations and rehydrate from seed.
Surface picker (desktop ↔ mobile) — switch viewport without resizing the browser. Engagement's mobile shell renders if registered.
Setup — jump into Setup for the active engagement.
Exit POC — back to the landing page.
The Automations panel duplicates what Setup-level docs say, but in-flight: you don't need to leave the demo to triage trigger behavior.
Things you can manipulate
Feature flags / toggles (engagement)
manifest.activeModules.{schema, seed, layouts, flows, automations, permissions} — switch whole subsystems on or off.
manifest.homePages — per-Profile home component override.
manifest.automations.<id> — register/unregister an automation handler.
manifest.components.<Name> — register a bespoke component for layout embeds.
Per-record UI overrides
<Object>__Edit.json — override the Edit modal's layout independent of the view layout.
Per-section recordTypes: [...] — show a section only when the record's RT matches.
Per-field readOnly: true — lock a specific cell in edit mode.
showIf expressions on tabs / sections / highlights / fields — runtime evaluates against the record.
Demo-only controls (avatar menu)
See Demo menu above for the full list.
How to build with this
Working assumption. Focused prompt patterns will land here after the team has validated approaches across a few real engagements. Until then, the general guidance:
Supply Claude Cowork with as much context for a given change as possible — discovery call transcript, post-call internal sync transcript, screenshots, smaller POCs you're cribbing from, sketches, anything. Ask Claude to:
Read README.md and the relevant docs/*.md for current conventions.
Review the materials you supplied.
Draft a build plan before touching files.
The more detailed your build plan up-front, the fewer turns you'll need to implement the changes. Iterate on the plan in chat; resist the urge to dive into JSX edits before you and Claude agree on what the end state looks like.
When designing faux screenflows, be diligent in reminding Claude to only use viable Salesforce patterns — some things will be embellished for presentation when they do not actually work that way. The point of this POC is sign-off-ready demos that map cleanly to a real implementation. If you don't catch the embellishment in the demo, you'll catch it as scope on the build.
TODO — placeholder. Prompt pattern will land after team validation. The general approach: feed Claude the discovery materials + ask for a build plan + iterate.
TODO — placeholder. Prompt pattern will land after team validation. The general approach: clone the smaller POC, rename, identify what's reusable vs needs replacing, build plan.
TODO — placeholder. Prompt pattern will land after team validation. General approach: clear scope, real Salesforce capabilities only, draft a build plan that names the surfaces and the metadata that backs them.