Overview
This tutorial shows how to combine HTMX with a
Sheerpower Fast-CGI handler to create a live dictionary lookup API.
HTMX handles the browser-side interactivity, while Sheerpower serves
high-speed HTML fragments from an always-running Fast-CGI endpoint.
What You Will Build
- An HTML page that submits a word using HTMX
- A Sheerpower Fast-CGI handler (
cgi://WORD)
- A dictionary lookup using an in-memory cluster
- A fuzzy "best match" engine using
compare()
Problem:
CGI programs restart for every request, forcing the app to reload data,
rebuild state, and reinitialize memory. This is painful for real-time
lookup features.
Solution:
Sheerpower Fast-CGI keeps your program alive. Data loads once at
startup, and each request arrives through a shared memory queue. HTMX
sends lightweight AJAX-style requests and updates only the target section
of the page.
Efficiency:
Cluster arrays hold data in memory for very fast lookups. Fast-CGI
avoids process churn. HTMX replaces heavyweight JavaScript frameworks with
simple HTML attributes.
Takeaway:
With Sheerpower Fast-CGI and HTMX, you get a dynamic web app architecture
without JSON APIs or front-end frameworks.
Request Flow: HTMX, SPINS, and Fast-CGI
This diagram shows how an HTMX request moves from the browser to
SPINS_WEBSERVER and into the Sheerpower Fast-CGI handler, then back as an
HTML fragment.
Browser
HTML + HTMX
- Form with
hx-get
- Targets
#definition-result
- Updates only part of the page
HTTP request
==>
/scripts/spiis.dll/word/get_def.html
SPINS_WEBSERVER
Queue + Routing
- Receives HTTP request
- Queues work for
cgi://WORD
- Uses shared memory for speed
Fast-CGI queue
==>
handler "WORD"
Fast-CGI Handler
Sheerpower
- Polls queue with
line input #cgi_ch:
- Looks up word in
words cluster
- Outputs HTML via
[[%spscript]]
HTML fragment
<==
HTML fragment inserted by HTMX into the browser's definition-result <div>
Frontend: HTMX Dictionary Lookup Page
This HTML file goes in your wwwroot. HTMX sends a GET request
to your Fast-CGI handler and loads the server response into
#definition-result.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sheerpower Dictionary (HTMX)</title>
<script src="https://unpkg.com/
[email protected]"></script>
</head>
<body>
<div class="page-shell">
<header class="header">
<div class="badge">SPINS + HTMX</div>
<h1>Sheerpower Dictionary</h1>
<p class="subtitle">
Type a word and let a Sheerpower Fast-CGI handler look up the definition.
</p>
</header>
<section class="card">
<h2 class="card-title">Lookup a Word</h2>
<form
class="lookup-form"
hx-get="/scripts/spiis.dll/word/get_def.html"
hx-target="#definition-result"
hx-trigger="submit"
>
<input
type="text"
name="word"
value="amorphous"
required
>
<button type="submit">Lookup</button>
<span class="htmx-indicator">Looking up...</span>
</form>
<div id="definition-result" class="definition-panel">
<em>Definition will appear here.</em>
</div>
</section>
<section class="card help-card">
<h2 class="card-title small">Tips</h2>
<ul class="tips-list">
<li>Words are loaded from <code>words.tsv</code> by a Sheerpower cluster.</li>
<li>The Fast-CGI handler stays alive, so lookups are very fast.</li>
<li>You can refresh the page or lookup again without restarting anything.</li>
</ul>
</section>
</div>
<!-- Styles moved toward the end for clarity -->
<style>
/* Full CSS listing is available in the toggle below. */
</style>
</body>
</html>
(Show/Hide Full HTMX Frontend CSS)
HTMX Frontend CSS Listing
<style>
:root {
--bg: #0f172a;
--bg-soft: #111827;
--accent: #38bdf8;
--accent-soft: rgba(56, 189, 248, 0.18);
--accent-strong: #0ea5e9;
--text-main: #e5e7eb;
--text-muted: #9ca3af;
--error: #f97373;
--card-bg: #020617;
--border-subtle: #1f2937;
}
html, body {
margin: 0;
padding: 0;
height: 100%;
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
sans-serif;
background: radial-gradient(circle at top, #1e293b 0, #020617 55%, #000 100%);
color: var(--text-main);
display: flex;
justify-content: center;
align-items: flex-start;
}
.page-shell {
width: 100%;
max-width: 720px;
margin: 3rem 1rem;
}
.header {
text-align: center;
margin-bottom: 2rem;
}
.badge {
display: inline-block;
padding: 0.15rem 0.6rem;
border-radius: 999px;
background: var(--accent-soft);
color: var(--accent);
font-size: 0.75rem;
letter-spacing: 0.06em;
text-transform: uppercase;
margin-bottom: 0.5rem;
border: 1px solid rgba(148, 163, 184, 0.25);
}
h1 {
margin: 0;
font-size: 2rem;
letter-spacing: 0.03em;
}
.subtitle {
margin: 0.4rem 0 0;
color: var(--text-muted);
font-size: 0.95rem;
}
.card {
background: linear-gradient(135deg, var(--card-bg), var(--bg-soft));
border-radius: 14px;
border: 1px solid var(--border-subtle);
padding: 1.2rem 1.4rem 1.4rem;
margin-bottom: 1.2rem;
box-shadow:
0 16px 40px rgba(0, 0, 0, 0.65),
0 0 0 1px rgba(15, 23, 42, 0.9);
}
.card-title {
margin: 0 0 0.75rem;
font-size: 1.1rem;
letter-spacing: 0.03em;
text-transform: uppercase;
color: var(--accent);
}
.card-title.small {
font-size: 0.95rem;
}
.lookup-form {
display: flex;
gap: 0.6rem;
align-items: center;
margin-bottom: 0.9rem;
flex-wrap: wrap;
}
.lookup-form input[type="text"] {
flex: 1 1 220px;
padding: 0.55rem 0.75rem;
border-radius: 999px;
border: 1px solid rgba(148, 163, 184, 0.6);
background: rgba(15, 23, 42, 0.9);
color: var(--text-main);
font-size: 0.95rem;
outline: none;
transition: border-color 0.15s ease, box-shadow 0.15s ease,
background 0.15s ease;
}
.lookup-form input[type="text"]::placeholder {
color: #6b7280;
}
.lookup-form input[type="text"]:focus {
border-color: var(--accent-strong);
box-shadow: 0 0 0 1px rgba(56, 189, 248, 0.5);
background: #020617;
}
.lookup-form button {
padding: 0.55rem 1rem;
border-radius: 999px;
border: 1px solid var(--accent-strong);
background: radial-gradient(circle at top left,
var(--accent-strong),
#0369a1);
color: white;
cursor: pointer;
font-size: 0.9rem;
font-weight: 500;
letter-spacing: 0.03em;
text-transform: uppercase;
white-space: nowrap;
transition: transform 0.08s ease, box-shadow 0.08s ease, filter 0.1s ease;
}
.lookup-form button:hover {
filter: brightness(1.05);
box-shadow: 0 10px 20px rgba(8, 47, 73, 0.9);
transform: translateY(-1px);
}
.lookup-form button:active {
transform: translateY(0);
box-shadow: none;
filter: brightness(0.96);
}
.htmx-indicator {
font-size: 0.84rem;
color: var(--text-muted);
opacity: 0;
transition: opacity 0.12s ease;
}
.htmx-request .htmx-indicator {
opacity: 1;
}
.definition-panel {
min-height: 3rem;
border-radius: 12px;
padding: 0.85rem 0.9rem;
background: rgba(15, 23, 42, 0.9);
border: 1px dashed rgba(148, 163, 184, 0.5);
transition: background 0.15s ease, border-color 0.15s ease;
font-size: 0.96rem;
}
.definition-card h2 {
margin: 0 0 0.4rem;
font-size: 1rem;
color: var(--accent);
}
.definition-card .word {
font-weight: 600;
font-size: 1.02rem;
letter-spacing: 0.05em;
}
.definition-card .def {
margin-top: 0.3rem;
color: var(--text-main);
}
.definition-card .meta {
margin-top: 0.55rem;
font-size: 0.8rem;
color: var(--text-muted);
}
.error {
color: var(--error);
font-weight: 600;
}
.help-card {
background: radial-gradient(circle at top left,
rgba(56, 189, 248, 0.32),
var(--bg-soft));
}
.tips-list {
margin: 0.2rem 0 0;
padding-left: 1.1rem;
color: var(--text-main);
font-size: 0.9rem;
}
.tips-list li + li {
margin-top: 0.2rem;
}
code {
font-family: "JetBrains Mono", SFMono-Regular, Menlo, Monaco,
Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 0.85rem;
background: rgba(15, 23, 42, 0.9);
padding: 0.05rem 0.25rem;
border-radius: 4px;
border: 1px solid rgba(30, 64, 175, 0.7);
}
@media (max-width: 520px) {
.lookup-form {
align-items: stretch;
}
.lookup-form button {
width: 100%;
justify-content: center;
text-align: center;
}
}
</style>
Backend: Sheerpower Fast-CGI Handler
Save this file as htmx_backend.spsrc inside your
sphandlers directory.
// Program: htmx_backend.spsrc
program htmx_backend
// The handler name "word" must match "word" in the
// HTML file -- hx-get="/scripts/spiis.dll/word/get_def.html"
handler_name$ = "cgi://WORD"
request_counter = 0
// The TSV file has the format of WORD<tab>DEFINITION
// where <TAB> is the ASCII tab character.
cluster words: word$, def$
cluster input name "@words.tsv", headers 0: words
print "Words loaded: "; size(words)
print sprintf$("%t WORD handler ready")
open file cgi_ch: name handler_name$
do
line input #cgi_ch: method$
if method$ = "" then repeat do
request_counter++
word$ = getsymbol$("word")
if word$ = "" then
[[%spscript]]
<div class="definition-card">
<p class="error">Enter a <code>word</code> to lookup.</p>
<p>Try typing something like <code>ZORI</code> or <code>ZOUK</code>.</p>
</div>
[[/%spscript]]
repeat do
end if
row = findrow(words->word$, word$)
if row = 0 then
not_found
repeat do
end if
def$ = words->def$
[[%spscript]]
<div class="definition-card">
<h2>Dictionary Result</h2>
<div class="word">[[word$]]</div>
<div class="def">[[htmlencode$(def$)]]</div>
<div class="meta">Request #[[str$(request_counter)]]</div>
</div>
[[/%spscript]]
loop
stop
routine not_found
best_word$ = ""
best_score = 0
collect cluster words
score = compare(word$, words->word$)
if score > best_score then
best_score = score
best_word$ = words->word$
end if
end collect
[[%spscript]]
<div class="definition-card">
<p class="error">
No definition found for <span class="word">[[word$]]</span>.
</p>
<p>
Try <span class="word">[[lcase$(best_word$)]]</span>
</p>
</div>
[[/%spscript]]
end routine
end
Understanding [[%spscript]] and [[...]]
Fast-CGI handlers often need to return HTML fragments. Sheerpower provides a
simple templating convention to make this easy and readable.
[[%spscript]] begins an HTML template block.
[[/%spscript]] ends that block.
[[expression]] inserts Sheerpower code inline.
Problem:
Writing HTML using many print #cgi_ch: lines is cumbersome and
error-prone.
Solution:
[[%spscript]] switches into template mode. Literal HTML becomes
output. [[...]] expressions jump back into Sheerpower without
breaking the HTML structure.
Efficiency:
There is no runtime template engine. Everything compiles into direct
output writes to cgi_ch.
Takeaway:
Use this syntax whenever you need to generate HTML fragments cleanly and
efficiently inside Fast-CGI handlers.
Example
[[%spscript]]
Hello, [[username$]] - the time is [[time$]].
[[/%spscript]]
Why Sheerpower and HTMX Work So Well Together
Sheerpower pairs exceptionally well with HTMX because both technologies
share the same core philosophy: simplicity, clarity, and raw HTML as the
primary interface. Instead of pushing developers into complex frontend
frameworks or heavyweight JSON API layers, both systems embrace the idea
that the browser already understands HTML and that the simplest road is
usually the most reliable.
Problem:
Modern web apps often become overloaded with layers: JavaScript frameworks,
client-side routing, virtual DOMs, build systems, JSON encoding, and
multiple serialization steps. This creates cognitive load and performance
penalties, especially for small or medium-sized applications.
Solution:
Sheerpower and HTMX share the same design philosophy: let HTML flow
directly from the server to the browser, and keep the browser-side logic as
small as possible. HTMX issues small AJAX-style requests from HTML
attributes, and Sheerpower generates HTML fragments efficiently using
Fast-CGI and the [[%spscript]] templating mechanism.
Efficiency:
HTMX removes the need for large JavaScript bundles. Sheerpower removes the
need for JSON APIs, ORMs, template engines, or heavyweight backend
frameworks. The server produces HTML directly, and HTMX swaps it into the
document. Zero build systems, zero framework lock-in, and extremely
predictable performance.
Takeaway:
Both HTMX and Sheerpower treat HTML as the core application language. This
makes your codebase smaller, clearer, faster, and easier to maintain.
The Shared Philosophy
- HTML-first: The browser speaks HTML. So should the backend.
- No heavyweight frameworks: HTMX avoids JS frameworks.
Sheerpower avoids backend frameworks.
- Directness: HTMX uses simple attributes. Sheerpower uses
direct output via
cgi_ch.
- Predictable performance: Fast-CGI stays alive. No reloading,
no churn, minimal overhead.
- Developer clarity: Code remains readable and close to the data
and HTML it manipulates.
The result is a development environment where the browser and the server
communicate in their natural language: HTML. No glue layers. No ceremony.
Just fast, direct, understandable code.
Summary
HTMX sends lightweight requests to a Sheerpower Fast-CGI handler, which
returns an HTML fragment for insertion into the page. Clusters keep
dictionary entries in memory for instant lookup. The spscript template
syntax keeps HTML readable while producing efficient compiled code.
(Show/Hide Sheerpower HTMX Fast-CGI Dictionary Takeaways)
Sheerpower HTMX Fast-CGI Dictionary Takeaways
- Fast-CGI keeps programs alive, so data loads only once.
- HTMX updates are targeted and efficient.
- Clusters provide fast case-insensitive lookups.
[[%spscript]] makes HTML output clean and readable.
compare() enables simple "best guess" suggestions.
- No JSON and no frameworks, just HTML/HTMX/CSS and Sheerpower.