The pins now update the hash so How it works / Submit / Submissions are
deep-linkable; the #<slug> app-focus link still works. One parseHash()
distinguishes them.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Turns the site into a small multi-view app. Three pinned nav buttons above
the search (App Center pin style) switch the main pane:
- How it works: the former footer text, expanded into cards (add an app,
point your box here, how trust works, run your own).
- Submit an app: the PR-based flow (fork → drop-in app folder → PR → reviewed
+ signed → published with a community badge + your name). No uploads/accounts.
- Submissions: renders a static submissions.json (operator-published, like the
catalog) — author names, status badges (pending/review/merged), PR links,
a pending-count badge on the pin, graceful empty/loading states.
Category/search return to browse; #<slug> deep-link still focuses one app.
REPO_URL is overridable via <meta name=lp-repo>.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The featured passthrough was removed when the dedicated-section commit was
reverted; the Featured sidebar row needs it. spec.json {"featured":true} now
sets meta.featured on the published app again.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
- Add a Featured sidebar row (gold star, publisher-curated meta.featured),
filters to featured apps; only shown when the catalog has any.
- Exact App Center font stack + 16px category text (was system-ui/14px).
- 'All' (and Featured) render as inline currentColor glyphs so the All icon
is white like the WebUI — all.svg uses currentColor and rendered dark as an
<img>; the fixed-blue category icons stay <img>.
- Apply --sidebar-bg to the sidebar itself too (double layer, matching the App
Center) so the sidebar tint matches instead of showing more aurora through.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
--surface-hover and --sidebar-border weren't defined in the site's :root (and
--sidebar-bg had a made-up value), so the category hover/active background and
the row separators resolved to nothing — the sidebar looked flat vs the App
Center. Set them to the exact nebula values.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
.focusbar's display:flex was overriding the hidden attribute, leaving a thin
empty bar under the status strip when no app was focused.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Website sidebar now mirrors the App Center (sidebar.css): a 220px full-height
column, full-width category rows with bottom-border separators and the real
per-category icons (bundled into the site by the install hook from the live
frontend), and the same search box — instead of the rounded pills + generic
circles + count badges it had. Marketplace app copy shortened to peer style:
description 'App Catalog & Registry', one-sentence long description.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Replaces the extra on-card marketplace tags (the separate Official trust
badge) with a richer, more elegant flow: a registry card keeps a single
'Available' pill, and clicking Add (or the card) opens a modal outlining the
app — full description, publisher + trust, version, the add command, and a
'View full page on the marketplace ↗' link to the app's page on the source
it came from — with the actual add as the confirm. Gives a beat to review a
community app before pulling it in.
- webui_registry_scan.sh re-emits source{base,channel}; loadApps stashes it
(window.registryCatalogSource) + carries per-app version.
- openRegistryDetails() builds the eo-modal; addRegistryApp() is now just the
task dispatch (the modal's confirm / a fallback).
- The marketplace website supports a #<slug> deep-link (focus one app, with a
'Show all' bar), so the modal's link lands on that app's page.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The standalone marketplace site now mirrors the App Center it feeds: nebula
aurora background, the same app-card grid (70px icon, title, description /
category / Available / Official tags, italic long description, indigo Add
button), a left category sidebar with search, and a top bar. Client-rendered
from the same signed index.json; the Add button copies
'libreportal app add <slug>'. The status strip probes for a detached
signature rather than asserting verification (boxes verify, not the site).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Reverts a4e65df. The maintainer prefers the more fluid model where registry
apps flow into the App Center grid as 'Available — Add' cards alongside
installed/local apps, rather than a separate /apps/marketplace destination.
Restores the Stage-4 merged-grid behaviour (loadApps merges
registry_catalog.json; createAppCard renders registry cards; addRegistryApp
dispatches app_add). The make_app.sh featured passthrough + the generator's
featured/source fields go with it (unused by the merged grid).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Splits the catalog out of the main grid into its own destination, the
WordPress 'Add Plugins' vs 'Installed Plugins' model: the grid is now
purely 'your apps' (local definitions), and the new Marketplace section is
'get more apps' (the remote signed catalog).
- New MarketplacePage (components/apps/marketplace/) mounts at
/apps/marketplace inside the apps feature (same sub-dispatch pattern as
/apps/overview — no new top-level component). Pinned sidebar entry with a
live 'available to add' count badge.
- Status strip: signed/unsigned, available + catalog counts, serial, source
base+channel, freshness, and a Refresh that re-runs the host-side registry
scan via updater_check.
- Publisher-curated Featured shelf (meta.featured, set at publish time — no
tracking/popularity), category chips + search, per-app detail modal
(long description, publisher/trust/version, add command), and the chained
Add & set-up flow: dispatch app_add, and when the definition lands, hand
off to the app's config/install page.
- State-aware cards: Available (Add) / Added (Set up →) / Installed (Open).
- Backend: make_app.sh passes through meta.featured; webui_registry_scan.sh
emits featured + source{base,channel} in registry_catalog.json.
- Removed the grid's registry-merge + registry card path + its CSS (moved to
the namespaced marketplace surface); app_add task wiring + completion
handler retained and reused.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
- .tabs-list now rounds its own top corners (12px, matching .tab-navigation);
the first/last .tab-button radii were invisible against the bar's square
background on the Backups/Migrate/Config sub-tab strips.
- All five fleet tabs share one rhythm under the header divider: a single
16px gap, no extra inset — .ov-tab-body drops its recessed margin/padding
box, and the Backups/Migrate sub-tab strips gain the matching gap.
- The Peers list keeps its recessed panel via .peers-body (in-card recipe),
no longer riding the flattened .ov-tab-body.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The embedded backup center's fragment kept its standalone-page skeleton
(.container + .sidebar restyled by a pile of overview.css overrides).
.container's fixed viewport height inflated the Backups tab pane to
~100vh, stretching the pane surface far past the content and under the
footer buttons, and the restyled strip never matched the app-detail
Config sub-tabs.
Rebuild backup-content.html on the canonical sub-tab idiom instead —
.tabs-wrapper > .tabs-list (emoji tab-buttons) + .tabs-content card,
with a .backup-actions footer below the card mirroring .config-actions.
The bespoke overview.css restyle block, the nebula special-cases, the
embed's id-stripping and BackupPage's dead page-header updater all fall
away; the export menu now opens upward from the footer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The generic installer copies only config+compose into the live dir, so the
compose's nginx.conf file mount had no source — and a premature container
start leaves a directory placeholder at the mount path. The post_setup hook
now removes the placeholder and copies the file from the definition, so
(re)install converges.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
containers/marketplace — nginx:alpine app (standard drop-in contract:
config + tagged compose + icon + install hook) whose docroot serves BOTH
halves of the marketplace: the signed catalog channel tree (index.json /
payloads, published into data/<channel>/ by the release tools) and a
self-contained client-rendered browse site over the same file (search,
category chips, trust badges, copyable 'libreportal app add <slug>' —
no third-party assets, no backend, no build step). The official
marketplace is an instance of this app; self-hosting one = installing it
and pointing CFG_RELEASE_BASE_URL at it. Boxes only ever trust the
minisign signature on the catalog, never the website.
New generic gating convention: CFG_<APP>_DEV_ONLY=true keeps an app out
of the App Center grid unless Developer Mode is on (CFG_DEV_MODE, the
same flag the **DEV** config-field filter uses); an installed dev-only
app always stays visible. The marketplace app is the first user.
Cache policy: catalog/channel manifests no-cache; payloads short
revalidating cache (same-id re-publish); version-pinned release
artifacts immutable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Roadmap: Phase 6 entry (app bundles + Browse-&-Add, built 2026-07-03),
the app-bundle format addendum (meta object, tarball contract, collision
policy, trust-then-quarantine ordering), deferred bullet trimmed to what
actually remains (community quarantine, taps, theme/component, canary
countersign) with a pointer to marketplace-website.md for the website
layer. development.md gains the 'Publish an app to the marketplace
catalog' section (make_app.sh flow + the app add loop).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The grid now also reads apps/generated/registry_catalog.json (optional
data: any failure = no registry cards, never a broken grid) and renders
signed-catalog apps that have no local definition as "Available — Add"
cards: indigo Available pill, teal Official trust badge, and an Add button
that dispatches the app_add task (task-actions/router/format wired). Local
definitions always win on a slug collision; registry cards sort last and
are excluded from the Installed view. No detail page until Add lands the
definition — then the card becomes a normal Install card, repainted by the
app_add branch in the task-completion handler without a manual refresh.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
webuiRegistryCatalogScan (run by updater check, same atomic keep-prior
pattern as webuiArtifactScan) writes apps/generated/registry_catalog.json:
the type:"app"/kind:"bundle" rows of the signed index annotated with
defined/installed, browse metadata from the envelope meta, and icons
mirrored into core/icons/apps/registry/ ONLY when their bytes match the
sha256 pin in the signed index — the browser stays same-origin; a tampered
or oversized icon is skipped, never served.
webuiArtifactScan now selects type=="hotfix" so app rows never render as
pseudo-hotfixes in the Improvements tab, and counts+logs artifacts of
unrecognized type instead of surfacing them (the §8.1 forward-compat
firewall on the scan path).
Harness vs a locally served registry: 14/14 (catalog row + meta + flags,
icon pin verify + tamper skip, hotfix-only stream, unknown-type skip+log,
unreachable-registry keeps prior files).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Opens the two designed seams (roadmap §8.4): _artifactResolve accepts
type:"app" (slug validated, the installed-app gate skipped — presence is
the collision policy's call), and payload.kind:"bundle" gets its own APPLY
flow. The download core (sha256 pin vs the signed index + minisig +
refuse-unsigned) is factored into _artifactDownloadVerified, shared by ops
and bundle payloads.
A bundle add: fetch → quarantine-validate → place in the definition tree
(staging + one rename, manager funnel) → lpRegenWebui → verify the app
surfaced in apps.json → applied-record with a precise undo → History.
The validator is fail-closed (traversal/absolute paths, links/devices,
single top-level dir == slug, charset, size/entry caps, set-id strip,
config TITLE+CATEGORY + compose present, bash -n every .sh) because the
definition tree is live-sourced on every CLI start — nothing lands there
before trust + quarantine pass. Collision policy: installed-live refused,
local definitions win, registry-owned re-add = reversible definition
update (prior tree packed into the undo). Revert removes/restores the
definition (refused while installed) and regens. Apps never auto-apply
(type filter kept + publisher forces auto:false).
New verb: libreportal app add <slug|artifact-id> (app_add task; resolves
by slug via appAddFromRegistry, ambiguity refused).
Also fixes the second half of the sigstate-propagation bug class:
artifactApply captured $(_artifactResolve) in a subshell, stranding
_ART_INDEX/_ART_APP/_ART_SCOPE AND the LP_INDEX_SIGSTATE the apply gate
enforces — on a signed box every apply would have refused as unsigned.
Resolve now assigns globals (_ART_JSON) and is called directly.
Source-and-mock harness: 46/46 (resolve gates, 14 validator refusals,
happy add, collision matrix, definition-update round-trip, revert
semantics, postcheck + record-failure rollbacks, apply-auto exclusion,
app add verb).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The marketplace publisher tool: packs containers/<slug>/ (the drop-in app
contract, unchanged) into a deterministic tarball (commit-clamped mtimes +
gzip -n; unchanged apps repack byte-identical and keep their published
version), signs it, copies a sha256-pinned catalog icon, and upserts a
type:"app" / payload.kind:"bundle" envelope into the same team-signed
index hotfixes ride. Catalog metadata (title/category/descriptions) is
parsed line-wise from the app's own .config — one source of truth with the
App Center generators. auto:false is hard-forced: apps never auto-apply.
The index-upsert/serial/freshness/publishers-map and signing logic is
factored out of make_hotfix.sh into lib/release_index.sh, shared by both
tools (make_hotfix.sh behavior preserved; regression-tested alongside an
app entry: serial bump, both payload kinds coexist, valid_until refreshed).
LP_INDEX_VALID_DAYS is the shared freshness knob (LP_HOTFIX_VALID_DAYS
kept as a legacy alias).
Verified: speedtest publish → deterministic repack (identical sha256) →
served via local http.server → real lpFetchIndex/accessors harness 10/10.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Every caller captured the index with var=$(lpFetchIndex), which runs the
fetch in a command-substitution subshell — the LP_INDEX_SIGSTATE global it
sets never reached the caller. On a box with real signing active the
artifactApply/apply-auto gates would therefore refuse a correctly signed
index (fail-closed, but the apply path would be dead on arrival the day
signing activates), and artifact index / the WebUI scan would report a
verified feed as UNSIGNED.
New lpFetchIndexInto <var> [cache] runs the fetch in the calling shell and
assigns via printf -v; all four call sites converted. Verified with a
source-and-mock harness against a locally served index: 10/10 (sigstate
reaches caller, serial high-water, anti-rollback refuse, staleness refuse,
id enumeration, envelope round-trip).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The marketplace system ships as containers/libreportal-marketplace (a normal
app definition, hidden behind CFG_DEV_MODE via a new CFG_<APP>_DEV_ONLY
convention): it serves the signed catalog tree plus a client-rendered browse
UI over the same index.json every box verifies. The official instance is our
own install of this app; self-hosting a marketplace = installing it.
Community submissions stay PR -> review -> sign (phase 2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>