Appearance
No-Build Todo App
A collaborative todo app using plain HTML and JavaScript — no bundler required.
Project Structure
Your app directory can contain any files or assets — everything is hosted by Poe. The only requirements are:
index.html— required for all appssynced-store-backend-config.js— required if your app uses Synced-Store (and you must callPoe.setupStore()in your client JS)
my-todo-app/
├── index.html # Entry point (required)
├── frontend.js # App logic
└── synced-store-backend-config.js # Backend mutators and actions (required for Synced-Store)Poe Employee Note
Currently the platform injects an import map into the app's index.html at serve time, which is what makes import { Poe } from "@poe/embed-api/v1.js" work without a bundler. In the future we'll probably want creators to include a script tag instead (e.g. <script src="https://poe.com/v1/embed-api.js"></script>) so the mechanism is more explicit and doesn't require server-side HTML rewriting.
index.html
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Todo App</title>
</head>
<body>
<input id="input" placeholder="New todo" />
<button id="add">Add</button>
<ul id="list"></ul>
<script type="module" src="frontend.js"></script>
</body>
</html>frontend.js
javascript
import { Poe } from "@poe/embed-api/v1.js";
async function addTodo(ctx, input) {
await ctx
.table("todos")
.set({ itemKey: input.id, value: { text: input.text } });
}
Poe.setupStore({ mutators: { addTodo }, schemaVersion: 1 });
// Subscribe to data changes — re-renders whenever data changes locally or from other users
Poe.store.subscribe(
(tx) => tx.table("todos").entries().toArray(),
(entries) => {
const list = document.getElementById("list");
list.innerHTML = entries.map(([, v]) => `<li>${v.text}</li>`).join("");
},
);
// Handle adding todos
let nextId = 0;
document.getElementById("add").addEventListener("click", () => {
const input = document.getElementById("input");
const text = input.value.trim();
if (text) {
Poe.store.mutate["addTodo"]({ id: String(nextId++), text });
input.value = "";
}
});synced-store-backend-config.js
The backend config exports the same mutators so the server can run them canonically.
javascript
async function addTodo(ctx, input) {
await ctx
.table("todos")
.set({ itemKey: input.id, value: { text: input.text } });
}
export default {
mutators: { addTodo },
actions: {},
};TIP
Mutators must behave identically on client and server. Keep the implementations in sync — or extract a shared file and import from both.
Upload to Poe
Publish the directory using the Slop-Poe CLI:
bash
slop-poe apps publish --handle my-todo-app --dir my-todo-app