Initial scaffold: ArchyHCL, a community hardware compatibility list for Archipelago
Build and validate / build (push) Successful in 1m38s
Build and validate / build (push) Successful in 1m38s
Modeled on three researched precedents (see README): OpenWrt's Table of Hardware for the browsable sortable/filterable table UX, postmarketOS's working/community/testing tiers for the status field (collapsed to working/partial/broken here), and RaspiBlitz's scattered GitHub-issues approach as a negative example to avoid — hence structured YAML report files validated against a JSON Schema instead of free-text issue threads. - data/reports/*.yml + data/schema.json: one file per report, schema requires `issues` whenever status is partial/broken - scripts/build.py: validates every report and builds site/data.json, fails loudly on bad data (same idea as archy's own validate-app-manifest.sh) - site/: plain HTML/CSS/JS, no framework or build step, fetches data.json client-side — search, filter by status/form-factor, sortable columns, click a row for issues/notes detail - .github/ISSUE_TEMPLATE/hardware-report.yml: structured submission path for contributors who don't want to touch git directly - .gitea/workflows/ci.yml: runs the build/validate step on push and PRs Not yet deployed anywhere — see README's Deployment section. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
name: Build and validate
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install pyyaml jsonschema
|
||||
|
||||
- name: Build and validate every report
|
||||
run: python3 scripts/build.py
|
||||
@@ -0,0 +1,116 @@
|
||||
name: Hardware report
|
||||
description: Report a device you've installed Archipelago on, working or not.
|
||||
title: "hw: <device model>"
|
||||
labels: ["hardware-report"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for reporting! A maintainer turns this into a data file under
|
||||
`data/reports/` and it shows up on the site. If you're comfortable
|
||||
with git, opening a PR directly is faster — see CONTRIBUTING.md.
|
||||
- type: input
|
||||
id: device_model
|
||||
attributes:
|
||||
label: Device model
|
||||
placeholder: "Lenovo ThinkPad T430"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: form_factor
|
||||
attributes:
|
||||
label: Form factor
|
||||
options:
|
||||
- laptop
|
||||
- desktop
|
||||
- mini-pc
|
||||
- sbc
|
||||
- server
|
||||
- other
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: cpu
|
||||
attributes:
|
||||
label: CPU
|
||||
placeholder: "Intel Core i5-3320M"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: ram_gb
|
||||
attributes:
|
||||
label: RAM (GB)
|
||||
placeholder: "8"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: storage
|
||||
attributes:
|
||||
label: Storage (type and size)
|
||||
placeholder: "256GB SSD"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: wifi_chip
|
||||
attributes:
|
||||
label: WiFi chip
|
||||
description: Exact chipset if you know it. Put "none" if wired-only.
|
||||
placeholder: "Intel Centrino Advanced-N 6205"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: ethernet
|
||||
attributes:
|
||||
label: Ethernet chip (optional)
|
||||
placeholder: "Intel 82579LM"
|
||||
- type: input
|
||||
id: archy_version
|
||||
attributes:
|
||||
label: Archipelago version tested
|
||||
placeholder: "1.8.4-alpha"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: install_method
|
||||
attributes:
|
||||
label: Install method
|
||||
options:
|
||||
- usb-iso
|
||||
- netboot
|
||||
- existing-os-script
|
||||
- other
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: status
|
||||
attributes:
|
||||
label: Status
|
||||
description: "working = no known issues. partial = runs with specific known issues (describe below). broken = does not install or run."
|
||||
options:
|
||||
- working
|
||||
- partial
|
||||
- broken
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: tested_date
|
||||
attributes:
|
||||
label: Date tested
|
||||
placeholder: "2026-08-21"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: issues
|
||||
attributes:
|
||||
label: Issues (required if status is partial or broken)
|
||||
description: What broke, and any workaround you found.
|
||||
- type: textarea
|
||||
id: notes
|
||||
attributes:
|
||||
label: Other notes
|
||||
description: BIOS/UEFI settings needed, quirks, links to a fuller writeup.
|
||||
- type: input
|
||||
id: submitted_by
|
||||
attributes:
|
||||
label: Attribution (optional)
|
||||
description: Your npub, Gitea username, or leave blank to stay anonymous.
|
||||
@@ -0,0 +1,3 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.DS_Store
|
||||
@@ -0,0 +1,50 @@
|
||||
# Contributing to ArchyHCL
|
||||
|
||||
Two ways to add a report, pick whichever's easier for you.
|
||||
|
||||
## Option A — open an issue (no git needed)
|
||||
|
||||
[Open a hardware report issue](https://source.archipelago-foundation.org/lfg2025/ArchyHCL/issues/new?template=hardware-report.yml)
|
||||
and fill in the form. A maintainer will turn it into a data file and it'll
|
||||
show up on the site.
|
||||
|
||||
## Option B — open a PR directly
|
||||
|
||||
1. Copy `data/reports/example-thinkpad-t430.yml` to a new file named
|
||||
`<device-slug>-<short-id>.yml` (e.g. `thinkpad-t430-a1b2.yml` — the
|
||||
suffix just needs to make the filename unique if the same model's been
|
||||
reported before).
|
||||
2. Fill in your report. Every field is described in
|
||||
[`data/schema.json`](data/schema.json); `issues` is required if your
|
||||
`status` is `partial` or `broken`.
|
||||
3. Validate locally before opening the PR:
|
||||
```bash
|
||||
pip install pyyaml jsonschema # if you don't have them
|
||||
python3 scripts/build.py
|
||||
```
|
||||
This fails loudly (and tells you exactly which field) if anything's
|
||||
wrong — same idea as [archy](https://source.archipelago-foundation.org/lfg2025/archy)'s
|
||||
own `scripts/validate-app-manifest.sh`.
|
||||
4. Open the PR. Once merged, `scripts/build.py` regenerates `site/data.json`
|
||||
and the site picks it up.
|
||||
|
||||
## Updating an existing report
|
||||
|
||||
Devices change over time (firmware updates fix WiFi issues, etc.) — if
|
||||
you're re-testing a device that's already listed, open a PR editing the
|
||||
existing file rather than adding a duplicate. Keep the old `tested_date`
|
||||
context in mind: bump it to your test date so readers know how fresh the
|
||||
report is.
|
||||
|
||||
## What makes a good report
|
||||
|
||||
- Be exact about the WiFi chip if you can (`iwconfig`/`lspci` on Linux,
|
||||
Device Manager on Windows if you dual-booted to check). "Realtek" alone
|
||||
isn't as useful as "Realtek RTL8821CE" — chip-specific driver issues are
|
||||
the single most common thing this list exists to surface.
|
||||
- If `status` is `partial` or `broken`, describe *what* broke and *how you
|
||||
noticed* (crash on boot? WiFi drops under load? specific app won't
|
||||
start?) — "doesn't work" isn't actionable for the next person.
|
||||
- If you found a workaround, put it in `notes` even if the underlying issue
|
||||
isn't fixed. A working-with-a-workaround report is more useful than no
|
||||
report at all.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 the Archipelago Project contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,84 @@
|
||||
# ArchyHCL
|
||||
|
||||
A community hardware compatibility list for [Archipelago](https://source.archipelago-foundation.org/lfg2025/archy) —
|
||||
what physical devices people have actually installed it on, what worked,
|
||||
what didn't, and why. Model, CPU, RAM, storage, and WiFi chip for every
|
||||
report, since WiFi chipsets are consistently the thing most likely to bite
|
||||
someone doing an install on repurposed hardware.
|
||||
|
||||
**[Browse the list →](https://hcl.archipelago-foundation.org)** (once deployed —
|
||||
see [Deployment](#deployment) below)
|
||||
|
||||
## Why this exists
|
||||
|
||||
Archipelago runs on whatever hardware people have lying around — old
|
||||
laptops, mini PCs, SBCs — and that's a much wider, messier compatibility
|
||||
surface than a project that only targets its own reference hardware. Before
|
||||
building this, I looked at how a few existing communities solve the same
|
||||
problem:
|
||||
|
||||
- **[OpenWrt's Table of Hardware](https://toh.openwrt.org/)** — a
|
||||
searchable table of routers, generated nightly from structured wiki
|
||||
pages into a `toh.json` file the site reads. Good model for the
|
||||
browsing UX (sortable/filterable table of CPU/RAM/flash/WiFi chip), but
|
||||
the underlying wiki pages are free-text and only loosely structured,
|
||||
which the maintainers themselves have called out as a maintenance
|
||||
problem over the years.
|
||||
- **[postmarketOS's device list](https://wiki.postmarketos.org/wiki/Devices)** —
|
||||
tiers devices into `main` / `community` / `testing` support levels, each
|
||||
with a per-device page describing exactly what works and what doesn't.
|
||||
Good model for the *status* field: a single working/broken flag loses
|
||||
the "boots fine but WiFi is flaky" middle ground that's actually the
|
||||
most common and most useful case to document.
|
||||
- **[RaspiBlitz](https://github.com/raspiblitz/raspiblitz)** (closest
|
||||
project to Archipelago in spirit — a Bitcoin/Lightning node OS for
|
||||
repurposed hardware) — hardware feedback mostly lives scattered across
|
||||
GitHub issues with no consistent fields, which makes it hard to search
|
||||
or compare reports. Useful as a negative example: this is what we're
|
||||
trying to avoid by using structured data files instead of free-form
|
||||
issue threads.
|
||||
|
||||
**The model this repo uses**: one small YAML file per report
|
||||
(`data/reports/*.yml`), validated against a JSON Schema, built into a
|
||||
single `data.json` a plain static site reads — OpenWrt's browsable-table UX,
|
||||
postmarketOS's tiered status field, and RaspiBlitz's actual subject matter
|
||||
(node-OS-on-repurposed-hardware), on top of git-native, PR-reviewable data
|
||||
instead of a wiki or a scattered issue tracker.
|
||||
|
||||
## Repo layout
|
||||
|
||||
```
|
||||
data/
|
||||
schema.json — the report schema (source of truth for what fields exist)
|
||||
reports/*.yml — one file per hardware report
|
||||
scripts/
|
||||
build.py — validates every report and builds site/data.json
|
||||
site/
|
||||
index.html/.css/.js — the static site (no framework, no build step — just fetches data.json)
|
||||
.gitea/issue_template/hardware-report.yml — structured issue form for reporting without git
|
||||
```
|
||||
|
||||
## Contributing a report
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) — either open an issue with the
|
||||
structured form, or add a YAML file directly and open a PR.
|
||||
|
||||
## Building the site locally
|
||||
|
||||
```bash
|
||||
pip install pyyaml jsonschema
|
||||
python3 scripts/build.py # writes site/data.json
|
||||
python3 -m http.server 8000 --directory site
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
Not yet deployed. This is a static site (`site/`) with no backend — any
|
||||
static host works (the same nginx-in-front-of-a-container pattern archy's
|
||||
other apps already use, GitHub/Gitea Pages, or a plain S3-style bucket).
|
||||
`scripts/build.py` should run in CI on every push to `main` so `data.json`
|
||||
never drifts from the source reports; wiring that up is the next step.
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](LICENSE).
|
||||
@@ -0,0 +1,22 @@
|
||||
device_model: "Raspberry Pi 5 (8GB)"
|
||||
form_factor: sbc
|
||||
cpu: "Broadcom BCM2712 (4x Cortex-A76)"
|
||||
ram_gb: 8
|
||||
storage:
|
||||
type: nvme
|
||||
size_gb: 512
|
||||
wifi_chip: "Broadcom BCM43455 (onboard)"
|
||||
ethernet: "Broadcom onboard Gigabit"
|
||||
archy_version: "1.8.4-alpha"
|
||||
install_method: netboot
|
||||
status: partial
|
||||
tested_date: "2026-08-10"
|
||||
submitted_by: "example-contributor"
|
||||
issues: |
|
||||
Example report — replace with a real one. Onboard WiFi drops the
|
||||
connection under sustained heavy P2P traffic (Bitcoin IBD); a workaround
|
||||
is to run over Ethernet instead. Everything else (Lightning, apps,
|
||||
dashboard) works fine.
|
||||
notes: |
|
||||
NVMe via the official PCIe HAT. Boots noticeably faster than SD-card
|
||||
installs.
|
||||
@@ -0,0 +1,18 @@
|
||||
device_model: "Lenovo ThinkPad T430"
|
||||
form_factor: laptop
|
||||
cpu: "Intel Core i5-3320M"
|
||||
ram_gb: 8
|
||||
storage:
|
||||
type: ssd
|
||||
size_gb: 256
|
||||
wifi_chip: "Intel Centrino Advanced-N 6205"
|
||||
ethernet: "Intel 82579LM"
|
||||
archy_version: "1.8.4-alpha"
|
||||
install_method: usb-iso
|
||||
status: working
|
||||
tested_date: "2026-08-15"
|
||||
submitted_by: "example-contributor"
|
||||
notes: |
|
||||
Example report — replace with a real one. Boots and installs cleanly from
|
||||
a USB stick made with the standard ISO. No BIOS changes needed beyond
|
||||
enabling AHCI mode (should already be default on most T430s).
|
||||
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ArchyHCL hardware report",
|
||||
"description": "One reported install of Archipelago on a physical device. One file per report under data/reports/.",
|
||||
"type": "object",
|
||||
"required": ["device_model", "form_factor", "cpu", "ram_gb", "storage", "wifi_chip", "archy_version", "install_method", "status", "tested_date"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"device_model": {
|
||||
"type": "string",
|
||||
"description": "Manufacturer + model, e.g. \"Lenovo ThinkPad T430\" or \"Raspberry Pi 5 8GB\"."
|
||||
},
|
||||
"form_factor": {
|
||||
"type": "string",
|
||||
"enum": ["laptop", "desktop", "mini-pc", "sbc", "server", "other"]
|
||||
},
|
||||
"cpu": {
|
||||
"type": "string",
|
||||
"description": "e.g. \"Intel Core i5-3320M\", \"Broadcom BCM2712\"."
|
||||
},
|
||||
"ram_gb": {
|
||||
"type": "number",
|
||||
"exclusiveMinimum": 0
|
||||
},
|
||||
"storage": {
|
||||
"type": "object",
|
||||
"required": ["type", "size_gb"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"type": { "type": "string", "enum": ["hdd", "ssd", "nvme", "emmc", "sd"] },
|
||||
"size_gb": { "type": "number", "exclusiveMinimum": 0 }
|
||||
}
|
||||
},
|
||||
"wifi_chip": {
|
||||
"type": "string",
|
||||
"description": "Exact chipset if known, e.g. \"Intel Centrino Advanced-N 6205\". Use \"none\" for wired-only setups."
|
||||
},
|
||||
"ethernet": {
|
||||
"type": "string",
|
||||
"description": "Optional — matters for a node OS more than most HCLs. e.g. \"Intel 82579LM\"."
|
||||
},
|
||||
"archy_version": {
|
||||
"type": "string",
|
||||
"description": "Exact Archipelago version tested, e.g. \"1.8.4-alpha\"."
|
||||
},
|
||||
"install_method": {
|
||||
"type": "string",
|
||||
"enum": ["usb-iso", "netboot", "existing-os-script", "other"]
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["working", "partial", "broken"],
|
||||
"description": "working = installs and runs with no known issues. partial = installs and runs but with specific known issues (detail in `issues`). broken = does not install or does not run at all."
|
||||
},
|
||||
"tested_date": {
|
||||
"type": "string",
|
||||
"pattern": "^\\d{4}-\\d{2}-\\d{2}$"
|
||||
},
|
||||
"submitted_by": {
|
||||
"type": "string",
|
||||
"description": "Optional attribution — nostr npub, Gitea username, or leave unset for anonymous."
|
||||
},
|
||||
"issues": {
|
||||
"type": "string",
|
||||
"description": "Required detail when status is partial or broken. Free text: what broke, workarounds found."
|
||||
},
|
||||
"notes": {
|
||||
"type": "string",
|
||||
"description": "Anything else useful: BIOS/UEFI settings needed, quirks, links to a fuller writeup."
|
||||
}
|
||||
},
|
||||
"if": {
|
||||
"properties": { "status": { "enum": ["partial", "broken"] } }
|
||||
},
|
||||
"then": {
|
||||
"required": ["issues"]
|
||||
}
|
||||
}
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build site/data.json from data/reports/*.yml.
|
||||
|
||||
Every report is validated against data/schema.json before being included —
|
||||
a malformed report fails the build loudly instead of shipping a broken row
|
||||
to the site (same idea as archy's own scripts/validate-app-manifest.sh:
|
||||
fail the build, don't ship the drift).
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
from jsonschema import Draft7Validator
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
REPORTS_DIR = ROOT / "data" / "reports"
|
||||
SCHEMA_PATH = ROOT / "data" / "schema.json"
|
||||
OUT_PATH = ROOT / "site" / "data.json"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
schema = json.loads(SCHEMA_PATH.read_text())
|
||||
validator = Draft7Validator(schema)
|
||||
|
||||
reports = []
|
||||
errors = []
|
||||
|
||||
for path in sorted(REPORTS_DIR.glob("*.yml")):
|
||||
try:
|
||||
data = yaml.safe_load(path.read_text())
|
||||
except yaml.YAMLError as e:
|
||||
errors.append(f"{path.name}: invalid YAML — {e}")
|
||||
continue
|
||||
|
||||
report_errors = sorted(validator.iter_errors(data), key=lambda e: e.path)
|
||||
if report_errors:
|
||||
for err in report_errors:
|
||||
loc = ".".join(str(p) for p in err.path) or "(root)"
|
||||
errors.append(f"{path.name}: {loc}: {err.message}")
|
||||
continue
|
||||
|
||||
data["_source_file"] = path.name
|
||||
reports.append(data)
|
||||
|
||||
if errors:
|
||||
print(f"Build failed — {len(errors)} error(s):", file=sys.stderr)
|
||||
for e in errors:
|
||||
print(f" {e}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
reports.sort(key=lambda r: (r["device_model"], r["tested_date"]))
|
||||
OUT_PATH.write_text(json.dumps(reports, indent=2) + "\n")
|
||||
print(f"Wrote {len(reports)} report(s) to {OUT_PATH.relative_to(ROOT)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
// No framework, no build step — this is a plain JSON file (site/data.json,
|
||||
// generated by scripts/build.py) rendered client-side. Keeps the "add a
|
||||
// device" contribution path as simple as adding one YAML file.
|
||||
|
||||
let reports = [];
|
||||
let sortKey = "device_model";
|
||||
let sortDir = 1;
|
||||
|
||||
const rowsEl = document.getElementById("rows");
|
||||
const searchEl = document.getElementById("search");
|
||||
const statusEl = document.getElementById("status-filter");
|
||||
const formFactorEl = document.getElementById("form-factor-filter");
|
||||
const countEl = document.getElementById("count");
|
||||
const emptyEl = document.getElementById("empty");
|
||||
|
||||
function storageLabel(storage) {
|
||||
return `${storage.size_gb}GB ${storage.type.toUpperCase()}`;
|
||||
}
|
||||
|
||||
function matchesFilters(r, query, status, formFactor) {
|
||||
if (status && r.status !== status) return false;
|
||||
if (formFactor && r.form_factor !== formFactor) return false;
|
||||
if (!query) return true;
|
||||
const haystack = [r.device_model, r.cpu, r.wifi_chip, r.ethernet]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
.toLowerCase();
|
||||
return haystack.includes(query);
|
||||
}
|
||||
|
||||
function sortValue(r, key) {
|
||||
if (key === "storage") return r.storage.size_gb;
|
||||
return r[key];
|
||||
}
|
||||
|
||||
function render() {
|
||||
const query = searchEl.value.trim().toLowerCase();
|
||||
const status = statusEl.value;
|
||||
const formFactor = formFactorEl.value;
|
||||
|
||||
const filtered = reports.filter((r) => matchesFilters(r, query, status, formFactor));
|
||||
|
||||
filtered.sort((a, b) => {
|
||||
const av = sortValue(a, sortKey);
|
||||
const bv = sortValue(b, sortKey);
|
||||
if (av < bv) return -1 * sortDir;
|
||||
if (av > bv) return 1 * sortDir;
|
||||
return 0;
|
||||
});
|
||||
|
||||
rowsEl.innerHTML = "";
|
||||
for (const r of filtered) {
|
||||
const tr = document.createElement("tr");
|
||||
tr.className = "expandable";
|
||||
tr.innerHTML = `
|
||||
<td>${escapeHtml(r.device_model)}</td>
|
||||
<td>${escapeHtml(r.form_factor)}</td>
|
||||
<td>${escapeHtml(r.cpu)}</td>
|
||||
<td>${r.ram_gb}GB</td>
|
||||
<td>${storageLabel(r.storage)}</td>
|
||||
<td>${escapeHtml(r.wifi_chip)}</td>
|
||||
<td><span class="badge ${r.status}">${r.status}</span></td>
|
||||
<td>${escapeHtml(r.archy_version)}</td>
|
||||
<td>${escapeHtml(r.tested_date)}</td>
|
||||
`;
|
||||
tr.addEventListener("click", () => toggleDetail(tr, r));
|
||||
rowsEl.appendChild(tr);
|
||||
}
|
||||
|
||||
countEl.textContent = `${filtered.length} of ${reports.length} report${reports.length === 1 ? "" : "s"}`;
|
||||
emptyEl.hidden = filtered.length !== 0;
|
||||
}
|
||||
|
||||
function toggleDetail(tr, r) {
|
||||
const next = tr.nextElementSibling;
|
||||
if (next && next.classList.contains("detail-row")) {
|
||||
next.remove();
|
||||
return;
|
||||
}
|
||||
document.querySelectorAll(".detail-row").forEach((el) => el.remove());
|
||||
|
||||
const detail = document.createElement("tr");
|
||||
detail.className = "detail-row";
|
||||
const parts = [];
|
||||
if (r.ethernet) parts.push(`<strong>Ethernet:</strong> ${escapeHtml(r.ethernet)}`);
|
||||
parts.push(`<strong>Install method:</strong> ${escapeHtml(r.install_method)}`);
|
||||
if (r.submitted_by) parts.push(`<strong>Submitted by:</strong> ${escapeHtml(r.submitted_by)}`);
|
||||
if (r.issues) parts.push(`<strong>Issues:</strong>\n${escapeHtml(r.issues.trim())}`);
|
||||
if (r.notes) parts.push(`<strong>Notes:</strong>\n${escapeHtml(r.notes.trim())}`);
|
||||
|
||||
const td = document.createElement("td");
|
||||
td.colSpan = 9;
|
||||
td.innerHTML = parts.join("\n\n");
|
||||
detail.appendChild(td);
|
||||
tr.after(detail);
|
||||
}
|
||||
|
||||
function escapeHtml(s) {
|
||||
const div = document.createElement("div");
|
||||
div.textContent = s ?? "";
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
document.querySelectorAll("th[data-sort]").forEach((th) => {
|
||||
th.addEventListener("click", () => {
|
||||
const key = th.dataset.sort;
|
||||
if (sortKey === key) {
|
||||
sortDir *= -1;
|
||||
} else {
|
||||
sortKey = key;
|
||||
sortDir = 1;
|
||||
}
|
||||
render();
|
||||
});
|
||||
});
|
||||
|
||||
searchEl.addEventListener("input", render);
|
||||
statusEl.addEventListener("change", render);
|
||||
formFactorEl.addEventListener("change", render);
|
||||
|
||||
fetch("data.json")
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
reports = data;
|
||||
render();
|
||||
})
|
||||
.catch((err) => {
|
||||
emptyEl.hidden = false;
|
||||
emptyEl.textContent = "Failed to load data.json — " + err;
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
[
|
||||
{
|
||||
"device_model": "Lenovo ThinkPad T430",
|
||||
"form_factor": "laptop",
|
||||
"cpu": "Intel Core i5-3320M",
|
||||
"ram_gb": 8,
|
||||
"storage": {
|
||||
"type": "ssd",
|
||||
"size_gb": 256
|
||||
},
|
||||
"wifi_chip": "Intel Centrino Advanced-N 6205",
|
||||
"ethernet": "Intel 82579LM",
|
||||
"archy_version": "1.8.4-alpha",
|
||||
"install_method": "usb-iso",
|
||||
"status": "working",
|
||||
"tested_date": "2026-08-15",
|
||||
"submitted_by": "example-contributor",
|
||||
"notes": "Example report \u2014 replace with a real one. Boots and installs cleanly from\na USB stick made with the standard ISO. No BIOS changes needed beyond\nenabling AHCI mode (should already be default on most T430s).\n",
|
||||
"_source_file": "example-thinkpad-t430.yml"
|
||||
},
|
||||
{
|
||||
"device_model": "Raspberry Pi 5 (8GB)",
|
||||
"form_factor": "sbc",
|
||||
"cpu": "Broadcom BCM2712 (4x Cortex-A76)",
|
||||
"ram_gb": 8,
|
||||
"storage": {
|
||||
"type": "nvme",
|
||||
"size_gb": 512
|
||||
},
|
||||
"wifi_chip": "Broadcom BCM43455 (onboard)",
|
||||
"ethernet": "Broadcom onboard Gigabit",
|
||||
"archy_version": "1.8.4-alpha",
|
||||
"install_method": "netboot",
|
||||
"status": "partial",
|
||||
"tested_date": "2026-08-10",
|
||||
"submitted_by": "example-contributor",
|
||||
"issues": "Example report \u2014 replace with a real one. Onboard WiFi drops the\nconnection under sustained heavy P2P traffic (Bitcoin IBD); a workaround\nis to run over Ethernet instead. Everything else (Lightning, apps,\ndashboard) works fine.\n",
|
||||
"notes": "NVMe via the official PCIe HAT. Boots noticeably faster than SD-card\ninstalls.\n",
|
||||
"_source_file": "example-raspberry-pi-5.yml"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,66 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>ArchyHCL — Archipelago Hardware Compatibility List</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>ArchyHCL</h1>
|
||||
<p>Community-reported hardware compatibility for <a href="https://source.archipelago-foundation.org/lfg2025/archy" target="_blank" rel="noopener">Archipelago</a>. Every row here is a real install someone tested and reported — see it on GitHub/Gitea as a plain data file, no login required to browse.</p>
|
||||
<p class="cta">
|
||||
Tested Archipelago on your own hardware? <a href="https://source.archipelago-foundation.org/lfg2025/ArchyHCL/issues/new?template=hardware-report.yml" target="_blank" rel="noopener">Report it</a> —
|
||||
takes two minutes and helps the next person know what to expect before they buy or repurpose a machine.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="controls">
|
||||
<input id="search" type="search" placeholder="Search model, CPU, WiFi chip…" autocomplete="off">
|
||||
<select id="status-filter">
|
||||
<option value="">All statuses</option>
|
||||
<option value="working">Working</option>
|
||||
<option value="partial">Partial</option>
|
||||
<option value="broken">Broken</option>
|
||||
</select>
|
||||
<select id="form-factor-filter">
|
||||
<option value="">All form factors</option>
|
||||
<option value="laptop">Laptop</option>
|
||||
<option value="desktop">Desktop</option>
|
||||
<option value="mini-pc">Mini PC</option>
|
||||
<option value="sbc">SBC</option>
|
||||
<option value="server">Server</option>
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
<span id="count" class="count"></span>
|
||||
</div>
|
||||
|
||||
<table id="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-sort="device_model">Device</th>
|
||||
<th data-sort="form_factor">Type</th>
|
||||
<th data-sort="cpu">CPU</th>
|
||||
<th data-sort="ram_gb">RAM</th>
|
||||
<th data-sort="storage">Storage</th>
|
||||
<th data-sort="wifi_chip">WiFi chip</th>
|
||||
<th data-sort="status">Status</th>
|
||||
<th data-sort="archy_version">Archy ver.</th>
|
||||
<th data-sort="tested_date">Tested</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="rows"></tbody>
|
||||
</table>
|
||||
|
||||
<p id="empty" class="empty" hidden>No reports match your filters.</p>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>Data lives in <code>data/reports/*.yml</code>, one file per report — browse or fork the raw data <a href="https://source.archipelago-foundation.org/lfg2025/ArchyHCL/src/branch/main/data/reports" target="_blank" rel="noopener">here</a>. See <a href="https://source.archipelago-foundation.org/lfg2025/ArchyHCL/src/branch/main/CONTRIBUTING.md" target="_blank" rel="noopener">CONTRIBUTING.md</a> for the two ways to add a report.</p>
|
||||
</footer>
|
||||
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
:root {
|
||||
--bg: #0a0e12;
|
||||
--panel: #10161c;
|
||||
--border: #223038;
|
||||
--text: #d8e4e8;
|
||||
--muted: #7f97a0;
|
||||
--accent: #00fff2;
|
||||
--accent-dim: #00b3a8;
|
||||
--working: #33d17a;
|
||||
--partial: #f5b23c;
|
||||
--broken: #f0506e;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: ui-monospace, "SF Mono", Consolas, monospace;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
header, main, footer {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--accent);
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
a { color: var(--accent-dim); }
|
||||
a:hover { color: var(--accent); }
|
||||
|
||||
header p { color: var(--muted); max-width: 70ch; }
|
||||
header .cta { color: var(--text); }
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
#search {
|
||||
flex: 1 1 260px;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
input, select {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
padding: 0.5rem 0.7rem;
|
||||
border-radius: 4px;
|
||||
font-family: inherit;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
input:focus, select:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-dim);
|
||||
}
|
||||
|
||||
.count { color: var(--muted); font-size: 0.85rem; margin-left: auto; }
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
thead th {
|
||||
text-align: left;
|
||||
padding: 0.6rem 0.7rem;
|
||||
border-bottom: 2px solid var(--border);
|
||||
color: var(--muted);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
thead th:hover { color: var(--accent); }
|
||||
|
||||
tbody tr {
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
tbody tr:hover { background: var(--panel); }
|
||||
|
||||
tbody tr.expandable { cursor: pointer; }
|
||||
|
||||
td {
|
||||
padding: 0.6rem 0.7rem;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.badge.working { background: rgba(51, 209, 122, 0.15); color: var(--working); }
|
||||
.badge.partial { background: rgba(245, 178, 60, 0.15); color: var(--partial); }
|
||||
.badge.broken { background: rgba(240, 80, 110, 0.15); color: var(--broken); }
|
||||
|
||||
.detail-row td {
|
||||
background: var(--panel);
|
||||
color: var(--muted);
|
||||
white-space: pre-wrap;
|
||||
font-size: 0.82rem;
|
||||
padding: 0.8rem 1.2rem;
|
||||
}
|
||||
|
||||
.detail-row strong { color: var(--text); }
|
||||
|
||||
.empty { color: var(--muted); text-align: center; padding: 2rem; }
|
||||
|
||||
footer {
|
||||
color: var(--muted);
|
||||
font-size: 0.8rem;
|
||||
border-top: 1px solid var(--border);
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
footer code {
|
||||
background: var(--panel);
|
||||
padding: 0.1rem 0.35rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
Reference in New Issue
Block a user