Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ jobs:
working-directory: ./packages/create-skybridge/templates/blank
run: pnpm pkg set 'dependencies.skybridge=^${{ steps.version.outputs.version }}'

- name: Pin ecom template skybridge dep for publish
working-directory: ./packages/create-skybridge/templates/ecom
run: pnpm pkg set 'dependencies.skybridge=^${{ steps.version.outputs.version }}'

- name: Publish create-skybridge to npm
working-directory: ./packages/create-skybridge
run: pnpm publish --tag ${{ steps.version.outputs.tag }} --access public --provenance --no-git-checks
Expand All @@ -101,7 +105,7 @@ jobs:

- name: Restore templates workspace deps
if: github.event_name == 'release'
run: git checkout -- packages/create-skybridge/templates/demo/package.json packages/create-skybridge/templates/blank/package.json
run: git checkout -- packages/create-skybridge/templates/demo/package.json packages/create-skybridge/templates/blank/package.json packages/create-skybridge/templates/ecom/package.json

- name: Deploy docs to Mintlify
if: github.event_name == 'release'
Expand Down
7 changes: 4 additions & 3 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,12 @@
"root": "guides/index",
"pages": [
"guides/csp",
"guides/ux",
"guides/ecommerce",
"guides/files",
"guides/skills",
"guides/auth-providers",
"guides/migrate"
"guides/migrate",
"guides/skills",
"guides/ux"
],
"expanded": true
},
Expand Down
2 changes: 1 addition & 1 deletion docs/examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Open-source apps that show how Skybridge supports maps, commerce, travel, games,
prompt="Show me some clothes I can buy."
src="/images/showcase-ecommerce.png"
alt="Ecommerce Carousel showcase"
description="Product carousel with persistent cart, localization support, theme switching, and modal dialogs."
description="Ecommerce with a search results carousel and a fullscreen product detail. The model curates from a viewless search tool, then renders its picks."
demo="https://ecommerce.skybridge.tech/try"
source="https://github.com/alpic-ai/skybridge/tree/main/examples/ecom-carousel"
/>
Expand Down
9 changes: 5 additions & 4 deletions docs/examples/ecommerce-carousel.mdx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
---
title: Ecommerce Carousel
description: Product carousel with persistent cart, localization, and modal checkout flow.
description: Ecommerce with search results carousel and product detail view.
---

import { ChatExample } from "/components/chat-example.jsx";

Ecommerce Carousel highlights localization, modal dialogs, persistent state, and external checkout flows.
Ask for something to buy and the model searches the catalog, curates the results, and renders its picks as a carousel. Tap a card to open a fullscreen detail with an image gallery, a variant picker, specs, and a link out to the store. Showcases a two-tool split: a viewless search tool the model curates from, then a render tool that carries the full product data to the view, where variants resolve in place and prices follow the user's locale.

<ChatExample
prompt="Show me some jackets I can buy."
prompt="Show me the Skybridge merch."
app="Ecommerce Carousel"
src="/images/showcase-ecommerce.png"
alt="Ecommerce Carousel"
Expand All @@ -18,9 +18,10 @@ Ecommerce Carousel highlights localization, modal dialogs, persistent state, and

## Skybridge APIs used

- [`useDisplayMode`](/api-reference/use-display-mode)
- [`useLayout`](/api-reference/use-layout)
- [`useOpenExternal`](/api-reference/use-open-external)
- [`useRequestModal`](/api-reference/use-request-modal)
- [`useSetOpenInAppUrl`](/api-reference/use-set-open-in-app-url)
- [`useToolInfo`](/api-reference/use-tool-info)
- [`useUser`](/api-reference/use-user)
- [`useViewState`](/api-reference/use-view-state)
129 changes: 129 additions & 0 deletions docs/guides/ecommerce.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
title: "Build an Ecommerce App"
description: "Scaffold a catalog search and carousel UI, then fill it with your data and brand"
sidebarTitle: "Ecommerce"
icon: "shopping-bag"
---

Ecommerce inside a conversation is a hard UX problem. A storefront's job has to fit the host-controlled frame, and merchandising moves to a model.

The `ecom` template ships that architecture as a skeleton, with opinionated patterns refined by the [Alpic build team](https://alpic.ai/solutions/build/studio): an iterative search loop, an inline carousel opening into a fullscreen product detail, sparse variant selection, and a token-based design system. The wiring is in place, the data is not: a coding agent skill fills in your catalog and brand, pausing for your sign-off at each design decision.

The template is backend-agnostic: it assumes a catalog you already run, on [Shopify](https://www.shopify.com), [PrestaShop](https://prestashop.com), [Medusa](https://medusajs.com), or a custom API or database, and builds the conversational storefront on top. It never stores products itself.

## Scaffold the Template

Scaffold a project with the `--ecom` flag:

```bash
npx skybridge create my-shop --ecom
```

The template works best with the `chatgpt-app-builder` skill, which drives the fill workflow below. The scaffolder installs it by default; to add it to an existing project:

```bash
npx skills add alpic-ai/skybridge --skill chatgpt-app-builder
```

## Install the Tooling

A few MCP servers let the agent do work it would otherwise hand back to you:

- **[Chrome DevTools MCP](https://github.com/ChromeDevTools/chrome-devtools-mcp)**: a browser for the agent: it [drives the local DevTools](/test/devtools#drive-devtools-from-a-coding-agent) to verify its own work, previews UI components, and inspects your live site's styles for token extraction.
- **[Playwright MCP](https://github.com/microsoft/playwright-mcp)**: the same, as a fallback when Chrome DevTools is unavailable.
- **[Figma MCP](https://www.figma.com/mcp-catalog/):** if your brand lives in Figma, the agent extracts color ramps, type scale, and spacing itself.

All are optional: without them, the agent asks you instead.

## What the Template Ships

Four patterns carry the app. The fill workflow customizes their values, never their shape.

### Search, Then Render

Two [tools](/build/tools) split the flow. `search-products` takes a keyword, filters, and a sort, and returns matching products as model-facing structured output. `render-carousel` takes the curated ids in display order and mounts the carousel [view](/build/view).

The split [separates data processing from UI rendering](https://developers.openai.com/plugins/build/chatgpt-ui#separate-data-processing-from-ui-rendering): the model applies its intelligence to the results before the user sees any UI. Prompts drive an iterative loop: it searches several times, varying keywords and filters to capture intent that facets alone can't express, drops products the conversation already ruled out, then curates a handful of ids. Raw search results never render: they ground the curation.

It also degrades gracefully: a host without view support still calls `search-products`, and the model answers in text.

```mermaid
sequenceDiagram
actor User
box Host
participant Model
participant View
end
participant Server as MCP Server
User->>Model: I need a warm jacket
Model->>Server: search-products("jacket")
Server-->>Model: 42 hits: ids + facts
Model->>Server: search-products("jacket", maxPrice: 300)
Server-->>Model: 12 hits: ids + facts
Model->>Server: render-carousel([a, c, f])
Server-->>View: full products in _meta
Server-->>Model: trimmed grounding
View-->>User: carousel of 3 products
```

`render-carousel` answers on three channels: full products (every variant, all media) ride `_meta` for the view; a trimmed projection goes to `structuredContent` so the model can answer follow-ups; `content` is a one-line status.

### Open the Product Detail

Tapping a card switches to `fullscreen` and renders the product detail over the carousel: one view, two screens. The detail reads the same `_meta` products, so no extra fetch. The model gets the full product spec through [view state](/build/state).

### Pick Variants

Every variant is a complete, buyable product; a `Product` groups siblings and declares option axes. The variant list is sparse: a combination that does not exist is simply absent, and availability is derived from the list, never encoded as rules. Each option value resolves to in stock, sold out (selectable, only the buy CTA locks), or nonexistent (disabled).

The model knows which variant the user is looking at. It can act as a salesperson: answer about any variant, compare, and advise on variations.

### Restyle from Tokens

A vanilla-extract design system under `src/design/` styles every component from one set of tokens: primitives, a semantic color contract, light and dark themes, sprinkles, and a typography recipe. A theme that leaves a contract slot unset fails the build. The template ships brand-neutral placeholders.

Every component comes with [Ladle](https://ladle.dev) stories covering its edge cases (long titles, missing images, sold-out variants); `npm run ladle` previews them against the tokens, light and dark one click apart.

## Fill It with Your Agent

Every decision the skeleton defers is marked with a `@todo` comment in `src/`: filter facets, image aspect ratios, section order, brand tokens. The skill walks a coding agent through that worklist in six gated phases, recording each decision in `SPEC.md`.

```mermaid
flowchart LR
G[1. Gather] --> E[2. Explore data] --> UX["3. Decide UX ✍️"] --> S[4. Server] --> C[6. Components] --> F["Final gate ✍️"]
G --> D["5. Design ✍️"] --> C
```

<Prompt description="`Fill the ecom template with my catalog`" actions={["copy", "cursor"]}>
Fill this ecom template following the chatgpt-app-builder skill's ecommerce reference. Start with phase 1 and ask me for everything you need.
</Prompt>

The agent asks for your inputs up front: the data source (a Shopify or Medusa API, your own database, docs, credentials), brand assets (Figma file, live site, or screenshots, plus fonts), and the live site for layout inspiration. Then it explores, proposes, and builds. You are pulled in at three gates:

- **Wireframes.** Before any UI code, the agent plays back the carousel card and the product detail as ASCII wireframes populated with real catalog values.
- **Retheme.** The extracted brand tokens, previewed on the Ladle stories.
- **Final gate.** The worklist is empty, the build passes, and both tools are verified against live data.

## Verify the Result

`npm run dev` serves DevTools on the root: call both tools with real arguments and drive the view through display modes, themes, mobile widths, and locales.

Add the [tunnel](/test/tunnel) flag to also get a [Playground](/test/playground) on `/try` of the printed public URL, and run the app in a real host with an actual model.

```bash
npm run dev -- --tunnel
```

For a finished build, the [ecommerce example](https://github.com/alpic-ai/skybridge/tree/main/examples/ecom-carousel) connects this template to a Medusa catalog.

<Columns cols={3}>
<Card title="Register Tools" icon="wrench" href="/build/tools">
Define what humans and agents can do
</Card>
<Card title="Manage State" icon="database" href="/build/state">
Decide what the model sees
</Card>
<Card title="DevTools" icon="square-dashed-mouse-pointer" href="/test/devtools">
Call tools and render views locally, without a host
</Card>
</Columns>
Binary file modified docs/images/showcase-ecommerce.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion examples/ecom-carousel/.env.example

This file was deleted.

3 changes: 3 additions & 0 deletions examples/ecom-carousel/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Copy to .env and fill in. Sourced by the server at startup.
MEDUSA_BASE_URL=
MEDUSA_PUBLISHABLE_KEY=
6 changes: 5 additions & 1 deletion examples/ecom-carousel/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
node_modules/
dist/
build/
.env*
!.env.example
!.env.template
.DS_store
*.tsbuildinfo
.skybridge/
.vercel/
26 changes: 26 additions & 0 deletions examples/ecom-carousel/.ladle/components.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { GlobalProvider } from "@ladle/react";
import "../src/index.css";
import { viewFrame } from "../src/components/view-frame.css";
import { darkTheme, lightTheme } from "../src/design/tokens";
import { cx } from "../src/lib/cx";

/**
* Ladle's built-in theme toggle (sun/moon in the toolbar) drives
* globalState.theme. We reuse it to flip the widget's own palette, so the
* preview theme and the widget theme always match. The Provider applies the
* real ViewFrame surface (viewFrame + theme class), and index.css loads the
* brand @font-face rules, so stories preview against the actual frame.
*/
export const Provider: GlobalProvider = ({ children, globalState }) => {
const themeClass = globalState.theme === "dark" ? darkTheme : lightTheme;

return (
<div
className={cx(viewFrame, themeClass)}
data-theme={globalState.theme}
style={{ minHeight: "100vh", padding: 32 }}
>
{children}
</div>
);
};
11 changes: 11 additions & 0 deletions examples/ecom-carousel/.ladle/config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/** @type {import('@ladle/react').UserConfig} */
export default {
stories: "src/**/*.stories.{ts,tsx}",
viteConfig: ".ladle/vite.config.ts",
addons: {
theme: {
enabled: true,
defaultState: "light",
},
},
};
11 changes: 11 additions & 0 deletions examples/ecom-carousel/.ladle/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";
import { defineConfig } from "vite";

/**
* Ladle-only vite config. Intentionally omits the Skybridge plugin (which
* transforms MCP view boilerplate and crashes Ladle's bundler) and the
* @vitejs/plugin-react call (Ladle provides its own).
*/
export default defineConfig({
plugins: [vanillaExtractPlugin()],
});
Loading
Loading