1156 Commits

Author SHA1 Message Date
librelad
cfb3a4f755 revert: drop the dedicated Marketplace section — keep catalog merged into the grid
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>
2026-07-04 21:50:57 +01:00
librelad
81b11e5675 Merge claude/2 2026-07-04 21:38:12 +01:00
librelad
a4e65df77f feat(webui/marketplace): dedicated Marketplace section in the App Center
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>
2026-07-04 21:38:12 +01:00
librelad
d79a999c4f Merge claude/1 2026-07-04 21:34:05 +01:00
librelad
b17ecdc192 fix(webui/overview): restore inner padding on the recessed tab body
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-04 21:34:05 +01:00
librelad
6a1e4e4795 Merge claude/1 2026-07-04 21:30:22 +01:00
librelad
31ce56c022 fix(webui/overview): keep the recessed tab-body surface, only drop its inset
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-04 21:30:22 +01:00
librelad
06d938e58a Merge claude/1 2026-07-04 21:24:42 +01:00
librelad
baa1ed4d8d fix(webui/overview): uniform tab rhythm + rounded sub-tab strip corners
- .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>
2026-07-04 21:24:42 +01:00
librelad
a8f7f53f4e Merge claude/1 2026-07-04 21:08:03 +01:00
librelad
aef0c15726 refactor(webui/backups): backup center sub-tabs use canonical tabs-wrapper
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>
2026-07-04 21:08:03 +01:00
librelad
9830382a4a Merge claude/2 2026-07-03 21:46:29 +01:00
librelad
7fd4a1c29e fix(marketplace/site): hide the light scrollbar strip on the add-command snippets
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-03 21:46:29 +01:00
librelad
0c8b13d2f1 Merge claude/2 2026-07-03 21:43:59 +01:00
librelad
c44e17967f fix(marketplace): converge the nginx.conf bind-mount source in the install hook
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>
2026-07-03 21:43:59 +01:00
librelad
58541b1fe3 Merge claude/2 2026-07-03 21:40:33 +01:00
librelad
897f514735 feat(marketplace): the marketplace server ships as a dev-mode LibrePortal app
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>
2026-07-03 21:40:33 +01:00
librelad
9b5a4d276d Merge claude/2 2026-07-03 21:35:30 +01:00
librelad
52be5968e9 docs: registry slice shipped — §8.4b bundle spec addendum + §8.7 status + publish guide
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>
2026-07-03 21:35:30 +01:00
librelad
2b54be933f Merge claude/2 2026-07-03 21:23:14 +01:00
librelad
1165ec1799 feat(webui/apps): marketplace cards — registry apps merge into the App Center grid
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>
2026-07-03 21:23:14 +01:00
librelad
3675b6b34c Merge claude/2 2026-07-03 21:17:26 +01:00
librelad
87edd09994 feat(webui/registry): catalog scan generator + hotfix-only Improvements stream
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>
2026-07-03 21:17:26 +01:00
librelad
d1958f6033 Merge claude/2 2026-07-03 21:14:14 +01:00
librelad
4a1aa43083 feat(artifact): app bundle applier + libreportal app add — the marketplace verb
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>
2026-07-03 21:14:14 +01:00
librelad
e04fdbf64f Merge claude/2 2026-07-03 20:45:02 +01:00
librelad
8351863719 feat(release): make_app.sh — publish app definition bundles to the artifact index
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>
2026-07-03 20:45:02 +01:00
librelad
fd5facc7cf Merge claude/2 2026-07-03 20:43:59 +01:00
librelad
36a5c87397 fix(artifacts): propagate LP_INDEX_SIGSTATE to callers via lpFetchIndexInto
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>
2026-07-03 20:43:59 +01:00
librelad
b0a194dd48 Merge claude/2 2026-07-03 20:30:07 +01:00
librelad
0afd1de819 docs(roadmap): marketplace app design — dev-mode container + catalog + submissions
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>
2026-07-03 20:30:07 +01:00
librelad
22d23cb359 Merge claude/1 2026-07-03 19:51:26 +01:00
librelad
4abafd4640 tweak(webui/system): status dot before app icon in Per-app rows
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-03 19:51:26 +01:00
librelad
0b2ed1cf1a Merge claude/1 2026-07-03 19:44:40 +01:00
librelad
de0025d1a7 feat(webui/system): app icons in Per-app usage rows
Prepend a boxed app icon (tasks-notification style) to each row of the
System page's Per-app usage table. Multi-instance slugs (<type>_<id>)
resolve to the base type's icon; missing icons fall back to default.svg.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-03 19:44:40 +01:00
librelad
61334d1e72 Merge claude/1 2026-06-25 22:06:03 +01:00
librelad
ab0822c46b feat(webui/loading): shared boxed spinner loader for page/panel loading states
Pages and panels showed inconsistent loading states: the Backup center
and several admin pages (System, SSH, admin Overview), the Overview
Migrate/Peers panels, the per-app updater section and the backup engine
details modal rendered a bare 'Loading…' text line (updater-empty /
backup-empty-state) with no spinner, while Services/Config/Tasks used a
boxed card + spinner.

Add one shared loader — window.lpLoadingBox(message) + .lp-loading CSS in
the core/loading subsystem (the boxed card + accent spinner the good tabs
already use) — and route those bare-text loaders through it. The system
metric graph keeps its absolute overlay but gains the same spinner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-06-25 22:06:03 +01:00
librelad
9e1adef2b8 Merge claude/2 2026-06-25 21:25:38 +01:00
librelad
cc026d4d68 fix(webui/updater): correct misleading improvements empty-state copy
'Checking for improvements… this usually takes a minute.' implied an active
scan finishing imminently. The scan is periodic and runs in the background
(CFG_UPDATER_SCAN_INTERVAL, default 30m), so reword to reflect that. Kept the
cadence generic since the interval is admin-configurable and not exposed here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-06-25 21:25:38 +01:00
librelad
ce8170c6ad Merge claude/2 2026-06-25 21:23:24 +01:00
librelad
f1d22b057a fix(webui/updater): friendlier Updates empty-state copy
'No installed apps to track.' was terse and gave no next step. Point the user
to the App Center so the empty Updates tab is actionable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-06-25 21:23:24 +01:00
librelad
712068a00e Merge claude/1 2026-06-25 21:18:23 +01:00
librelad
dac824a161 fix(webui/forms): enhance per-app config & port dropdowns with themed select
The custom-select enhancer only matched select.form-control /
select.theme-selector, so dropdowns rendered by the per-app config form
(config-form.js uses form-select / form-input / config-input), the app
tools form, the port-manager grid (port-* classes) and the instance
domain picker stayed as plain native OS dropdowns. The LibrePortal app's
Theme option is one of these.

Add those classes to ENHANCE_CLASSES, give the classless instance domain
select a form-control class, and add a compact button override so the
themed dropdown matches the dense port-manager input metrics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-06-25 21:18:15 +01:00
librelad
4e90328b84 Merge claude/2 2026-06-25 21:17:31 +01:00
librelad
754864571c fix(webui/updater): friendlier, shorter improvements empty-state copy
'No hotfix data yet — the automatic scan fetches the signed improvements index
within a couple of minutes.' leaked jargon (hotfix data, signed index) onto the
interface. Replace with a short, plain 'Checking for improvements…' line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-06-25 21:17:31 +01:00
librelad
e054cbc946 Merge claude/2 2026-06-25 21:07:07 +01:00
librelad
f4c24340b7 fix(webui): lighten faint empty-state messages on dark panels
Empty-state 'no data yet' messages rendered at rgba(text, 0.55) (and one at
text-muted), which is hard to read on the dark recessed panels. Switch them to
the theme's --text-secondary muted token so they stay de-emphasized but legible.

Covers .updater-empty, .updater-detail-empty, .sys-detail-empty, .eo-modal-empty.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-06-25 21:07:07 +01:00
librelad
209ced11be Merge claude/2 2026-06-25 20:54:42 +01:00
librelad
246e687e45 fix(webui/tasks): equalize task-list panel top/bottom padding
The last task tile's 10px bottom margin stacked on the list's 16px bottom
padding, leaving a larger gap below the last row than above the first.
Zero the last tile's bottom margin so the dark panel reads symmetric.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-06-25 20:54:42 +01:00
librelad
b1f935dc3b Merge claude/1 2026-06-25 13:26:51 +01:00