Frontend-Only UI Extension¶
A word counter badge that tracks total words generated in the current session.
spindle.json¶
{
"version": "1.0.0",
"name": "Word Counter",
"identifier": "word_counter",
"author": "Dev",
"github": "https://github.com/dev/word-counter",
"homepage": "https://github.com/dev/word-counter",
"permissions": [],
"entry_frontend": "dist/frontend.js"
}
src/frontend.ts¶
import type { SpindleFrontendContext } from 'lumiverse-spindle-types'
export function setup(ctx: SpindleFrontendContext) {
const removeStyle = ctx.dom.addStyle(`
.wc-badge {
position: fixed;
bottom: 16px;
right: 16px;
padding: 6px 12px;
background: var(--lumiverse-fill-subtle);
border: 1px solid var(--lumiverse-border);
border-radius: var(--lumiverse-radius);
font-size: 11px;
color: var(--lumiverse-text-muted);
z-index: 100;
pointer-events: none;
}
`)
ctx.dom.inject('body', '<div class="wc-badge">Words: 0</div>')
let totalWords = 0
const unsub = ctx.events.on('GENERATION_ENDED', (payload: any) => {
if (payload.content) {
totalWords += payload.content.split(/\s+/).filter(Boolean).length
const badge = ctx.dom.query('.wc-badge')
if (badge) badge.textContent = `Words: ${totalWords.toLocaleString()}`
}
})
// Return cleanup function
return () => {
unsub()
removeStyle()
ctx.dom.cleanup()
}
}
How It Works¶
- Injects a small fixed badge in the bottom-right corner using Lumiverse CSS variables for theming
- Subscribes to
GENERATION_ENDEDevents - Counts words in each generation and updates the badge
- Returns a cleanup function that removes the event listener, styles, and DOM elements