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/htmx.org@latest"></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
// The compare() function returns the likelihood (0-100%) that two strings are the same.
// It takes into account common typographical and keyboarding errors.
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 (except for itself, of course).
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.
(Show/Hide Sheerpower vs Modern Web Stack Deep Dive)
Why This Architecture Removes Modern Web Bloat
This section explains how the Sheerpower + Fast-CGI + HTMX
architecture solves two of the biggest headaches in modern web
development: performance bloat and stack complexity.
1. The Performance Problem: Hydration and Bloat
Problem: Slow Startup and "Hydration"
-
The Modern Stack: Frameworks like React, Vue, and Svelte
often send a minimal HTML shell, then large JavaScript bundles that
must be downloaded, parsed, executed, and "hydrated" before the
page really comes alive.
-
The Sheerpower Solution: The server sends fully formed HTML.
The browser just renders it. No large JS bundle, no hydration step,
no client-side rendering engine to boot up.
Problem: Heavy JavaScript Bundles
-
The Modern Stack: Users, especially on mobile, download
hundreds of kilobytes or megabytes of JavaScript just to click a
button.
-
The Sheerpower Solution: Interactivity is handled by a small
HTMX script file. The server sends tiny HTML fragments instead of
JSON, keeping network traffic and CPU usage low.
Problem: Slow Server Startup
-
The Modern Stack: Classic CGI restarts the whole program per
request, and many "serverless" setups suffer from cold start
latency.
-
The Sheerpower Solution: A Fast-CGI handler is persistent.
It loads once, keeps the dictionary in memory, and then simply
answers requests. Data is already in RAM, so responses are nearly
instantaneous.
2. The Complexity Problem: The Stack and Glue Code
Problem: Too Many Layers
-
The Modern Stack: A "simple" app often needs a frontend
framework + build tools, state management, a separate backend API,
and glue code to turn data into JSON, ship it, and turn it back
into HTML on the client.
-
The Sheerpower Solution: One Sheerpower program both accesses
the data and renders the HTML. No separate API, no JSON, no
serialization layer, no extra build tooling.
Problem: Constant Churn and "Build Tool Hell"
-
The Modern Stack: Tools, frameworks, and build systems
change rapidly. Developers spend large amounts of time just keeping
configs and dependencies working.
-
The Sheerpower Solution: The system is self-contained:
language, web server, and templating are all built in. Fewer moving
parts means simpler deployment and long-term maintainability.
In Short
The Sheerpower/HTMX model returns to a simpler architecture with
modern performance benefits: a persistent, in-memory application
server that sends HTML directly. It solves the performance problem by
avoiding JS bloat and hydration, and solves the complexity problem by
integrating the stack into a single, cohesive system.
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.