Skip to content

Instantly share code, notes, and snippets.

@oeway
Last active December 1, 2025 12:58
Show Gist options
  • Select an option

  • Save oeway/6b99a802ed7778da79e9560f0c207c8e to your computer and use it in GitHub Desktop.

Select an option

Save oeway/6b99a802ed7778da79e9560f0c207c8e to your computer and use it in GitHub Desktop.
<!doctype html>
<html>
<head>
<title>Todo list</title>
<style>
body {
margin: 0;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: radial-gradient(circle at top left, #1e293b, #020617);
color: #e5e7eb;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.app {
width: 100%;
max-width: 480px;
background: rgba(15, 23, 42, 0.92);
border-radius: 18px;
box-shadow: 0 24px 80px rgba(0,0,0,0.65);
border: 1px solid rgba(148, 163, 184, 0.35);
overflow: hidden;
backdrop-filter: blur(18px);
}
.app-header {
padding: 18px 22px 14px;
border-bottom: 1px solid rgba(51, 65, 85, 0.9);
display: flex;
align-items: center;
justify-content: space-between;
background: linear-gradient(135deg, rgba(59,130,246,0.2), rgba(236,72,153,0.08));
}
.title {
font-size: 20px;
font-weight: 650;
letter-spacing: 0.02em;
display: flex;
align-items: center;
gap: 8px;
}
.title-pill {
font-size: 11px;
text-transform: uppercase;
padding: 2px 8px;
border-radius: 999px;
background: rgba(15,23,42,0.85);
border: 1px solid rgba(148,163,184,0.5);
color: #e5e7eb;
}
.glow-dot {
width: 9px;
height: 9px;
border-radius: 999px;
background: #22c55e;
box-shadow: 0 0 12px rgba(34,197,94,0.9);
}
.app-body {
padding: 18px 18px 16px;
}
.input-row {
display: flex;
gap: 10px;
margin-bottom: 14px;
}
#new-todo {
flex: 1;
padding: 9px 11px;
border-radius: 999px;
border: 1px solid rgba(51,65,85,0.9);
background: rgba(15,23,42,0.9);
color: #e5e7eb;
font-size: 13px;
outline: none;
}
#new-todo::placeholder {
color: #6b7280;
}
#add-btn {
padding: 8px 14px;
border-radius: 999px;
border: none;
background: linear-gradient(135deg,#3b82f6,#6366f1);
color: white;
font-size: 13px;
font-weight: 550;
cursor: pointer;
box-shadow: 0 10px 25px rgba(37,99,235,0.45);
transition: transform 0.08s ease-out, box-shadow 0.08s ease-out, filter 0.1s;
display: flex;
align-items: center;
gap: 6px;
}
#add-btn:hover {
transform: translateY(-1px);
filter: brightness(1.05);
box-shadow: 0 14px 32px rgba(37,99,235,0.6);
}
#add-btn:active {
transform: translateY(0px) scale(0.98);
box-shadow: 0 6px 18px rgba(15,23,42,0.9);
}
#todo-list {
list-style: none;
padding: 0;
margin: 6px 0 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.todo-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 10px;
border-radius: 12px;
background: radial-gradient(circle at top left, rgba(30,64,175,0.55), rgba(15,23,42,0.98));
border: 1px solid rgba(51,65,85,0.95);
box-shadow: 0 10px 28px rgba(15,23,42,0.95);
font-size: 13px;
}
.todo-item span.text {
flex: 1;
}
.todo-item button {
background: transparent;
border: none;
color: #9ca3af;
cursor: pointer;
padding: 2px 4px;
border-radius: 999px;
font-size: 11px;
}
.todo-item button:hover {
background: rgba(148,163,184,0.16);
color: #e5e7eb;
}
.checkbox {
width: 16px;
height: 16px;
border-radius: 999px;
border: 1px solid rgba(148,163,184,0.85);
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 11px;
color: #22c55e;
background: radial-gradient(circle at top left, rgba(15,23,42,1), rgba(15,23,42,0.4));
}
.checkbox.checked {
background: radial-gradient(circle at top left, rgba(34,197,94,0.85), rgba(22,163,74,0.9));
border-color: rgba(187,247,208,0.9);
color: #022c22;
box-shadow: 0 0 12px rgba(34,197,94,0.85);
}
.todo-item.completed span.text {
text-decoration: line-through;
color: #6b7280;
}
.meta-row {
margin-top: 10px;
font-size: 11px;
display: flex;
justify-content: space-between;
color: #9ca3af;
}
</style>
</head>
<body>
<div class="app">
<div class="app-header">
<div class="title">
<div class="glow-dot"></div>
Todo list
<span class="title-pill">Hypha</span>
</div>
<div style="font-size:11px;color:#cbd5f5;opacity:0.9;">RPC-ready</div>
</div>
<div class="app-body">
<div class="input-row">
<input id="new-todo" placeholder="Add a task and hit Enter" />
<button id="add-btn">
<span>+ Add</span>
</button>
</div>
<ul id="todo-list">
<li class="todo-item"><div class="checkbox"></div><span class="text">Learn Hypha RPC</span><button class="del"></button></li>
<li class="todo-item"><div class="checkbox"></div><span class="text">Build awesome apps</span><button class="del"></button></li>
</ul>
<div class="meta-row">
<span id="count"></span>
<span style="opacity:0.85;">Double-click a task to edit</span>
</div>
</div>
</div>
<script type="module">
import * as hyphaWebsocketClient from "https://cdn.jsdelivr.net/npm/[email protected]/dist/hypha-rpc-websocket.mjs";
const api = await hyphaWebsocketClient.setupLocalClient({})
console.log("Connected to Hypha Core", api);
const listEl = document.getElementById("todo-list");
const inputEl = document.getElementById("new-todo");
const countEl = document.getElementById("count");
const addBtn = document.getElementById("add-btn");
function updateCount() {
const total = listEl.querySelectorAll('.todo-item').length;
const done = listEl.querySelectorAll('.todo-item.completed').length;
countEl.textContent = `${total-done} open • ${done} done`;
}
function makeItem(text) {
const li = document.createElement('li');
li.className = 'todo-item';
li.innerHTML = `<div class="checkbox"></div><span class="text"></span><button class="del">✕</button>`;
li.querySelector('span.text').textContent = text;
return li;
}
function wireItem(li) {
const box = li.querySelector('.checkbox');
const textSpan = li.querySelector('span.text');
const delBtn = li.querySelector('.del');
box.addEventListener('click', () => {
box.classList.toggle('checked');
li.classList.toggle('completed');
updateCount();
});
delBtn.addEventListener('click', () => {
li.remove();
updateCount();
});
textSpan.addEventListener('dblclick', () => {
const current = textSpan.textContent;
const input = document.createElement('input');
input.value = current;
input.style.flex = '1';
input.style.background = 'transparent';
input.style.border = 'none';
input.style.color = 'inherit';
input.style.outline = 'none';
li.replaceChild(input, textSpan);
input.focus();
input.select();
const finish = () => {
const newSpan = document.createElement('span');
newSpan.className = 'text';
newSpan.textContent = input.value || current;
li.replaceChild(newSpan, input);
textSpan.textContent = newSpan.textContent;
wireItem(li);
};
input.addEventListener('blur', finish, { once: true });
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
input.blur();
}
});
});
}
// Wire existing items
listEl.querySelectorAll('.todo-item').forEach(wireItem);
updateCount();
function addFromInput() {
const value = inputEl.value.trim();
if (!value) return;
const li = makeItem(value);
listEl.appendChild(li);
wireItem(li);
inputEl.value = '';
updateCount();
}
addBtn.addEventListener('click', addFromInput);
inputEl.addEventListener('keydown', (e) => {
if (e.key === 'Enter') addFromInput();
});
// Export services that the parent can call
await api.export({
getTodos: async () => {
const todos = [];
listEl.querySelectorAll('.todo-item span.text').forEach(span => {
todos.push(span.textContent);
});
return todos;
},
addTodo: async (todo) => {
const li = makeItem(todo);
listEl.appendChild(li);
wireItem(li);
updateCount();
return true;
}
});
console.log("Hypha Service Exported");
</script>
</body>
</html>
src = '''
<!doctype html>
<html>
<head>
<title>Todo list</title>
</head>
<body>
<h1>Todo list</h1>
<ul id="todo-list">
<li>Learn Hypha RPC</li>
<li>Build awesome apps</li>
</ul>
<script type="module">
import * as hyphaWebsocketClient from "https://cdn.jsdelivr.net/npm/[email protected]/dist/hypha-rpc-websocket.mjs";
const api = await hyphaWebsocketClient.setupLocalClient({})
console.log("Connected to Hypha Core", api);
// Export services that the parent can call
await api.export({
getTodos: async () => {
const todos = [];
document.querySelectorAll("#todo-list li").forEach(li => {
todos.push(li.textContent);
});
return todos;
},
addTodo: async (todo) => {
const li = document.createElement("li");
li.textContent = todo;
document.getElementById("todo-list").appendChild(li);
return true;
}
});
console.log("Hypha Service Exported");
</script>
</body>
</html>
'''
todo = await api.createWindow(src=src, name='My Todo List')
await todo.addTodo('buy TV')
print(await todo.getTodos())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment