Replace Rails app with the static-site rebuild (Project Daedalus live site)#121
Open
AgentKush wants to merge 6 commits into
Open
Replace Rails app with the static-site rebuild (Project Daedalus live site)#121AgentKush wants to merge 6 commits into
AgentKush wants to merge 6 commits into
Conversation
… site) Replaces the entire project_daedalus Rails 7.1 + Firestore application with the static-site rebuild that's been incubated at AgentKush/daedalus-static-poc, rebranded as the live Project Daedalus site (no longer a 'POC'). ## What's in this commit - 172 Rails files deleted: app/, config/, db/, lib/, spec/, Gemfile, Procfile, Dockerfile, the lot. - 52 static-site files added: Eleventy 3 templates (src/), client assets (public/), GitHub Actions sync + discovery + deploy workflows (.github/workflows/), README + LICENSE + CONTRIBUTING + SECURITY + CODE_OF_CONDUCT, package.json + lockfile. ## Functional surface (parity with Rails app + extras) - Mods listing: search, author/source/week/sort filters, 25-page pagination, custom-chevron dropdowns, theme toggle, mobile-aware layout. - 491 curated mods + 133 Nexus mods bundled. Compatibility 100% (game-week derived from git commit history when blank). - Per-mod detail page (pre-rendered for all 491): file-type badges, download buttons, live README rendering via marked, ratings widget, analytics panel, GitHub stats card, comments via Giscus. - /home/ landing page, /tools.html, /info.html, /requests.html (Mod Requests via Giscus Ideas), /guides/* (PDFs). - Hourly sync-mods workflow aggregates every modder's modinfo.json + toolinfo.json into bundled JSON. - Weekly discover-modders workflow auto-PRs new candidate Icarus mod repos. ## Data flow Browser → Firestore REST API (projectdaedalus-fb09f, public-read) for live data, with bundled JSON as offline fallback. No application server needed. ## Post-merge actions Donovan needs to take 1. **Enable GitHub Pages** on this repo: Settings → Pages → Source = GitHub Actions. 2. **Install Giscus** on this repo (https://github.com/apps/giscus → Only select repositories → tick project_daedalus). Enable Discussions in repo Settings → Features. 3. **Generate giscus IDs** at https://giscus.app for the General + Ideas categories, replace the two `TODO_AFTER_GISCUS_INSTALL` placeholders in `src/mods/mod-detail.njk` and `src/requests.njk`. 4. **Point projectdaedalus.app DNS** at `donovanmods.github.io` (or however the custom domain is currently routed) once the Pages deploy is green. ## Closed obsolete PRs All 8 of my previously open Rails PRs (DonovanMods#111, DonovanMods#112, DonovanMods#113, DonovanMods#114, DonovanMods#115, DonovanMods#116, DonovanMods#117, DonovanMods#119) have been closed with a pointer to this consolidated PR. The functionality each one was adding is either already in this static rebuild or noted in the porting list inside the PR thread.
Firestore is authoritative when populated, but for fields the modder left blank (e.g. compatibility on the Coracks mods), the listing rendered '—' even though the hourly sync had derived the value into the bundled JSON. The loader was picking REST and never merging. Fix: backfill missing/empty fields onto live data by (slug(author)+name) match against the bundled JSON. Firestore wins where it has values; bundled fills the gaps. New status-badge state '● LIVE + backfilled'.
Previous backfill commit changed the loader's status.source from 'rest'/'websdk' to 'rest+backfill'/'websdk+backfill' when it merges bundled JSON onto live data. mod-analytics-live.js and mod-readme-live.js both early-returned on anything that wasn't exactly 'rest' or 'websdk', so the analytics panel ('Mod Age', 'Freshness') and the live README fetch silently bailed out — the placeholders never got replaced.
Fix: gate on status.live === true instead of matching exact source strings. Covers rest, rest+backfill, websdk, websdk+backfill; still skips 'bundled'/'rest+bundled'/'websdk+bundled' which have no Firestore timestamps to read.
The info page subscribed to info_content + attached the status badge, but production's Firestore info_content collection is empty (the dynamic info-page work that would populate it was PR DonovanMods#104, closed). So every load showed an alarming orange '◐ LIVE + bundled (empty collection)' even though the page rendered correctly from the bundled JSON. The cards are bundled-by-design until/unless Donovan populates info_content live; the badge added noise without information. Drop it. subscribe() stays so the page automatically goes live the moment the Firestore collection has docs. Mods listing + tools pages keep their badges — those genuinely run on live data.
Five new feature surfaces enabled by the static-site architecture:
1. /authors/<slug>/ — paginated Eleventy template, one page per distinct author, listing their mods with file-type breakdowns. 'By <author>' on the mod-detail page and the listing's author column both link here.
2. /search/ — Pagefind full-text search indexing every mod-detail page. Build script runs `pagefind --site _site --output-subdir pagefind` after 11ty so the index ships with the deploy.
3. /feed.xml + /sitemap.xml + /robots.txt — Atom feed of recent mods, complete sitemap covering 491 mod pages + ~47 author pages + top-level routes, robots.txt allowing all crawl and pointing at the sitemap.
4. /api/v1/{mods,tools,nexus_mods,info_content}.json — stable static JSON URLs for external consumers. /api/ index page documents the endpoints and freshness model.
5. .github/workflows/notify-discord.yml — diffs public/data/mods.json after every sync commit and posts a Discord embed for each new or version-bumped mod. Skipped silently if DISCORD_WEBHOOK_URL secret isn't set.
Search page: rewrote the Pagefind theming using its CSS custom properties (--pagefind-ui-primary, --pagefind-ui-text, etc.) plus targeted overrides for elements without variables. Result-titles are now icarus-500 gold and clearly clickable; descriptions use proper contrast in both light and dark modes; placeholder text is slate-400 (readable on both backgrounds); cards have rounded borders that hover to gold instead of white-line separators. Highlight marks tinted gold. Listing: pre-warm both bundled JSONs at app init via Promise.all, so state.mods and state.nexusMods are populated before the first paint. Previously a hard refresh could land the listing with curated mods rendered but nexus_mods still empty (until the user touched the source filter to force a re-render). Subscribe callbacks still overlay live Firestore data once it arrives.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR replaces the entire Rails 7.1 + Firestore application with a static-site rebuild that becomes the live
projectdaedalus.app. It's been incubated atAgentKush/daedalus-static-pocand rebranded for production. What ships here is exactly what the POC site shows right now, byte-for-byte.Single GitHub-API-signed commit (
da962410). 221 files changed: 47 added, 169 removed, 2 modified. NoCo-Authored-Bytrailers.Updates since this PR was first opened
The PR has stayed open while I kept iterating on the POC. 39 commits land on top of the original
da962410snapshot. Highlights, grouped:Mobile / responsive
f747e86Stop horizontal overflow on mobile —w-screen→w-full, bodyoverflow-x-hidden, prose tables contained1d10603Hamburger nav (was a row of 4 tabs that wrapped awkwardly on phones);overflow-x-hiddenon<html>not just<body>d02da38Hamburger inline with the logo, plusgrid-cols-1baseline on every grid (implicit auto-tracks were sizing to max-content and overflowing)1e4e17dMod-detail header stacks vertically on mobile so download buttons + file-type badges don't clipMod pack export
ba2f20cFix selection: clicking a row now selects, not navigates96bec31b6b5ce7Actually bundle EXMODZ/PAK files into a zip; drop the manifest-only flow (no one wanted that)45a59d8007f2ec83b08f728620f1CORS hardening: rewritegithub.com/.../raw|blob/...→raw.githubusercontent.com/.../..., route release-asset URLs viaapi.github.com(CORS-friendly redirect chain), magic-number validate before adding to zip, write SKIPPED.txt with the failure reasonTags
8ef45661e68b439fe9b00e1ce11aTag regex tightening: dropx\d+from speed, dropdropshipfrom creatures, addvehicles/performance/cosmetic, only match keywords in name + first 120 chars of description ("primary purpose" zone), handle plurals and suffix variations (spoilage,attachments, etc.). Nexus mods now appear on/tag/<name>/pages with a NEXUS badge.4aae227Fix tag filter: was keying off Firestore doc IDs, the tag index was keyed offslug(author)+slug(name)Tools (new)
d0511aePer-tool detail pages at/tools/<author>/<name>/with live-fetched GitHub Releases history (lazy-loaded), release card per tag with title/date/changelog/asset list, prev/next nav5f8e9a3Release-history filter: shared-repo tools (e.g. Mod Editor + Mod Manager both ship fromJimk72/Icarus_Software) only show releases tagged with their own keyword instead of echoing each other'sMod detail / analytics
7670170Analytics: GitHub repo card pinned to the mod's actual files (not the repo root). Mod Status reflects current state.399a7d9Mod Status now readshealth.json(broken downloads) +validation.json(validator findings) and includes 6 in-browser checks (empty description, placeholder text, no image, short description, compat age, no tags)98997b3Validator runner extended to lightweight checks for standalone PAK mods (filename_P.pak, file size, Unreal Pak magic number0x5A6F12E1); EXMODZ still go through the full upstream validator9aa814eMod detail surfaces the mod'simage_url+readme_urlfrommods.json(was only checkingmod_details.json, missing 329 mods)52f09a8Move "You might also like" + share row to the bottom of mod-detail058ce45Remove per-mod "Server install snippets" — server admins install many mods at once, snippets are impracticalSearch
c773329Harden padding so search-result text isn't flush against the card edgec2bdf1dInclude Nexus mods in/search/via client-side filterOnboarding / data sources
e86b55esync-modinfos.mjsis case-insensitive onmodinfo.jsonfilename (some modders useModInfo.json)fc72f68Onboardjgentil/IcarusMods+ addmodders_extras.jsonmechanism so locally-curated repos can be added without waiting for upstream changes6f825d6Backfill: append bundled-only mods so locally-onboarded entries don't get overridden by the REST snapshotAuthor profiles
104453a(yesterday) Fix 404 when clicking the author of a Nexus mod —src/_data/authors.jsnow reads bothmods.jsonandnexus_mods.json, profile pages render Nexus mods as cards that link out tonexusmods.com. 52 previously-404 author URLs now return 200, leaderboard auto-includes Nexus modders too.Security / CodeQL
495eb7eFix CodeQL XSS alerts (js/xss+js/xss-through-dom) onshare.js— rebuild QR overlay with the DOM API instead of innerHTML interpolationQC + housekeeping
5024486Sitemap was missing 124 routable pages (/search/,/stats/,/leaderboard/,/status/,/api/...+ every tag/week page); now complete9eacaac5372e88Health-check + Nexus catalog refreshedc447ecdHazer's PNGs flagged for baked transparency-checker (10/24 PNGs); upstream issue opened (Hazer-Bazer/Icarus-Mods#2)Issues opened upstream as part of this work
These are friendly onboarding/cleanup issues opened on modders' own repos to nudge them toward catalog-compatible structure:
jgentil/IcarusMods#1— brokenreadmeURL+ author/case mismatchRevenantKitsune/Revs_Icarus_Mods#1— onboardingCrystalFerrai/IcarusMods#1— onboardingHazer-Bazer/Icarus-Mods#2— 10 PNGs with baked-in transparency-checker patternJimk72/Icarus_Software#38—toolinfo.jsonsays 2.4.0 but Mod Manager is on 2.4.5; missing release tag; Mod Editor description rewriteFeraniShades/Icarus_Mods#2— onboarding (FeraniShades openedJimk72/Icarus_Software#37asking to be added; their repo has nomodinfo.jsonyet — issue body has a pre-filled template)Post-merge action checklist (you, Donovan)
Without these, the site won't fully work. They're listed in roughly the order they need to happen.
Enable GitHub Pages. Settings → Pages → Source = GitHub Actions. The first push to
mainafter merge triggers.github/workflows/deploy.yml, which builds with Eleventy and publishes_site/.Set
NEXUS_API_KEYrepo secret (optional). Settings → Secrets and variables → Actions → New repository secret. Without it, Nexus mods are still served from the bundled JSON (133 mods, last refreshed locally). With it,.github/workflows/sync-nexus.ymlruns daily and refreshes from the live Nexus API.Install Giscus app on this repo (https://github.com/apps/giscus → Only select repositories → tick
project_daedalus).Enable Discussions (Settings → Features → Discussions). Create the General and Ideas categories if they don't exist.
Generate Giscus IDs at https://giscus.app — pick this repo, the General category for mod-detail comments, and the Ideas category for the Mod Requests board. Copy the four IDs (repo-id once, category-id once each for General and Ideas).
Replace placeholders. In
src/mods/mod-detail.njkandsrc/requests.njk, replace eachTODO_AFTER_GISCUS_INSTALLwith the real IDs. There are exactly 4 of them, all marked with<!-- TODO -->HTML comments above each<script>.Custom-domain cutover for
projectdaedalus.app. Do this after Pages is green ondonovanmods.github.io/project_daedalus/. Steps in order:a. Verify the domain on the
DonovanModsorg (recommended — prevents takeover if the repo is ever deleted, billing changes, or Pages is disabled while DNS still points at GitHub):projectdaedalus.app. (This is at the org level, not repo level — repo Pages settings don't have this.)_github-pages-challenge-DonovanMods(the value rotates per attempt — copy it from the GitHub UI)._github-pages-challenge-DonovanModsdig _github-pages-challenge-DonovanMods.projectdaedalus.app +short TXTto confirm propagation.b. Add the
CNAMEfile to the repo root containing exactlyprojectdaedalus.app(no protocol, no trailing slash, single line). This survives across deploys; without it the custom domain gets dropped on the next Pages publish. Easiest: Settings → Pages → Custom domain → enterprojectdaedalus.app→ Save — that auto-creates the file onmain.c. Flip Cloudflare DNS to GitHub Pages. Replace the existing A/CNAME records for
projectdaedalus.appwith:A projectdaedalus.app 185.199.108.153A projectdaedalus.app 185.199.109.153A projectdaedalus.app 185.199.110.153A projectdaedalus.app 185.199.111.153CNAME www projectdaedalus.app(if you want www → apex)d. Wait for the certificate. GitHub auto-issues a Let's Encrypt cert for the apex domain via the Pages settings. Settings → Pages → "DNS check successful" → tick Enforce HTTPS. Usually 5–15 min, sometimes longer if DNS is still propagating.
e. Sanity-check.
curl -sI https://projectdaedalus.app/ | head -1should returnHTTP/2 200, andcurl -sI http://projectdaedalus.app/should redirect to https://.Confirm Firestore public-read rules. The site queries
mods,tools,nexus_mods,info_contentdirectly via Firestore REST. Existing rules already allow this; no change needed unless you've tightened them.What's live the moment Pages deploys
Pages and routes
/src/index.njk/home/src/home.njk/tools.htmlsrc/tools.njk/info.htmlsrc/info.njkinfo_contentcollection/requests.htmlsrc/requests.njk/guides/IMM_Setup_Guide.pdfpublic/assets/guides//guides/GPortal_Mod_Install_Guide.pdfpublic/assets/guides//mods/<author-slug>/<mod-slug>/src/mods/mod-detail.njk(paginated)Mods listing UI
localeCompare), refreshes when the author set changesappearance-none+ a custom inline-SVG chevron so the roundedrounded-lgcorner stays clean instead of being clipped by the browser-native arrow« Prev | 1 | 2 | … | last | Next », "Showing X to Y of N mods" counter, deep-linkable via#page=NlocalStorage, defaults to OS preference● LIVE Firestore (REST),◐ LIVE + bundled (empty collection), or○ Bundled snapshotMod detail page (per mod, pre-rendered for all 491)
exmodz= gold,pak= green, others = slate)DOWNLOAD <TYPE>button per file typemd:and upreadmeURLfrom Firestore on page load, fetches markdown, renders viamarked@13. GFM tables, code blocks, shields.io badges, links all style correctly against the dark theme.localStorage, per browserAll Clear/ errors / warningslocalStorage-only ("visible only to you"); with Firestore configured, ratings aggregate live across all visitors viaonSnapshot.mod-{modId}term, on the General category. Preconnect + defer in the shared<head>for faster first-click latency.Data flow
Three-mode loader (
public/assets/firebase-loader.js):onSnapshot— sub-second push updates (whenFIREBASE_CONFIGis set)public/data/— offline fallbackPlus an empty-collection fallback: if a REST poll returns 0 documents on a collection that has a bundled fallback (e.g.
nexus_modsbefore the live Firestore collection is populated), the loader uses the bundled JSON. Polling continues, so the moment the live collection fills, the listing automatically swaps over.Bundled data shipped with this PR
public/data/mods.jsonpublic/data/nexus_mods.jsonpublic/data/tools.jsonpublic/data/modders.jsonmeta/repos)public/data/discovered-candidates.jsonCompatibility coverage: 491/491. The hourly sync derives the game week from each mod's latest GitHub commit when the modder didn't fill in
compatibilityin theirmodinfo.json. The URL parser handles/raw/,/blob/,raw.githubusercontent.com, andreleases/download/URLs, decodes percent-encoded paths, and passes&sha=<branch>for non-default branches.Workflows
.github/workflows/deploy.ymlmain.github/workflows/sync-mods.ymlmeta/repos, aggregate every modder'smodinfo.json/toolinfo.json(acrossmain/master/EXMODZ/Icarusbranches), commitmods.json+tools.jsonif changed.github/workflows/discover-modders.ymlpublic/data/discovered-candidates.json, open a verified-commit PR titled "Auto: new candidate Icarus mod repos discovered" if any new candidates turned up.github/workflows/sync-nexus.ymlpublic/data/nexus_mods.jsonfrom the Nexus API. Skipped ifNEXUS_API_KEYsecret isn't set.The discovery workflow uses
peter-evans/create-pull-request@v8withsign-commits: true, so the bot's PR commit is GitHub-API-signed and shows the green Verified badge. Auto-PRs only fire when the candidate set actually changes — the JSON file is deterministic across runs.What's NOT in this PR (intentional gaps)
TODO_AFTER_GISCUS_INSTALLsince the IDs are repo-specific and have to be regenerated after you install Giscus on this repo. Both occurrences are flagged with HTML<!-- TODO -->comments.NEXUS_API_KEYsecret — must be set if you want live Nexus refreshes. Until set, the daily workflow no-ops gracefully and the site uses the bundled snapshot.CNAMEfile for projectdaedalus.app — adding the custom domain via Settings → Pages auto-creates it.notify-discord.ymlworkflow that diffsmods.jsonbetween commits.t()helpers across the new templates is a follow-up.Closed obsolete PRs
All 8 of my previously open PRs against this repo are closed with a comment pointing here:
notify-discord.yml)Tech stack
package.json)Verifying locally before merging
git fetch origin pull/121/head:feat/agentkush git checkout feat/agentkush npm install npm run serve # http://localhost:8080npm run buildwrites_site/— that's exactly whatdeploy.ymlpublishes to Pages.What changed from the previous Rails version