What is a Surface?
A surface is the runtime render of a Receipt. Reproducible, versioned, capability-bound, and ready to host a PhiSo conversation the moment it ships.
Page vs Surface
Pages are documents you author once and update by hand. Surfaces are the materialised form of a Receipt — a signed, line-numbered recipe of blocks, props, capabilities, and theme tokens that PhiCo proposes and you accept before publish.
| Page | Surface |
|---|---|
| Static document | Receipt-materialised render |
| Hand-authored content | Composable from a typed catalog |
| One-shot render | Reproducible from the Receipt hash |
| No audit trail | PhiSo-ready post-publish |
Surfaces exist
Every surface has its own identity within PhiWebs. It’s not just a URL or a file — it’s an entity with:
- Identity — A unique identifier that persists across the ecosystem
- Metadata — Title, description, preview image, and SEO properties
- State — Memory that persists user interactions and context
- Lifecycle — Birth, publication, versioning, and retirement
How surfaces change over time
Every change is a new Receipt. Nothing publishes until a human accepts the diff.
- Add capabilities — Declare any of the canonical 5:
platform/persistence,platform/entities,platform/navigation,platform/interaction,platform/input. Capability config goes into the Receipt. (Visual appearance is not a capability — it ships as a separateui-appearanceatom kind.) - Version history — Every accepted Receipt is an immutable snapshot. Roll back to any previous version with a single API call.
- Responsive layout — Block layouts declare
xs/sm/md/lgbreakpoints; the runtime renders the correct grid span per device. - PhiSo intelligence — Once published, surfaces host a PhiSo widget that answers visitor questions from the live Surface contract — no extra training, no external scraping.
Surfaces connect
Surfaces don’t exist in isolation. They:
- Compose — Surfaces can contain other surfaces.
- Link — Reference and navigate to other surfaces.
- Share — Publish to the marketplace for others to discover.
- Inherit — Start from starter surfaces and customize.
Surface composition
Surfaces are composed from blocks — primitive building elements:
Surface
├── Block (container)
│ ├── Block (heading)
│ ├── Block (text)
│ └── Block (button)
├── Block (grid)
│ ├── Block (image)
│ └── Block (image)
└── Capabilities
├── auth
├── analytics
└── seoLearn more about blocks and the composition model.
Surface lifecycle
- Birth — Created from scratch or from a starter surface.
- Development — Composed with blocks, enhanced with capabilities.
- Publication — Deployed to a domain, made accessible.
- Evolution — Updated, versioned, capabilities added.
- Retirement — Archived or removed when no longer needed.
Developer reference — surface schema
Every surface has a consistent JSON structure with metadata and content:
{
"id": "landing-page",
"name": "Landing Page",
"slug": "landing",
"version": 1,
"blocks": [
{ "type": "container", "children": [] }
],
"settings": {
"theme": "default",
"layout": "full-width"
}
}| Property | Type | Description |
|---|---|---|
id | string | Unique identifier |
name | string | Human-readable display name |
slug | string | URL-friendly path segment |
version | number | Version number for tracking changes |
blocks | Block[] | Array of root-level blocks |
settings | object | Theme, layout, and rendering options |
Surface types
- Page surface — A complete page with its own URL route. Used for landing pages, about pages, and standalone content.
- Section surface — An embeddable section that can be placed within existing pages. Used for dynamic content areas.
- Template surface — A reusable template with placeholder content. Used as a starting point for new surfaces.
- Dynamic surface — Generated from data at runtime. Used for product pages, blog posts, and data-driven content.
Rendering surfaces
Use the SurfaceRenderer component to render a surface:
import { SurfaceRenderer } from '@pwfabric/sdk/react'
function Page({ surface }) {
return (
<SurfaceRenderer
surface={surface}
theme="default"
components={customComponents}
/>
)
}