Skip to content

Releases: JustFlows/justflows-ce

v0.2.7

Choose a tag to compare

@NOOBBase NOOBBase released this 30 Sep 12:42
d50ddf6

[0.2.7]

Added

  • Polish, Ukrainian, and Russian. Added public-site and admin interface translations, including navigation, comments, search, app installation prompts, and error and maintenance pages. All three languages are available in the admin language selector and server-rendered admin pages. Ukrainian and Russian are also offered in the installation language picker.

  • Demo mode. The Demo mode plugin adds a Demo role and a demo sign-in for trying the site. That account can edit content. It is not an administrator, so settings, users, plugins, and themes stay closed to it, and the plugin never edits the administrator account. On a schedule it deletes the content, media, and comments that demo user created, then asks the plugins an administrator selected to restore their own data through the justflows.demo.targets, justflows.demo.snapshot, and justflows.demo.restore hooks.

  • SDK: ctx.content.deleteCreatedBy(userId). Permanently deletes content that user authored, media they uploaded, and comments they wrote, and drops their unpublished working revisions on other entries. It does not delete the user, and it refuses an administrator. Requires content:delete.

  • Placeholder images. Justflows ships neutral placeholder images for a generic image, a featured image, a thumbnail, an avatar, and the social share image. Featured Image and Post List show the placeholder when a post has no image, an Image block without an image shows the generic one, and a page with no share image falls back to the site logo and then the share placeholder. Settings → Placeholders replaces any placeholder with your own image or turns them all off, and Featured Image and Post List can each turn them off. og:image is now an absolute URL.

  • SDK: ctx.media placeholders. ctx.media.placeholder(kind) and placeholderHtml(kind) return the site's placeholder from a block's render(). ctx.media.registerPlaceholder() ships a default for a plugin's own kind, and the media.placeholder filter replaces or clears one. See docs/PLUGINS.md.

  • Shop page. /shop lists published catalog products. A sidebar can hold search and category and tag filters. Products show in a grid by default, or as a list. Commerce → Shop page turns the sidebar, search, each filter, the default layout, and the visitor layout switch on or off.

  • Shop blocks choose from the catalog. Product list and Related products show all products, products related to a product, products you pick, or products from chosen categories or tags, with a sort order and a product count. Gallery, Buy box, Breadcrumbs, and the details accordion can show a picked product on any page. Breadcrumbs follow the product's category, and every shop block field has a label and help text.

  • SDK: richer plugin block fields. A block schema field can set label, help, optionLabels, optionsUrl (choices loaded from a same-origin route), multiple (a checklist saved as a string array), and showWhen (hide the field unless another prop matches). See PluginBlockField and docs/PLUGINS.md.

  • Product pages sell the catalog item. A product detail page shows that product's price, stock, and variations. Choosing an option such as size updates the price, SKU, and stock, and Add to cart stores the selected variation. Related products come from the catalog. The product template no longer repeats the blog title and date.

  • Shop cart page. /shop/cart lists each product with its photo, options, unit price, and line total. Quantities can be changed or removed, the subtotal follows the catalog tax display, and checkout stays closed until every line can be sold. Shipping is left for checkout. The cart template no longer shows the blog date and excerpt.

  • Shop checkout page. /shop/checkout collects contact, shipping, and billing, then prices delivery, tax, and discounts from the shop settings. Country lists follow the selling and shipping countries. Guest checkout closes when the shop requires an account. Enabled payment methods are offered without collecting card details, and a placed order is stored as awaiting payment.

  • Shop registers a Customer role. Activating the Shop plugin adds a customer user role with no administration access. It appears in New User Default Role, user invites, and the user editor, and can be the role new registrations receive. Deactivating Shop removes it from those lists.

  • Shop → Customers can add a customer. The form collects the sign-in account, phone, company, tax ID, invoice address, and shipping address. The new account is also created under Admin → Users with the customer role. Plugins that declare users:manage can create users only in a role they registered.

  • Shop → Customers can edit a customer. Open a customer to change their name, phone, company, tax ID, invoice address, and shipping address. The sign-in address stays on the user account.

  • Shop → Customers can import customers. CSV, JSON, and XML files create or update customers, including invoice and shipping addresses. A new email also creates a user with the customer role. A matching email updates the shop record and leaves the existing sign-in alone.

  • Shop → Payments connects the provider accounts. Turn on Stripe, PayPal, Mollie, Adyen, Square, bank transfer, cash on delivery, or check. Sandbox and live secrets stay in plugin secrets and are not returned to the browser. The page lists payment attempts, captures, cancellations, refunds, and disputes. Another plugin can add a gateway, including Justflows Payments, through the justflows.shop.payments.gateways filter.

  • Shop → Payments has a page per provider. Open Mollie and choose Mollie checkout or the methods active on that profile. Checkout offers one of those, not both. Each method is its own choice and sends the customer straight to that method. Payment method logos at checkout can be turned on or off.

  • Mollie payments include the shop order. Starting a Mollie payment sends the Justflows user id, the shop customer and order ids, billing and shipping addresses, the visitor's language, a cancel link, and the order lines with tax. The ids are stored in the payment's metadata.

  • Shop → Shipping calls DHL, PostNL, and DPD. Test connection asks DHL Express for live rates, PostNL for delivery options, and DPD to sign in and list pickup points. Sandbox mode uses each carrier's test host. PostNL and DPD do not return a contract price, so the rate on the zone is still what the customer pays for those two.

  • Shop → Shipping covers zones and carriers. Add shipping zones by country, postcode, or the rest of the world, then offer flat rate, free shipping, local pickup, or a carrier service. DHL, DPD, and PostNL are included. Another plugin can add a carrier through the justflows.shop.shipping.carriers filter. Carrier API keys stay in plugin secrets and are never sent back to the browser.

  • Plugins can keep pages out of the static export. The new staticExport.exclude filter lists paths the exporter leaves to the live app, such as a cart, checkout, or account page. Those paths are never crawled, even when linked. Copies from earlier runs are removed, and the generated .htaccess and _nginx.conf route them to the app. With STATIC_EXPORT_ORIGIN_URL set, links and form actions that point at them go to that origin. Shop uses it for its cart, checkout, order confirmation, customer account, and order tracking pages. See docs/STATIC-EXPORT.md. (#24)

  • Admin → Users opens a user page. Click a user's name or row to open /admin/users/<id>. The page shows the account ID, role, created and last-updated dates, whether two-factor sign-in is on, the user's effective capabilities, and their authored content with counts by status and links to the 10 most recently updated items. Editors with users:read can now open this page too, read-only. Administrators also see the user's 20 most recent audit entries, IP addresses included, and can download the user's personal data. GET /api/users/:id returns these fields.

  • Users can have additional roles. A user keeps one main role and can also hold other built-in or plugin roles, such as a subscriber who is also a shop Customer. Tick them under Additional roles on the user page, or send additionalRoles to PATCH /api/users/:id. Additional roles only add capabilities. Admin-only checks and the last-administrator guard still read the main role, and Administrator can only be a main role. An author or contributor can still edit only their own content when the role is an additional one. Admin → Users shows the extra roles next to the main one. Adds migration 0035_user_additional_roles.

  • Existing users can become shop customers. Adding a customer in Shop → Customers, or importing one, with an email that already signs in now links that user and gives them the Customer role. Before, this failed with "already exists". Their password and main role stay unchanged. A signed-in user who places their first order also gets the Customer role. The other way round works too: anyone who gets the Customer role, as main or additional role, from Admin → Users, an invite, or sign-up, now appears in Shop → Customers.

  • SDK: ctx.users.addRole(), ctx.users.get(), and session.roles. ctx.users.addRole({ userId } | { email }, role, actor) gives an existing user one of the plugin's own roles as an additional role. ctx.users.get(userId) reads a user with all their roles. Both need users:manage, like ctx.users.create. Plugin HTTP sessions now include roles, with the main role first. All three are optional on older hosts. See docs/PERMISSIONS.md.

  • Plugin errors show up in Diagnostics. Every plugin's ctx.logger.error() call, every exception from a plugin hook handler, and every uncaught plugin route failure now lands in Admin → System → Diagnostics → Recent errors, ta...

Read more

v0.2.6

Choose a tag to compare

@NOOBBase NOOBBase released this 24 Sep 10:11
9f82318

[0.2.6]

Added

  • Inline images, footnotes, and math formulas in the paragraph/quote editor.
    The inline rich-text toolbar gained an image button (upload a file, paste a
    URL, or pick from the media library), strikethrough, and a "More formats"
    menu covering inline code, highlight, subscript/superscript, keyboard
    input, language markup, footnotes, and inline math (LaTeX, rendered with
    KaTeX). Footnotes are numbered in document order and collected into a
    footnotes list at the end of the page on the public site; math renders
    identically — self-hosted, no external requests — in both the editor's
    canvas and the published page.

Fixed

  • Public navigation accessibility labels and PWA install/update prompts now use the site translation catalogs, while preserving custom install text. (#127)

  • Completed missing translation keys for builder controls, security settings, and update progress in all five admin languages. Restart messages, block counts, and language previews now respect localization; blank translations are filled, and under-construction and static error pages translate their text and declare the correct document language. (#17)

  • Selected paragraph/heading blocks looked cluttered in the page builder.
    A selected-and-focused text block stacked three near-identical highlight
    layers — the block row's own selection background, a redundant duplicate
    outline, and the text field's hover tint — into one oversized, muddy box
    with no visible boundary between "this block is selected" and "this is the
    text field." The editing field now renders as a clean white inset with a
    clear border, and grows a little on focus instead of hugging the text.

  • Site URL changes in Settings could revert after a restart. Saving a new
    site URL only updated the running process's environment, not the .env
    file it was loaded from, so the change was lost the next time the server
    restarted and loadConfig() re-read the stale value from disk.

v0.2.5

Choose a tag to compare

@NOOBBase NOOBBase released this 18 Sep 13:35
52ad837

Post-release update (2026-09-18): the self-hosting installer/updater in this release's assets had a bug — on hosts using nodenv for Node version management, in-app updates and CSS-provider installs could fail with command not found even when a valid Node version was installed, and the core updater never removed files a later release renamed or deleted (harmless on a fresh install, but left stale files behind on an in-place update). Both are fixed as of this update; the justflows.zip/SBOM/checksum below and the v0.2.5 tag now point at the patched commit. No functional/feature changes — same v0.2.5.

Added

  • Configurable public-site PWA. Settings → PWA lets a site owner turn
    their public site into an installable, offline-friendly Progressive Web
    App: app identity and generated icons, theme/background colors, display
    mode, a validated start URL, up to four app shortcuts, an install prompt,
    a branded offline fallback, and bounded static-asset caching. Disabled by
    default; enabling requires no build step, plugin, or file edit. Disabling
    retires the service worker cleanly at its existing URL so installed apps
    recover without manual cleanup. (#127)
  • Customizable 404 and error pages. Theme customize → Error pages lets
    an admin choose, per class (404, 403, 410, 429), what renders: the theme's
    own template resolution, Justflows' built-in page, or a specific published
    page — offered once per translation group, so the matching locale renders
    automatically. The template hierarchy gains 403, 410, 429, and a
    shared error fallback slot alongside the existing 404; themes can ship
    templates/403.json / 410.json / 429.json / error.json through the
    same per-site override mechanism (draft/publish/reset) 404 already had. The
    chosen source never changes the HTTP status — a selected page still answers
    403/404/410/429, it cannot 200 a blocked or missing resource — and every
    error response is sent Cache-Control: private, no-store. 500 and a new,
    separate maintenance mode (Settings → Site visibility, distinct from
    "Site is live") always render a static, dependency-free page with an
    admin-editable heading/message and no database, cache, or plugin-runtime
    access, so they still work during a database outage; the pre-boot server.js
    layer's boot-failure response no longer leaks the underlying error message
    and now renders the same branded page. Both the static fallback and the
    built-in 404/403/410/429 pages are localized from the request (URL prefix,
    then Accept-Language) across the site's bundled languages. A real 410 now
    fires for a URL whose page was trashed (soft-deleted) rather than never
    existing, until trash retention expires it into a normal 404; the public
    site's global and search rate limiters now answer 429 with the themed page
    instead of plain JSON/text. Admin-provided heading/message text is
    sanitized before storage and HTML-escaped at render; built-in copy never
    reflects the request path, query, or headers.
    (#92)

v0.2.4

Choose a tag to compare

@NOOBBase NOOBBase released this 17 Sep 08:30
de8c0c1

Added

  • Comment spam filtering and submission throttling. Public comment
    submissions now go through a local heuristic scorer (links, keywords,
    repeated text, disposable email domains, a render-age token, and terms
    learned from "mark as spam") that auto-approves, holds, or auto-marks spam
    per configurable score thresholds in Settings → Discussion → Spam
    protection. Signed-in commenters get a separate rate-limit bucket alongside
    the existing per-IP one. Admins get author/domain/IP/phrase block and allow
    lists (with one-click "Block email/domain/IP" actions from the moderation
    queue), a "hold a first-time commenter" option, an "auto-approve a
    returning commenter" option, and a configurable spam retention/purge
    window. A new comments.spamBackend SDK filter lets a plugin attach an
    external spam-scoring service (Akismet-style) without becoming a hard
    dependency — the host always applies its own thresholds. All checks run
    server-side; blocklist/allowlist changes are audit-logged.
    (#109)

Fixed

  • Admin content list and menu/content pickers hid content in non-default languages. Admin → Content, the "add items" picker in Menus, and the page picker in the menu designer all scoped their /api/content requests to the site's default published language, so editors couldn't find or select pages/posts written in any other language. These admin views now list content across every language; Admin → Content gained a language filter chip row (shown once the site has more than one language), and the menu content picker shows each item's locale next to its slug.

  • Replying to a comment on a translated post could fail with "Cannot reply to that comment". The public comment thread lists comments from every translation of a post — a comment left on one language shows under every locale of that post — but submitting a reply checked the parent comment's content id against only the page currently being viewed, rejecting replies to a comment that lived under a sibling translation. Reply submission now matches the parent against the whole translation group, the same grouping already used to render the thread.

v0.2.3

Choose a tag to compare

@NOOBBase NOOBBase released this 15 Sep 09:22
9d062db

[0.2.3]

Added

  • Scheduled publishing and expiry. Schedule posts, pages, custom content and
    working revisions from the editor, with browser/site timezone display, a
    filterable publishing agenda and signed draft-preview links. Database-backed
    jobs catch up after restarts and publish or expire each entry independently of
    its translations. Actual transitions update webhooks, feed hooks, sitemap and
    caches, with revision history and schedule audit entries. (#98)

  • Force reinstall from Admin → Updates. A "Force reinstall" button
    re-downloads and reapplies whatever the update gateway currently publishes
    as latest, even when it's the version already installed, to repair a
    corrupted core install (a bad copy, an interrupted dependency install, a
    manually edited file) without waiting on a new release. It runs the exact
    same verified pipeline as a normal remote update — checksum and signature
    checks included — and still restarts the site.

Fixed

  • Core update dependency install could fail with ERR_PNPM_OUTDATED_LOCKFILE.
    scripts/make-zip.sh shipped the full monorepo pnpm-lock.yaml in release
    archives alongside package.json manifests that prepare-hosting.js had
    already stripped devDependencies from for npm hosting, so
    pnpm install --frozen-lockfile rejected the mismatch on any host with pnpm
    on PATH. Release archives no longer include pnpm-lock.yaml, and applying
    a core update now removes a stale one left by an earlier release before
    installing, so dependency install falls through to the npm +
    package-lock.json the package actually ships.

v0.2.2

Choose a tag to compare

@NOOBBase NOOBBase released this 11 Sep 08:57
fac4d55

[0.2.2]

Added

  • Diagnostics: email and storage test actions. Admin → System →
    Diagnostics' "Test services" panel now covers email (sends a real test
    message through the configured transport to the site's admin address) and
    storage (a write/read/delete round trip against the configured uploads
    directory) alongside the existing database, cache, and jobs checks. The
    server-host reproduction commands are now copyable with one click.
    (#57)

  • Built-in site search. Public /search pages and the core.search block
    support locale, type, taxonomy/date filters, relevance, highlighting, and
    pagination. Admin content search uses a shared incremental database full-text
    index with access scopes; Tools adds visibility settings and index rebuild.
    The headless search API and documented plugin backend interface preserve
    live publication checks, with rate limiting and opt-in anonymous metrics stored in the database (no console or file logging).
    (#101)

  • Automatic responsive images and modern formats. Raster uploads now
    generate a configurable set of width-scaled variants plus WebP (and AVIF when
    enabled) alongside the untouched original, inline on upload and backfillable
    from a new Admin → Tools → Responsive images job with progress and
    per-file failures. Every public surface that renders an uploaded image now
    goes through one resolver — core.image, the Gallery block (grid,
    masonry, carousel, slideshow, list, and lightbox), blog-post-list featured
    thumbnails, and the Featured Image theme block — emitting <picture> /
    srcset / sizes with format fallback, intrinsic width/height to prevent
    layout shift, and loading="lazy" / decoding="async" defaults with an
    eager opt-out (fetchpriority="high") for above-the-fold images; the
    exported renderResponsiveImage / renderMediaImage helpers give plugin and
    theme blocks the same output. Each asset carries a focal point, set by
    clicking the subject in the Media Library, that drives thumbnail crops and
    object-position. Generation, a public-markup toggle
    (JF_IMAGE_RESPONSIVE_MARKUP — serve originals everywhere without deleting
    variants), AVIF, quality, widths, max dimension, EXIF/GPS stripping, thumbnail
    size, and a keep-original filename allowlist are all configurable in the same
    panel and written to .env as JF_IMAGE_* with no restart. SVGs are never
    rasterised, variants live under the same uploads/ path so CDN/S3 offload and
    static export cover them for free, and migration 0027_media_responsive
    supports all database dialects. See docs/MEDIA.md.
    (#103)

  • Headless federated management API. Revocable API keys (Admin → Settings →
    API) authenticate a versioned /api/manage/v1 surface that federates content
    (CRUD, publish, revisions), media, comments, menus, content types, users,
    roles, settings, languages, redirects, plugin/theme listing and activation,
    cache and static-export triggers, and diagnostics/health behind the same
    capability checks as the admin UI — no parallel business logic. Each key
    carries an explicit capability set never broader than its creator's,
    re-checked against the owner's current access on every request, plus optional
    AccessScope, expiry, and allowed-IP / allowed-origin lists; the secret is
    shown once and stored only as a hash with a visible jfk_ prefix. A master
    switch and per-key / global rate limits apply without a restart, wildcard CORS
    never reaches an authenticated route, and every create / rotate / revoke and
    auth failure is audited by key id. Admin → Settings → API is now the single
    home for both HTTP-API switches — the public-content API toggle moves here
    from Admin → Settings. GET /api/manage/v1/events publishes the event catalog with payload
    schemas, and a key with settings:manage self-registers and manages its own
    webhook endpoints. The full surface is described by a bearerAuth OpenAPI 3.1
    document at GET /api/manage/v1/openapi.json. Migration 0026_api_keys
    supports all database dialects.
    (#135)

Fixed

  • Unknown multi-segment public URLs (for example /foo/bar) now render the
    site's themed 404 — the theme's templates/404.json when it ships one,
    otherwise the built-in Justflows 404 — instead of Express's bare
    Cannot GET. Single-segment paths already did this; deep paths fell through
    the public router.

v0.2.1

Choose a tag to compare

@NOOBBase NOOBBase released this 08 Sep 09:43
9da03ea

[0.2.1]

Fixed

  • Admin links and navigation now use the configured custom admin URL, including
    content lists, sidebar links, builder shortcuts, and plugin links opened in a
    new tab. Rendered links preserve query strings and fragments, so copying or
    opening a link no longer falls back to /admin and returns a 404.
    (#51)

Added

  • Redirect manager. Administrators can create, edit and disable exact,
    prefix and restricted-regex redirects with internal, content or validated
    external destinations; import/export CSV; review slug/permalink URL history;
    and turn aggregate public 404 reports into redirects. Loop checks include
    permalink history, safe equal-status chains collapse, and mutations are
    audited. Migration 0025_redirect_manager supports all database dialects.
    (#100)

  • Permalink settings. Administrators can choose post URL presets or custom
    token structures, configure content-type and taxonomy bases, and select a
    trailing-slash policy. Known previous URLs redirect to current URLs with 301s;
    reserved routes and URL collisions are checked before saving. Core public
    links, canonical tags, and sitemap entries follow the active structure.
    (#96)

  • Visual menu designer. Admin → Menus is now a full designer instead of a
    flat link list: a drag-and-drop item tree with indent/outdent and undo/redo, a
    live ?preview=1 iframe, and one-click design presets. Each menu carries a
    layout/design contract (design column, migration 0024_menu_designer) —
    layout (horizontal, vertical, dropdown, multi-level-dropdown, mega,
    footer, drawer), hover/click activation, alignment, a per-menu mobile
    breakpoint with a collapse pattern (dropdown, accordion, drawer-right,
    drawer-left, fullscreen) and enter/exit motion, plus depth and
    items-per-level caps. A NULL design renders the built-in defaults, so every
    existing menu is unchanged. Edits autosave to a draft_items / draft_design
    working copy that only the preview path reads; Publish promotes it and
    clears the draft. New front-end: the partials/nav-menu.ejs renderer,
    /js/site-nav.js (desktop flyouts, off-canvas mobile drawer, keyboard
    navigation, prefers-reduced-motion), and the .jf-nav styles shipped in the
    default theme's global.css.
    (#61)

  • Per-item menu options. Menu items gain a style preset and per-button
    styling (background / text / border colours validated as safe CSS values,
    radius, size, full-width), an icon or image (validated as safe asset URLs), a
    badge, a description line, title text, and extra rel tokens (nofollow,
    sponsored, ugc, external — noopener noreferrer is always added for
    target="_blank"). A mega layout's top-level items hold multi-column
    regions whose content is authored as blocks, sanitized on write against a
    fixed safe-block allowlist (no core.html / core.code / core.embed) and
    rendered through the same block pipeline as page content.
    (#61)

  • Menu item visibility rules. An item can be shown or hidden by visitor auth
    state, role, locale, or a plugin-provided condition. The checks are enforced
    server-side (fail-closed — an unknown or deactivated condition hides the item),
    and a menu that uses any auth/role/condition rule bypasses the shared public
    cache so every request resolves against the real session; the cacheability
    test itself is cached and invalidated on every menu save, so menus without
    rules cost nothing extra. Device targeting (desktop / tablet / mobile)
    is presentation-only, applied with the shared data-jf-devices CSS primitive
    now emitted into /theme.css for every theme.
    (#61)

  • SDK: menu.design.presets and menu.visibility.evaluate filters for plugins
    and themes, MenuDesignSeed / MenuDesignPreset types, and the menu
    layout / mobile-pattern / activation / alignment enums plus the mega-menu
    safe-block allowlist exported from @justflows/sdk as the single source the
    host re-exports. navigation.items now runs after the host resolves a menu
    (visibility applied), so appended items sit alongside the author's. See
    Hooks → Contributing a menu design preset.
    (#61)

  • Platform support for syndication feeds. The RSS 2.0 / Atom 1.0 / JSON Feed
    1.1 feature itself ships in the first-party SEO Toolkit plugin
    (justflows.seo, via the plugin registry); this release adds the host and SDK
    surfaces it needs, all of them generally useful:

    • ctx.content.listPublished(query?) — a plugin can read published entries
      (by type / locale / author / date; scheduled and expired excluded), requires
      the content:read permission.
    • ctx.i18n.defaultLocale() / ctx.i18n.locales() — the site's configured
      locales, read-only.
    • The html.head filter context gains locale (the page's content locale),
      so a plugin can emit locale-aware <head> tags.
    • A manifest may declare hostCooperative: true. The runtime normally leaves
      the first-party ids it renders itself (justflows.seo, …) inactive; the flag
      means the installed module only augments (feed routes, autodiscovery) and is
      safe to activate.
    • /admin/seo redirects to /admin/plugins/justflows.seo/settings, so the SEO
      Toolkit plugin can contribute a "SEO" nav entry with a dotless adminMenu
      path (its id has a dot, which the manifest validator rejects in a path).
    • The hard-coded justflows.seo settings schema was removed from the host — an
      installed SEO plugin now supplies its own.
    • Content editor → SEO tab gains an Exclude from RSS / Atom / JSON feeds
      checkbox for every content type (fields.seoFeedExclude), shown only while an
      SEO plugin that owns feeds is active.
      Per-taxonomy feeds remain open — the taxonomies / terms tables have no
      term-assignment UI or public archive yet.
      (#102)

Changed

  • Hand-authored scripts and styles under public/ (/js/site-nav.js,
    /js/site-chrome.js, …) are served with Cache-Control: no-cache instead of
    a day-long max-age. They sit at stable, unversioned URLs and are not
    content-hashed, so a long TTL pinned stale copies after an update; no-cache
    still keeps the file cached and revalidates cheaply against the ETag (304).
    Content-hashed admin bundles keep their long cache lifetime.
    (#61)

v0.2.0

Choose a tag to compare

@NOOBBase NOOBBase released this 04 Sep 09:53
eb39d54

[0.2.0]

Fixed

  • Core updates (Admin → Updates, both the uploaded justflows.zip and the
    remote "Update" button) no longer hang forever on "Updating…". The
    copy/migrate/pnpm install/build/restart pipeline used to run synchronously
    inside the HTTP request that started it, blocking the single Passenger worker
    for minutes — so the site served nothing (the browser's request, /admin,
    everything 499'd) and, if the process was recycled or OOM-killed mid-run, the
    admin page was left with a request that never resolved. The work now runs in a
    detached worker process (apps/server/dist/lib/core-update-worker.js); the
    request returns immediately and both the caller and the admin UI follow
    progress through .updates/status.json (new GET /api/updates/status). A file
    lock rejects a second concurrent update with 409, a run whose worker has
    died self-heals instead of wedging the UI, the admin page re-attaches to a running
    update after a reload, and its fetches carry timeouts. Dependency install
    now uses pnpm --frozen-lockfile (matching the workspace) instead of
    npm install at the repo root, falling back to npm only when pnpm is not on
    PATH. The one transitional upgrade to this build runs the pipeline in the
    foreground (still writing status) when the worker cannot be bootstrapped from
    the archive.

v0.1.9

Choose a tag to compare

@NOOBBase NOOBBase released this 04 Sep 08:24
caf9711

Added

  • Plugin-owned admin apps. A plugin can declare adminApp in its manifest and
    ship a self-contained HTML build; the host serves it at
    /ext/<pluginId>/admin/** and mounts each declared route in a same-origin
    <iframe> inside the admin shell (PluginHostPage). The two sides talk only
    over postMessage via the new @justflows/admin-bridge package — no shared
    React runtime, no core page, no core route. See
    PLUGINS.md → Ship your own admin app.
    (#24)

Changed

  • Forms is now a fully standalone plugin. The host no longer implements it:
    lib/forms-public.ts, routes/forms.ts (/api/forms), the
    /justflows-forms/submit endpoint, the justflows.forms.form render
    special-case and /js/jf-forms.js injection in public-site.ts, the
    plugin-runtime activation skip, the admin-menu / plugins-db fallbacks,
    and the admin-UI FormsPage + BlockInspector picker were all removed. The
    plugin (plugin-registry-service/plugins/forms v0.2.0) ships its own block,
    submit route, admin app, and public enhancement script. The additive,
    permission-gated ctx.mail.send() SDK API lets Forms send submission
    notifications through the host-configured transport without exposing mail
    credentials; a multi-form site loses the block's form picker until a generic
    plugin-block inspector lands.
    (#24)

  • Static / edge export. A new exporter
    (apps/server/src/lib/static-export/) crawls the site's own running server
    over loopback and writes every published page, its assets, locale variants,
    sitemap.xml, robots.txt, favicon.ico, the themed 404, and the
    /theme.css build to STATIC_EXPORT_DIR (default ./static-export), plus a
    _static-export.json manifest with per-file sha256 and Cache-Control
    advice, plus a Cloudflare Pages / Netlify _headers file derived from the
    active Performance suite browser-cache settings. Run it from Admin → System →
    Tools → “Static site export”, pnpm export:static, or
    justflows export static. A master
    STATIC_EXPORT_ENABLED switch, output directory, public base URL,
    dynamic-endpoint origin, crawl limits, and auto-rebuild are editable from the
    Tools page and written to .env (STATIC_EXPORT_*), applied without a
    restart. Clear export (Tools card / pnpm export:static -- --clear)
    deletes the whole output folder; disabling the feature or auto-rebuild leaves
    files on disk. STATIC_EXPORT_AUTO=1 re-runs a targeted incremental export (and
    prunes removed pages) after publish, unpublish, delete, menu, theme, or
    settings changes, debounced via the existing cache.revalidated action.
    Every same-origin sub-resource a page references is downloaded — /theme.css,
    /js/*, uploads, and plugin / custom-theme scripts and assets on their own
    paths (/ext/<plugin>/…, /themes/<theme>/…); only /admin, /api and the
    auth/submit endpoints are skipped. A targeted incremental run also fetches any
    asset a rebuilt page newly references (an image or gallery block added to a
    page) even though it skips re-downloading unchanged ones — previously those
    files 404'd on the static host until the next full export. The
    staticExport.assets filter adds URLs the scanner cannot see. Plugins can now ship client-side assets first-class: a
    manifest.assets block ({ dir?, scripts?, styles? }) makes the host serve
    <dir>/** at /ext/<id>/** — no ctx.http route or html.head filter —
    and concatenate every active plugin's scripts/styles into one
    content-hashed /jf-plugins.<hash>.{js,css} bundle
    added to each public
    page (PLUGIN_ASSETS_BUNDLE=0 for one tag per file). A marketplace plugin's
    front-end lands in the export automatically as that single bundle
    (plugins/hello-world demonstrates it). Pageview analytics keep counting: the
    Analytics plugin ships a jf-analytics.js beacon via manifest.assets, so it
    rides the /jf-plugins.<hash>.js bundle into the export with no
    analytics-specific code in the exporter. The exporter only stamps every page
    with a generic window.__JF_ORIGIN__ hint; the beacon acts on that stamp
    (absent on the live server render, where views are counted server-side) and
    POSTs to the /justflows-analytics/collect ingest endpoint (rate-limited,
    CORS). The Cookie Consent runtime ships the same way, and its two calls (the
    record beacon and the cookie-disclosure fetch) resolve against
    window.__JF_ORIGIN__ too, so a split-origin export still reaches them. Two
    additive PluginHttpResponse fields back this: revalidate: true runs the
    site-wide cache revalidation that PUT /api/plugins/<id>/settings does (so
    saving Consent settings regenerates the export under auto-rebuild, not only
    after a manual full export), and cors: true adds
    Access-Control-Allow-Origin for a vouched-for export origin on a public read
    the runtime fetches cross-origin (the Consent cookie-disclosure route). The
    Forms enhancement script ships the same way. Form blocks now
    submit in place: jf-forms.js
    posts by fetch() and shows the confirmation without leaving the page
    (native <form> POST is the no-JS / CAPTCHA fallback); /justflows-forms/submit
    gained a JSON response mode and CORS for allowed origins
    (APP_URL / STATIC_EXPORT_BASE_URL / STATIC_EXPORT_ALLOWED_ORIGINS, plus
    localhost off production). Keep the submit endpoint reachable via a hybrid
    proxy or STATIC_EXPORT_ORIGIN_URL (which also rewrites <form action> to an
    absolute URL). Object-storage/CDN deployment is documented (aws s3 sync /
    rclone / rsync + manifest-driven invalidation) with additive
    staticExport.routes / staticExport.assets / staticExport.formAction /
    staticExport.completed / staticExport.deploy SDK hooks. See
    docs/STATIC-EXPORT.md.
    (#24)

  • The page builder now has a categorized block-pattern library with theme-width
    previews, editable insertion, six accessible token-driven section starters,
    local and optionally synced site patterns, locale variants and RTL-aware UI,
    validated JSON import/export, additive theme SDK registration, required-block
    checking, ctx.patterns.register() contributions with automatic plugin
    lifecycle cleanup, and a bounded, sanitized opt-in marketplace directory.
    (#110)

  • Admin → System → Diagnostics completes the developer-tooling workflow with
    plugin scheduler status and safe failed-job retries, non-destructive database,
    cache, and scheduler test actions, copyable CLI reproduction guidance, and an
    additive ctx.diagnostics.register() SDK for permission-gated, namespaced,
    sanitized plugin health checks. This builds on the debug toolbar, request
    traces, support bundles, and core diagnostics shipped in 0.1.8.
    (#57)

Fixed

  • Static export on a proxied host (Passenger, Plesk) no longer fails with
    "The site at http://127.0.0.1:3000 is not installed yet — nothing to
    export."
    The crawl origin now resolves to STATIC_EXPORT_CRAWL_URL (new,
    editable on the Tools page) or, on production, APP_URL — the site's real
    domain — falling back to loopback only in development or when neither is set.
    The production bootstrap wrapper (server.js) answered /api/healthz without
    the installed flag the full app includes, so over the public URL the export
    read the site as uninstalled; the wrapper now mirrors the full app's healthz
    shape. As a belt-and-braces measure the readiness check no longer trusts a
    bare /api/healthz: an unclear health result is cross-checked against /,
    which only redirects to /install when the site genuinely is not set up, so
    an installed site behind any intercepting layer still exports. The exporter
    also retries once against APP_URL, and the failure message now names every
    origin it tried.
    (#24)

v0.1.8

Choose a tag to compare

@NOOBBase NOOBBase released this 02 Sep 14:16
5bef8f9

Changed

  • Database migrations no longer ship a separate .mariadb.sql file. The runner
    resolves MariaDB to NNNN_name.mariadb.sql, then the MySQL file, then the bare
    .sql (migrationFileCandidates in run-migrations.ts) — the DDL for the two
    has been byte-for-byte identical in every tracked migration. The redundant
    0013–0016 .mariadb.sql files are removed (MariaDB now reads the identical
    .mysql.sql); 0012_baseline keeps its three-file set. Existing installs are
    unaffected: those migrations are already recorded in _migrations and never
    re-read, and a fresh MariaDB install applies the same statements as before. A
    future migration adds a MariaDB-specific file only if the DDL must diverge.

  • Per-theme customization documents (Customizer mods, homepage design, blog
    design, plus their draft copies) move out of site_settings into a dedicated
    theme_designs table — one row per (site, theme, kind) with a doc /
    draft_doc pair, the same shape as template_parts. site_settings is for
    site-level preferences, not theme/plugin configuration (plugins already use
    plugin_data). Migration 0015_theme_designs adds the table; a one-time
    application backfill on boot (theme-designs-migrate.ts) copies the legacy
    theme_mods.* / theme_home.* / theme_blog.* (and *_draft.*) rows over and
    deletes them, draft-only customizations included. No API or UI change.

Added

  • Themes gain a WordPress-style template hierarchy. A theme ships one JSON
    block document per slot under templates/ (index, front-page, single,
    single-<type>, page, page-<slug>, singular, archive, 404, …) and
    shared chrome under parts/; the renderer resolves a request to an ordered
    candidate list (template-hierarchy.ts) and uses the first that exists, so a
    theme now owns page structure without touching the core EJS. New context
    blocks — core.post-title, core.post-content, core.post-meta,
    core.post-excerpt, core.featured-image, core.template-part — render the
    current request's content inside a template. Theme builder → Templates
    edits any template visually; edits are stored per-site in the new
    theme_templates table (migration 0023_templates, draft/publish/reset like
    template_parts) and a per-slug override still yields to a more specific theme
    file. The bundled Default theme ships index / front-page / single /
    page. demo/home.json, demo/blog.json, and demo/footer.json stay as
    back-compat fallbacks for the front-page / home / footer slots. New CLI:
    justflows theme templates and justflows theme scaffold <slug>; new SDK
    exports: TEMPLATE_SLOTS, TemplateDocSchema, ThemeTemplatesManifestSchema.

  • Admin → Emails adds a versioned system-email design and template editor with
    global branding, typed variables, locale variants, draft/publish workflow,
    desktop/mobile/plain-text previews, sanitized test sending, safe built-in
    render fallbacks, and core account, password, two-factor, security, and
    administrative templates. Published template/version identity follows each
    delivery into the existing privacy-masked mail diagnostics, while security
    templates remain mandatory. Access is gated by a dedicated
    email-templates:read / email-templates:manage capability pair, separate
    from mail:read / mail:manage, so administrators can grant template editing
    per user or per custom role without exposing the mail delivery log.
    (#63)

  • Admin → Content → Trash adds recoverable, site-scoped deletion for every
    built-in and custom content type, media, comments, and menus. Restore keeps
    revisions and relationships intact and reports slug collisions explicitly;
    configurable per-site retention drives a daily purge job, while manual purge
    and empty-trash actions are administrator-only. Referenced media requires a
    confirmation before permanent deletion, and trash, restore, and purge actions
    are audited. (#108)

  • Admin → Settings → Outgoing mail adds separate From, Reply-To, and envelope
    sender identities, provider transport registration, full test-transport
    responses, privacy-masked delivery/dead-letter logs, manual retry,
    per-type suppression, delivery limits, and SPF/DKIM/DMARC guidance. Transport
    secrets and retry payloads remain encrypted at rest; mail:read and
    mail:manage capabilities control operational access. (#104)

  • Admin → Users now supports site-local custom roles with a capability editor,
    safe built-in defaults, assignment guards, audit events, SDK hooks, and
    capability-first enforcement across the user and content APIs.
    (#22)

  • Per-user capability grants and explicit denies can be layered on a role,
    with server-enforced content-type, locale, site, and ownership scopes plus a
    human-readable effective-access preview. Policy changes revoke existing
    cookies and cannot be applied to the acting administrator's own account.
    (#53)

  • Account Security lists database-backed device sessions, marks the current
    device, revokes one session or all other sessions, and makes ordinary logout
    end only the current device. This ships the session-control slice of the
    larger identity roadmap; OIDC/OAuth, SAML, and administrator MFA policy still
    remain before that roadmap item is complete.
    (#54)

  • SDK: access-policy contracts (AccessPolicy, AccessScope, effective
    capability/scope helpers), access-change hooks, resolved capability and
    scope fields on authenticated plugin HTTP sessions, and a runtime capability
    registry (ctx.capabilities.register()). Commerce and other extension-owned
    capabilities are no longer hard-coded in core; only active plugins contribute
    their domains to the role editor and authorization policy.
    (#53)

  • First-party Cookie Consent plugin (plugins/consent): a categorized
    consent banner and preference center (necessary, preferences, analytics,
    marketing) with accept-all / reject-all parity, granular toggles, a keyboard-
    and screen-reader-accessible modal that respects prefers-reduced-motion, and
    a re-open trigger. Every visitor-facing string is stored per site language and
    the runtime picks the visitor's locale from <html lang>; translating the
    banner does not invalidate stored consent. Admin → Extensions → Cookie Consent
    also carries a full design panel — layout (bar / floating box / blocking
    modal), placement (top, bottom, any corner), theme-inherited or explicit
    colours (validated, applied as CSS custom properties), and panel/button radius
    and width — with a live preview. It exposes a first-party
    window.justflowsConsent API that the custom-code injector, Analytics, and
    other plugins can query before loading anything non-essential; gates tagged
    <script type="text/plain" data-jf-consent="…"> snippets and off-site oEmbeds
    behind their category with a per-embed unlock; and stores versioned consent
    records (policy hash, timestamp, choices, locale, coarse device) that are
    exportable as CSV and erasable per record — or turns record logging off
    entirely so no plugin_data rows are written and no beacon is sent. Best-effort EU-only display uses the
    visitor's timezone — no IP lookup, no third-party dependency, all logic and
    storage first-party. A new synchronous analytics.head filter lets the plugin
    defer the Analytics plugin's Google Tag until analytics consent is granted,
    without blocking first paint.
    (#113)

  • SDK: a site cookie registry. Extensions declare every non-essential cookie
    they set through ctx.cookies.declare({ name, category, purpose, … }) — one
    of necessary / preferences / analytics / marketing — and read the full
    resolved registry (host cookies plus every active plugin's) with
    ctx.cookies.list(). Operators re-classify any cookie by name in
    Admin → Extensions → Cookie Consent, stored site-wide
    (GET/PUT /api/cookies). The Cookie Consent plugin uses it to disclose
    cookies per category in the preference center and to expire a category's
    cookies the moment it is withdrawn; window.justflowsConsent.allowed(name)
    resolves a single cookie against it.
    (#113)

  • Admin → System → Diagnostics adds administrator-only runtime, database,
    migration, cache, plugin and typed-hook inspection; correlation IDs on every
    HTTP response; bounded sanitized error retention; a persistent production
    debug-mode warning; and explicitly confirmed, size-limited support bundles
    containing exactly the redacted information previewed in the dashboard.
    (#57)

  • SDK: Published compatibility and deprecation policy for plugins, themes,
    and CSS providers; their shared engines.justflows manifest range is enforced
    by the installer before a package leaves staging; plugins additionally receive
    explicit Justflows, SDK package, and SDK API versions through ctx.runtime;
    and CI snapshots the public SDK export surface so an export cannot disappear
    without review and the required deprecation cycle. Existing top-level
    justflows package ranges remain supported as a deprecated compatibility
    alias.
    (#20)

  • Self-service password reset. A "Forgot password?" link on the sign-in and
    registration screens emails a single-use, time-limited link
    (JF_PASSWORD_RESET_TTL_MINUTES, default 60) that lets an administrator or a...

Read more