| name | interactive-json-form |
| description | Build a self-contained, single-file interactive HTML page (explanations + multiple-choice votes + rank + free-form comments) whose responses round-trip as downloadable/importable JSON — no backend, no tracking — and publish it privately on eon.25u.com behind noindex + an unguessable path. |
| allowed-tools | Read, Write, Edit, Bash |
Interactive JSON Form — decision/survey pages with a client-side JSON round-trip
Self-Evolving Skill: This skill improves through use. If instructions are
wrong, parameters drifted, or a workaround was needed — fix this file
immediately, don't defer. Only update for real, reproducible issues.
A single self-contained .html file that presents extensive explanations,
lets a small known audience (e.g. a CEO + data scientists) pick options, rank
them, and comment, then hands their input back as a downloadable / copyable /
re-importable JSON — with no server, no submit endpoint, and no tracking.
Published privately on eon.25u.com behind a noindex header + an unguessable
path, so it is reachable by whoever has the link (incl. from China via the
Telus-direct door) but blocked from crawlers/indexing.
When to use this skill
- You need a few named humans to make structured choices + free-form comments
on a set of options, and you want their answers back as machine-readable JSON
you ingest directly.
- Mobile-friendly, re-openable (import a prior JSON to resume), zero tooling for
the responder, and no backend to stand up.
- The audience is small and known → a downloadable JSON they send you beats a
hosted collector; a public form would over-expose.
When NOT to use it
- Anonymous mass surveys / thousands of respondents (use a real survey backend).
- You need server-side aggregation in real time (add a collector, or use Lark Base
— see
../../lark-automation for the Base decision-harness alternative).
- Multi-page sitemap sites with no JS → use the sibling
page-template skill.
Why this over Lark Base / Forms / cards (decided by research 2026-07-13)
Lark Base grids truncate long text (row-height/width are NOT API-settable), Forms
are read-only after submit with no create API, and cards are one-shot with a 30-day
window + a callback webhook. A self-contained HTML page has none of those
limits: full text wraps natively, edits are unlimited, and the JSON round-trip is
pure client-side. The cost is that responses come back as files, not a live table
— acceptable for a small known audience.
The pattern (what makes it work)
- Data-driven options. An
OPTIONS array at the top of the inline <script>
is the single source of truth; the DOM is generated from it. To reskin for a
new decision, edit that array only.
collect() → JSON. Reads every control (respondent, per-option vote/rank/
comment, overall) into a versioned object {schema, respondent, role, firstPick, overallComment, choices:[{id,title,vote,rank,comment}]}.
- Three return paths, all client-side:
- Download:
Blob + URL.createObjectURL + a[download] → a .json file.
- Copy:
navigator.clipboard.writeText (with execCommand fallback).
- Import:
<input type=file> → FileReader → hydrate() repopulates the
form. Import→export is byte-identical (resumable editing) — assert this.
schema version string on every payload so the ingester can evolve.
beforeunload guard warns on unsaved edits.
- Self-contained: inline
<style> + <script>, no external requests, no
fonts/CDN, <meta name="robots" content="noindex, nofollow, noarchive, nosnippet">, <meta name="referrer" content="no-referrer">.
Start from templates/index.html in this skill — it is the working page
(built for the ODB × time-bar decision) with the mechanism intact; swap the
OPTIONS array + the two intro .note blocks and you have a new page.
Verify before you ship (headless, local)
Render + exercise the JS with Playwright against file:// (no login needed):
fill the form via page.evaluate (the radios are visually hidden labels — set
.checked + dispatch change rather than page.check), call collect(), then
reload a fresh page, hydrate() the JSON, collect() again, and assert the two
JSON strings are identical. Screenshot fullPage:false and eyeball it. See the
local-shot.mjs pattern.
Publish privately on eon.25u.com (no Caddy change, no restart)
eon.25u.com:443 on bigblack is a Caddy static+proxy front door whose
docroot is /var/www/eon-25u-com-openclaw-guide and which already emits
header X-Robots-Tag "noindex, nofollow, noarchive, nosnippet" globally. So a
static file dropped into a subdir is served instantly with noindex, over the
already-WAN-forwarded :443 chain — no Caddyfile edit, no caddy reload
(zero risk to the co-hosted :8450 inference other collaborators use).
scp -q your-page.html bigblack:/tmp/page.html
ssh bigblack 'set -e
P="<slug>-$(openssl rand -hex 16)" # unguessable path segment
D="/var/www/eon-25u-com-openclaw-guide/$P"
sudo mkdir -p "$D"; sudo cp /tmp/page.html "$D/index.html"
sudo chown -R caddy:caddy "$D"; rm -f /tmp/page.html
echo "URL: https://eon.25u.com/$P/"
curl -sSI --resolve eon.25u.com:443:192.168.0.111 "https://eon.25u.com/$P/" | grep -iE "^HTTP|x-robots-tag"'
ssh aws-dev 'curl -sS -o /dev/null -w "EXTERNAL %{http_code} TLS=%{ssl_verify_result}\n" https://eon.25u.com/<PATH>/'
Crawler-blocking layers (this skill's default): (a) global X-Robots-Tag noindex header (already on the door), (b) page <meta robots noindex>, (c) an
unguessable path with no inbound links. A 4th layer — User-Agent blocking —
needs a Caddy @bots matcher in the edge-route registry (edge-routes.toml +
generate-edge-config.py); it is a production config change, so treat it as
optional hardening, not default.
Registry SSoT for the door: ~/eon/claude-sys/.../edge-routes.toml
(eon-openclaw-guide-and-installer-https-443). Precedent for unguessable-path
publishing: the odb-live-trade-console capability door ("noindex inherited").
Ingesting responses
The JSON is exactly collect()'s output. Consume it directly; key on
schema for versioning and respondent/role to attribute. Merge multiple
respondents' files by choices[].id.
Hard rules
- One file, no external network requests, no tracking,
noindex in <meta>.
- Never a real submit/collector endpoint unless the user explicitly opts in
(it turns a private artifact into a hosted service + a data-handling duty).
- Never publish to the eon.25u.com door by hand-editing the live Caddyfile — drop
a static file into the docroot (config-free) or change the registry + regen.
- Always verify the import→export round-trip is byte-identical before shipping.
Post-Execution Reflection
After this skill completes, reflect before closing the task:
- Locate yourself. — Find this SKILL.md's canonical path before editing.
- What failed? — Fix the instruction. If the deploy recipe drifted (docroot,
header, sudo), fix it here AND cross-check
edge-routes.toml.
- What worked better than expected? — If a new control type (multi-select,
drag-rank) recurs, fold it into
templates/index.html.
- What drifted? — Keep the JSON schema, the deploy commands, and the
crawler-block layers aligned with reality.
- Log it. — Evolution-log entry with trigger, fix, evidence.
Do NOT defer. The next invocation inherits whatever you leave behind.