Replace the single-page layout with a view-based one (Games / Settings / Logs via the left nav) and fold all download UI into the game tile — the separate download queue is gone. Per tile while downloading: an in-tile progress bar with auto-scaled size (B/KB/MB/GB/TB), live transfer speed and ETA from the poll deltas, and a green "Downloading" marker. One fixed-width action button that no longer reflows with its label: a blue Update/Download/Install that morphs into an amber Cancel while the download runs, plus a ghost Delete for a finished package. Settings and Logs moved to their own pages. Mobile-first responsive layout (rail collapses to a top bar, icon-only nav, single-column tiles, >=44px touch targets, 16px inputs). Real free space (statvfs) is shown auto-scaled in the rail and status strip. New "Home-screen shortcut" toggle (default on, persisted as home_shortcut in config.json). The actual PS5 tile install is not wired yet: it needs a prebuilt deeplinkUri stub PKG installed via sceAppInstUtil. Fixes from an adversarial review pass: - Reconcile in-flight downloads from /api/downloads onto the cards, so progress + Cancel appear after a reload or a download started elsewhere, and stop the per-poll full-grid rebuild. - Clear the downloaded flag on install and once the server reports the title up to date, so a patched title no longer shows Install/Delete forever. - Zero a stale speed/ETA if the byte counter goes backwards. - a11y: nav buttons keep an accessible name when the label is hidden on small screens; visible focus ring on the search box; filter group is role=group with aria-pressed; drop the noisy grid-level aria-live; fold the transient "checking" state into a visible filter bucket.
PatchDL Web UI
Standalone static UI for the planned PS5 patchdl.elf web server.
Open index.html directly for the mock UI, or serve this directory from the
ELF web server. The JavaScript first tries the real API and falls back to demo
data when the API is not available.
The embedded ELF server serves this UI on port 12880 by default.
Expected API
GET /api/status
GET /api/config
POST /api/config
GET /api/titles
GET /api/downloads
POST /api/titles/:title_id/check
POST /api/titles/:title_id/download
POST /api/titles/:title_id/install
POST /api/titles/:title_id/enable
POST /api/titles/:title_id/disable
Policy Model
The UI assumes deny-by-default behavior:
{
"default_policy": "deny",
"download_dir": "/data/patchdl (internal)",
"install_after_download": false,
"delete_pkg_after_install": true,
"source_policy": {
"official": { "allow_check": true, "allow_download": true, "allow_install": true },
"external": { "allow_check": true, "allow_download": true, "allow_install": true },
"shadowmount": { "allow_check": true, "allow_download": true, "allow_install": false },
"unknown": { "allow_check": true, "allow_download": false, "allow_install": false }
},
"cdn_allowlist": [
"sgst.prod.dl.playstation.net",
"gst.prod.dl.playstation.net",
"gs2.ww.prod.dl.playstation.net"
]
}
Per-title modes:
disabled
download_only
latest_compatible
pin
check_only
Per-title source fields:
{
"title_id": "PPSA90001_00",
"name": "Shadowmounted Test Title",
"source_type": "shadowmount",
"source_path": "/system_ex/app/PPSA90001_00",
"mount_from": "/mnt/usb0/itemzflow/Shadowmounted Test Title",
"enabled": true,
"mode": "download_only"
}
Supported source_type values:
official
external
shadowmount
unknown
The frontend treats shadowmount as download-only and unknown as blocked for
downloads and installs. Backend code should enforce the same policy even if a
client sends a forged request.
For PS5 game updates, the backend may turn a Sony manifest_url into a merged
local .pkg by downloading all manifest pieces. The delta_url *-DP.pkg is
not shown as a separate user action because it can bootstrap the storage/master
title instead of the installed regional target.