A minimalist, decentralised webring β rebuilt for stability, scale, and extensibility.
| Area | Change |
|---|---|
| Stability | Shared src/core.js β single loadNodes with ETag validation, stale-while-error (72 h grace) |
| Stability | Two-phase HEADβGET probe with exponential backoff in scripts/prune.js |
| Stability | Pruner runs daily instead of weekly; --dry-run mode for safe testing |
| Usability | Cancel-redirect button on ring.html; "Visit site β" link after cancelling |
| Usability | directory.html β searchable, filterable, sortable node directory |
| Usability | Tag filtering on wander button (index.html) and transit page |
| Automation | join.yml workflow β validates URL, verifies embed, commits, closes issue automatically |
| Features | <rhizome-spore> supports lang, size, theme, filter-tag attributes |
| Features | scripts/feed.js β aggregates member RSS/Atom feeds into feed.xml |
| Features | Federation via peers option in loadNodes (opt-in cross-ring discovery) |
Rhizome/
βββ src/
β βββ core.js # Shared browser module: uid, loadNodes, liveNodes
β βββ index.js # Landing page: wander, tag bar, stats
β βββ ring.js # Transit page: node selection, progress, cancel
βββ scripts/
β βββ lib/
β β βββ semaphore.js # Async bounded-concurrency primitive
β β βββ probe.js # Two-phase HTTP health check
β β βββ verify.js # Spore embed verifier
β βββ prune.js # Node health check (daily CI)
β βββ join.js # Auto-join processor (triggered by join.yml)
β βββ feed.js # RSS feed aggregator
βββ assets/css/
β βββ ring.css # Transit page styles
β βββ directory.css # Directory page styles
βββ spore.js # <rhizome-spore> web component (standalone IIFE)
βββ index.html # Landing page
βββ ring.html # Transit page
βββ directory.html # Node directory
βββ nodes.json # Node registry
βββ feed.xml # Generated RSS feed (auto-committed)
βββ .github/workflows/
βββ prune.yml # Daily health check
βββ join.yml # Auto-process join requests
βββ feed.yml # 6-hourly feed aggregation
- Fork this repository
- Enable GitHub Pages (Settings β Pages β Source: GitHub Actions or main branch
/) - Update the
hostURL inindex.htmlandring.htmlto your Pages URL - Update
SELFinscripts/feed.jsto your feed URL - Edit
nodes.jsonβ replace the seed node with your own site
Add to any page on your member site:
<rhizome-spore
host="https://YOUR-USERNAME.github.io/Rhizome"
lang="en"
size="default"
theme="auto">
</rhizome-spore>
<script src="https://YOUR-USERNAME.github.io/Rhizome/spore.js" defer></script>| Attribute | Values | Default | Description |
|---|---|---|---|
host |
URL | β | Required. Your Rhizome registry base URL |
lang |
en zh ja fr de es |
en |
Button label language |
size |
default compact wide |
default |
Widget size |
theme |
auto light dark |
auto |
Color scheme |
filter-tag |
any tag string | β | Only traverse nodes with this tag |
[
{
"name": "My Site",
"url": "https://mysite.com",
"bio": "A short description (max 200 chars)",
"tags": ["blog", "dev"],
"joined": "2026-01-15",
"feed": "https://mysite.com/feed.xml",
"status": "dormant"
}
]Fields tags, joined, feed, and status are all optional.
status: "dormant" is set automatically by the pruner; never set it manually.
All scripts require Node.js β₯ 20 (for native fetch). No npm install needed.
# Health check β mark unreachable nodes as dormant
npm run prune
# Health check + verify embed is still present
npm run prune:verify
# Report changes without writing nodes.json
npm run prune:dry
# Aggregate member feeds into feed.xml
npm run feed
# Preview feed without writing
npm run feed:dryTo cross-discover nodes from another Rhizome fork, pass peers to loadNodes:
import { loadNodes } from './src/core.js';
const nodes = await loadNodes({
peers: ['https://other-ring.github.io/Rhizome/nodes.json'],
});Peer nodes are deduplicated by URL and marked with _peer: true in the data.
Open a Join issue using the issue template. The bot will:
- Parse your submission fields
- Probe your URL for reachability (HEAD β GET, with retry)
- Verify
<rhizome-spore>is present on your page - Append your node to
nodes.jsonand commit - Close the issue with a welcome message
If validation fails, the bot comments with the reason. Fix and re-apply the join label.
Zero-clause BSD. Fork freely, credit appreciated.