Changelog — GalleryHub
All notable changes to this app are recorded here. Newest entries on top.
[2026-05-13] — Demo galleries now showcase all 8 new designs, padded to 22 items each
- Every demo gallery now uses one of the 8 newer designs. The four original demos previously showed off the original Aperture / Reel / Atlas / Stage styles; they're now Boutique, Ember, Minimal, and Prism. Combined with the existing Bold / Luxe / Glass / Techno demos, all 8 new designs are represented out of the box. The original four designs are still available — pick them from the gallery editor → Design picker.
- Each demo gallery has 22 items. Galleries used to ship with 4–8 sample items, which looked sparse in masonry / grid layouts. We cycle the 24 sample images across all 8 galleries with unique titles, so each demo reads as a real, lived-in gallery from the moment the app is installed.
Internal
- [seeds/demo.sql](apps/galleryhub/seeds/demo.sql) regenerated: 8 galleries × 22 items = 176 rows. Each gallery starts the sample rotation at a different offset so consecutive galleries don't open with identical thumbnails.
galleryhub_backfill_demo_samples_if_needed() in [helpers.php](apps/galleryhub/helpers.php) rewritten to match — same 8 galleries, same 22-per-gallery shape, same sample cycling, generated by a small loop rather than the previous hard-coded 16-row table.
[2026-05-13] — Page-chrome display toggles + cleaner demo seeds
- Three new "What to show" toggles in the gallery editor — turn the Gallery name, Short sentence (description), and Light / dark mode button on or off independently. Useful for embedded galleries where the surrounding page already carries the title and brand. All three default to on. Lives at the top of the "What to show" section, separated from the per-item toggles by a "Page chrome" subheading.
- Same toggles in Settings → Gallery defaults, so new galleries inherit your preference.
- Demo galleries ship without categories assigned. The seed and the backfill helper no longer pre-fill
category on items or pre-populate categories on galleries. Adding categories is something the admin opts into; nothing should appear there by default.
Internal
- New keys
show_gallery_name, show_gallery_description, show_theme_toggle added to the display_options default everywhere it's declared: [helpers.php](apps/galleryhub/helpers.php), [schema.sql](apps/galleryhub/schema.sql), [app.json](apps/galleryhub/app.json), [api/galleries.php](apps/galleryhub/api/galleries.php), [seeds/demo.sql](apps/galleryhub/seeds/demo.sql), [seeds/fresh.sql](apps/galleryhub/seeds/fresh.sql), [partials/gallery-editor-sheet.php](apps/galleryhub/views/partials/gallery-editor-sheet.php), [partials/settings-sections.php](apps/galleryhub/views/partials/settings-sections.php).
- All 12 style files in [views/public/styles/](apps/galleryhub/views/public/styles/) now respect the three toggles (h1, description
<p>, theme button each guarded; whole header wrapper hidden when all three off).
galleryhub_backfill_demo_samples_if_needed() in helpers.php updated to insert items with empty category instead of pre-set categories.
[2026-05-13] — Eight new gallery designs
- Added eight new gallery designs — Bold, Boutique, Glass, Luxe, Minimal, Prism, Ember, and Techno. Each ships with light and dark variants that flip via the same theme toggle as the original four. Twelve total designs now in the picker (when you edit a gallery → Design, and in Settings → Default gallery design).
- Demo galleries expanded. The demo install now ships with eight galleries — one per design family — and each existing gallery (Spring, Hudson, Studio Archive) was padded out with people + lifestyle photos so the layouts read full and lived-in rather than half-empty.
- More sample images. Added eight portrait + lifestyle JPEGs (
people-1..4, lifestyle-1..4) to the bundled samples for a total of 24 (~1.3 MB).
Internal
- New style PHP files in [apps/galleryhub/views/public/styles/](apps/galleryhub/views/public/styles/):
bold.php, boutique.php, glass.php, luxe.php, minimal.php, prism.php, ember.php, techno.php. Each is self-contained: scoped CSS under .gh-{style}, light/dark via html[data-theme="..."] selectors, and a shared [data-gh-grid] mount the JS renders into.
galleries.style CHECK constraint widened in schema.sql to include the eight new ids.
galleryhub_palette() in helpers.php returns light/dark palettes for each new style so the <body> background + theme-color meta read right before JS boots.
gallery.js got a bindGenericGrid() path that handles the eight new styles uniformly — same lightbox, same filter chips, same card template.
gallery-editor-sheet.php and style-preview.php style pickers expanded to twelve.
[2026-05-12] — Storage tracking delegated to platform + sample images now load
- Fixed: sample images now actually display in the demo galleries. They were stored at
apps/galleryhub/marketing/samples/*.jpg but Apache's whitelist in [.htaccess](.htaccess) only allowed files directly under marketing/ — not subdirectories. The regex now allows zero-or-more safe subdirectory segments, so anything under marketing/{any-dirs}/file.jpg serves directly.
- Removed the in-app Storage card. GalleryHub no longer displays a "0.0 MB used of X GB" widget on the home dashboard, gallery detail page, or settings. Storage is the platform's concern — your global usage shows on the platform Home page. Apps don't need to duplicate it.
- Removed GalleryHub's redundant pre-upload storage check. The platform's
block_if_storage_full() already gates every POST at [core/index.php:2253](core/index.php#L2253), so re-checking inside the app was duplicate work. Single source of truth.
Internal
- Deleted
galleryhub_storage_summary() from helpers.php.
- Stripped storage UI from
views/home.php, views/gallery-detail.php, and the "Storage" section in views/partials/settings-sections.php (with the nth-of-type CSS rules re-shifted).
api/stats.php simplified to a placeholder for future app-specific stats (view counts, etc.). The storage action is gone.
refresh_storage_used($userId) calls are kept in api/media.php after upload/delete so the platform's cached counter (users.storage_used_mb) stays accurate.
[2026-05-12] — Backfill demo samples for existing installs
- Existing tenants now get the demo galleries automatically. The lazy migrator only handles schema changes —
seeds/demo.sql doesn't re-run for installs that pre-date the samples. Added a one-time backfill that detects "empty install, no real media yet" and inserts the four sample galleries + 16 media items on the next dashboard or galleries-list request. Guarded by a samples_backfill_done flag in settings so it runs once and never again. Tenants with their own real media are detected and left untouched.
Internal
galleryhub_backfill_demo_samples_if_needed() in helpers.php: idempotent, wrapped in a transaction with rollback on error, uses INSERT OR IGNORE on galleries to avoid colliding with any slug the tenant has already used.
- Triggered from
api/dashboard.php summary and api/galleries.php list — both common entry points that run on app open.
[2026-05-12] — Upload fix, AI prompt helper, demo samples
- Fixed: uploads now succeed. The drag-drop zone was rejecting every upload with "Invalid CSRF token". Root cause: the script was reading
window.APP_CONFIG, but APP_CONFIG is declared with const in the platform's layout, which doesn't add it to window — so the token never reached the server.
- Added: an AI prompt helper inside the Edit gallery → Advanced section. Same pattern as Donately. Click "Copy AI prompt" to grab a ready-made prompt you can paste into ChatGPT, Claude, or Gemini — it already references the right class names for whichever design your gallery uses (Aperture / Reel / Atlas / Stage), so the AI can write working CSS without trial and error.
- Added: sample images bundled in the demo galleries. The demo install now ships with four pre-built galleries — Spring Collection (Aperture), Hudson Features (Reel), Studio Archive (Atlas), and On Stage (Stage) — each with four hand-picked sample photos. Images live inside the app folder at
apps/galleryhub/marketing/samples/ (~1 MB total) and load directly from there — no remote URLs.
Internal
media-upload-zone.php switched from window.APP_CONFIG.csrfToken (always undefined — const doesn't bind to window) to bare APP_CONFIG.csrfToken, matching every other app's pattern.
- 16 sample JPEGs added at
apps/galleryhub/marketing/samples/ (4 per design, varied aspect ratios). The root .htaccess already whitelists apps/{id}/marketing/*.{jpg,png,webp,…} so Apache serves them directly.
- Demo seed inserts
media_items rows with file_path set to the absolute /apps/galleryhub/marketing/samples/... path. galleryhub_media_public_url() returns that as-is since it's already a public path.
- These sample paths are intentionally outside the per-user upload tree —
gh_delete_media_file() realpath-sandboxes to USERS_PATH, so the demo files can't be deleted by one tenant on behalf of others. The DB row deletes; the shared file stays.
[2026-05-12] — Pre-production polish
Internal
- Dropped the dead
identity block from app.json (legacy pre-pivot artifact; the platform no longer reads it).
- Fixed a SQL error in the storage card:
galleryhub_storage_summary() was selecting plan_id from the users table, but plan_id lives on user_subscriptions. Settings now loads without throwing.
- Fixed the file-upload CSRF header: the upload zone was reading
PS.csrf, which doesn't exist on the SDK. Now reads APP_CONFIG.csrfToken so uploads actually succeed.
- Fixed the
uninstall.php type signature: was int $userId which fatal-errors when the platform calls it with a UUID string. Now string $userId with a UUIDv4 shape guard, so the upload tree is actually reclaimed on app removal.
- Hardened the public view's JSON config block against
</script> injection via gallery name (JSON_HEX_TAG instead of JSON_UNESCAPED_SLASHES).
- Copy fix: "Toggle what donors see" → "Toggle what viewers see" in marketplace benefits.
[2026-05-12]
- Initial release. GalleryHub is here. Build photo and video galleries that look like a magazine, not a CMS — each gallery has its own URL, its own design, and its own settings.
- Four hand-crafted designs. Pick from Aperture (Pinterest-style masonry), Reel (cinematic editorial with sidebar + hero), Atlas (Photos.app-style uniform grid with an inspector panel), or Stage (fullscreen slideshow with thumbnail filmstrip). Each works in light and dark mode and lets viewers toggle.
- Per-gallery custom domains. Map any verified domain to one specific gallery so
gallery.yourbrand.com opens your latest collection. Configure under Account → Domains.
- Drag-and-drop multi-file upload. Drop photos and short videos directly onto the upload zone. JPG, PNG, WebP, GIF, AVIF, MP4, WebM, MOV are all supported.
- YouTube and Vimeo embeds. Paste any link and GalleryHub treats it as a media item — zero storage cost, plays inline. Mix uploads and embeds in the same gallery.
- Per-gallery display toggles. Choose what viewers see on each gallery: title, description, author, category, date, tags, click-to-zoom (lightbox), and download. The default is set under Settings → Gallery defaults.
- Storage quota tracker. A live progress bar on the dashboard and on each gallery page shows how much space you've used. Uploads are blocked cleanly when you hit your plan limit — no surprises, no broken files.
- Embed code. Drop a gallery into your own website with a single iframe. Transparent background so it blends with the host page.
- Edit metadata in place. Click any media item to edit its title, description, author, category, and tags. Drag tiles to reorder; pick a cover image for the gallery.
- Staff seats. Invite teammates as view-only staff (when the platform identity layer reopens). Until then, GalleryHub is single-admin per install.
- Notifications via ntfy. Get a push to your phone whenever a new upload lands. Free, no signup, just install the ntfy app and subscribe to your auto-generated topic.
Internal
- The four design files (
views/public/styles/*.php) are vanilla-JS ports of the original JSX designs at apps/galleryhub/source/_extracted/. The Stage design is new — fullscreen slideshow with thumbnail filmstrip, keyboard nav, autoplay toggle. Palettes for all four (light + dark) live in galleryhub_palette().
- File uploads land under
users/{uuid}/uploads/galleryhub/{galleryId}/{random}.{ext} and are served via the platform's .htaccess rewrite for sandbox + bandwidth accounting.
- Storage is gated through
block_if_storage_full($userId) (HTTP 507) and refreshed after every upload/delete via refresh_storage_used($userId).
- Image dimensions are read with
getimagesize(). Video dimensions/duration are best-effort via ffprobe if installed; otherwise null — the public view degrades cleanly.
- YouTube and Vimeo URLs are sniffed at insert time; the video id is extracted for clean iframe construction (
youtube.com/embed/{id}, player.vimeo.com/video/{id}).