jx-3p.com — site architecture
How the public website is built, where things live, and how to change it.
The site is the GitHub Pages source under docs/, brand-aligned with the app
(see design-system.md). This doc is the website side; for the in-app
lending library + relay infra, see CLAUDE.md’s “User Lending Library” section.
Deploy model (read this first)
- Hosted on GitHub Pages, built from
docs/on themainbranch ONLY. Push tomain→ GH Pages rebuilds + deploys in ~30–90 s. Thewindows-portbranch does not deploy — website edits made there go nowhere until merged to main. Always land site changes onmain. - No local Jekyll in this repo (no
Gemfile). You can’tjekyll servelocally; preview by pushing to main and checking live. A failed Jekyll build does NOT take the site down — GH Pages keeps serving the last good deploy and emails the owner — so pushing an unverified change is low-risk, but verify after:gh api repos/danielspils/JP-Patches-App/pages/builds/latest -q .statuscurl -sI https://jx-3p.com/<path>
- Custom domain
jx-3p.comviadocs/CNAME.jekyll-sitemapplugin emits/sitemap.xml. - Convention: site changes commit + push immediately, on their own, separate from app-code commits (CLAUDE.md convention #10).
No-theme Jekyll
_config.yml sets no theme: — the custom _layouts/default.html owns
all rendering and assets/css/style.css owns all styling. defaults gives
_posts the post layout + /notes/:title/ permalinks.
Layouts (_layouts/)
default.html— the skeleton every page uses:- Dark header with the JX-3P A/B/C/D bank-indicator motif (green/blue
“swoops” + red C/D swoops + stripes), JP logo + tagline, and a
panel-button download CTA (→
/releases/latest). - Nav tabs (
<nav class="cd-nav-tabs">, right-anchored in the header): Notes (green,/notes/) · Patches (red,/patches/) · Sequences (red,/sequences/). Active state viapage.url(Notes also active on post pages viapage.layout == 'post'). Hidden below 820px (would collide with the swoops). - `<h1 id="session-handoff--2026-06-10">Session handoff — 2026-06-10</h1>
- Dark header with the JX-3P A/B/C/D bank-indicator motif (green/blue
“swoops” + red C/D swoops + stripes), JP logo + tagline, and a
panel-button download CTA (→
Supersedes
session-handoff-2026-05-26.md. Read after CLAUDE.md on a fresh session. This was the User Lending Library day — the single biggest feature day since the sequencer editor, and the first time the project owns hosted infrastructure beyond GitHub Pages.
What shipped today (all on main, all pushed — NOT yet released)
The User Lending Library, end to end, in every direction:
| Surface | What |
|---|---|
| Site | /patches/ + /sequences/ catalog pages (“THE USER LENDING LIBRARY”), driven by docs/_data/patches.yml + sequences.yml; payload .json files in docs/library/; machine manifest at /library/index.json (Liquid-generated); red borrow buttons; in-page lend form (posts to the relay); hearts per entry; header nav tabs un-hidden (shipped display:none May 29, destinations exist now) |
| App — borrow | “explore the user lending library” buttons (green on Tones / blue on Sequences) open the explore modal: 3 latest entries from the live manifest, borrow routes through the same import path as drag-drop (names restore via _slotMeta/_sequenceMeta), offline manifest cache in library.community, heart counts in bylines (display-only) |
| App — lend | Consent checkboxes (reset EVERY modal open — deliberate, Daniel: “good reminder”) gate blue lend buttons over the user’s own items → “Lending Library submission” confirm (editable TONES/SEQUENCE YOU ARE LENDING + YOUR NAME / HOMETOWN / NOTES, name+hometown persist in library.lending) → relay POST → submitted (persisted as item.lending with full metadata; shown in the (i) info modals). Relay-down fallback: clipboard + pre-filled GitHub issue form |
| Relay | relay/ — Cloudflare Worker at lend.jx-3p.com (deployed). POST /lend files labeled GitHub issues via Daniel’s PAT (no user GitHub account needed); POST /heart + GET /hearts back the heart counts (KV namespace HEARTS, one-per-IP salted-hash dedupe). CORS origin-locked to https://jx-3p.com |
| Curation | Submissions land as community-tones / community-sequences issues. A GitHub Action (.github/workflows/lending-notify.yml) @mention-pings Daniel on each — REQUIRED because the relay authors issues with Daniel’s own PAT and GitHub never notifies you of your own activity. Verified end-to-end (issues #1/#3/#4 were smoke tests, closed; #2 is Daniel’s first real submission, still open/uncurated) |
| Also | .json drag-drop import (was WAV-only; embedded names now restore); sequence-delete dirty-index bug fixed (remap helpers + guardSeqNav on create); Tones (i) info modal; rename-edit hides hover icons; assorted copy/color work (borrow/lend terminology, bank-colored buttons) |
Tests: 423 → 438 (remap, latestLendingEntries, lending URL/payload, catalog consistency). Smoke test gained §8a User Lending Library (~25 rows) — run it before the next release.
Infrastructure Daniel now owns (NEW — first hosted components)
- Cloudflare Worker
jp-patches-lendatlend.jx-3p.com(custom domain; workers.dev deliberately not registered). Sourcerelay/worker.js, configrelay/wrangler.toml, deploy stepsrelay/README.md. Wrangler is OAuth’d on this Mac (danielspils@gmail.com) — Claude can deploy withcd relay && npx wrangler deploy. - Secret
GITHUB_TOKENon the Worker: Daniel’s fine-grained PAT (jp-patches-lend-relay, repo-scoped to JP-Patches-App, Issues read+write only). Expires ~June 2027 — renewal = new PAT on github.com +npx wrangler secret put GITHUB_TOKEN(no redeploy needed). Token never touches repo/app/chat. - KV namespace
HEARTS(id in wrangler.toml):c:<id>counts +h:<id>:<salted-ip-hash>dedupe markers. Increments non-atomic by accepted design.
The curation workflow (Daniel’s recurring job) — SUPERSEDED June 11
Submissions auto-publish now (Daniel: “Why do I need to review the submissions?”).
.github/workflows/lending-publish.yml+scripts/publish-lend.mjsvalidate, dedupe (content hash of banks/pages), gate on the catalog test, commit, and close with a receipt — live in ~3 min. Doubtful submissions getneeds-reviewand stay open. Withdraw shipped too (in-app submitted button → token-hash-matched removal vialending-withdraw.yml). Post-moderation: email ping (lending-notify.yml) +scripts/remove-lend.sh <id>. Pure logic inscripts/lend-publish-lib.mjs(unit-tested intest/lend-publish-lib.test.js). See CLAUDE.md → User Lending Library.
The original manual flow, for the record:
- Email arrives (Actions bot @mention) → open the
[Lend …]issue. - Review: own work? reasonable content? Payload JSON is in the issue body.
- Approve = save the JSON to
docs/library/patches/<id>.json(orsequences/), add an entry todocs/_data/<kind>.yml(id/name/author/hometown/description/added/file/size_bytes/tags/audio_preview), push.test/community-catalog.test.jsfails CI on typos (missing file, wrong size_bytes, dup id). - Close the issue. Decline = close with a comment.
Open threads / next moves (pickup-ready)
- v0.8.0 release — the natural cut for all of today. Needs: release notes (user-facing voice; this is a marquee feature), full smoke run incl. §8a,
./scripts/release.sh 0.8.0. Issue #2 should probably be curated first so the catalog has a real second entry at launch. Withdraw endpoint— ✅ built June 11:/withdrawon the Worker + author-gatedlending-withdraw.yml; in-app submitted (now Roland green) → “Remove from Lending Library” confirm, works for patches AND sequences.- Community markers on borrow — borrowed items aren’t tagged, so they appear in the user’s own lend list (honor-system consent is the only guard) and re-borrowing creates duplicates (no “borrow anyway?” conflict prompt — spec’d in future-features).
- Hearts in-app giving? Display-only in-app today (IP dedupe misbehaves behind shared NAT). Revisit if requested.
- Parked UI experiment: align the green explore button with the PG-200’s red bottom border (Daniel asked, then redirected to QA — never built).
- library.json grew fields ad hoc again (
community,lending,item.lending) — the schema-versioning scaffolding exists (library-schema.js); consider formalizing before Phase 3 adds MIDI prefs. - Phase 3 (MIDI) — still blocked on the Series Circuits kit, unchanged.
Decisions log (today’s verbal/design calls)
- Terminology: borrow / lend (“the user lending library”) — everywhere.
Curated queue (not instant publish): lend = “submitted”, Daniel reviews.Reversed June 11 → auto-publish. Third button state reads submitted (Roland green) and doubles as the withdraw affordance.- Consent checkboxes re-check on every modal open — deliberately unpersisted.
- Explore modal caps at 3 latest (was 10 in spec) — browse-at-scale lives on the site.
- Blue = lend, red = borrow — and all green/blue UI now uses the two canonical hexes (
#1f6e5b/#33508f) verified app-wide. - GitHub-form fallback link removed from the site pages — the form is the path; the issue templates remain (app’s relay-down fallback still opens them).
- Heart counts: simple beats robust — KV non-atomic accepted, one-per-IP is “right-sized, failure mode is 5 hearts not 100.”
Gotchas discovered today (also see CLAUDE.md pitfalls)
sanitizeWavFilenameAPPENDS.wav— strip it when building non-WAV filenames (borrowed-label bug).- Relay-authored issues = Daniel’s own activity = no GitHub notifications without the Actions @mention ping.
- GitHub fine-grained PAT custom expiry rejects exactly-one-year dates — pick 364 days.
wrangler logincan’t run from a non-TTY for the secret prompt —secret putis a Daniel-in-iTerm2 step by design (token never transits Claude).- Index-keyed
dirtySequences/originalSequenceSnapshotscorrupt on ANYlibrary.sequencessplice — always remap (seeremapIndexAfter*in library-math.js).
` slot inside <main><article class="site-content">, then
the footer.
post.html— blog-post layout; declareslayout: defaultso it nests in the skeleton. Renders post title + date + body + a “← All notes” link.
Pages
| URL | Source | What |
|---|---|---|
/ |
index.md |
Landing page (hero, screenshot gallery + lightbox, YouTube embed, download CTAs) |
/patches/ |
patches.md |
Lending-library catalog (Tones) — data-driven |
/sequences/ |
sequences.md |
Lending-library catalog (Sequences) — data-driven |
/notes/ |
notes.md |
Blog listing, newest-first |
/notes/<title>/ |
_posts/*.md |
Individual blog posts |
Other .md files in docs/ (release-notes-*, smoke-test, design-system,
this doc, etc.) have no layout front matter → they render bare and aren’t
linked site pages; they’re dev docs that happen to live in the Pages source.
The Notes blog
- Posts are Jekyll
_posts/YYYY-MM-DD-title.mdwithtitle:+date:front matter (thepostlayout +/notes/<title>/permalink come from_config.ymldefaults, so each note needs nothing else). notes.mdloopssite.postsnewest-first (date + excerpt → per-post page), with a.notes-emptystate when there are none, and a.notes-subheadtagline under the heading.- To add a post: create
_posts/YYYY-MM-DD-my-title.md, settitle:+date:, write Markdown below the---. It auto-appears on the Notes tab._posts/2026-06-24-example-note.mdis apublished: falsetemplate. - Styling:
.notes-subhead/.notes-list/.note-entry*(listing) and.note-post*(post), all on the white body background.
Lending-library catalog (website side)
_data/patches.yml+_data/sequences.yml— one entry per shared package; drive BOTH the/patches/+/sequences/pages AND the/library/index.jsonmanifest the app reads.library/— payload.jsonfiles + the Liquid-generatedindex.json.- Entries are appended by the auto-publish CI (see CLAUDE.md “User Lending
Library” +
scripts/publish-lend.mjs); don’t hand-edit_data//library/except a manual takedown (scripts/remove-lend.sh <id>).
CSS + JS
assets/css/style.css— single stylesheet.:rootdesign tokens: the brand triad (--roland-red/--roland-green/--roland-blue+-hotvariants), the cream family, and a white content area (--body-bg#fff, dark--body-text*tokens) — header/footer stay dark. Breakpoints: 820px (hide nav tabs), 540px (mobile — drop swoops/tabs/download).assets/js/site.js— gallery lightbox (prev/next + arrow keys), tracking-param stripper (fbclid/utm_*), header LED-flash on download.
Assets duplicated app↔site (keep in sync)
- The logo lives in two places:
renderer/assets/jp-logo.png(app panel) anddocs/assets/img/jp-logo.png(site). Update both + regeneratedocs/assets/img/favicon-*.pngviasips(CLAUDE.md pitfall #19). - Download CTAs all point at
/releases/latest— never hardcode a version (pitfall #20).
How to make common changes
- New blog post →
_posts/YYYY-MM-DD-title.md(title + date). Push main. - New nav tab → add an
<a class="cd-nav-tab …">indefault.html, a page with its ownpermalink:, and (for a new color) a.cd-nav-tab--xrule. Re-check the 820px breakpoint — more tabs need more right-anchored width before they overlap the swoops. - New landing-page section → edit
index.md+ add styles tostyle.css. - Catalog entry → don’t hand-add; the auto-publish CI owns it.