Widget UI Catalog
A premium, standardized design system for PIE plugin widgets. Use this catalog as your single source of truth for building polished, consistent widget UIs — whether you are writing HTML by hand or using the AI builder.
This page is specifically for the authenticated ===widget=== surface inside PIE. If you are building a public website or shareable route at /apps/..., use the Public Apps and Routes guide for packaging, routing, and public action patterns.
Widgets are sandboxed iframes. They communicate with the host via PIE.onData, PIE.sendAction, PIE.resize, and PIE.onTheme. All CSS, HTML, and JS live inside the ===widget=== section of your .pie file.
Foundations
Design Tokens
Every widget should define its palette through CSS custom properties on :root. These tokens are aligned with the PIE host app's design system so widgets look native inside the shell.
<style>
:root {
/* Surfaces — Light */
--bg: #FFFFFF;
--bg-secondary: #FAFAFA;
--bg-tertiary: #F5F5F5;
/* Text */
--fg: #171717;
--fg-secondary: #525252;
--fg-muted: #A3A3A3;
/* Accent — PIE Pink */
--accent: #FF0066;
--accent-hover: #FF3385;
--accent-soft: rgba(255, 0, 102, 0.08);
/* Semantic */
--success: #22C55E;
--success-soft: rgba(34, 197, 94, 0.10);
--warning: #F59E0B;
--warning-soft: rgba(245, 158, 11, 0.10);
--danger: #EF4444;
--danger-soft: rgba(239, 68, 68, 0.08);
/* Chrome */
--border: #E5E5E5;
--border-light: rgba(0, 0, 0, 0.06);
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 24px rgba(0, 0, 0, 0.1);
/* Shape */
--radius-sm: 6px;
--radius: 10px;
--radius-lg: 14px;
--radius-xl: 16px;
--radius-full: 9999px;
/* Typography */
--font: 'Inter', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', Menlo, Monaco, monospace;
--text-xs: 11px;
--text-sm: 13px;
--text-base: 14px;
--text-lg: 16px;
--text-xl: 20px;
/* Spacing scale */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-8: 32px;
/* Motion */
--duration-fast: 100ms;
--duration-normal: 200ms;
--duration-slow: 300ms;
--easing: ease;
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
Dark Mode via PIE.onTheme
Use PIE.onTheme to respond to the host app's theme. Dark mode uses PIE's deep dark palette — near-black with a subtle cool tint.
<script>
PIE.onTheme(function(theme, colors) {
var r = document.documentElement.style;
if (theme === 'dark') {
r.setProperty('--bg', '#0A0A0F');
r.setProperty('--bg-secondary', '#111118');
r.setProperty('--bg-tertiary', '#1A1A24');
r.setProperty('--fg', '#F0F0F5');
r.setProperty('--fg-secondary', '#8888A0');
r.setProperty('--fg-muted', '#525260');
r.setProperty('--accent-soft', 'rgba(255, 0, 102, 0.15)');
r.setProperty('--border', '#262626');
r.setProperty('--border-light', 'rgba(255, 255, 255, 0.06)');
r.setProperty('--shadow-sm', '0 1px 2px rgba(0, 0, 0, 0.3)');
r.setProperty('--shadow-md', '0 4px 6px rgba(0, 0, 0, 0.4)');
r.setProperty('--shadow-lg', '0 10px 24px rgba(0, 0, 0, 0.5)');
} else {
r.setProperty('--bg', '#FFFFFF');
r.setProperty('--bg-secondary', '#FAFAFA');
r.setProperty('--bg-tertiary', '#F5F5F5');
r.setProperty('--fg', '#171717');
r.setProperty('--fg-secondary', '#525252');
r.setProperty('--fg-muted', '#A3A3A3');
r.setProperty('--accent-soft', 'rgba(255, 0, 102, 0.08)');
r.setProperty('--border', '#E5E5E5');
r.setProperty('--border-light', 'rgba(0, 0, 0, 0.06)');
r.setProperty('--shadow-sm', '0 1px 2px rgba(0, 0, 0, 0.05)');
r.setProperty('--shadow-md', '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)');
r.setProperty('--shadow-lg', '0 10px 24px rgba(0, 0, 0, 0.1)');
}
if (colors && colors.accent) {
r.setProperty('--accent', colors.accent);
}
});
</script>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Base Reset and Body
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: var(--font);
font-size: var(--text-sm);
line-height: 1.5;
color: var(--fg);
background: var(--bg);
padding: var(--space-3);
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-feature-settings: 'cv11', 'ss01';
}
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(120, 120, 128, 0.28); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: rgba(120, 120, 128, 0.42); }
::selection { background: rgba(255, 0, 102, 0.2); }
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Premium Rules
- Never show raw browser defaults (unstyled selects, default checkboxes, system fonts).
- Use strong visual hierarchy: one dominant element per section, muted secondary text, restrained accents.
- Minimum touch target: 32px height for interactive elements.
- Use
var(--fg-secondary)for labels and metadata,var(--fg-muted)for timestamps and hints. - Limit accent color to primary actions and active states. Everything else is neutral.
- Animate state changes with
transition: all var(--duration-normal) var(--easing). - Use
var(--shadow-sm)for cards at rest,var(--shadow-md)on hover. - Use frosted glass effects (
backdrop-filter: blur()) for overlays and sticky headers where appropriate. - Scrollbars should be slim and translucent — never default system scrollbars.
- Enable Inter font features:
font-feature-settings: 'cv11', 'ss01'.
Widget Contract
manifest.widget:
allowedScripts: [] # HTTPS CDN URLs if needed
allowedStyles: [] # HTTPS CDN URLs if needed
maxHeight: 100–800 # pixels, enforced by host
defaultHeight: 280 # initial iframe height
launchable: true|false # show in agent launcher2
3
4
5
6
PIE.onData(callback)— receive data from handler viacontext.widget.show()/context.widget.update()PIE.sendAction(actionId, payload)— trigger handler withinput._widgetAction = truePIE.resize(height)— request height change (clamped tomaxHeightand 800px)PIE.onTheme(callback)— receive(theme, colors)from host- Action responses arrive as
PIE.onDatawithdata.__actionMeta: { ok, error, result }
Primitive Components
Buttons
<style>
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
padding: 6px 16px; height: 32px; border-radius: var(--radius-sm);
font-size: var(--text-sm); font-weight: 500; font-family: var(--font);
cursor: pointer; border: none; outline: none;
transition: all var(--duration-fast) var(--easing);
}
.btn:active { transform: scale(0.97); }
.btn-primary {
background: var(--accent); color: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}
.btn-primary:hover { background: var(--accent-hover); }
.btn-secondary {
background: var(--bg); color: var(--fg);
border: 1px solid var(--border);
box-shadow: var(--shadow-sm);
}
.btn-secondary:hover { background: var(--bg-tertiary); }
.btn-ghost {
background: transparent; color: var(--fg-secondary);
}
.btn-ghost:hover { background: var(--bg-tertiary); color: var(--fg); }
.btn-danger {
background: var(--danger); color: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}
.btn-danger:hover { filter: brightness(1.08); }
.btn-sm { padding: 3px 10px; height: 26px; font-size: var(--text-xs); }
.btn-icon {
padding: 0; width: 32px; height: 32px; border-radius: var(--radius-sm);
background: transparent; border: none; color: var(--fg-secondary); cursor: pointer;
display: flex; align-items: center; justify-content: center;
transition: all var(--duration-fast) var(--easing);
}
.btn-icon:hover { background: var(--bg-tertiary); color: var(--fg); }
.btn-icon:active { transform: scale(0.92); }
.btn[disabled], .btn-icon[disabled] { opacity: 0.4; pointer-events: none; }
.btn .spinner { width: 14px; height: 14px; border: 2px solid rgba(255,255,255,0.3);
border-top-color: #fff; border-radius: 50%;
animation: spin 0.6s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Inputs
<style>
.input, .select, .textarea {
width: 100%; padding: 6px 12px; height: 32px;
font-size: var(--text-sm); font-family: var(--font);
background: var(--bg); color: var(--fg);
border: 1px solid var(--border); border-radius: var(--radius-sm);
transition: all var(--duration-fast) var(--easing);
outline: none;
}
.input:focus, .select:focus, .textarea:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
}
.input::placeholder { color: var(--fg-muted); }
.textarea { height: auto; resize: vertical; min-height: 64px; padding: 8px 12px; line-height: 1.5; }
.select { appearance: none; cursor: pointer;
background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%23A3A3A3' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
background-repeat: no-repeat; background-position: right 12px center; padding-right: 32px;
}
.field { display: flex; flex-direction: column; gap: 3px; }
.field-label { font-size: var(--text-xs); font-weight: 500; color: var(--fg-secondary); }
.search-input {
padding-left: 34px;
background-image: url("data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='11' cy='11' r='7' stroke='%23A3A3A3' stroke-width='2'/%3E%3Cpath d='M16 16L20 20' stroke='%23A3A3A3' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
background-repeat: no-repeat; background-position: 12px center;
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Tabs and Navigation
<style>
.tabs {
display: flex; gap: 1px; padding: 2px;
background: var(--bg-secondary); border-radius: var(--radius);
}
.tab {
padding: 4px 14px; height: 28px; font-size: var(--text-xs); font-weight: 500;
border-radius: calc(var(--radius) - 2px); border: none; cursor: pointer;
background: transparent; color: var(--fg-secondary); font-family: var(--font);
transition: all var(--duration-fast) var(--easing);
display: flex; align-items: center; justify-content: center;
}
.tab:hover { color: var(--fg); }
.tab.active {
background: var(--bg); color: var(--fg); font-weight: 600;
box-shadow: var(--shadow-sm);
}
.tab .count {
display: inline-flex; align-items: center; justify-content: center;
min-width: 16px; height: 16px; padding: 0 4px; margin-left: 5px;
font-size: 10px; font-weight: 600; border-radius: var(--radius-full);
background: var(--bg-tertiary); color: var(--fg-muted);
}
.tab.active .count { background: var(--accent-soft); color: var(--accent); }
.breadcrumb {
display: flex; align-items: center; gap: var(--space-2);
font-size: var(--text-xs); color: var(--fg-muted);
}
.breadcrumb-link { color: var(--accent); cursor: pointer; text-decoration: none; }
.breadcrumb-link:hover { text-decoration: underline; }
.breadcrumb-sep::before { content: '\203A'; color: var(--fg-muted); }
.filter-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
padding: 3px 12px; height: 26px; font-size: var(--text-xs); font-weight: 500;
border-radius: var(--radius-full);
border: 1px solid var(--border);
background: var(--bg); color: var(--fg-secondary); cursor: pointer;
display: flex; align-items: center;
transition: all var(--duration-fast) var(--easing);
}
.chip:hover { border-color: var(--accent); color: var(--accent); }
.chip.active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); font-weight: 600; }
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Cards and Display
<style>
.card {
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius); padding: var(--space-3);
box-shadow: var(--shadow-sm);
transition: all var(--duration-normal) var(--easing);
}
.card:hover { box-shadow: var(--shadow-md); }
.card-clickable { cursor: pointer; }
.card-title {
font-size: var(--text-sm); font-weight: 600; color: var(--fg);
margin-bottom: 2px;
}
.card-meta { font-size: var(--text-xs); color: var(--fg-secondary); }
.section-title {
font-size: 10px; font-weight: 600; text-transform: uppercase;
letter-spacing: 0.2em; color: var(--fg-muted); margin-bottom: var(--space-2);
}
.badge {
display: inline-flex; align-items: center; padding: 1px 7px; height: 18px;
font-size: 10px; font-weight: 500; border-radius: var(--radius-full);
background: var(--bg-tertiary); color: var(--fg-secondary);
}
.badge-accent { background: var(--accent-soft); color: var(--accent); }
.badge-success { background: var(--success-soft); color: var(--success); }
.badge-warning { background: var(--warning-soft); color: var(--warning); }
.badge-danger { background: var(--danger-soft); color: var(--danger); }
.status-dot {
width: 7px; height: 7px; border-radius: 50%; display: inline-block;
}
.status-dot-success { background: var(--success); }
.status-dot-warning { background: var(--warning); }
.status-dot-danger { background: var(--danger); }
.stat-card { text-align: center; padding: var(--space-4); }
.stat-value { font-size: var(--text-xl); font-weight: 700; color: var(--fg); }
.stat-label { font-size: var(--text-xs); color: var(--fg-secondary); margin-top: 2px; }
.kv-row { display: flex; justify-content: space-between; align-items: center;
padding: 6px 0; border-bottom: 1px solid var(--border-light); }
.kv-label { font-size: var(--text-xs); color: var(--fg-secondary); }
.kv-value { font-size: var(--text-sm); color: var(--fg); font-weight: 500; }
.avatar {
width: 32px; height: 32px; border-radius: var(--radius-sm);
background: var(--accent-soft); color: var(--accent);
display: flex; align-items: center; justify-content: center;
font-size: var(--text-xs); font-weight: 600; flex-shrink: 0;
}
.avatar-round { border-radius: 50%; }
.list-row {
display: flex; align-items: center; gap: var(--space-3);
padding: 6px var(--space-3); min-height: 36px;
border-radius: var(--radius-sm); cursor: pointer;
transition: background var(--duration-fast) var(--easing);
}
.list-row:hover { background: var(--bg-secondary); }
.list-row.active { background: var(--accent-soft); }
.list-row-title { font-size: var(--text-sm); font-weight: 500; color: var(--fg); }
.list-row-sub { font-size: var(--text-xs); color: var(--fg-secondary); }
.table { width: 100%; border-collapse: collapse; font-size: var(--text-xs); }
.table th {
text-align: left; padding: 6px var(--space-3);
font-weight: 600; color: var(--fg-muted); font-size: 10px;
text-transform: uppercase; letter-spacing: 0.15em;
border-bottom: 1px solid var(--border);
background: var(--bg-secondary); position: sticky; top: 0; z-index: 1;
}
.table td {
padding: 6px var(--space-3); border-bottom: 1px solid var(--border-light);
color: var(--fg);
}
.table tr:hover td { background: var(--bg-secondary); }
.table tr.selected td { background: var(--accent-soft); }
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
Feedback and States
<style>
.loading { display: flex; flex-direction: column; align-items: center;
justify-content: center; gap: var(--space-3); padding: var(--space-8); }
.loading-spinner {
width: 22px; height: 22px; border: 2px solid var(--border);
border-top-color: var(--accent); border-radius: 50%;
animation: spin 0.7s linear infinite;
}
.loading-text { font-size: var(--text-xs); color: var(--fg-muted); }
.skeleton {
background: linear-gradient(90deg, var(--bg-secondary) 25%, var(--bg-tertiary) 50%, var(--bg-secondary) 75%);
background-size: 200% 100%; animation: shimmer 1.5s ease-in-out infinite;
border-radius: var(--radius-sm);
}
@keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
.empty-state { display: flex; flex-direction: column; align-items: center;
justify-content: center; padding: var(--space-8); text-align: center; }
.empty-icon { width: 44px; height: 44px; margin-bottom: var(--space-3);
color: var(--fg-muted); opacity: 0.4; }
.empty-title { font-size: var(--text-base); font-weight: 600; color: var(--fg-secondary);
margin-bottom: 2px; }
.empty-desc { font-size: var(--text-xs); color: var(--fg-muted); max-width: 220px; line-height: 1.5; }
.toast {
position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
padding: 8px 16px; border-radius: var(--radius-lg); font-size: var(--text-xs);
font-weight: 500; color: #fff; z-index: 1000;
box-shadow: var(--shadow-lg);
backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
animation: toast-in var(--duration-normal) var(--easing);
}
.toast-success { background: rgba(34, 197, 94, 0.9); }
.toast-error { background: rgba(239, 68, 68, 0.9); }
@keyframes toast-in { from { opacity: 0; transform: translateX(-50%) translateY(8px); }
to { opacity: 1; transform: translateX(-50%) translateY(0); } }
.banner {
display: flex; align-items: center; gap: var(--space-2);
padding: 8px var(--space-3); border-radius: var(--radius-sm);
font-size: var(--text-xs); font-weight: 500;
}
.banner-success { background: var(--success-soft); color: var(--success); }
.banner-warning { background: var(--warning-soft); color: var(--warning); }
.banner-error { background: var(--danger-soft); color: var(--danger); }
.progress-bar { height: 4px; background: var(--bg-tertiary); border-radius: 2px;
overflow: hidden; }
.progress-fill { height: 100%; background: var(--accent); border-radius: 2px;
transition: width var(--duration-normal) var(--easing); }
.confirm-dialog {
position: fixed; inset: 0; background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
display: flex; align-items: center; justify-content: center; z-index: 1000;
animation: fade-in var(--duration-fast) var(--easing);
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
.confirm-box {
background: var(--bg); border-radius: var(--radius-xl);
padding: var(--space-5);
max-width: 300px; width: 90%; box-shadow: var(--shadow-lg);
border: 1px solid var(--border-light);
animation: scale-in var(--duration-normal) ease-out;
}
@keyframes scale-in { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
.confirm-title { font-size: var(--text-base); font-weight: 600; margin-bottom: var(--space-2); }
.confirm-desc { font-size: var(--text-sm); color: var(--fg-secondary);
line-height: 1.5; margin-bottom: var(--space-4); }
.confirm-actions { display: flex; justify-content: flex-end; gap: var(--space-2); }
.timeline { display: flex; flex-direction: column; }
.timeline-item { display: flex; gap: var(--space-3); padding-bottom: var(--space-3); }
.timeline-marker {
width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
margin-top: 5px; flex-shrink: 0; position: relative;
}
.timeline-item:not(:last-child) .timeline-marker::after {
content: ''; position: absolute; left: 2.5px; top: 9px;
width: 2px; height: calc(100% + var(--space-3) - 2px);
background: var(--border-light);
}
.timeline-content { flex: 1; }
.timeline-label { font-size: var(--text-xs); color: var(--fg-muted); }
.timeline-text { font-size: var(--text-sm); color: var(--fg); }
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
Workflow Patterns
Workflow patterns standardize interaction flows — not just components, but the sequence of states and events that a widget moves through. Each pattern maps to PIE.sendAction, PIE.onData, and __actionMeta.
Search → Filter → Inspect → Act
User types query → sendAction('search', { query, filters })
Widget shows loading → onData({ type: 'results', items }) → render list
User clicks item → local state update → render detail
User clicks action → sendAction('act', { id }) → __actionMeta → toast2
3
4
States: idle, searching, results, no-results, detail, acting, success, error
Queue → Review → Approve/Reject → Next
onData({ type: 'queue', items, total })
User reviews top item → clicks approve/reject
sendAction('approve', { id }) → __actionMeta → advance to next
When queue empty → onData({ type: 'queue', items: [] }) → empty state2
3
4
States: loading, reviewing, acting, empty, error
Generate → Preview → Accept/Regenerate
onData({ type: 'generating', progress }) → progress bar
onData({ type: 'result', output }) → render preview
User accepts → sendAction('accept', { id })
User regenerates → sendAction('regenerate', { id }) → back to generating2
3
4
States: idle, generating, preview, accepting, regenerating, done, error
Connect → Authorize → Validate → Success
Widget starts → check connection state via onData
Not connected → show connect button
sendAction('connect') → host opens OAuth → onData({ type: 'connected' })
Validate → sendAction('validate') → success or retry2
3
4
States: checking, disconnected, connecting, connected, validating, ready, error
Run Job → Progress → Complete/Retry
sendAction('start_job', { params }) → __actionMeta { ok }
onData({ type: 'progress', percent, message }) → progress bar
onData({ type: 'complete', result }) → show result
onData({ type: 'failed', error }) → show error + retry button
User retries → sendAction('retry', { jobId })2
3
4
5
States: idle, running, progress, complete, failed, retrying
Browse → Select → Compare → Confirm
onData({ type: 'items', items }) → grid/list
User selects items → local state (selectedIds)
User opens comparison → render side-by-side
User confirms → sendAction('confirm', { selectedId })2
3
4
States: browsing, selecting, comparing, confirming, confirmed
Create/Edit → Autosave → Publish
Widget loads document → onData({ type: 'document', content })
User edits → debounced sendAction('save', { content }) → __actionMeta
User publishes → sendAction('publish', { id }) → __actionMeta → banner2
3
States: loading, editing, saving, saved, publishing, published, error
Install → Configure → Validate → Launch
First load → onData({ type: 'setup', step }) or onData({ type: 'ready' })
If setup needed → show config fields
sendAction('configure', { settings }) → validate → onData({ type: 'ready' })2
3
States: checking, needs-setup, configuring, validating, ready, error
Monitor → Investigate → Resolve
onData({ type: 'status', items, alerts }) → dashboard
User clicks alert → detail view
sendAction('resolve', { alertId, action }) → __actionMeta2
3
States: monitoring, investigating, resolving, resolved
Screen Templates
A screen template is a reusable page/workspace structure. It defines layout, hierarchy, action placement, and default states. The same template powers multiple domains by swapping the nouns and data.
Every screen template below includes cross-domain examples showing how it generalizes.
Master-Detail Screen
Layout: list on the left/top, detail pane on the right/bottom. Selecting an item updates the detail.
Cross-domain examples:
- Plugin Marketplace: plugin list → plugin detail with install
- HubSpot CRM: contact/deal list → record detail with inline edit
- File Browser: document list → preview and metadata
<style>
.master-detail { display: flex; height: 100vh; overflow: hidden; }
.master-list {
width: 260px; min-width: 200px; border-right: 1px solid var(--border);
display: flex; flex-direction: column; overflow: hidden;
background: var(--bg-secondary);
}
.master-list-header {
padding: var(--space-3); border-bottom: 1px solid var(--border);
display: flex; flex-direction: column; gap: var(--space-2);
}
.master-list-body { flex: 1; overflow-y: auto; padding: var(--space-1); }
.detail-pane { flex: 1; overflow-y: auto; display: flex; flex-direction: column; background: var(--bg); }
.detail-header {
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--border-light);
display: flex; align-items: center; justify-content: space-between;
}
.detail-body { flex: 1; padding: var(--space-4); overflow-y: auto; }
.detail-footer {
padding: var(--space-3) var(--space-4);
border-top: 1px solid var(--border-light);
display: flex; justify-content: flex-end; gap: var(--space-2);
}
@media (max-width: 500px) {
.master-detail { flex-direction: column; }
.master-list { width: 100%; max-height: 40vh; border-right: none;
border-bottom: 1px solid var(--border); }
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Search + Filters + Results Screen
Layout: sticky header with search and filter controls, scrollable results area, optional footer with pagination or stats.
Cross-domain examples:
- Travel Search: flights/hotels with price and date filters
- Vehicle Search: cars with make/model/price filters
- Job Board: positions with location/salary/role filters
<style>
.search-screen { display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
.search-header {
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--border);
display: flex; flex-direction: column; gap: var(--space-2);
background: var(--bg);
}
.search-bar { display: flex; gap: var(--space-2); }
.search-bar .input { flex: 1; }
.search-results { flex: 1; overflow-y: auto; padding: var(--space-3); }
.results-grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: var(--space-3);
}
.search-footer {
padding: 6px var(--space-4);
border-top: 1px solid var(--border-light);
display: flex; align-items: center; justify-content: space-between;
font-size: var(--text-xs); color: var(--fg-muted);
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
KPI Dashboard Screen
Layout: headline stats row, chart/content area below, optional action strip.
Cross-domain examples:
- Finance Analyst: stock quote + price chart + fundamentals
- Marketing Dashboard: traffic, conversions, revenue metrics
- Operations Console: uptime, error rates, queue depths
<style>
.dashboard-screen { display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
.dashboard-header {
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--border);
display: flex; align-items: center; justify-content: space-between;
}
.dashboard-title { font-size: var(--text-lg); font-weight: 700; }
.stats-row {
display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: var(--space-3); padding: var(--space-3) var(--space-4);
}
.dashboard-body { flex: 1; overflow-y: auto; padding: var(--space-3) var(--space-4); }
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
Table + Inspector Screen
Layout: full-width data table with optional side inspector for the selected row.
Cross-domain examples:
- Postgres Analysis: query results with row inspector
- SEO Audit: pages with metrics table and detail drawer
- Inventory: stock items with quantities and supplier detail
<style>
.table-screen { display: flex; height: 100vh; overflow: hidden; }
.table-main { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
.table-toolbar {
padding: 6px var(--space-3);
border-bottom: 1px solid var(--border);
display: flex; align-items: center; justify-content: space-between;
font-size: var(--text-xs); color: var(--fg-muted);
}
.table-body { flex: 1; overflow: auto; }
.inspector {
width: 260px; border-left: 1px solid var(--border);
overflow-y: auto; padding: var(--space-4); background: var(--bg-secondary);
display: flex; flex-direction: column; gap: var(--space-3);
}
.inspector-title { font-size: var(--text-base); font-weight: 600; margin-bottom: var(--space-2); }
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Document Editor Screen
Layout: toolbar at top, editor workspace center, optional preview or sidebar.
Cross-domain examples:
- MarkPad: markdown editor with live preview
- Blog Agent: post editor with metadata sidebar
- Slide Deck: outline editor with slide preview
<style>
.editor-screen { display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
.editor-toolbar {
padding: 4px var(--space-3); height: 36px;
border-bottom: 1px solid var(--border);
display: flex; align-items: center; gap: var(--space-2);
background: var(--bg-secondary);
}
.editor-toolbar-title {
font-size: var(--text-sm); font-weight: 600; flex: 1;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.editor-body { display: flex; flex: 1; overflow: hidden; }
.editor-main { flex: 1; overflow-y: auto; padding: var(--space-4); }
.editor-sidebar {
width: 220px; border-left: 1px solid var(--border);
overflow-y: auto; padding: var(--space-3);
background: var(--bg-secondary);
}
.editor-statusbar {
padding: 2px var(--space-3); height: 22px;
border-top: 1px solid var(--border-light);
font-size: 10px; color: var(--fg-muted);
display: flex; align-items: center; justify-content: space-between;
background: var(--bg-secondary);
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Queue / Review Screen
Layout: queue list with review controls. Items are processed one at a time with approve/reject/skip actions.
Cross-domain examples:
- LinkedIn Engagement: pending actions with approve/reject
- Content Moderation: flagged items with resolution controls
- AI Output Review: generated results with accept/regenerate
<style>
.review-screen { display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
.review-header {
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--border);
display: flex; align-items: center; justify-content: space-between;
}
.review-count { font-size: var(--text-xs); color: var(--fg-muted); }
.review-body { flex: 1; overflow-y: auto; padding: var(--space-3); }
.review-item {
background: var(--bg); border: 1px solid var(--border);
border-radius: var(--radius); padding: var(--space-4); margin-bottom: var(--space-3);
box-shadow: var(--shadow-sm);
}
.review-actions {
position: sticky; bottom: 0; padding: var(--space-3) var(--space-4);
background: var(--bg);
border-top: 1px solid var(--border);
display: flex; gap: var(--space-2); justify-content: flex-end;
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Background Job Monitor Screen
Layout: progress indicator, status message, result or error display with retry action.
Cross-domain examples:
- Slide Deck Creator: generation progress with slide-by-slide status
- Data Export: processing progress with download link
- Bulk Operations: batch processing with item-level results
<style>
.job-screen { display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
.job-header {
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--border);
display: flex; align-items: center; gap: var(--space-3);
}
.job-body { flex: 1; overflow-y: auto; padding: var(--space-4);
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-4); }
.job-progress { width: 100%; max-width: 360px; }
.job-status { font-size: var(--text-sm); color: var(--fg-secondary); text-align: center; }
.job-result { width: 100%; }
</style>2
3
4
5
6
7
8
9
10
11
12
13
Settings / Setup Screen
Layout: vertical form sections with labels, inputs, and validation states. Optional onboarding checklist.
Cross-domain examples:
- OAuth Connection: connect account flow
- Plugin Configuration: API key and preferences setup
- First-Run Onboarding: step-by-step checklist
<style>
.setup-screen { display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
.setup-header {
padding: var(--space-4); border-bottom: 1px solid var(--border);
}
.setup-header-title { font-size: var(--text-lg); font-weight: 700; }
.setup-header-desc { font-size: var(--text-sm); color: var(--fg-secondary); margin-top: 2px; }
.setup-body { flex: 1; overflow-y: auto; padding: var(--space-4); }
.setup-section { margin-bottom: var(--space-6); }
.setup-section-title {
font-size: var(--text-sm); font-weight: 600; margin-bottom: var(--space-3);
padding-bottom: var(--space-2); border-bottom: 1px solid var(--border-light);
}
.setup-fields { display: flex; flex-direction: column; gap: var(--space-4); }
.setup-footer {
padding: var(--space-3) var(--space-4);
border-top: 1px solid var(--border);
display: flex; justify-content: flex-end; gap: var(--space-2);
}
.checklist-item {
display: flex; align-items: center; gap: var(--space-3);
padding: var(--space-3); border-radius: var(--radius-sm);
}
.checklist-check {
width: 20px; height: 20px; border-radius: 50%; border: 1.5px solid var(--border);
display: flex; align-items: center; justify-content: center; flex-shrink: 0;
transition: all var(--duration-fast) var(--easing);
}
.checklist-check.done { background: var(--success); border-color: var(--success); color: #fff; }
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Launchable Workspace Screen
Layout: full-panel chrome for standalone mini-apps. Header with title and navigation, main content area, optional footer actions.
Cross-domain examples:
- MarkPad: full document browser and editor
- Todo List: grouped task manager
- Cursor Cloud Agents: agent list → detail → launch form
<style>
.workspace-screen { display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
.workspace-header {
padding: 6px var(--space-3); height: 40px;
border-bottom: 1px solid var(--border);
display: flex; align-items: center; gap: var(--space-3);
background: var(--bg-secondary);
}
.workspace-header-icon {
width: 26px; height: 26px; border-radius: var(--radius-sm);
background: var(--accent-soft); color: var(--accent);
display: flex; align-items: center; justify-content: center;
font-size: var(--text-xs); font-weight: 700;
}
.workspace-header-title { font-size: var(--text-sm); font-weight: 600; flex: 1; }
.workspace-body { flex: 1; overflow-y: auto; background: var(--bg); }
.workspace-footer {
padding: 4px var(--space-3); height: 24px;
border-top: 1px solid var(--border-light);
display: flex; align-items: center; justify-content: space-between;
font-size: 10px; color: var(--fg-muted);
background: var(--bg-secondary);
}
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Compact Inline Screen
Layout: minimal chrome for inline chat widgets. No header bar, just padded content with optional compact footer.
Cross-domain examples:
- Weather Tool: current conditions card
- Calculator: result display
- Quick Status: single metric or confirmation
<style>
.inline-screen { padding: var(--space-3); }
.inline-card {
background: var(--bg); border: 1px solid var(--border);
border-radius: var(--radius); padding: var(--space-4);
box-shadow: var(--shadow-sm);
}
.inline-footer {
margin-top: var(--space-3); display: flex; justify-content: flex-end; gap: var(--space-2);
}
</style>2
3
4
5
6
7
8
9
10
11
Data Contracts
Standard onData Payload Shape
PIE.onData(function(data) {
if (data.__actionMeta) {
if (data.__actionMeta.ok) { showToast('success', 'Done'); }
else { showToast('error', data.__actionMeta.error); }
return;
}
switch (data.type || data.event) {
case 'init':
case 'loaded':
break;
case 'item_added':
case 'item_updated':
case 'item_removed':
break;
case 'progress':
break;
case 'error':
showBanner('error', data.message);
break;
default:
break;
}
render();
});2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Standard sendAction Pattern
function doAction(actionId, payload) {
setLoading(actionId, true);
PIE.sendAction(actionId, payload || {});
}2
3
4
Standard Resize Pattern
function autoResize() {
var body = document.getElementById('app');
var h = Math.min(Math.max(body.scrollHeight + 24, 200), 600);
PIE.resize(h);
}2
3
4
5
Flash / Toast Helper
function showToast(type, message, duration) {
var el = document.createElement('div');
el.className = 'toast toast-' + type;
el.textContent = message;
document.body.appendChild(el);
setTimeout(function() { el.remove(); }, duration || 3000);
}2
3
4
5
6
7
Layout Utilities
<style>
.flex { display: flex; }
.flex-col { flex-direction: column; }
.flex-1 { flex: 1; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.justify-end { justify-content: flex-end; }
.gap-1 { gap: var(--space-1); }
.gap-2 { gap: var(--space-2); }
.gap-3 { gap: var(--space-3); }
.gap-4 { gap: var(--space-4); }
.p-3 { padding: var(--space-3); }
.p-4 { padding: var(--space-4); }
.px-3 { padding-left: var(--space-3); padding-right: var(--space-3); }
.py-2 { padding-top: var(--space-2); padding-bottom: var(--space-2); }
.mt-2 { margin-top: var(--space-2); }
.mb-3 { margin-bottom: var(--space-3); }
.w-full { width: 100%; }
.truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
overflow: hidden; clip: rect(0,0,0,0); border: 0; }
.scroll-y { overflow-y: auto; }
.hidden { display: none !important; }
</style>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24