Releases: JustFlows/justflows-ce
Release list
v0.2.7
[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, andjustflows.demo.restorehooks. -
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. Requirescontent: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:imageis now an absolute URL. -
SDK:
ctx.mediaplaceholders.ctx.media.placeholder(kind)andplaceholderHtml(kind)return the site's placeholder from a block'srender().ctx.media.registerPlaceholder()ships a default for a plugin's own kind, and themedia.placeholderfilter replaces or clears one. See docs/PLUGINS.md. -
Shop page.
/shoplists 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), andshowWhen(hide the field unless another prop matches). SeePluginBlockFieldand 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/cartlists 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/checkoutcollects 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
customeruser 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:managecan 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.gatewaysfilter. -
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.carriersfilter. 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.excludefilter 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.htaccessand_nginx.confroute them to the app. WithSTATIC_EXPORT_ORIGIN_URLset, 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 withusers:readcan 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/:idreturns 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
additionalRolestoPATCH /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 migration0035_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(), andsession.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 needusers:manage, likectx.users.create. Plugin HTTP sessions now includeroles, 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...
v0.2.6
[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 andloadConfig()re-read the stale value from disk.
v0.2.5
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 foundeven 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; thejustflows.zip/SBOM/checksum below and thev0.2.5tag 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 gains403,410,429, and a
sharederrorfallback slot alongside the existing404; themes can ship
templates/403.json/410.json/429.json/error.jsonthrough 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 sentCache-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-bootserver.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,
thenAccept-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
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 newcomments.spamBackendSDK 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/contentrequests 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
[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.shshipped the full monorepopnpm-lock.yamlin release
archives alongsidepackage.jsonmanifests thatprepare-hosting.jshad
already strippeddevDependenciesfrom for npm hosting, so
pnpm install --frozen-lockfilerejected the mismatch on any host with pnpm
onPATH. Release archives no longer includepnpm-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.jsonthe package actually ships.
v0.2.2
[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
/searchpages and thecore.searchblock
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/sizeswith format fallback, intrinsicwidth/heightto prevent
layout shift, andloading="lazy"/decoding="async"defaults with an
eageropt-out (fetchpriority="high") for above-the-fold images; the
exportedrenderResponsiveImage/renderMediaImagehelpers 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.envasJF_IMAGE_*with no restart. SVGs are never
rasterised, variants live under the sameuploads/path so CDN/S3 offload and
static export cover them for free, and migration0027_media_responsive
supports all database dialects. Seedocs/MEDIA.md.
(#103) -
Headless federated management API. Revocable API keys (Admin → Settings →
API) authenticate a versioned/api/manage/v1surface 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 visiblejfk_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/eventspublishes the event catalog with payload
schemas, and a key withsettings:manageself-registers and manages its own
webhook endpoints. The full surface is described by abearerAuthOpenAPI 3.1
document atGET /api/manage/v1/openapi.json. Migration0026_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'stemplates/404.jsonwhen 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
[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/adminand 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. Migration0025_redirect_managersupports 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=1iframe, and one-click design presets. Each menu carries a
layout/design contract (designcolumn, migration0024_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 NULLdesignrenders the built-in defaults, so every
existing menu is unchanged. Edits autosave to adraft_items/draft_design
working copy that only the preview path reads; Publish promotes it and
clears the draft. New front-end: thepartials/nav-menu.ejsrenderer,
/js/site-nav.js(desktop flyouts, off-canvas mobile drawer, keyboard
navigation,prefers-reduced-motion), and the.jf-navstyles shipped in the
default theme'sglobal.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,titletext, and extrareltokens (nofollow,
sponsored,ugc,external—noopener noreferreris always added for
target="_blank"). Amegalayout's top-level items hold multi-column
regions whose content is authored as blocks, sanitized on write against a
fixed safe-block allowlist (nocore.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 shareddata-jf-devicesCSS primitive
now emitted into/theme.cssfor every theme.
(#61) -
SDK:
menu.design.presetsandmenu.visibility.evaluatefilters for plugins
and themes,MenuDesignSeed/MenuDesignPresettypes, and the menu
layout / mobile-pattern / activation / alignment enums plus the mega-menu
safe-block allowlist exported from@justflows/sdkas the single source the
host re-exports.navigation.itemsnow 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
thecontent:readpermission.ctx.i18n.defaultLocale()/ctx.i18n.locales()— the site's configured
locales, read-only.- The
html.headfilter context gainslocale(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/seoredirects to/admin/plugins/justflows.seo/settings, so the SEO
Toolkit plugin can contribute a "SEO" nav entry with a dotlessadminMenu
path (its id has a dot, which the manifest validator rejects in a path).- The hard-coded
justflows.seosettings 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 — thetaxonomies/termstables 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 withCache-Control: no-cacheinstead of
a day-longmax-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
[0.2.0]
Fixed
- Core updates (Admin → Updates, both the uploaded
justflows.zipand 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(newGET /api/updates/status). A file
lock rejects a second concurrent update with409, 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 itsfetches carry timeouts. Dependency install
now usespnpm --frozen-lockfile(matching the workspace) instead of
npm installat 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
Added
- Plugin-owned admin apps. A plugin can declare
adminAppin 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
overpostMessagevia the new@justflows/admin-bridgepackage — 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/submitendpoint, thejustflows.forms.formrender
special-case and/js/jf-forms.jsinjection inpublic-site.ts, the
plugin-runtimeactivation skip, theadmin-menu/plugins-dbfallbacks,
and the admin-UIFormsPage+BlockInspectorpicker were all removed. The
plugin (plugin-registry-service/plugins/formsv0.2.0) ships its own block,
submit route, admin app, and public enhancement script. The additive,
permission-gatedctx.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 themed404, and the
/theme.cssbuild toSTATIC_EXPORT_DIR(default./static-export), plus a
_static-export.jsonmanifest with per-filesha256andCache-Control
advice, plus a Cloudflare Pages / Netlify_headersfile 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_ENABLEDswitch, 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=1re-runs a targeted incremental export (and
prunes removed pages) after publish, unpublish, delete, menu, theme, or
settings changes, debounced via the existingcache.revalidatedaction.
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,/apiand 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.assetsfilter adds URLs the scanner cannot see. Plugins can now ship client-side assets first-class: a
manifest.assetsblock ({ dir?, scripts?, styles? }) makes the host serve
<dir>/**at/ext/<id>/**— noctx.httproute orhtml.headfilter —
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=0for one tag per file). A marketplace plugin's
front-end lands in the export automatically as that single bundle
(plugins/hello-worlddemonstrates it). Pageview analytics keep counting: the
Analytics plugin ships ajf-analytics.jsbeacon viamanifest.assets, so it
rides the/jf-plugins.<hash>.jsbundle into the export with no
analytics-specific code in the exporter. The exporter only stamps every page
with a genericwindow.__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/collectingest 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
additivePluginHttpResponsefields back this:revalidate: trueruns the
site-wide cache revalidation thatPUT /api/plugins/<id>/settingsdoes (so
saving Consent settings regenerates the export under auto-rebuild, not only
after a manual full export), andcors: trueadds
Access-Control-Allow-Originfor 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 byfetch()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
localhostoff production). Keep the submit endpoint reachable via a hybrid
proxy orSTATIC_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.deploySDK 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
additivectx.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 toSTATIC_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/healthzwithout
theinstalledflag 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/installwhen the site genuinely is not set up, so
an installed site behind any intercepting layer still exports. The exporter
also retries once againstAPP_URL, and the failure message now names every
origin it tried.
(#24)
v0.1.8
Changed
-
Database migrations no longer ship a separate
.mariadb.sqlfile. The runner
resolves MariaDB toNNNN_name.mariadb.sql, then the MySQL file, then the bare
.sql(migrationFileCandidatesinrun-migrations.ts) — the DDL for the two
has been byte-for-byte identical in every tracked migration. The redundant
0013–0016.mariadb.sqlfiles are removed (MariaDB now reads the identical
.mysql.sql);0012_baselinekeeps its three-file set. Existing installs are
unaffected: those migrations are already recorded in_migrationsand 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 ofsite_settingsinto a dedicated
theme_designstable — one row per (site, theme, kind) with adoc/
draft_docpair, the same shape astemplate_parts.site_settingsis for
site-level preferences, not theme/plugin configuration (plugins already use
plugin_data). Migration0015_theme_designsadds 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 undertemplates/(index,front-page,single,
single-<type>,page,page-<slug>,singular,archive,404, …) and
shared chrome underparts/; 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_templatestable (migration0023_templates, draft/publish/reset like
template_parts) and a per-slug override still yields to a more specific theme
file. The bundled Default theme shipsindex/front-page/single/
page.demo/home.json,demo/blog.json, anddemo/footer.jsonstay as
back-compat fallbacks for thefront-page/home/footerslots. New CLI:
justflows theme templatesandjustflows 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:managecapability pair, separate
frommail: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:readand
mail:managecapabilities 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 respectsprefers-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.justflowsConsentAPI 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 noplugin_datarows 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 synchronousanalytics.headfilter 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 throughctx.cookies.declare({ name, category, purpose, … })— one
ofnecessary/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 sharedengines.justflowsmanifest range is enforced
by the installer before a package leaves staging; plugins additionally receive
explicit Justflows, SDK package, and SDK API versions throughctx.runtime;
and CI snapshots the public SDK export surface so an export cannot disappear
without review and the required deprecation cycle. Existing top-level
justflowspackage 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...