Skip to content

Latest commit

History

History
144 lines (104 loc) · 5.41 KB

File metadata and controls

144 lines (104 loc) · 5.41 KB

Guide: Exploring the Contact Manager

A full-stack contact manager with a Python backend and a React frontend. Two separate SLOP providers — one per language — that any AI consumer can subscribe to simultaneously.

Setup

Backend (Python)

cd examples/full-stack/python-react/backend
uv sync # or: pip install -e .
uv run contacts-api

The server starts at http://localhost:8000. SLOP WebSocket endpoint at ws://localhost:8000/slop.

Frontend (React)

cd examples/full-stack/python-react/frontend
bun install
bun run dev

Opens at http://localhost:5173. The Vite dev server proxies /api and /slop to the Python backend.

Part 1: The web app

Open http://localhost:5173 in your browser. You see a contact manager with 10 seed contacts:

  • Left panel: Searchable, filterable contact list. Starred contacts (Alice, Dave, Grace) sort to the top.
  • Right panel: Contact detail with tags, notes, and activity log.
  • Tag bar: Filter by tag (work, family, vip, etc.).
  • Compose form: Click "+ New Contact" to add someone.

Try it out: search for "Acme", filter by "vip", click Alice to see her notes and activity. This is a normal web app. Nothing unusual.

Part 2: What the AI sees

Two SLOP providers are running:

Server provider (Python — data)

Connect a SLOP consumer to ws://localhost:8000/slop. Subscribe to see the full data tree:

[root] contacts-api
[collection] contacts (count=10, starred=3)
[item] contact-7 Grace Kim — Startup Co CEO salience=1.0 pinned
[item] contact-1 Alice Chen — Acme Corp Engineer salience=0.9 pinned
[item] contact-4 Dave Wilson — Acme Corp CTO salience=0.9 pinned
[item] contact-9 Iris Nakamura — BigTech DevRel salience=0.7
[item] contact-2 Bob Martinez — Acme Corp PM salience=0.6
[item] contact-6 Frank Lee — Acme Corp EM salience=0.5
[item] contact-3 Carol Davis — Design Studio salience=0.45
[item] contact-10 James Wright — Attorney salience=0.2
[item] contact-5 Eva Thompson salience=0.2
[item] contact-8 Henry Park salience=0.2
[collection] tags (count=8)
[item] tag-work (12 contacts)
[item] tag-vip (3 contacts)
...

Notice:

  • Salience ordering. Grace Kim (starred + activity today) is at the top. Eva and Henry (no recent activity) are at the bottom.
  • Pinned nodes. All three starred contacts have meta.pinned: true.
  • Content references. Alice has a content_ref with a preview of her notes — the AI can read the summary without loading the full text.
  • Activity sub-collections. Each contact has an activity child collection showing recent interactions.

The AI can invoke actions directly:

// Create a contact
{ "type": "invoke", "id": "i1", "path": "/contacts", "action": "create",
"params": { "name": "Sarah Park", "company": "Studio Five" } }
// Star someone
{ "type": "invoke", "id": "i2", "path": "/contacts/contact-3", "action": "star" }
// Search
{ "type": "invoke", "id": "i3", "path": "/contacts", "action": "search",
"params": { "query": "Acme" } }

Browser provider (React — UI state)

The SLOP browser extension discovers the in-page provider via <meta name="slop" content="postmessage">. It exposes:

[root] contacts-ui
[status] search query="" result_count=10 {set_query, clear}
[status] filter active_tag=null {set_tag, clear}
[status] selection contact_id=null {select, deselect}
[view] compose open=false name="" email="" {open, close, fill, submit}

This is what the user is looking at right now. The AI can:

// Search for someone
{ "type": "invoke", "path": "/search", "action": "set_query",
"params": { "query": "Acme" } }
// Filter by tag
{ "type": "invoke", "path": "/filter", "action": "set_tag",
"params": { "tag": "vip" } }
// Select a contact
{ "type": "invoke", "path": "/selection", "action": "select",
"params": { "contact_id": "contact-1" } }
// Open the compose form and fill it
{ "type": "invoke", "path": "/compose", "action": "fill",
"params": { "name": "New Person", "email": "new@example.com" } }

Part 3: Two providers, one picture

The AI consumer sees both providers in its workspace:

Workspace: "Contacts"
contacts-api ws ← Python server: data + actions
contacts-ui pm ← React browser: UI state + navigation

When a user asks "Find Alice's email and add a note about our meeting":

  1. AI reads contacts-api → finds contact-1alice@example.com
  2. AI invokes add_note on contacts-api:/contacts/contact-1 with the meeting note
  3. AI invokes select on contacts-ui:/selection to navigate the UI to Alice
  4. The browser shows Alice's detail view, now with the new note

The Python server handled the data mutation. The React app handled the UI navigation. The AI coordinated both through standard SLOP — no shared runtime, no language coupling.

The takeaway

This is a normal full-stack app. Python API, React SPA, REST in between. Adding SLOP was:

  • Backend: ~80 lines in slop_tree.py to register the data tree. One SlopMiddleware line in main.py.
  • Frontend: ~5 lines per component (useSlop calls). One createSlop initialization.

Two providers, two languages, two transports — but the AI sees one coherent picture of the app. That's SLOP.

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
slop/examples/full-stack/python-react/GUIDE.md at main · devteapot/slop · GitHub
Skip to content

Latest commit

History

History
144 lines (104 loc) · 5.41 KB

File metadata and controls

144 lines (104 loc) · 5.41 KB

Guide: Exploring the Contact Manager

A full-stack contact manager with a Python backend and a React frontend. Two separate SLOP providers — one per language — that any AI consumer can subscribe to simultaneously.

Setup

Backend (Python)

cd examples/full-stack/python-react/backend
uv sync # or: pip install -e .
uv run contacts-api

The server starts at http://localhost:8000. SLOP WebSocket endpoint at ws://localhost:8000/slop.

Frontend (React)

cd examples/full-stack/python-react/frontend
bun install
bun run dev

Opens at http://localhost:5173. The Vite dev server proxies /api and /slop to the Python backend.

Part 1: The web app

Open http://localhost:5173 in your browser. You see a contact manager with 10 seed contacts:

  • Left panel: Searchable, filterable contact list. Starred contacts (Alice, Dave, Grace) sort to the top.
  • Right panel: Contact detail with tags, notes, and activity log.
  • Tag bar: Filter by tag (work, family, vip, etc.).
  • Compose form: Click "+ New Contact" to add someone.

Try it out: search for "Acme", filter by "vip", click Alice to see her notes and activity. This is a normal web app. Nothing unusual.

Part 2: What the AI sees

Two SLOP providers are running:

Server provider (Python — data)

Connect a SLOP consumer to ws://localhost:8000/slop. Subscribe to see the full data tree:

[root] contacts-api
[collection] contacts (count=10, starred=3)
[item] contact-7 Grace Kim — Startup Co CEO salience=1.0 pinned
[item] contact-1 Alice Chen — Acme Corp Engineer salience=0.9 pinned
[item] contact-4 Dave Wilson — Acme Corp CTO salience=0.9 pinned
[item] contact-9 Iris Nakamura — BigTech DevRel salience=0.7
[item] contact-2 Bob Martinez — Acme Corp PM salience=0.6
[item] contact-6 Frank Lee — Acme Corp EM salience=0.5
[item] contact-3 Carol Davis — Design Studio salience=0.45
[item] contact-10 James Wright — Attorney salience=0.2
[item] contact-5 Eva Thompson salience=0.2
[item] contact-8 Henry Park salience=0.2
[collection] tags (count=8)
[item] tag-work (12 contacts)
[item] tag-vip (3 contacts)
...

Notice:

  • Salience ordering. Grace Kim (starred + activity today) is at the top. Eva and Henry (no recent activity) are at the bottom.
  • Pinned nodes. All three starred contacts have meta.pinned: true.
  • Content references. Alice has a content_ref with a preview of her notes — the AI can read the summary without loading the full text.
  • Activity sub-collections. Each contact has an activity child collection showing recent interactions.

The AI can invoke actions directly:

// Create a contact
{ "type": "invoke", "id": "i1", "path": "/contacts", "action": "create",
"params": { "name": "Sarah Park", "company": "Studio Five" } }
// Star someone
{ "type": "invoke", "id": "i2", "path": "/contacts/contact-3", "action": "star" }
// Search
{ "type": "invoke", "id": "i3", "path": "/contacts", "action": "search",
"params": { "query": "Acme" } }

Browser provider (React — UI state)

The SLOP browser extension discovers the in-page provider via <meta name="slop" content="postmessage">. It exposes:

[root] contacts-ui
[status] search query="" result_count=10 {set_query, clear}
[status] filter active_tag=null {set_tag, clear}
[status] selection contact_id=null {select, deselect}
[view] compose open=false name="" email="" {open, close, fill, submit}

This is what the user is looking at right now. The AI can:

// Search for someone
{ "type": "invoke", "path": "/search", "action": "set_query",
"params": { "query": "Acme" } }
// Filter by tag
{ "type": "invoke", "path": "/filter", "action": "set_tag",
"params": { "tag": "vip" } }
// Select a contact
{ "type": "invoke", "path": "/selection", "action": "select",
"params": { "contact_id": "contact-1" } }
// Open the compose form and fill it
{ "type": "invoke", "path": "/compose", "action": "fill",
"params": { "name": "New Person", "email": "new@example.com" } }

Part 3: Two providers, one picture

The AI consumer sees both providers in its workspace:

Workspace: "Contacts"
contacts-api ws ← Python server: data + actions
contacts-ui pm ← React browser: UI state + navigation

When a user asks "Find Alice's email and add a note about our meeting":

  1. AI reads contacts-api → finds contact-1alice@example.com
  2. AI invokes add_note on contacts-api:/contacts/contact-1 with the meeting note
  3. AI invokes select on contacts-ui:/selection to navigate the UI to Alice
  4. The browser shows Alice's detail view, now with the new note

The Python server handled the data mutation. The React app handled the UI navigation. The AI coordinated both through standard SLOP — no shared runtime, no language coupling.

The takeaway

This is a normal full-stack app. Python API, React SPA, REST in between. Adding SLOP was:

  • Backend: ~80 lines in slop_tree.py to register the data tree. One SlopMiddleware line in main.py.
  • Frontend: ~5 lines per component (useSlop calls). One createSlop initialization.

Two providers, two languages, two transports — but the AI sees one coherent picture of the app. That's SLOP.

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' slop/examples/full-stack/python-react/GUIDE.md at main · devteapot/slop · GitHub
Skip to content

Latest commit

History

History
144 lines (104 loc) · 5.41 KB

File metadata and controls

144 lines (104 loc) · 5.41 KB

Guide: Exploring the Contact Manager

A full-stack contact manager with a Python backend and a React frontend. Two separate SLOP providers — one per language — that any AI consumer can subscribe to simultaneously.

Setup

Backend (Python)

cd examples/full-stack/python-react/backend
uv sync # or: pip install -e .
uv run contacts-api

The server starts at http://localhost:8000. SLOP WebSocket endpoint at ws://localhost:8000/slop.

Frontend (React)

cd examples/full-stack/python-react/frontend
bun install
bun run dev

Opens at http://localhost:5173. The Vite dev server proxies /api and /slop to the Python backend.

Part 1: The web app

Open http://localhost:5173 in your browser. You see a contact manager with 10 seed contacts:

  • Left panel: Searchable, filterable contact list. Starred contacts (Alice, Dave, Grace) sort to the top.
  • Right panel: Contact detail with tags, notes, and activity log.
  • Tag bar: Filter by tag (work, family, vip, etc.).
  • Compose form: Click "+ New Contact" to add someone.

Try it out: search for "Acme", filter by "vip", click Alice to see her notes and activity. This is a normal web app. Nothing unusual.

Part 2: What the AI sees

Two SLOP providers are running:

Server provider (Python — data)

Connect a SLOP consumer to ws://localhost:8000/slop. Subscribe to see the full data tree:

[root] contacts-api
[collection] contacts (count=10, starred=3)
[item] contact-7 Grace Kim — Startup Co CEO salience=1.0 pinned
[item] contact-1 Alice Chen — Acme Corp Engineer salience=0.9 pinned
[item] contact-4 Dave Wilson — Acme Corp CTO salience=0.9 pinned
[item] contact-9 Iris Nakamura — BigTech DevRel salience=0.7
[item] contact-2 Bob Martinez — Acme Corp PM salience=0.6
[item] contact-6 Frank Lee — Acme Corp EM salience=0.5
[item] contact-3 Carol Davis — Design Studio salience=0.45
[item] contact-10 James Wright — Attorney salience=0.2
[item] contact-5 Eva Thompson salience=0.2
[item] contact-8 Henry Park salience=0.2
[collection] tags (count=8)
[item] tag-work (12 contacts)
[item] tag-vip (3 contacts)
...

Notice:

  • Salience ordering. Grace Kim (starred + activity today) is at the top. Eva and Henry (no recent activity) are at the bottom.
  • Pinned nodes. All three starred contacts have meta.pinned: true.
  • Content references. Alice has a content_ref with a preview of her notes — the AI can read the summary without loading the full text.
  • Activity sub-collections. Each contact has an activity child collection showing recent interactions.

The AI can invoke actions directly:

// Create a contact
{ "type": "invoke", "id": "i1", "path": "/contacts", "action": "create",
"params": { "name": "Sarah Park", "company": "Studio Five" } }
// Star someone
{ "type": "invoke", "id": "i2", "path": "/contacts/contact-3", "action": "star" }
// Search
{ "type": "invoke", "id": "i3", "path": "/contacts", "action": "search",
"params": { "query": "Acme" } }

Browser provider (React — UI state)

The SLOP browser extension discovers the in-page provider via <meta name="slop" content="postmessage">. It exposes:

[root] contacts-ui
[status] search query="" result_count=10 {set_query, clear}
[status] filter active_tag=null {set_tag, clear}
[status] selection contact_id=null {select, deselect}
[view] compose open=false name="" email="" {open, close, fill, submit}

This is what the user is looking at right now. The AI can:

// Search for someone
{ "type": "invoke", "path": "/search", "action": "set_query",
"params": { "query": "Acme" } }
// Filter by tag
{ "type": "invoke", "path": "/filter", "action": "set_tag",
"params": { "tag": "vip" } }
// Select a contact
{ "type": "invoke", "path": "/selection", "action": "select",
"params": { "contact_id": "contact-1" } }
// Open the compose form and fill it
{ "type": "invoke", "path": "/compose", "action": "fill",
"params": { "name": "New Person", "email": "new@example.com" } }

Part 3: Two providers, one picture

The AI consumer sees both providers in its workspace:

Workspace: "Contacts"
contacts-api ws ← Python server: data + actions
contacts-ui pm ← React browser: UI state + navigation

When a user asks "Find Alice's email and add a note about our meeting":

  1. AI reads contacts-api → finds contact-1alice@example.com
  2. AI invokes add_note on contacts-api:/contacts/contact-1 with the meeting note
  3. AI invokes select on contacts-ui:/selection to navigate the UI to Alice
  4. The browser shows Alice's detail view, now with the new note

The Python server handled the data mutation. The React app handled the UI navigation. The AI coordinated both through standard SLOP — no shared runtime, no language coupling.

The takeaway

This is a normal full-stack app. Python API, React SPA, REST in between. Adding SLOP was:

  • Backend: ~80 lines in slop_tree.py to register the data tree. One SlopMiddleware line in main.py.
  • Frontend: ~5 lines per component (useSlop calls). One createSlop initialization.

Two providers, two languages, two transports — but the AI sees one coherent picture of the app. That's SLOP.

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' slop/examples/full-stack/python-react/GUIDE.md at main · devteapot/slop · GitHub
Skip to content

Latest commit

History

History
144 lines (104 loc) · 5.41 KB

File metadata and controls

144 lines (104 loc) · 5.41 KB

Guide: Exploring the Contact Manager

A full-stack contact manager with a Python backend and a React frontend. Two separate SLOP providers — one per language — that any AI consumer can subscribe to simultaneously.

Setup

Backend (Python)

cd examples/full-stack/python-react/backend
uv sync # or: pip install -e .
uv run contacts-api

The server starts at http://localhost:8000. SLOP WebSocket endpoint at ws://localhost:8000/slop.

Frontend (React)

cd examples/full-stack/python-react/frontend
bun install
bun run dev

Opens at http://localhost:5173. The Vite dev server proxies /api and /slop to the Python backend.

Part 1: The web app

Open http://localhost:5173 in your browser. You see a contact manager with 10 seed contacts:

  • Left panel: Searchable, filterable contact list. Starred contacts (Alice, Dave, Grace) sort to the top.
  • Right panel: Contact detail with tags, notes, and activity log.
  • Tag bar: Filter by tag (work, family, vip, etc.).
  • Compose form: Click "+ New Contact" to add someone.

Try it out: search for "Acme", filter by "vip", click Alice to see her notes and activity. This is a normal web app. Nothing unusual.

Part 2: What the AI sees

Two SLOP providers are running:

Server provider (Python — data)

Connect a SLOP consumer to ws://localhost:8000/slop. Subscribe to see the full data tree:

[root] contacts-api
[collection] contacts (count=10, starred=3)
[item] contact-7 Grace Kim — Startup Co CEO salience=1.0 pinned
[item] contact-1 Alice Chen — Acme Corp Engineer salience=0.9 pinned
[item] contact-4 Dave Wilson — Acme Corp CTO salience=0.9 pinned
[item] contact-9 Iris Nakamura — BigTech DevRel salience=0.7
[item] contact-2 Bob Martinez — Acme Corp PM salience=0.6
[item] contact-6 Frank Lee — Acme Corp EM salience=0.5
[item] contact-3 Carol Davis — Design Studio salience=0.45
[item] contact-10 James Wright — Attorney salience=0.2
[item] contact-5 Eva Thompson salience=0.2
[item] contact-8 Henry Park salience=0.2
[collection] tags (count=8)
[item] tag-work (12 contacts)
[item] tag-vip (3 contacts)
...

Notice:

  • Salience ordering. Grace Kim (starred + activity today) is at the top. Eva and Henry (no recent activity) are at the bottom.
  • Pinned nodes. All three starred contacts have meta.pinned: true.
  • Content references. Alice has a content_ref with a preview of her notes — the AI can read the summary without loading the full text.
  • Activity sub-collections. Each contact has an activity child collection showing recent interactions.

The AI can invoke actions directly:

// Create a contact
{ "type": "invoke", "id": "i1", "path": "/contacts", "action": "create",
"params": { "name": "Sarah Park", "company": "Studio Five" } }
// Star someone
{ "type": "invoke", "id": "i2", "path": "/contacts/contact-3", "action": "star" }
// Search
{ "type": "invoke", "id": "i3", "path": "/contacts", "action": "search",
"params": { "query": "Acme" } }

Browser provider (React — UI state)

The SLOP browser extension discovers the in-page provider via <meta name="slop" content="postmessage">. It exposes:

[root] contacts-ui
[status] search query="" result_count=10 {set_query, clear}
[status] filter active_tag=null {set_tag, clear}
[status] selection contact_id=null {select, deselect}
[view] compose open=false name="" email="" {open, close, fill, submit}

This is what the user is looking at right now. The AI can:

// Search for someone
{ "type": "invoke", "path": "/search", "action": "set_query",
"params": { "query": "Acme" } }
// Filter by tag
{ "type": "invoke", "path": "/filter", "action": "set_tag",
"params": { "tag": "vip" } }
// Select a contact
{ "type": "invoke", "path": "/selection", "action": "select",
"params": { "contact_id": "contact-1" } }
// Open the compose form and fill it
{ "type": "invoke", "path": "/compose", "action": "fill",
"params": { "name": "New Person", "email": "new@example.com" } }

Part 3: Two providers, one picture

The AI consumer sees both providers in its workspace:

Workspace: "Contacts"
contacts-api ws ← Python server: data + actions
contacts-ui pm ← React browser: UI state + navigation

When a user asks "Find Alice's email and add a note about our meeting":

  1. AI reads contacts-api → finds contact-1alice@example.com
  2. AI invokes add_note on contacts-api:/contacts/contact-1 with the meeting note
  3. AI invokes select on contacts-ui:/selection to navigate the UI to Alice
  4. The browser shows Alice's detail view, now with the new note

The Python server handled the data mutation. The React app handled the UI navigation. The AI coordinated both through standard SLOP — no shared runtime, no language coupling.

The takeaway

This is a normal full-stack app. Python API, React SPA, REST in between. Adding SLOP was:

  • Backend: ~80 lines in slop_tree.py to register the data tree. One SlopMiddleware line in main.py.
  • Frontend: ~5 lines per component (useSlop calls). One createSlop initialization.

Two providers, two languages, two transports — but the AI sees one coherent picture of the app. That's SLOP.

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' slop/examples/full-stack/python-react/GUIDE.md at main · devteapot/slop · GitHub
Skip to content

Latest commit

History

History
144 lines (104 loc) · 5.41 KB

File metadata and controls

144 lines (104 loc) · 5.41 KB

Guide: Exploring the Contact Manager

A full-stack contact manager with a Python backend and a React frontend. Two separate SLOP providers — one per language — that any AI consumer can subscribe to simultaneously.

Setup

Backend (Python)

cd examples/full-stack/python-react/backend
uv sync # or: pip install -e .
uv run contacts-api

The server starts at http://localhost:8000. SLOP WebSocket endpoint at ws://localhost:8000/slop.

Frontend (React)

cd examples/full-stack/python-react/frontend
bun install
bun run dev

Opens at http://localhost:5173. The Vite dev server proxies /api and /slop to the Python backend.

Part 1: The web app

Open http://localhost:5173 in your browser. You see a contact manager with 10 seed contacts:

  • Left panel: Searchable, filterable contact list. Starred contacts (Alice, Dave, Grace) sort to the top.
  • Right panel: Contact detail with tags, notes, and activity log.
  • Tag bar: Filter by tag (work, family, vip, etc.).
  • Compose form: Click "+ New Contact" to add someone.

Try it out: search for "Acme", filter by "vip", click Alice to see her notes and activity. This is a normal web app. Nothing unusual.

Part 2: What the AI sees

Two SLOP providers are running:

Server provider (Python — data)

Connect a SLOP consumer to ws://localhost:8000/slop. Subscribe to see the full data tree:

[root] contacts-api
[collection] contacts (count=10, starred=3)
[item] contact-7 Grace Kim — Startup Co CEO salience=1.0 pinned
[item] contact-1 Alice Chen — Acme Corp Engineer salience=0.9 pinned
[item] contact-4 Dave Wilson — Acme Corp CTO salience=0.9 pinned
[item] contact-9 Iris Nakamura — BigTech DevRel salience=0.7
[item] contact-2 Bob Martinez — Acme Corp PM salience=0.6
[item] contact-6 Frank Lee — Acme Corp EM salience=0.5
[item] contact-3 Carol Davis — Design Studio salience=0.45
[item] contact-10 James Wright — Attorney salience=0.2
[item] contact-5 Eva Thompson salience=0.2
[item] contact-8 Henry Park salience=0.2
[collection] tags (count=8)
[item] tag-work (12 contacts)
[item] tag-vip (3 contacts)
...

Notice:

  • Salience ordering. Grace Kim (starred + activity today) is at the top. Eva and Henry (no recent activity) are at the bottom.
  • Pinned nodes. All three starred contacts have meta.pinned: true.
  • Content references. Alice has a content_ref with a preview of her notes — the AI can read the summary without loading the full text.
  • Activity sub-collections. Each contact has an activity child collection showing recent interactions.

The AI can invoke actions directly:

// Create a contact
{ "type": "invoke", "id": "i1", "path": "/contacts", "action": "create",
"params": { "name": "Sarah Park", "company": "Studio Five" } }
// Star someone
{ "type": "invoke", "id": "i2", "path": "/contacts/contact-3", "action": "star" }
// Search
{ "type": "invoke", "id": "i3", "path": "/contacts", "action": "search",
"params": { "query": "Acme" } }

Browser provider (React — UI state)

The SLOP browser extension discovers the in-page provider via <meta name="slop" content="postmessage">. It exposes:

[root] contacts-ui
[status] search query="" result_count=10 {set_query, clear}
[status] filter active_tag=null {set_tag, clear}
[status] selection contact_id=null {select, deselect}
[view] compose open=false name="" email="" {open, close, fill, submit}

This is what the user is looking at right now. The AI can:

// Search for someone
{ "type": "invoke", "path": "/search", "action": "set_query",
"params": { "query": "Acme" } }
// Filter by tag
{ "type": "invoke", "path": "/filter", "action": "set_tag",
"params": { "tag": "vip" } }
// Select a contact
{ "type": "invoke", "path": "/selection", "action": "select",
"params": { "contact_id": "contact-1" } }
// Open the compose form and fill it
{ "type": "invoke", "path": "/compose", "action": "fill",
"params": { "name": "New Person", "email": "new@example.com" } }

Part 3: Two providers, one picture

The AI consumer sees both providers in its workspace:

Workspace: "Contacts"
contacts-api ws ← Python server: data + actions
contacts-ui pm ← React browser: UI state + navigation

When a user asks "Find Alice's email and add a note about our meeting":

  1. AI reads contacts-api → finds contact-1alice@example.com
  2. AI invokes add_note on contacts-api:/contacts/contact-1 with the meeting note
  3. AI invokes select on contacts-ui:/selection to navigate the UI to Alice
  4. The browser shows Alice's detail view, now with the new note

The Python server handled the data mutation. The React app handled the UI navigation. The AI coordinated both through standard SLOP — no shared runtime, no language coupling.

The takeaway

This is a normal full-stack app. Python API, React SPA, REST in between. Adding SLOP was:

  • Backend: ~80 lines in slop_tree.py to register the data tree. One SlopMiddleware line in main.py.
  • Frontend: ~5 lines per component (useSlop calls). One createSlop initialization.

Two providers, two languages, two transports — but the AI sees one coherent picture of the app. That's SLOP.

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' slop/examples/full-stack/python-react/GUIDE.md at main · devteapot/slop · GitHub
Skip to content

Latest commit

History

History
144 lines (104 loc) · 5.41 KB

File metadata and controls

144 lines (104 loc) · 5.41 KB

Guide: Exploring the Contact Manager

A full-stack contact manager with a Python backend and a React frontend. Two separate SLOP providers — one per language — that any AI consumer can subscribe to simultaneously.

Setup

Backend (Python)

cd examples/full-stack/python-react/backend
uv sync # or: pip install -e .
uv run contacts-api

The server starts at http://localhost:8000. SLOP WebSocket endpoint at ws://localhost:8000/slop.

Frontend (React)

cd examples/full-stack/python-react/frontend
bun install
bun run dev

Opens at http://localhost:5173. The Vite dev server proxies /api and /slop to the Python backend.

Part 1: The web app

Open http://localhost:5173 in your browser. You see a contact manager with 10 seed contacts:

  • Left panel: Searchable, filterable contact list. Starred contacts (Alice, Dave, Grace) sort to the top.
  • Right panel: Contact detail with tags, notes, and activity log.
  • Tag bar: Filter by tag (work, family, vip, etc.).
  • Compose form: Click "+ New Contact" to add someone.

Try it out: search for "Acme", filter by "vip", click Alice to see her notes and activity. This is a normal web app. Nothing unusual.

Part 2: What the AI sees

Two SLOP providers are running:

Server provider (Python — data)

Connect a SLOP consumer to ws://localhost:8000/slop. Subscribe to see the full data tree:

[root] contacts-api
[collection] contacts (count=10, starred=3)
[item] contact-7 Grace Kim — Startup Co CEO salience=1.0 pinned
[item] contact-1 Alice Chen — Acme Corp Engineer salience=0.9 pinned
[item] contact-4 Dave Wilson — Acme Corp CTO salience=0.9 pinned
[item] contact-9 Iris Nakamura — BigTech DevRel salience=0.7
[item] contact-2 Bob Martinez — Acme Corp PM salience=0.6
[item] contact-6 Frank Lee — Acme Corp EM salience=0.5
[item] contact-3 Carol Davis — Design Studio salience=0.45
[item] contact-10 James Wright — Attorney salience=0.2
[item] contact-5 Eva Thompson salience=0.2
[item] contact-8 Henry Park salience=0.2
[collection] tags (count=8)
[item] tag-work (12 contacts)
[item] tag-vip (3 contacts)
...

Notice:

  • Salience ordering. Grace Kim (starred + activity today) is at the top. Eva and Henry (no recent activity) are at the bottom.
  • Pinned nodes. All three starred contacts have meta.pinned: true.
  • Content references. Alice has a content_ref with a preview of her notes — the AI can read the summary without loading the full text.
  • Activity sub-collections. Each contact has an activity child collection showing recent interactions.

The AI can invoke actions directly:

// Create a contact
{ "type": "invoke", "id": "i1", "path": "/contacts", "action": "create",
"params": { "name": "Sarah Park", "company": "Studio Five" } }
// Star someone
{ "type": "invoke", "id": "i2", "path": "/contacts/contact-3", "action": "star" }
// Search
{ "type": "invoke", "id": "i3", "path": "/contacts", "action": "search",
"params": { "query": "Acme" } }

Browser provider (React — UI state)

The SLOP browser extension discovers the in-page provider via <meta name="slop" content="postmessage">. It exposes:

[root] contacts-ui
[status] search query="" result_count=10 {set_query, clear}
[status] filter active_tag=null {set_tag, clear}
[status] selection contact_id=null {select, deselect}
[view] compose open=false name="" email="" {open, close, fill, submit}

This is what the user is looking at right now. The AI can:

// Search for someone
{ "type": "invoke", "path": "/search", "action": "set_query",
"params": { "query": "Acme" } }
// Filter by tag
{ "type": "invoke", "path": "/filter", "action": "set_tag",
"params": { "tag": "vip" } }
// Select a contact
{ "type": "invoke", "path": "/selection", "action": "select",
"params": { "contact_id": "contact-1" } }
// Open the compose form and fill it
{ "type": "invoke", "path": "/compose", "action": "fill",
"params": { "name": "New Person", "email": "new@example.com" } }

Part 3: Two providers, one picture

The AI consumer sees both providers in its workspace:

Workspace: "Contacts"
contacts-api ws ← Python server: data + actions
contacts-ui pm ← React browser: UI state + navigation

When a user asks "Find Alice's email and add a note about our meeting":

  1. AI reads contacts-api → finds contact-1alice@example.com
  2. AI invokes add_note on contacts-api:/contacts/contact-1 with the meeting note
  3. AI invokes select on contacts-ui:/selection to navigate the UI to Alice
  4. The browser shows Alice's detail view, now with the new note

The Python server handled the data mutation. The React app handled the UI navigation. The AI coordinated both through standard SLOP — no shared runtime, no language coupling.

The takeaway

This is a normal full-stack app. Python API, React SPA, REST in between. Adding SLOP was:

  • Backend: ~80 lines in slop_tree.py to register the data tree. One SlopMiddleware line in main.py.
  • Frontend: ~5 lines per component (useSlop calls). One createSlop initialization.

Two providers, two languages, two transports — but the AI sees one coherent picture of the app. That's SLOP.

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' slop/examples/full-stack/python-react/GUIDE.md at main · devteapot/slop · GitHub
Skip to content

Latest commit

History

History
144 lines (104 loc) · 5.41 KB

File metadata and controls

144 lines (104 loc) · 5.41 KB

Guide: Exploring the Contact Manager

A full-stack contact manager with a Python backend and a React frontend. Two separate SLOP providers — one per language — that any AI consumer can subscribe to simultaneously.

Setup

Backend (Python)

cd examples/full-stack/python-react/backend
uv sync # or: pip install -e .
uv run contacts-api

The server starts at http://localhost:8000. SLOP WebSocket endpoint at ws://localhost:8000/slop.

Frontend (React)

cd examples/full-stack/python-react/frontend
bun install
bun run dev

Opens at http://localhost:5173. The Vite dev server proxies /api and /slop to the Python backend.

Part 1: The web app

Open http://localhost:5173 in your browser. You see a contact manager with 10 seed contacts:

  • Left panel: Searchable, filterable contact list. Starred contacts (Alice, Dave, Grace) sort to the top.
  • Right panel: Contact detail with tags, notes, and activity log.
  • Tag bar: Filter by tag (work, family, vip, etc.).
  • Compose form: Click "+ New Contact" to add someone.

Try it out: search for "Acme", filter by "vip", click Alice to see her notes and activity. This is a normal web app. Nothing unusual.

Part 2: What the AI sees

Two SLOP providers are running:

Server provider (Python — data)

Connect a SLOP consumer to ws://localhost:8000/slop. Subscribe to see the full data tree:

[root] contacts-api
[collection] contacts (count=10, starred=3)
[item] contact-7 Grace Kim — Startup Co CEO salience=1.0 pinned
[item] contact-1 Alice Chen — Acme Corp Engineer salience=0.9 pinned
[item] contact-4 Dave Wilson — Acme Corp CTO salience=0.9 pinned
[item] contact-9 Iris Nakamura — BigTech DevRel salience=0.7
[item] contact-2 Bob Martinez — Acme Corp PM salience=0.6
[item] contact-6 Frank Lee — Acme Corp EM salience=0.5
[item] contact-3 Carol Davis — Design Studio salience=0.45
[item] contact-10 James Wright — Attorney salience=0.2
[item] contact-5 Eva Thompson salience=0.2
[item] contact-8 Henry Park salience=0.2
[collection] tags (count=8)
[item] tag-work (12 contacts)
[item] tag-vip (3 contacts)
...

Notice:

  • Salience ordering. Grace Kim (starred + activity today) is at the top. Eva and Henry (no recent activity) are at the bottom.
  • Pinned nodes. All three starred contacts have meta.pinned: true.
  • Content references. Alice has a content_ref with a preview of her notes — the AI can read the summary without loading the full text.
  • Activity sub-collections. Each contact has an activity child collection showing recent interactions.

The AI can invoke actions directly:

// Create a contact
{ "type": "invoke", "id": "i1", "path": "/contacts", "action": "create",
"params": { "name": "Sarah Park", "company": "Studio Five" } }
// Star someone
{ "type": "invoke", "id": "i2", "path": "/contacts/contact-3", "action": "star" }
// Search
{ "type": "invoke", "id": "i3", "path": "/contacts", "action": "search",
"params": { "query": "Acme" } }

Browser provider (React — UI state)

The SLOP browser extension discovers the in-page provider via <meta name="slop" content="postmessage">. It exposes:

[root] contacts-ui
[status] search query="" result_count=10 {set_query, clear}
[status] filter active_tag=null {set_tag, clear}
[status] selection contact_id=null {select, deselect}
[view] compose open=false name="" email="" {open, close, fill, submit}

This is what the user is looking at right now. The AI can:

// Search for someone
{ "type": "invoke", "path": "/search", "action": "set_query",
"params": { "query": "Acme" } }
// Filter by tag
{ "type": "invoke", "path": "/filter", "action": "set_tag",
"params": { "tag": "vip" } }
// Select a contact
{ "type": "invoke", "path": "/selection", "action": "select",
"params": { "contact_id": "contact-1" } }
// Open the compose form and fill it
{ "type": "invoke", "path": "/compose", "action": "fill",
"params": { "name": "New Person", "email": "new@example.com" } }

Part 3: Two providers, one picture

The AI consumer sees both providers in its workspace:

Workspace: "Contacts"
contacts-api ws ← Python server: data + actions
contacts-ui pm ← React browser: UI state + navigation

When a user asks "Find Alice's email and add a note about our meeting":

  1. AI reads contacts-api → finds contact-1alice@example.com
  2. AI invokes add_note on contacts-api:/contacts/contact-1 with the meeting note
  3. AI invokes select on contacts-ui:/selection to navigate the UI to Alice
  4. The browser shows Alice's detail view, now with the new note

The Python server handled the data mutation. The React app handled the UI navigation. The AI coordinated both through standard SLOP — no shared runtime, no language coupling.

The takeaway

This is a normal full-stack app. Python API, React SPA, REST in between. Adding SLOP was:

  • Backend: ~80 lines in slop_tree.py to register the data tree. One SlopMiddleware line in main.py.
  • Frontend: ~5 lines per component (useSlop calls). One createSlop initialization.

Two providers, two languages, two transports — but the AI sees one coherent picture of the app. That's SLOP.

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); slop/examples/full-stack/python-react/GUIDE.md at main · devteapot/slop · GitHub
Skip to content

Latest commit

History

History
144 lines (104 loc) · 5.41 KB

File metadata and controls

144 lines (104 loc) · 5.41 KB

Guide: Exploring the Contact Manager

A full-stack contact manager with a Python backend and a React frontend. Two separate SLOP providers — one per language — that any AI consumer can subscribe to simultaneously.

Setup

Backend (Python)

cd examples/full-stack/python-react/backend
uv sync # or: pip install -e .
uv run contacts-api

The server starts at http://localhost:8000. SLOP WebSocket endpoint at ws://localhost:8000/slop.

Frontend (React)

cd examples/full-stack/python-react/frontend
bun install
bun run dev

Opens at http://localhost:5173. The Vite dev server proxies /api and /slop to the Python backend.

Part 1: The web app

Open http://localhost:5173 in your browser. You see a contact manager with 10 seed contacts:

  • Left panel: Searchable, filterable contact list. Starred contacts (Alice, Dave, Grace) sort to the top.
  • Right panel: Contact detail with tags, notes, and activity log.
  • Tag bar: Filter by tag (work, family, vip, etc.).
  • Compose form: Click "+ New Contact" to add someone.

Try it out: search for "Acme", filter by "vip", click Alice to see her notes and activity. This is a normal web app. Nothing unusual.

Part 2: What the AI sees

Two SLOP providers are running:

Server provider (Python — data)

Connect a SLOP consumer to ws://localhost:8000/slop. Subscribe to see the full data tree:

[root] contacts-api
[collection] contacts (count=10, starred=3)
[item] contact-7 Grace Kim — Startup Co CEO salience=1.0 pinned
[item] contact-1 Alice Chen — Acme Corp Engineer salience=0.9 pinned
[item] contact-4 Dave Wilson — Acme Corp CTO salience=0.9 pinned
[item] contact-9 Iris Nakamura — BigTech DevRel salience=0.7
[item] contact-2 Bob Martinez — Acme Corp PM salience=0.6
[item] contact-6 Frank Lee — Acme Corp EM salience=0.5
[item] contact-3 Carol Davis — Design Studio salience=0.45
[item] contact-10 James Wright — Attorney salience=0.2
[item] contact-5 Eva Thompson salience=0.2
[item] contact-8 Henry Park salience=0.2
[collection] tags (count=8)
[item] tag-work (12 contacts)
[item] tag-vip (3 contacts)
...

Notice:

  • Salience ordering. Grace Kim (starred + activity today) is at the top. Eva and Henry (no recent activity) are at the bottom.
  • Pinned nodes. All three starred contacts have meta.pinned: true.
  • Content references. Alice has a content_ref with a preview of her notes — the AI can read the summary without loading the full text.
  • Activity sub-collections. Each contact has an activity child collection showing recent interactions.

The AI can invoke actions directly:

// Create a contact
{ "type": "invoke", "id": "i1", "path": "/contacts", "action": "create",
"params": { "name": "Sarah Park", "company": "Studio Five" } }
// Star someone
{ "type": "invoke", "id": "i2", "path": "/contacts/contact-3", "action": "star" }
// Search
{ "type": "invoke", "id": "i3", "path": "/contacts", "action": "search",
"params": { "query": "Acme" } }

Browser provider (React — UI state)

The SLOP browser extension discovers the in-page provider via <meta name="slop" content="postmessage">. It exposes:

[root] contacts-ui
[status] search query="" result_count=10 {set_query, clear}
[status] filter active_tag=null {set_tag, clear}
[status] selection contact_id=null {select, deselect}
[view] compose open=false name="" email="" {open, close, fill, submit}

This is what the user is looking at right now. The AI can:

// Search for someone
{ "type": "invoke", "path": "/search", "action": "set_query",
"params": { "query": "Acme" } }
// Filter by tag
{ "type": "invoke", "path": "/filter", "action": "set_tag",
"params": { "tag": "vip" } }
// Select a contact
{ "type": "invoke", "path": "/selection", "action": "select",
"params": { "contact_id": "contact-1" } }
// Open the compose form and fill it
{ "type": "invoke", "path": "/compose", "action": "fill",
"params": { "name": "New Person", "email": "new@example.com" } }

Part 3: Two providers, one picture

The AI consumer sees both providers in its workspace:

Workspace: "Contacts"
contacts-api ws ← Python server: data + actions
contacts-ui pm ← React browser: UI state + navigation

When a user asks "Find Alice's email and add a note about our meeting":

  1. AI reads contacts-api → finds contact-1alice@example.com
  2. AI invokes add_note on contacts-api:/contacts/contact-1 with the meeting note
  3. AI invokes select on contacts-ui:/selection to navigate the UI to Alice
  4. The browser shows Alice's detail view, now with the new note

The Python server handled the data mutation. The React app handled the UI navigation. The AI coordinated both through standard SLOP — no shared runtime, no language coupling.

The takeaway

This is a normal full-stack app. Python API, React SPA, REST in between. Adding SLOP was:

  • Backend: ~80 lines in slop_tree.py to register the data tree. One SlopMiddleware line in main.py.
  • Frontend: ~5 lines per component (useSlop calls). One createSlop initialization.

Two providers, two languages, two transports — but the AI sees one coherent picture of the app. That's SLOP.