Breaking changes — review before upgrading.
Release history
Lowdefy releases
Build internal tools, BI dashboards, admin panels, CRUD apps and workflows in minutes using YAML / JSON on an self-hosted, open-source platform. Connect to your data sources, host via Serverless, Netlify or Docker.
All releases
10 shown
- `@lowdefy/blocks-markdown` now declares `antd` (>=6) as a peer dependency; ensure it is installed in consuming projects.
- PageHeaderMenu gains a default `borderBottom` for visual consistency—override via `styles.header` if needed.
- AI agents with `@lowdefy/ai-utils` runtime, multi‑provider connections (`anthropic`, `openai`, `google`, `ai-gateway`) and `AgentChat` composite block for streaming chat UI.
- `blocks-aggrid` button cell renderer enabling per‑row action buttons with full antd Button configuration.
- Ag‑Grid upgraded to v32.3.9, tag cells support arrays, auto‑colour by default, and suppressed cell focus/overflow clipping.
Full changelog
Highlights
-
AI agents are now first-class in Lowdefy. New
@lowdefy/ai-utilsruntime, a config-drivenAgentChatcomposite block built on Ant Design X, and four new provider connections (@lowdefy/connection-anthropic,@lowdefy/connection-openai,@lowdefy/connection-google,@lowdefy/connection-ai-gateway) let you wire a streaming, tool-using AI agent into any page from YAML. Agents support multi-turn tool calling, server-side hooks (instructions,onStart,onStepStart,onToolCallStart,onToolCallFinish,onStepFinish,onFinish) callable as Lowdefy endpoints, sub-agents exposed as tools, file attachments with S3 upload, reasoning/thinking display, source citation streaming, and context compaction viapruneMessages. Provider-agnostic by design through the Vercel AI SDK. -
AgentChat block: real-time streaming chat UI. Sequential message part rendering with configurable reasoning display, tool approval UI for endpoint and MCP tools marked
confirm: true, file attachments (configurable accept types and max size, S3 upload integration), drawer display mode with aFloatButtontrigger, source citation rendering, Mermaid diagrams, LaTeX, and syntax-highlighted code blocks (toggled viarenderMermaidandcodeHighlighter). Includes copy, feedback, regenerate, and delete message actions, plus suggestions andSender.Header/Sender.Switchaffordances. The newAgentConversationsblock provides a standalone conversations sidebar for independent placement. -
MCP integration: agents can use external tools via Model Context Protocol. New
@lowdefy/connection-mcpconnection type supports HTTP, SSE, and stdio transports. Agents can reference MCP connections viaconnectionIdor inline config with build-time validation. Runtime MCP client creation does automatic tool discovery, merging, and cleanup, and tool approval support viaconfirm: trueworks for both endpoint tools and MCP sources. -
sharedStatetwo-way binding: agents can read and write page state. The built-inupdate-page-statetool lets an agent write to the page state of theAgentChatblock, so the agent can drive UI changes, fill forms, or read context the user has set elsewhere on the page. -
File system agent tools. Sandboxed
listFiles,readFile,searchFiles,statFile, andresolvePathtools give agents scoped access to agent-specific file directories.copyAgentFileSystemsemits anagentFileSystems.jsonmanifest so the production server includes each agent'sfileSystem.basePathin Next.js file tracing, so agents that read files work on Vercel and standalone (output: 'standalone') deployments without manualnext.config.jsconfiguration. -
blocks-aggrid: newcell.type: buttonsrenderer. Render a list of action buttons in a column with each button firing its own block-level event with the row data on the payload. Per-button properties mirror the antd Button block (title,icon,type,variant,color,size,shape,danger,ghost,hideTitle,disabled) plus row-data-path variants (titleField,iconField,disabledField,hiddenField) for per-row state. Use this for inline Edit/Delete/Approve actions without_ifdispatching. -
blocks-aggrid: ag-grid upgraded to v32.3.9. Pulls in two majors of upstream fixes. The column header UX (hamburger column menu with filter popup) is preserved by default; opt into the new ag-grid v32 column menu viacolumnMenu: 'new'on the block. Cell focus is suppressed by default (suppressCellFocus: true) so the keyboard focus outline doesn't visually compete with built-in cell renderers; override withsuppressCellFocus: false. Cell overflow is clipped so flex-rendered content stays inside its column. -
blocks-aggrid: tag cells render one tag per item for array-valued fields. Thecell.type: tagrenderer now accepts an array of strings in addition to a single string. Each item is rendered as its own styled tag and resolves its colour through the existingcolorMap/colorFrom/defaultconfiguration. Empty arrays and arrays containing only null/empty entries render the em-dash placeholder. Single-string values are unchanged. -
blocks-aggrid: tag cells auto-colour by default. When acell.type: tagcolumn is used with nocolorMap,colorFrom, ordefault, tag values are coloured from a stable hash so the same value always gets the same colour across rows, columns, and tables. The palette uses 12 antd named hues (red, volcano, orange, gold, yellow, lime, green, cyan, blue, geekblue, purple, magenta) and respects the active theme. Opt out withcell: { type: tag, default: default }. -
blocks-antd:PageHeaderMenunow has a defaultborderBottom, matching the existing default borders onPageSiderMenuandPageSidebarLayout, for visual consistency across the page menu blocks. The default can still be overridden viastyles.header. -
blocks-markdown:antddeclared as a peer dependency.MarkdownWithCodeimportsantdfor theme-aware syntax highlighting but the package did not declare it inpeerDependencies, causing module resolution failures when bundling apps that include@lowdefy/blocks-markdownwithout already pulling inantd. Now declaresantd(>=6) as a peer dependency.
Changes
feat: Add AI agent support with multi-provider chat and tool use
Packages: @lowdefy/api, @lowdefy/build, @lowdefy/blocks-antd-x, @lowdefy/connection-ai-gateway, @lowdefy/connection-anthropic, @lowdefy/connection-google, @lowdefy/connection-mcp, @lowdefy/connection-openai, @lowdefy/server, @lowdefy/server-dev, @lowdefy/ai-utils
Agent Runtime (@lowdefy/ai-utils)
handleAgentChatorchestrates the full agent lifecycle: tool merging, MCP client lifecycle, hook callbacks, and stream compositionToolLoopAgenthandles multi-turn tool calling, streaming responses, and artifact cleaningcreateAgentUIStreamResponseconverts agent output to a streaming HTTP response for the clientbuildAgentToolsmerges endpoint tools, MCP tools, and sub-agent tools into AI SDK tool objectsbuildPrepareStepenables dynamic tool phasing per stepbuildUpdatePageStateToolprovides a built-in tool for the agent to write to page state via the AgentChat block- File system agent tools:
listFiles,readFile,searchFiles,statFile,resolvePathfor sandboxed access to agent-scoped file directories pruneMessagesfor context compactionexperimental_repairToolCallintegration- Sub-agent support — agents can be exposed as tools to other agents
- Reserved tool name collision detection (e.g.
update-page-state) - Server-side hooks (
instructions,onStart,onStepStart,onToolCallStart,onToolCallFinish,onStepFinish,onFinish) callable as Lowdefy endpoints - Provider-agnostic design using the Vercel AI SDK — supports reasoning/thinking display,
providerOptionspassthrough, and source citation streaming viasendSources - Strip
data:URL prefix from file attachments before AI SDK processing
AgentChat Block (@lowdefy/blocks-antd-x)
- New
AgentChatcomposite block built on Ant Design X with real-time streaming display - Sequential message part rendering with configurable reasoning/thinking display
- Tool approval UI for endpoint and MCP tools marked
confirm: true - File attachment support (configurable accept types and max size) with S3 upload integration
- Drawer display mode with a
FloatButtontrigger for embedding chat on any page - Source citation rendering for
source-urlandsource-documentparts - Mermaid diagrams, LaTeX, and syntax-highlighted code blocks (with copy + language label) — toggled via
renderMermaidandcodeHighlighter - Copy, feedback, regenerate, and delete message actions
- Suggestions and
Sender.Header/Sender.SwitchUI affordances - Configurable roles, avatars, and names per message role
- Event bridging for agent lifecycle events (
onSuccess,onError,onFinish,onFeedback) sharedStatetwo-way binding lets the agent read and write page state via theupdate-page-statetool
AgentConversations Block (@lowdefy/blocks-antd-x)
- New standalone conversations sidebar block, extracted from AgentChat for independent placement
Connection Plugins
@lowdefy/connection-anthropic: Anthropic connection withAnthropicAgentresolver supporting Claude models@lowdefy/connection-openai: OpenAI connection withOpenAIAgentresolver supporting GPT models@lowdefy/connection-google: Google AI connection withGeminiAgentresolver, includingthinkingConfigandsafetySettingssugar props@lowdefy/connection-ai-gateway: Vercel AI Gateway connection withAIGatewayAgentresolver for routing to multiple providers through a single endpoint
MCP Integration (@lowdefy/connection-mcp, @lowdefy/ai-utils, @lowdefy/build)
- New
Mcpconnection type for HTTP, SSE, and stdio transport config - Agents can reference MCP connections via
connectionIdor inline config with build-time validation - Runtime MCP client creation with automatic tool discovery, merging, and cleanup
- Tool approval support via
confirm: trueon both endpoint tools and MCP sources
Build Pipeline (@lowdefy/build)
buildAgentsvalidates agent config (model, tools, sub-agents, MCP) and normalizes tool definitionswriteAgentswrites agent artifacts for server consumption- Sub-agent circular reference detection
- Tool object format with
confirmsupport - MCP
connectionIdnormalization (inline config vs reference) - Lazy module variable resolution for agent properties referenced from modules
- Agent schema validation integrated into the build pipeline
copyAgentFileSystemsemits anagentFileSystems.jsonmanifest so the production server can include each agent'sfileSystem.basePathdirectory in Next.js file tracing — agents that read files now work on Vercel and standalone (output: 'standalone') deployments without manualnext.config.jsconfiguration
API (@lowdefy/api)
- Agent route handler (
callAgent) for streaming agent responses - Endpoint tool execution context with operator evaluation
- Sub-agent resolver methods for agents-as-tools
- MCP
connectionIdresolution at request time getAgentConfigandgetAgentResolverhelpers for runtime agent resolution
Servers (@lowdefy/server, @lowdefy/server-dev)
- Agent API route (
/api/agent/[...path]) added to both production and development servers urlQueryvalidation- 10 MB request body limit for file attachments
- Server-side hooks for agent lifecycle callbacks (
instructions,onFinish)
feat(blocks-aggrid): Buttons cell renderer and ag-grid v32 update.
Packages: @lowdefy/blocks-aggrid
New cell.type: buttons renderer — render a list of action buttons in a column with each button firing its own block-level event with the row data on the payload. Per-button properties mirror the antd Button block (title, icon, type, variant, color, size, shape, danger, ghost, hideTitle, disabled) plus row-data-path variants (titleField, iconField, disabledField, hiddenField) for per-row state. Use this for inline Edit/Delete/Approve actions without _if dispatching.
ag-grid updated to v32.3.9 — pulls in two majors of upstream fixes. The column header UX (hamburger column menu with filter popup) is preserved by default; opt into the new ag-grid v32 column menu via columnMenu: 'new' on the block.
Cell focus suppressed by default — suppressCellFocus now defaults to true so the keyboard focus outline doesn't visually compete with built-in cell renderers (tags, buttons, links). Override with suppressCellFocus: false if needed. Cell overflow is also clipped so flex-rendered content stays inside its column.
feat(blocks-aggrid): Tag cell renders one tag per item for array-valued fields.
Packages: @lowdefy/blocks-aggrid
The cell.type: tag renderer now accepts an array of strings in addition to a single string. Each item is rendered as its own styled tag and resolves its colour through the existing colorMap / colorFrom / default configuration. Empty arrays and arrays containing only null/empty entries render the em-dash placeholder, matching the existing null-value behaviour. Single-string values are unchanged.
feat(blocks-aggrid): Auto-colour tag cells by default for consistent per-value colouring.
Packages: @lowdefy/blocks-aggrid
When a cell.type: tag column is used with no colorMap, no colorFrom, and no default, tag values are now coloured from a stable hash so the same value always gets the same colour across rows, columns, and tables. The palette uses 12 antd named hues (red, volcano, orange, gold, yellow, lime, green, cyan, blue, geekblue, purple, magenta) and respects the active theme.
The grey fallback is still available — set cell: { type: tag, default: default } on any column to opt out. When colorMap, colorFrom, or default is set, behaviour is unchanged.
Fixes & Improvements
-
fix(blocks-antd): Add default header border to PageHeaderMenu. (
@lowdefy/blocks-antd)PageHeaderMenu now has a default
borderBottommatching the existing default borders on PageSiderMenu and PageSidebarLayout, for visual consistency across the page menu blocks. The default can still be overridden viastyles.header. -
fix(blocks-markdown): Declare
antdas a peer dependency. (@lowdefy/blocks-markdown)MarkdownWithCodeimportsantdfor theme-aware syntax highlighting but the package did not declareantdin itspeerDependencies, causing module resolution failures (e.g., when bundling apps that include@lowdefy/blocks-markdownwithout already pulling inantd). Addedantd(>=6) as a peer dependency to match the version range used by@lowdefy/blocks-antd.
First‑class reusable modules, new rich‑text editor blocks (Tiptap), improved S3 upload handling and metadata encoding, holdValue request flag for stale data prevention, and breaking SQLite/MySQL driver upgrades.
Full changelog
Highlights
-
Modules: a first-class system for reusable config packages. Apps can now declare modules in
lowdefy.yamlfrom GitHub repos (github:owner/repo[/path]@ref) or local paths (file:./path), with namespaced page/connection/endpoint IDs, validatedvars, declaredexports, plugin compatibility checks, secret allowlists, connection remapping, and cross-moduledependencies— replacing the copy-paste-between-projects pattern with a declarative dependency. New_module.var/_module.pageId/_module.connectionId/_module.endpointId/_module.idoperators,_ref: { module, component, vars }for reusing config fragments, picomatch glob patterns in auth page rules, and server route support for slashed page IDs (e.g./team-users/users-list). -
Rich-text editing out of the box: new
@lowdefy/blocks-tiptappackage. Two new default blocks —TiptapInput(bold/italic/strike, multi-color highlight, headings, lists, tables, links, bubble menu) andTiptapMentionInput(adds @-mention dropdown from static options or a request). Both emit{ html, text, markdown, fileList, mentions? }and exposeclear/setContent/focusmethods. Configurable per-extension viaproperties.starterKit/image/table/link/highlight/mentions. Image drag/drop and paste are supported by wiringproperties.s3PostPolicyRequestIdto an S3 presigned POST request. Uses the open-source@tiptap/extension-file-handler— noTIPTAP_PRO_TOKENor scoped-registry config needed. -
Server-to-server API calls via
CallApi/InternalApi. API endpoint routines can now call other endpoints in-process (no HTTP) with isolatedsteps/payloadnamespaces and a recursion cap of 10. NewInternalApiendpoint type is blocked from HTTP access; clientCallAPIactions targetingInternalApiget a build warning (error in production).CallApiroutine steps are validated at build time (requireproperties.endpointId, rejectconnectionId). -
_jsoperator now accepts pre-computedargs. Use the{ fn, args }form and resolve values with any Lowdefy operator (_state,_request,_user, nested_js) before the JS runs — values are injected as theargsobject inside the function body. Keeps JS focused on computation while operator lookups stay in YAML. The string form still works, and identicalfnbodies still share a compiled function at build time. -
holdValueflag onRequestandCallAPIactions. UI bound to_request: <id>or_api: <endpointId>retains the previous response while a refetch is loading (and on error), instead of flashing tonull. The error is still observable via_request_details/_api. -
ControlledListgainsonAdd/onRemoveevents. Both fire after the list mutation with{ index, item }—onRemovecaptures the row value before deletion, so handlers can reference deleted data (e.g._event: item._idto delete from a backend). The remove icon now defaults tovar(--ant-color-error)atvar(--ant-font-size-lg)with hover/active states, and list-type blocks now receive avalueprop with the current list data. -
S3 upload blocks: new
onBeforeUploadevent and antd v6 fixes.S3UploadButton,S3UploadDragger, andS3UploadPhotofireonBeforeUploadbefore the upload starts — throw to cancel (validation, confirmation prompts, etc.).S3UploadDraggerproperties.heightworks again on antd v6 and accepts CSS lengths ("50vh","300px"); all three blocks expose antd v6 semantic slots (trigger,list,item) through Lowdefy'sclassNames/stylesAPI and are wrapped inwithTheme('Upload', …)for per-instance Upload token overrides.S3DownloadaddsshowRemoveIconand anonRemoveevent. Migration: styles previously targetingstyle: { .element: { … } }on the inner drag div should move tostyle: { .trigger: { … } }. -
AwsS3PresignedPostPolicyauto URL-encodesx-amz-meta-*fields. S3 user-metadata values containing names, URLs, or non-ASCII characters no longer need to be wrapped with_uri.encodeper request — the connection encodes them before signing. Note: readers of the metadata (e.g. Lambda S3-event triggers) shoulddecodeURIComponentx-amz-meta-*values before use. -
SQL drivers consolidated onto actively-maintained packages.
@lowdefy/connection-knexupgraded knex2.5.1 → 3.2.9, replacedsqlite3withbetter-sqlite312.9.0, replacedmysqlwithmysql23.22.3, and replacedmssqlwith thetediousdriver knex actually loads. Migration: apps usingclient: sqlite3must switch toclient: better-sqlite3(or theclient: sqlitealias); apps usingclient: mysqlmust switch toclient: mysql2— both throw aConfigErrorwith a migration message.client: mssqlis unchanged.better-sqlite3is allowlisted inpnpm.onlyBuiltDependenciesso its native binding builds correctly under pnpm 10. -
Build validation: schema errors are now warnings, with focused per-step validations. AJV schema mismatches no longer block the build — they emit warnings, and dedicated validations in
validateBlock,buildConnections,buildEvents, etc. produce errors with full context (page, block, event names) instead of generic schema messages. New focused validation for connections and menu items. -
Client rendering fixes.
Container,InputContainer, andListno longer create emptycontent[slotKey]functions — blocks using thecontent.X && content.X()pattern correctly render nothing (no wrappingArea) when a slot is empty.Linkvariants (backLink,newOriginLink,sameOriginLink,noLink) now forward thestyleprop, fixing inlinecolor: 'inherit'on header notification/profile/dark-mode rows, the disabled-anchor color, and per-linkstyle:config in menu items. -
Logger crash fix on literal
nulllines.JSON.parsecan returnnullfor"null"input — non-object parsed values are now treated as plain text instead of crashing the CLI log handler.
Changes
-
@lowdefy/api: ### Minor Changes
-
73fa2b9: feat: Internal API endpoint calls
Endpoint-to-Endpoint Calls (
@lowdefy/api)- API endpoint routines can call other endpoints server-side via
CallApisteps, without HTTP - Each called endpoint runs in an isolated context with its own
stepsandpayloadnamespaces - Recursive endpoint call depth is capped at 10 to prevent infinite loops
InternalApiendpoints are blocked from HTTP access — they return the same response as a missing endpoint
Build Support (
@lowdefy/build)CallApiroutine steps validated at build time: requireproperties.endpointId, rejectconnectionIdInternalApiendpoint type accepted alongsideApi- Client-side
CallAPIactions targetingInternalApiendpoints produce a build warning (error in production)
Operator Parser (
@lowdefy/operators)ServerParser.parse()acceptsstepsandpayloadper call for routine context isolation
- API endpoint routines can call other endpoints server-side via
Patch Changes
-
@lowdefy/build: ### Minor Changes
-
73fa2b9: feat: Internal API endpoint calls
Endpoint-to-Endpoint Calls (
@lowdefy/api)- API endpoint routines can call other endpoints server-side via
CallApisteps, without HTTP - Each called endpoint runs in an isolated context with its own
stepsandpayloadnamespaces - Recursive endpoint call depth is capped at 10 to prevent infinite loops
InternalApiendpoints are blocked from HTTP access — they return the same response as a missing endpoint
Build Support (
@lowdefy/build)CallApiroutine steps validated at build time: requireproperties.endpointId, rejectconnectionIdInternalApiendpoint type accepted alongsideApi- Client-side
CallAPIactions targetingInternalApiendpoints produce a build warning (error in production)
Operator Parser (
@lowdefy/operators)ServerParser.parse()acceptsstepsandpayloadper call for routine context isolation
- API endpoint routines can call other endpoints server-side via
-
69a59c0: feat(_js): Pass pre-computed values into
_jsvia anargsobject.The
_jsoperator now accepts an object form{ fn, args }alongside the existing string form. Values inargsare resolved by the parser — using any Lowdefy operator (_state,_request,_user, nested_js, etc.) — before the JavaScript function runs, and are injected as theargsobject inside the function body._js: fn: | const { products, target } = args; return products .filter((p) => p.category === target) .reduce((a, p) => a + p.price, 0); args: products: _request: get_products.data.products target: smartphonesThis lets you precompute or normalize values in YAML and keep the JavaScript body focused on computation, rather than mixing operator lookups into the function. The string form continues to work unchanged, and identical
fnbodies still share a single compiled function at build time — onlyargsvaries per call. -
0f38c9f: feat: First-class module system for reusable config packages
Modules are reusable bundles of Lowdefy config — pages, connections, API endpoints, menus, and exposed components — hosted in GitHub repositories or local directories. Apps install modules in
lowdefy.yamland configure them throughvars, replacing the copy-paste-between-projects pattern with a declarative dependency.Module entries (
@lowdefy/build)- Apps declare entries in the
modulesarray oflowdefy.yamlwithid,source, and optionalvars,connections, anddependencies. - The entry
idnamespaces the module's content and forms the URL prefix for its pages (e.g./team-users/users-list). - Multi-instance: the same module source can be installed multiple times under different entry IDs, each with its own vars and namespace.
- GitHub sources (
github:owner/repo[/path]@ref) are fetched as tarballs and locally cached. Private repos useGITHUB_TOKEN, theghCLI, or git credential helpers. - Local sources (
file:./relative/path) resolve relative to the project root.
Module manifest (
module.lowdefy.yaml)- Declares the module's interface:
name,description,vars,connections,pages,api,components,menus,dependencies,exports,plugins, andsecrets. varsdeclarations validate consumer values withtype,required,default, anddescription. Consumer values override manifest defaults; omitted values fall back to the declared default.exportsdeclares the module's public interface — the IDs other modules and apps may reference. The build validates cross-module references against exports.pluginsdeclarations are validated against the app's installed plugins with semver compatibility checks.secretsis an allowlist of secrets the module may access; undeclared_secretreferences fail the build. Remapped connections skip the module's secret references for that connection.
Module operators
_module.var— read manifest-validated vars, including consumer overrides and declared defaults._module.pageId,_module.connectionId,_module.endpointId— produce scoped IDs from a module-author's unscoped ID._module.id— the entry ID of the current module.
Auto-scoped IDs
Page, connection, API endpoint, and menu item IDs are auto-prefixed with the entry ID. Block and request IDs inherit page scope and are not rewritten.
Consuming module resources
- Pages and APIs are auto-included and auto-scoped — they appear in the app under the entry-ID prefix.
- Components are reusable config fragments included with
_ref: { module, component, vars }. They can export any config — UI blocks, enum maps, config templates, schema fragments — and accept vars at the call site. - Menus are included with
_ref: { module, menu }, typically wrapped in aMenuGroup.
Connection remapping
Apps can redirect a module connection to an existing app connection via the entry's
connectionsmap. The module's connection definition and its declared secrets are skipped — the app connection handles them.Cross-module dependencies
Modules can reference each other's pages, components, menus, connections, and APIs via abstract dependencies declared in
module.lowdefy.yaml.- Auto-wiring: when a module entry's
idmatches a declared dependency name, the build wires it automatically. - Explicit wiring: the entry's
dependenciesmap overrides auto-wiring and supports multi-instance topologies where each instance points at a different partner. - The build validates every wiring, detects dependency cycles, and reports unmapped or undeclared dependencies with remediation hints.
Auth page rules
Picomatch glob patterns in auth page rules (e.g.
team-users/*) for wildcard module page matching.Slashed page IDs (
@lowdefy/server,@lowdefy/server-dev)Server routes support module page IDs containing
/(e.g./team-users/users-list). - Apps declare entries in the
Patch Changes
-
762755c: feat(blocks-tiptap): Add new default block package with
TiptapInputandTiptapMentionInputrich-text editors.@lowdefy/blocks-tiptapships two rich-text editor blocks built on TipTap:TiptapInput— standard rich-text editor with bold/italic/strike-through, multi-color highlight, headings, lists, tables, links, and a bubble menu.TiptapMentionInput— everythingTiptapInputdoes, plus an @-mention dropdown populated from a static options list or a Lowdefy request. Resolved mentions are returned on the block value asmentions: [...].
Both blocks emit an object value shaped
{ html, text, markdown, fileList, mentions? }and registerclear,setContent, andfocusmethods.Configurable extensions — defaults preserve the bundled editor; override any of these to trim the editor down or tune it:
properties.starterKit— object forwarded to TipTap StarterKit, e.g.{ heading: false, codeBlock: false }.properties.image—{ enabled, maxWidth, zoom }properties.table—{ enabled, resizable }properties.link—{ enabled, autolink, linkOnPaste, openOnClick, defaultProtocol }properties.highlight—{ enabled, multicolor }properties.mentions.char/properties.mentions.allowSpaces— change the trigger char (e.g.#for hashtags) or disable spaces inside a mention query (TiptapMentionInputonly).
Image drag/drop and paste are supported by pointing
properties.s3PostPolicyRequestIdat a request that returns an S3 presigned POST policy (e.g.AwsS3PresignedPostPolicy). The file handler is optional — omit the request id to disable uploads entirely.The blocks are registered in the default types map and are available out of the box on
@lowdefy/server-dev. No private-registry tokens are required: the blocks use the open-source@tiptap/extension-file-handlerinstead of@tiptap-pro/extension-file-handler, so projects that migrated from a custom TipTap plugin can drop theirTIPTAP_PRO_TOKENenvironment variable and.npmrcscoped-registry config. -
72b6159: fix(build): Replace schema validation errors with warnings and add focused validations.
AJV schema validation now emits warnings instead of blocking the build. Focused validations in each build step (validateBlock, buildConnections, buildEvents, etc.) provide better error messages with full context — page, block, and event names — instead of generic schema messages. Added focused validation for connections and menu items that previously relied on schema checks alone.
-
@lowdefy/client: ### Patch Changes
-
01e249b: feat(blocks-antd):
ControlledListnow firesonAdd/onRemoveevents and defaults the remove icon to the antd error color at a standard size.Events. Both events fire after the list mutation completes. The event payload is
{ index, item }:onAdd—indexis where the new row was inserted (0foraddToFront: true, elselist.length).itemis the newly added value (typicallyundefinedfor an empty row).onRemove—indexis the removed row's position.itemis the row value captured before removal, so handlers can reference the deleted data (e.g.,_event: item._idto delete from a backend).
- id: tags type: ControlledList events: onRemove: - id: notify type: DisplayMessage params: content: _string.concat: ['Removed at index ', { _event: index }] blocks: - id: tags.$.label type: TextInputRemove icon styling. The remove icon now defaults to
var(--ant-color-error)atvar(--ant-font-size-lg), with--ant-color-error-hover/--ant-color-error-activeon hover/press — no more hardcoded hex colors, and the size no longer swings withproperties.size. Override viaclass.removeIcon/style.removeIcon(both slots target the icon wrapper). Existing configs that hardcodedcolor: '#ff4d4f'onremoveItemIconcan drop it — the default is already danger.@lowdefy/clientalso now passes the list's current state value to list-type block components via avalueprop, so any list block can read its own array data. -
a4ecee5: fix(client): Skip rendering content slots that have no blocks.
Container,InputContainer, andListno longer create acontent[slotKey]function when the slot's blocks array is empty. Blocks that use thecontent.X && content.X()pattern (for optional header, footer, extra, etc.) now correctly render nothing — including no wrappingAreaelement — when the user leaves the slot empty. -
6ec0dd4: fix(client): Forward
styleprop to all Link variants.createLinkComponentpreviously destructured every prop exceptstyle, so any<Link style={...}>passed by a block was silently dropped. Inline style overrides only worked viaclassName+ CSS. All four link variants (backLink,newOriginLink,sameOriginLink— both newTab and same-origin branches — andnoLink) now threadstylethrough to the rendered<a>(or<span>fornoLink).Surfaces fixes in three places that were already passing
styleand silently broken:headerActions.jsnotifications/profile/dark-mode rows hadcolor: 'inherit'that didn't reach the<a>(label rendered as antd link blue);Anchor.jsdisabled state setcolor: '#BEBEBE'that never applied;buildMenuItems.jsper-linkstyle:config was discarded. -
@lowdefy/engine: ### Minor Changes
-
1d18a13: feat(actions):
holdValueflag onRequestandCallAPIactions.RequestandCallAPIactions now accept aholdValue: trueflag that retains the previous response value while a new call is loading. UI bound to_request: <id>or_api: <endpointId>keeps showing the previous response instead of flashing tonullduring a refetch. The previous response is also retained if the new call errors — the error is still observable via_request_details/_api.- id: refresh_table type: Request params: requestId: my_table_request holdValue: trueThe
Requestaction's object-form params now also support{ requestId, holdValue }and{ requestIds, holdValue }shapes alongside the existing{ all }shape.
Patch Changes
-
d105b81: fix(engine): Preserve input values across
visibletoggles when set viaSetState.When an input was inside a hidden container and a
SetStateaction set its value (e.g. ononInitbefore the container becomes visible), the value was silently reset to the input'senforceTypedefault (typicallynull/"") on the nextSetState. The nextSetStatetriggeredRootSlots.reset(), which found the field missing fromcontext.state(becauseSlots.updateStatecorrectly deletes invisible blocks' state fields) and overwrotethis.valuewith the type default.Block.reset()now skips the type-default fallback when the block was hidden in the previous eval cycle and has in-memory state to preserve —this.valuefor inputs, or existingsubSlotsfor lists (which would otherwise be truncated by the rebuild loop reading an emptyenforceType('array', null)). The nextupdateStaterepublishes the value tocontext.stateif the block becomes visible, or leaves the field absent if it stays hidden.This brings
SetState-driven visibility toggles into parity withsetValue-driven toggles, which already preserved the value viaBlock.evaluate. Lists with nested inputs also retain per-item values across hide/reveal cycles.Behaviour change: apps that relied on a hidden input being silently reset to its default by an unrelated
SetStatewill now see the previously-set value preserved. To explicitly clear a value, useSetState({ myInput: null }). Invisible blocks continue to have no representation incontext.state, the user-facingResetaction still producesenforceTypedefaults, andListsub-slot rebuilding onSetState({ list: [...] })is unchanged. -
@lowdefy/operators: ### Patch Changes
-
73fa2b9: feat: Internal API endpoint calls
Endpoint-to-Endpoint Calls (
@lowdefy/api)- API endpoint routines can call other endpoints server-side via
CallApisteps, without HTTP - Each called endpoint runs in an isolated context with its own
stepsandpayloadnamespaces - Recursive endpoint call depth is capped at 10 to prevent infinite loops
InternalApiendpoints are blocked from HTTP access — they return the same response as a missing endpoint
Build Support (
@lowdefy/build)CallApiroutine steps validated at build time: requireproperties.endpointId, rejectconnectionIdInternalApiendpoint type accepted alongsideApi- Client-side
CallAPIactions targetingInternalApiendpoints produce a build warning (error in production)
Operator Parser (
@lowdefy/operators)ServerParser.parse()acceptsstepsandpayloadper call for routine context isolation
- API endpoint routines can call other endpoints server-side via
-
1e964c4: fix(operators): Preserve source location on build operator results.
Build operator results (e.g. from
_build.array.concat) now retain the source file and line number of the expression that produced them. Previously, operator evaluation replaced the expression object with a fresh result, losing the source location markers. This caused build errors inside operator-produced arrays (such as null blocks) to show the file path but no line number. -
@lowdefy/actions-core: ### Minor Changes
-
1d18a13: feat(actions):
holdValueflag onRequestandCallAPIactions.RequestandCallAPIactions now accept aholdValue: trueflag that retains the previous response value while a new call is loading. UI bound to_request: <id>or_api: <endpointId>keeps showing the previous response instead of flashing tonullduring a refetch. The previous response is also retained if the new call errors — the error is still observable via_request_details/_api.- id: refresh_table type: Request params: requestId: my_table_request holdValue: trueThe
Requestaction's object-form params now also support{ requestId, holdValue }and{ requestIds, holdValue }shapes alongside the existing{ all }shape.
Patch Changes
-
@lowdefy/[email protected]
-
@lowdefy/[email protected]
-
@lowdefy/blocks-aggrid: ### Patch Changes
-
186a57d: fix(blocks-aggrid): Suppress cell focus by default and clip overflowing cell content.
The ag-grid cell focus outline visually competed with built-in cell renderers (buttons, links, tags), so
suppressCellFocusnow defaults totrueand can still be overridden per grid. The antd cell wrapper also clips overflowing flex children so long text and inline cell components no longer blow out the cell width. -
@lowdefy/blocks-antd: ### Minor Changes
-
01e249b: feat(blocks-antd):
ControlledListnow firesonAdd/onRemoveevents and defaults the remove icon to the antd error color at a standard size.Events. Both events fire after the list mutation completes. The event payload is
{ index, item }:onAdd—indexis where the new row was inserted (0foraddToFront: true, elselist.length).itemis the newly added value (typicallyundefinedfor an empty row).onRemove—indexis the removed row's position.itemis the row value captured before removal, so handlers can reference the deleted data (e.g.,_event: item._idto delete from a backend).
- id: tags type: ControlledList events: onRemove: - id: notify type: DisplayMessage params: content: _string.concat: ['Removed at index ', { _event: index }] blocks: - id: tags.$.label type: TextInputRemove icon styling. The remove icon now defaults to
var(--ant-color-error)atvar(--ant-font-size-lg), with--ant-color-error-hover/--ant-color-error-activeon hover/press — no more hardcoded hex colors, and the size no longer swings withproperties.size. Override viaclass.removeIcon/style.removeIcon(both slots target the icon wrapper). Existing configs that hardcodedcolor: '#ff4d4f'onremoveItemIconcan drop it — the default is already danger.@lowdefy/clientalso now passes the list's current state value to list-type block components via avalueprop, so any list block can read its own array data.
Patch Changes
-
6ec2cd9: fix(PageSidebarLayout): Pin the sider to the viewport so the bottom actions stay visible.
The sider is now
position: stickywithheight: 100vh, so the menu, notifications, profile avatar, dark-mode toggle, and logo remain on screen as the page content scrolls. The sticky footer container fades from transparent to the container background so content passing behind it doesn't cut off abruptly. -
fd1604f: feat(blocks-antd):
DropdownButtonnow supports the standard Lowdefy event-shortcut schema (events.<eventName>.shortcut) for item shortcuts, alongside the existing item-levelshortcutproperty.When a shortcut is configured via the event, the framework-level shortcut manager binds and fires it — consistent with
Button. The shortcut badge renders next to the item label in both cases. If both are set on the same item, the event-level shortcut wins. The split-button's main action now also renders a badge whenevents.onClick.shortcutis configured.Event-level (preferred, matches
Button):- id: actions type: DropdownButton properties: items: - title: Undo eventName: onUndo events: onUndo: shortcut: mod+z try: - id: undo type: ...Item-level (still supported):
- id: actions type: DropdownButton properties: items: - title: Undo eventName: onUndo shortcut: mod+z events: onUndo: - id: undo type: ... -
cea34ac: fix(PageSidebarLayout): Render notifications, profile, and dark-mode toggle as labeled, left-aligned rows when the sider is expanded.
When the sider is open, the bottom actions now render as
[icon] [label]rows that match the visual style of the menu items above (e.g. "Notifications", "Profile", "Light mode"). When the sider is collapsed, the actions remain as a centered icon stack. Two new optional schema fields —notifications.title(defaultNotifications) andprofile.title(defaultProfile) — let consumers customise the expanded labels; consumers can also bind_user: nametoprofile.titleto show the authenticated user's name.The profile dropdown's default
triggernow depends on whether the sider is expanded:clickwhen expanded (the labeled row invites click),hoverwhen collapsed (original small-avatar behavior). Consumers passingprofile.triggerexplicitly are unaffected.No change to
PageSiderMenuorPageHeaderMenu— their header-bar rendering still uses the icon-only layout andhovertrigger. -
@lowdefy/blocks-loaders: ### Patch Changes
-
c91003d: refactor(blocks-loaders): Modernize skeleton shimmer animation.
The skeleton placeholder now uses a compositor-accelerated
transform: translateXshimmer on a::afterpseudo-element, replacing the olderleft-animated::beforegradient. The shimmer is softer (three-stop gradient over secondary/quaternary fills), the base element usesisolation: isolateto scope its stacking context, andprefers-reduced-motiondisables the animation for accessibility.SkeletonInputandSkeletonParagraphalso switch to a flex-column layout with gap spacing: tighter rhythm between rows, distinct border radii for the label vs input, and a slightly wider trailing line (60%) in paragraphs for a more readable look. Purely visual — no API changes. -
@lowdefy/blocks-tiptap: ### Minor Changes
-
762755c: feat(blocks-tiptap): Add new default block package with
TiptapInputandTiptapMentionInputrich-text editors.@lowdefy/blocks-tiptapships two rich-text editor blocks built on TipTap:TiptapInput— standard rich-text editor with bold/italic/strike-through, multi-color highlight, headings, lists, tables, links, and a bubble menu.TiptapMentionInput— everythingTiptapInputdoes, plus an @-mention dropdown populated from a static options list or a Lowdefy request. Resolved mentions are returned on the block value asmentions: [...].
Both blocks emit an object value shaped
{ html, text, markdown, fileList, mentions? }and registerclear,setContent, andfocusmethods.Configurable extensions — defaults preserve the bundled editor; override any of these to trim the editor down or tune it:
properties.starterKit— object forwarded to TipTap StarterKit, e.g.{ heading: false, codeBlock: false }.properties.image—{ enabled, maxWidth, zoom }properties.table—{ enabled, resizable }properties.link—{ enabled, autolink, linkOnPaste, openOnClick, defaultProtocol }properties.highlight—{ enabled, multicolor }properties.mentions.char/properties.mentions.allowSpaces— change the trigger char (e.g.#for hashtags) or disable spaces inside a mention query (TiptapMentionInputonly).
Image drag/drop and paste are supported by pointing
properties.s3PostPolicyRequestIdat a request that returns an S3 presigned POST policy (e.g.AwsS3PresignedPostPolicy). The file handler is optional — omit the request id to disable uploads entirely.The blocks are registered in the default types map and are available out of the box on
@lowdefy/server-dev. No private-registry tokens are required: the blocks use the open-source@tiptap/extension-file-handlerinstead of@tiptap-pro/extension-file-handler, so projects that migrated from a custom TipTap plugin can drop theirTIPTAP_PRO_TOKENenvironment variable and.npmrcscoped-registry config.
Patch Changes
-
@lowdefy/connection-knex: ### Minor Changes
-
596fddc: chore(connection-knex): update knex and SQL drivers; replace
sqlite3withbetter-sqlite3; replacemysqlwithmysql2.Bumped knex and its dialect drivers, and consolidated onto the actively-maintained drivers — replaced
sqlite3withbetter-sqlite3andmysqlwithmysql2. Subsumes the prior[email protected]darwin-arm64 fix.@lowdefy/connection-knexdependency changes:knex2.5.1→3.2.9. Knex 3.x drops Node < 16; Lowdefy already requires Node 18+. Theknex(config),.raw(), and dynamic query-builder API surface used byKnexRaw/KnexBuilderis unchanged.pg8.11.3→8.20.0.- Removed
mssql. Knex'smssqldialect actually requirestedious(not themssqlpackage), and Lowdefy never importedmssqldirectly — it was only ever a vehicle for pulling tedious into the install tree.client: mssqlin user YAML is unchanged: the knex client name stays the same, only the underlying npm package shipped withconnection-knexchanges. - Added
tedious19.2.1as the SQL Server driver — the package knex actually loads whenclient: mssqlis used. - Removed
sqlite3. The driver is in maintenance-only mode upstream (the v6 release marked the repo unmaintained). - Added
better-sqlite312.9.0as the SQLite driver. Selectable asclient: better-sqlite3(orclient: sqlite, which is now an alias ofbetter-sqlite3— see runtime client handling below). - Removed
mysql. Unmaintained upstream since 2020. - Added
mysql23.22.3as the MySQL / MariaDB driver. Selectable asclient: mysql2in connection YAML.
Runtime client handling (in
createKnex):client: sqliteis silently remapped toclient: better-sqlite3.sqlitewas historically a knex-level alias ofsqlite3; this preserves the YAML alias while the underlying driver changes.client: sqlite3now throws aConfigErrorwith a migration message:Knex connection "client: sqlite3" is no longer supported. Use "client: better-sqlite3" or "client: sqlite" instead.Existing apps usingclient: sqlite3need to update their connection YAML.client: mysqlnow throws aConfigErrorwith a migration message:Knex connection "client: mysql" is no longer supported. Use "client: mysql2" instead.Existing apps usingclient: mysqlneed to update their connection YAML.mysqlis not silently remapped because knex treatsmysqlandmysql2as separate dialects with subtly different SQL formatters, not aliases — the migration is a deliberate user choice.
pnpm.onlyBuiltDependenciesallowlist forbetter-sqlite3:better-sqlite3runs a native-binding install script (prebuild-installwith anode-gyp rebuildfallback). pnpm 10 silently suppresses postinstall scripts for unapproved packages, which leaves the binding unbuilt and crashesKnexRaw/KnexBuilderat runtime.- Added
better-sqlite3to the allowlist on@lowdefy/server,@lowdefy/server-dev, and@lowdefy/server-e2e. These are the install roots in the CLI fetch flow under.lowdefy/{dev,build}/, where pnpm honors the per-packagepnpm.onlyBuiltDependenciesfield. - Also added the same allowlist to the monorepo root
package.json. The per-package field is ignored at workspace-root install (pnpm 10 only honors it on the install root), so contributors runningpnpm installat the repo root would otherwise have topnpm rebuild better-sqlite3manually.
Patch Changes
-
@lowdefy/[email protected]
-
@lowdefy/[email protected]
-
@lowdefy/operators-js: ### Minor Changes
-
69a59c0: feat(_js): Pass pre-computed values into
_jsvia anargsobject.The
_jsoperator now accepts an object form{ fn, args }alongside the existing string form. Values inargsare resolved by the parser — using any Lowdefy operator (_state,_request,_user, nested_js, etc.) — before the JavaScript function runs, and are injected as theargsobject inside the function body._js: fn: | const { products, target } = args; return products .filter((p) => p.category === target) .reduce((a, p) => a + p.price, 0); args: products: _request: get_products.data.products target: smartphonesThis lets you precompute or normalize values in YAML and keep the JavaScript body focused on computation, rather than mixing operator lookups into the function. The string form continues to work unchanged, and identical
fnbodies still share a single compiled function at build time — onlyargsvaries per call. -
0d44433: feat(_string): Add
_string.formatfor template-style string interpolation._string.formatsubstitutes placeholders in a template string with values, accepting either a positional array form or a named object form.null/undefinedvalues render as empty strings, which often makes_if_noneunnecessary.# Positional placeholders _string.format: - 'Updates ({0})' - _request: get_counts.0.update # Named placeholders _string.format: template: 'Updates ({count}) since {date}' on: count: _request: get_counts.0.update date: _date.format: - YYYY-MM-DD - _state: lastSyncUse
{{/}}to include literal braces. Prefer_string.formatover_string.concatfor label-style interpolation, and use_nunjuckswhen you need conditionals, loops, or filters.
Patch Changes
-
1d18a13: feat(actions):
holdValueflag onRequestandCallAPIactions.RequestandCallAPIactions now accept aholdValue: trueflag that retains the previous response value while a new call is loading. UI bound to_request: <id>or_api: <endpointId>keeps showing the previous response instead of flashing tonullduring a refetch. The previous response is also retained if the new call errors — the error is still observable via_request_details/_api.- id: refresh_table type: Request params: requestId: my_table_request holdValue: trueThe
Requestaction's object-form params now also support{ requestId, holdValue }and{ requestIds, holdValue }shapes alongside the existing{ all }shape. -
@lowdefy/plugin-aws: ### Minor Changes
-
235e219: feat(plugin-aws): Add onBeforeUpload event to S3 upload blocks.
S3UploadButton, S3UploadDragger, and S3UploadPhoto now fire an
onBeforeUploadevent before the upload starts. If any action throws, the upload is cancelled. This allows validation, confirmation prompts, or other logic to run before files are sent to S3.
Patch Changes
-
a52db1c: feat(plugin-aws): Auto URL-encode
x-amz-meta-*fields onAwsS3PresignedPostPolicy.AwsS3PresignedPostPolicynow URL-encodes any field whose name starts withx-amz-meta-(case-insensitive) before signing the policy. S3 user metadata must be ASCII, so values containing names, URLs, or other non-ASCII characters previously had to be wrapped with_uri.encodein every request config.With this change, upload policies can pass values through directly:
fields: x-amz-meta-uploaded-by-name: _user: profile.name x-amz-meta-uploaded-by-url: _payload: urlOther protocol fields (
acl,Content-Type,success_action_redirect, etc.) continue to pass through literally. Readers of the metadata (e.g., Lambda triggers that ingest S3 events) should URL-decodex-amz-meta-*values viadecodeURIComponentbefore use. -
825f86c: fix(plugin-aws): S3 upload blocks — working
heightonS3UploadDraggerin antd v6, antd semantic-slot passthrough across all three upload blocks, cleanerclassNames/styleswiring.After the antd v6 upgrade the Upload wrapper changed from a
<div>to an inline<span>, which caused the antdDraggerheightprop to resize only the inner drag box — the wrapping element and the clickableant-upload-btn(which usesheight: 100%) collapsed, so the drag area looked unchanged.This release:
- Makes
properties.heightactually resize the full drag surface. Height is applied to the block's outer wrapper, and the antdUploadwrapper is forced todisplay: block; height: 100%so nestedheight: 100%rules resolve correctly. - Defaults
heightto the antdcontrolHeighttheme token (matches the default button height and follows the compact algorithm). - Accepts
heightas a number or a string so CSS lengths like"50vh"/"300px"are supported. - Exposes the antd v6 Upload semantic slots (
trigger,list,item) through Lowdefy'sclassNames/stylesAPI, so file-list rows and the drop trigger can be styled from YAML (style: { .trigger: { … }, .list: { … }, .item: { … } }). - Moves
classNames.element/styles.elementonto the block's outer wrapper (matching the convention used inblocks-antd/Search), merges marker classes viacn(), and adds alf-s3-upload-dragger-hintmarker to the hint node for future scoped CSS. - Fixes precedence:
style.element.heightnow overridesproperties.heightagain, matching the original schema docs.
S3UploadButtonandS3UploadPhotoreceive the same treatment:classNames.element/styles.elementmove to an outer block wrapper carrying a marker class (lf-s3-upload-button,lf-s3-upload-photo), and the antd v6 semantic slotstrigger/list/itemare piped through as Lowdefy slots.S3UploadPhotoalso keeps.iconand.titleslots for styling the content of the upload trigger card. Theavatar-uploaderhardcoded class onS3UploadPhoto(which had no CSS attached) was removed in favour of the marker-class pattern.S3UploadDragger,S3UploadButton, andS3UploadPhotoare now wrapped inwithTheme('Upload', …)(previously onlyS3Downloadwas). Each block'smeta.jsexposes athemeproperty whose object is forwarded into a scoped<ConfigProvider theme={{ components: { Upload: theme } }}>, giving per-instance overrides of antd Upload design tokens (actionsColor,pictureCardSize,controlItemBgHover,colorIcon,fontSize,borderRadiusSM) — the tokens that semanticclassNames/stylesslots cannot reach. The shared schema lives atpackages/plugins/plugins/plugin-aws/src/schemas/uploadTheme.jsand is imported by all four upload/download block metas.S3Downloadgains ashowRemoveIconproperty (defaultfalse) and anonRemoveevent. When the remove icon is clicked, the block firesonRemovewith the clickedfileand returnsfalseto antd — the controlledfileListstays authoritative, and the action handler decides whether to update state (e.g. viaSetState).Migration note: Apps that relied on
style: { .element: { … } }styling the inner.ant-upload-drag/.ant-upload-selectdiv (e.g. custom hover interactions coupled to that selector) should move those declarations tostyle: { .trigger: { … } }. All visual styling cases in the gallery (background, border, shadow, padding) render identically on the outer wrapper, so typical apps need no changes. - Makes
-
@lowdefy/server: ### Minor Changes
-
0f38c9f: feat: First-class module system for reusable config packages
Modules are reusable bundles of Lowdefy config — pages, connections, API endpoints, menus, and exposed components — hosted in GitHub repositories or local directories. Apps install modules in
lowdefy.yamland configure them throughvars, replacing the copy-paste-between-projects pattern with a declarative dependency.Module entries (
@lowdefy/build)- Apps declare entries in the
modulesarray oflowdefy.yamlwithid,source, and optionalvars,connections, anddependencies. - The entry
idnamespaces the module's content and forms the URL prefix for its pages (e.g./team-users/users-list). - Multi-instance: the same module source can be installed multiple times under different entry IDs, each with its own vars and namespace.
- GitHub sources (
github:owner/repo[/path]@ref) are fetched as tarballs and locally cached. Private repos useGITHUB_TOKEN, theghCLI, or git credential helpers. - Local sources (
file:./relative/path) resolve relative to the project root.
Module manifest (
module.lowdefy.yaml)- Declares the module's interface:
name,description,vars,connections,pages,api,components,menus,dependencies,exports,plugins, andsecrets. varsdeclarations validate consumer values withtype,required,default, anddescription. Consumer values override manifest defaults; omitted values fall back to the declared default.exportsdeclares the module's public interface — the IDs other modules and apps may reference. The build validates cross-module references against exports.pluginsdeclarations are validated against the app's installed plugins with semver compatibility checks.secretsis an allowlist of secrets the module may access; undeclared_secretreferences fail the build. Remapped connections skip the module's secret references for that connection.
Module operators
_module.var— read manifest-validated vars, including consumer overrides and declared defaults._module.pageId,_module.connectionId,_module.endpointId— produce scoped IDs from a module-author's unscoped ID._module.id— the entry ID of the current module.
Auto-scoped IDs
Page, connection, API endpoint, and menu item IDs are auto-prefixed with the entry ID. Block and request IDs inherit page scope and are not rewritten.
Consuming module resources
- Pages and APIs are auto-included and auto-scoped — they appear in the app under the entry-ID prefix.
- Components are reusable config fragments included with
_ref: { module, component, vars }. They can export any config — UI blocks, enum maps, config templates, schema fragments — and accept vars at the call site. - Menus are included with
_ref: { module, menu }, typically wrapped in aMenuGroup.
Connection remapping
Apps can redirect a module connection to an existing app connection via the entry's
connectionsmap. The module's connection definition and its declared secrets are skipped — the app connection handles them.Cross-module dependencies
Modules can reference each other's pages, components, menus, connections, and APIs via abstract dependencies declared in
module.lowdefy.yaml.- Auto-wiring: when a module entry's
idmatches a declared dependency name, the build wires it automatically. - Explicit wiring: the entry's
dependenciesmap overrides auto-wiring and supports multi-instance topologies where each instance points at a different partner. - The build validates every wiring, detects dependency cycles, and reports unmapped or undeclared dependencies with remediation hints.
Auth page rules
Picomatch glob patterns in auth page rules (e.g.
team-users/*) for wildcard module page matching.Slashed page IDs (
@lowdefy/server,@lowdefy/server-dev)Server routes support module page IDs containing
/(e.g./team-users/users-list). - Apps declare entries in the
Patch Changes
-
596fddc: chore(connection-knex): update knex and SQL drivers; replace
sqlite3withbetter-sqlite3; replacemysqlwithmysql2.Bumped knex and its dialect drivers, and consolidated onto the actively-maintained drivers — replaced
sqlite3withbetter-sqlite3andmysqlwithmysql2. Subsumes the prior[email protected]darwin-arm64 fix.@lowdefy/connection-knexdependency changes:knex2.5.1→3.2.9. Knex 3.x drops Node < 16; Lowdefy already requires Node 18+. Theknex(config),.raw(), and dynamic query-builder API surface used byKnexRaw/KnexBuilderis unchanged.pg8.11.3→8.20.0.- Removed
mssql. Knex'smssqldialect actually requirestedious(not themssqlpackage), and Lowdefy never importedmssqldirectly — it was only ever a vehicle for pulling tedious into the install tree.client: mssqlin user YAML is unchanged: the knex client name stays the same, only the underlying npm package shipped withconnection-knexchanges. - Added
tedious19.2.1as the SQL Server driver — the package knex actually loads whenclient: mssqlis used. - Removed
sqlite3. The driver is in maintenance-only mode upstream (the v6 release marked the repo unmaintained). - Added
better-sqlite312.9.0as the SQLite driver. Selectable asclient: better-sqlite3(orclient: sqlite, which is now an alias ofbetter-sqlite3— see runtime client handling below). - Removed
mysql. Unmaintained upstream since 2020. - Added
mysql23.22.3as the MySQL / MariaDB driver. Selectable asclient: mysql2in connection YAML.
Runtime client handling (in
createKnex):client: sqliteis silently remapped toclient: better-sqlite3.sqlitewas historically a knex-level alias ofsqlite3; this preserves the YAML alias while the underlying driver changes.client: sqlite3now throws aConfigErrorwith a migration message:Knex connection "client: sqlite3" is no longer supported. Use "client: better-sqlite3" or "client: sqlite" instead.Existing apps usingclient: sqlite3need to update their connection YAML.client: mysqlnow throws aConfigErrorwith a migration message:Knex connection "client: mysql" is no longer supported. Use "client: mysql2" instead.Existing apps usingclient: mysqlneed to update their connection YAML.mysqlis not silently remapped because knex treatsmysqlandmysql2as separate dialects with subtly different SQL formatters, not aliases — the migration is a deliberate user choice.
pnpm.onlyBuiltDependenciesallowlist forbetter-sqlite3:better-sqlite3runs a native-binding install script (prebuild-installwith anode-gyp rebuildfallback). pnpm 10 silently suppresses postinstall scripts for unapproved packages, which leaves the binding unbuilt and crashesKnexRaw/KnexBuilderat runtime.- Added
better-sqlite3to the allowlist on@lowdefy/server,@lowdefy/server-dev, and@lowdefy/server-e2e. These are the install roots in the CLI fetch flow under.lowdefy/{dev,build}/, where pnpm honors the per-packagepnpm.onlyBuiltDependenciesfield. - Also added the same allowlist to the monorepo root
package.json. The per-package field is ignored at workspace-root install (pnpm 10 only honors it on the install root), so contributors runningpnpm installat the repo root would otherwise have topnpm rebuild better-sqlite3manually.
-
@lowdefy/server-dev: ### Minor Changes
-
0f38c9f: feat: First-class module system for reusable config packages
Modules are reusable bundles of Lowdefy config — pages, connections, API endpoints, menus, and exposed components — hosted in GitHub repositories or local directories. Apps install modules in
lowdefy.yamland configure them throughvars, replacing the copy-paste-between-projects pattern with a declarative dependency.Module entries (
@lowdefy/build)- Apps declare entries in the
modulesarray oflowdefy.yamlwithid,source, and optionalvars,connections, anddependencies. - The entry
idnamespaces the module's content and forms the URL prefix for its pages (e.g./team-users/users-list). - Multi-instance: the same module source can be installed multiple times under different entry IDs, each with its own vars and namespace.
- GitHub sources (
github:owner/repo[/path]@ref) are fetched as tarballs and locally cached. Private repos useGITHUB_TOKEN, theghCLI, or git credential helpers. - Local sources (
file:./relative/path) resolve relative to the project root.
Module manifest (
module.lowdefy.yaml)- Declares the module's interface:
name,description,vars,connections,pages,api,components,menus,dependencies,exports,plugins, andsecrets. varsdeclarations validate consumer values withtype,required,default, anddescription. Consumer values override manifest defaults; omitted values fall back to the declared default.exportsdeclares the module's public interface — the IDs other modules and apps may reference. The build validates cross-module references against exports.pluginsdeclarations are validated against the app's installed plugins with semver compatibility checks.secretsis an allowlist of secrets the module may access; undeclared_secretreferences fail the build. Remapped connections skip the module's secret references for that connection.
Module operators
_module.var— read manifest-validated vars, including consumer overrides and declared defaults._module.pageId,_module.connectionId,_module.endpointId— produce scoped IDs from a module-author's unscoped ID._module.id— the entry ID of the current module.
Auto-scoped IDs
Page, connection, API endpoint, and menu item IDs are auto-prefixed with the entry ID. Block and request IDs inherit page scope and are not rewritten.
Consuming module resources
- Pages and APIs are auto-included and auto-scoped — they appear in the app under the entry-ID prefix.
- Components are reusable config fragments included with
_ref: { module, component, vars }. They can export any config — UI blocks, enum maps, config templates, schema fragments — and accept vars at the call site. - Menus are included with
_ref: { module, menu }, typically wrapped in aMenuGroup.
Connection remapping
Apps can redirect a module connection to an existing app connection via the entry's
connectionsmap. The module's connection definition and its declared secrets are skipped — the app connection handles them.Cross-module dependencies
Modules can reference each other's pages, components, menus, connections, and APIs via abstract dependencies declared in
module.lowdefy.yaml.- Auto-wiring: when a module entry's
idmatches a declared dependency name, the build wires it automatically. - Explicit wiring: the entry's
dependenciesmap overrides auto-wiring and supports multi-instance topologies where each instance points at a different partner. - The build validates every wiring, detects dependency cycles, and reports unmapped or undeclared dependencies with remediation hints.
Auth page rules
Picomatch glob patterns in auth page rules (e.g.
team-users/*) for wildcard module page matching.Slashed page IDs (
@lowdefy/server,@lowdefy/server-dev)Server routes support module page IDs containing
/(e.g./team-users/users-list). - Apps declare entries in the
Patch Changes
-
596fddc: chore(connection-knex): update knex and SQL drivers; replace
sqlite3withbetter-sqlite3; replacemysqlwithmysql2.Bumped knex and its dialect drivers, and consolidated onto the actively-maintained drivers — replaced
sqlite3withbetter-sqlite3andmysqlwithmysql2. Subsumes the prior[email protected]darwin-arm64 fix.@lowdefy/connection-knexdependency changes:knex2.5.1→3.2.9. Knex 3.x drops Node < 16; Lowdefy already requires Node 18+. Theknex(config),.raw(), and dynamic query-builder API surface used byKnexRaw/KnexBuilderis unchanged.pg8.11.3→8.20.0.- Removed
mssql. Knex'smssqldialect actually requirestedious(not themssqlpackage), and Lowdefy never importedmssqldirectly — it was only ever a vehicle for pulling tedious into the install tree.client: mssqlin user YAML is unchanged: the knex client name stays the same, only the underlying npm package shipped withconnection-knexchanges. - Added
tedious19.2.1as the SQL Server driver — the package knex actually loads whenclient: mssqlis used. - Removed
sqlite3. The driver is in maintenance-only mode upstream (the v6 release marked the repo unmaintained). - Added
better-sqlite312.9.0as the SQLite driver. Selectable asclient: better-sqlite3(orclient: sqlite, which is now an alias ofbetter-sqlite3— see runtime client handling below). - Removed
mysql. Unmaintained upstream since 2020. - Added
mysql23.22.3as the MySQL / MariaDB driver. Selectable asclient: mysql2in connection YAML.
Runtime client handling (in
createKnex):client: sqliteis silently remapped toclient: better-sqlite3.sqlitewas historically a knex-level alias ofsqlite3; this preserves the YAML alias while the underlying driver changes.client: sqlite3now throws aConfigErrorwith a migration message:Knex connection "client: sqlite3" is no longer supported. Use "client: better-sqlite3" or "client: sqlite" instead.Existing apps usingclient: sqlite3need to update their connection YAML.client: mysqlnow throws aConfigErrorwith a migration message:Knex connection "client: mysql" is no longer supported. Use "client: mysql2" instead.Existing apps usingclient: mysqlneed to update their connection YAML.mysqlis not silently remapped because knex treatsmysqlandmysql2as separate dialects with subtly different SQL formatters, not aliases — the migration is a deliberate user choice.
pnpm.onlyBuiltDependenciesallowlist forbetter-sqlite3:better-sqlite3runs a native-binding install script (prebuild-installwith anode-gyp rebuildfallback). pnpm 10 silently suppresses postinstall scripts for unapproved packages, which leaves the binding unbuilt and crashesKnexRaw/KnexBuilderat runtime.- Added
better-sqlite3to the allowlist on@lowdefy/server,@lowdefy/server-dev, and@lowdefy/server-e2e. These are the install roots in the CLI fetch flow under.lowdefy/{dev,build}/, where pnpm honors the per-packagepnpm.onlyBuiltDependenciesfield. - Also added the same allowlist to the monorepo root
package.json. The per-package field is ignored at workspace-root install (pnpm 10 only honors it on the install root), so contributors runningpnpm installat the repo root would otherwise have topnpm rebuild better-sqlite3manually.
-
762755c: feat(blocks-tiptap): Add new default block package with
TiptapInputandTiptapMentionInputrich-text editors.@lowdefy/blocks-tiptapships two rich-text editor blocks built on TipTap:TiptapInput— standard rich-text editor with bold/italic/strike-through, multi-color highlight, headings, lists, tables, links, and a bubble menu.TiptapMentionInput— everythingTiptapInputdoes, plus an @-mention dropdown populated from a static options list or a Lowdefy request. Resolved mentions are returned on the block value asmentions: [...].
Both blocks emit an object value shaped
{ html, text, markdown, fileList, mentions? }and registerclear,setContent, andfocusmethods.Configurable extensions — defaults preserve the bundled editor; override any of these to trim the editor down or tune it:
properties.starterKit— object forwarded to TipTap StarterKit, e.g.{ heading: false, codeBlock: false }.properties.image—{ enabled, maxWidth, zoom }properties.table—{ enabled, resizable }properties.link—{ enabled, autolink, linkOnPaste, openOnClick, defaultProtocol }properties.highlight—{ enabled, multicolor }properties.mentions.char/properties.mentions.allowSpaces— change the trigger char (e.g.#for hashtags) or disable spaces inside a mention query (TiptapMentionInputonly).
Image drag/drop and paste are supported by pointing
properties.s3PostPolicyRequestIdat a request that returns an S3 presigned POST policy (e.g.AwsS3PresignedPostPolicy). The file handler is optional — omit the request id to disable uploads entirely.The blocks are registered in the default types map and are available out of the box on
@lowdefy/server-dev. No private-registry tokens are required: the blocks use the open-source@tiptap/extension-file-handlerinstead of@tiptap-pro/extension-file-handler, so projects that migrated from a custom TipTap plugin can drop theirTIPTAP_PRO_TOKENenvironment variable and.npmrcscoped-registry config. -
@lowdefy/server-e2e: ### Patch Changes
-
596fddc: chore(connection-knex): update knex and SQL drivers; replace
sqlite3withbetter-sqlite3; replacemysqlwithmysql2.Bumped knex and its dialect drivers, and consolidated onto the actively-maintained drivers — replaced
sqlite3withbetter-sqlite3andmysqlwithmysql2. Subsumes the prior[email protected]darwin-arm64 fix.@lowdefy/connection-knexdependency changes:knex2.5.1→3.2.9. Knex 3.x drops Node < 16; Lowdefy already requires Node 18+. Theknex(config),.raw(), and dynamic query-builder API surface used byKnexRaw/KnexBuilderis unchanged.pg8.11.3→8.20.0.- Removed
mssql. Knex'smssqldialect actually requirestedious(not themssqlpackage), and Lowdefy never importedmssqldirectly — it was only ever a vehicle for pulling tedious into the install tree.client: mssqlin user YAML is unchanged: the knex client name stays the same, only the underlying npm package shipped withconnection-knexchanges. - Added
tedious19.2.1as the SQL Server driver — the package knex actually loads whenclient: mssqlis used. - Removed
sqlite3. The driver is in maintenance-only mode upstream (the v6 release marked the repo unmaintained). - Added
better-sqlite312.9.0as the SQLite driver. Selectable asclient: better-sqlite3(orclient: sqlite, which is now an alias ofbetter-sqlite3— see runtime client handling below). - Removed
mysql. Unmaintained upstream since 2020. - Added
mysql23.22.3as the MySQL / MariaDB driver. Selectable asclient: mysql2in connection YAML.
Runtime client handling (in
createKnex):client: sqliteis silently remapped toclient: better-sqlite3.sqlitewas historically a knex-level alias ofsqlite3; this preserves the YAML alias while the underlying driver changes.client: sqlite3now throws aConfigErrorwith a migration message:Knex connection "client: sqlite3" is no longer supported. Use "client: better-sqlite3" or "client: sqlite" instead.Existing apps usingclient: sqlite3need to update their connection YAML.client: mysqlnow throws aConfigErrorwith a migration message:Knex connection "client: mysql" is no longer supported. Use "client: mysql2" instead.Existing apps usingclient: mysqlneed to update their connection YAML.mysqlis not silently remapped because knex treatsmysqlandmysql2as separate dialects with subtly different SQL formatters, not aliases — the migration is a deliberate user choice.
pnpm.onlyBuiltDependenciesallowlist forbetter-sqlite3:better-sqlite3runs a native-binding install script (prebuild-installwith anode-gyp rebuildfallback). pnpm 10 silently suppresses postinstall scripts for unapproved packages, which leaves the binding unbuilt and crashesKnexRaw/KnexBuilderat runtime.- Added
better-sqlite3to the allowlist on@lowdefy/server,@lowdefy/server-dev, and@lowdefy/server-e2e. These are the install roots in the CLI fetch flow under.lowdefy/{dev,build}/, where pnpm honors the per-packagepnpm.onlyBuiltDependenciesfield. - Also added the same allowlist to the monorepo root
package.json. The per-package field is ignored at workspace-root install (pnpm 10 only honors it on the install root), so contributors runningpnpm installat the repo root would otherwise have topnpm rebuild better-sqlite3manually.
-
762755c: feat(blocks-tiptap): Add new default block package with
TiptapInputandTiptapMentionInputrich-text editors.@lowdefy/blocks-tiptapships two rich-text editor blocks built on TipTap:TiptapInput— standard rich-text editor with bold/italic/strike-through, multi-color highlight, headings, lists, tables, links, and a bubble menu.TiptapMentionInput— everythingTiptapInputdoes, plus an @-mention dropdown populated from a static options list or a Lowdefy request. Resolved mentions are returned on the block value asmentions: [...].
Both blocks emit an object value shaped
{ html, text, markdown, fileList, mentions? }and registerclear,setContent, andfocusmethods.Configurable extensions — defaults preserve the bundled editor; override any of these to trim the editor down or tune it:
properties.starterKit— object forwarded to TipTap StarterKit, e.g.{ heading: false, codeBlock: false }.properties.image—{ enabled, maxWidth, zoom }properties.table—{ enabled, resizable }properties.link—{ enabled, autolink, linkOnPaste, openOnClick, defaultProtocol }properties.highlight—{ enabled, multicolor }properties.mentions.char/properties.mentions.allowSpaces— change the trigger char (e.g.#for hashtags) or disable spaces inside a mention query (TiptapMentionInputonly).
Image drag/drop and paste are supported by pointing
properties.s3PostPolicyRequestIdat a request that returns an S3 presigned POST policy (e.g.AwsS3PresignedPostPolicy). The file handler is optional — omit the request id to disable uploads entirely.The blocks are registered in the default types map and are available out of the box on
@lowdefy/server-dev. No private-registry tokens are required: the blocks use the open-source@tiptap/extension-file-handlerinstead of@tiptap-pro/extension-file-handler, so projects that migrated from a custom TipTap plugin can drop theirTIPTAP_PRO_TOKENenvironment variable and.npmrcscoped-registry config. -
@lowdefy/logger: ### Patch Changes
-
e3fc007: fix(logger): Handle non-object JSON values in stdout line handler.
JSON.parsecan returnnullfor literal"null"input, crashing the CLI log handler. Non-object parsed values are now treated as plain text lines.
- Migrate existing `DataDiff` usages to the new blocks in `@lowdefy/blocks-diff` using the provided mapping.
- Update AgGrid columns to use the new `cell` object for built‑in renderers if leveraging number formatting, links, etc.
- Removal of `DataDiff` block from `@lowdefy/blocks-antd`; migrate to corresponding blocks in `@lowdefy/blocks-diff` (DiffList, DiffSideBySide, DiffTimeline, DiffGit).
- Theme values interpolated into pre-hydration inline script are now fully escaped via `safeScriptJson`, closing six CodeQL `js/bad-code-sanitization` alerts.
- Dark mode polish: per-mode antd tokens (`lightToken`, `darkToken`, `lightComponents`, `darkComponents`) and pre-hydration script to eliminate white flash.
- New package `@lowdefy/blocks-diff` with four diff blocks (DiffList, DiffSideBySide, DiffTimeline, DiffGit).
- AgGrid built‑in cell renderers (`tag`, `avatar`, `link`, `date`, `boolean`, `progress`, `number`) and tooltip schema additions.
Full changelog
Highlights
- Dark mode polish across the framework — new
lightToken/darkTokenandlightComponents/darkComponentskeys ontheme.antdlet you override antd tokens per mode, a pre-hydration inline script paints the correct background before first paint (no more white flash on navigation), and the generatedglobals.cssnow ships themed scrollbars that auto-swap with the active theme. - New
@lowdefy/blocks-diffpackage — Four dedicated blocks:DiffList,DiffSideBySide,DiffTimeline, andDiffGit. - New
PageSidebarLayoutblock — full-page layout with a full-height collapsible sider, localStorage-persisted open state, mobile drawer, 8 content slots, and dark-mode-aware logo switching.PageSiderMenualso now persists its sider state (sharedsiderStorageKey). - AgGrid built-in cell renderers — every column now accepts a
cellobject withtag,avatar,link,date,boolean,progress, ornumbertypes, Excel-stylenumberformatting viaIntl.NumberFormat, row-data field paths (nameField,srcField, etc.), anellipsiscolumn helper, and a newonCellLinkevent. Tooltip props (tooltipField,tooltipValueGetter,enableBrowserTooltips) are now declared in the schemas, and loading overlays have been rewritten to fix a long-standing Safari stuck-overlay bug. - User-role operators —
_user.hasRole,_user.hasSomeRoles, and_user.hasAllRolescheck theuser.rolesarray and return a boolean, making role-gated UI and request auth easier to express. selectorcssKey on Select blocks —Selector,MultipleSelector, andAutoCompletenow expose aselectorcssKey so you can cap the tag container height and scroll internally without reaching for Tailwind arbitrary variants or global CSS.- Security hardening — theme values interpolated into the pre-hydration
<script>are now fully escaped (<,>, U+2028, U+2029) via a newsafeScriptJsonhelper, closing six CodeQLjs/bad-code-sanitizationalerts.
What's New
feat(build): Themed default scrollbars in generated globals.css.
Packages: @lowdefy/build
Every Lowdefy app now ships with themed scrollbars out of the box. Native Windows/Linux scrollbars were rendering as light grey on dark surfaces (Modal, Drawer, overflowing containers), clashing with dark themes — macOS overlay scrollbars hid the problem. The generated globals.css now emits a @layer base block that:
- Sets
scrollbar-width: thinandscrollbar-colorfor Firefox and modern browsers. - Styles
::-webkit-scrollbar(10px, transparent track, subtle thumb with inset border, hover darkens) for Chromium / WebKit. - Drives all colors from antd CSS custom properties (
--ant-color-border-secondary,--ant-color-text-tertiary) so they auto-swap on dark / light mode toggle.
User-provided CSS remains in @layer components, so any app-level ::-webkit-scrollbar overrides in public/styles.css still win.
feat(client): Per-mode theme tokens for dark/light customization.
Packages: @lowdefy/client, @lowdefy/server, @lowdefy/server-dev
theme.antd now accepts four new sibling keys so apps can soften base surfaces without juggling two theme files. Each is merged on top of the shared equivalent only when the matching mode is active:
lightToken/darkToken— override antd design tokens (e.g.colorBgLayout,colorBgContainer,colorBgElevated) per mode.lightComponents/darkComponents— override component-level tokens per mode (e.g.Layout.siderBg,Layout.headerBg,Menu.darkItemBg) that aren't reachable via seed tokens.
The <html> pre-hydration inline script now reads darkToken.colorBgLayout / lightToken.colorBgLayout from the built theme, so the first paint matches your configured surface color with no flash of #000 or #fff.
theme:
antd:
token:
colorPrimary: '#6366f1'
darkToken:
colorBgLayout: '#131419'
colorBgContainer: '#1a1b22'
darkComponents:
Layout:
headerBg: '#0e0f13'
siderBg: '#0e0f13'
Menu:
darkItemBg: '#0e0f13'
darkItemSelectedBg: '#252731'
darkMode: system
Backwards compatible — apps that only use theme.antd.token keep antd's default base colors (dark #000, light browser-default).
feat(blocks-aggrid): Add built-in cell renderer types.
Packages: @lowdefy/blocks-aggrid
Every AgGrid column now accepts a cell object on columnDefs entries that selects a first-class renderer — tag, avatar, link, date, boolean, progress, number — plus an ellipsis: N column-level helper that auto-enables wrapText + autoHeight with an N-line clamp.
The number renderer wraps Intl.NumberFormat with Excel-style config: format (number / currency / percent / compact), locale, currency, decimals, accounting-style negative: parentheses, signColor (green/red by sign), and optional prefix / suffix. Number columns auto-right-align (cellStyle.justifyContent: flex-end + ag-right-aligned-header) and every cell.type supports an align: left | center | right override. Renderer output is React, vertically centred, and styled entirely through antd CSS tokens (--ant-control-height, --ant-margin-xs, --ant-color-*, --ant-border-radius, --ant-font-size, etc.) so the grid adapts to Material vs Balham row heights and to dark / compact antd theme.algorithm without per-theme overrides.
Field-valued keys (nameField, srcField, idField, colorFrom, and every value inside link.urlQuery) are plain row-data path strings — no _function wrapping required. Null values render a muted em-dash across every built-in type.
Link navigation: cell.type: link and avatar.link render anchors and emit a new onCellLink block event with the resolved link config; wire it to a Link action (params: { _event: link }) to navigate — matches the existing Lowdefy event → action pattern.
antd, @ant-design/icons, and dayjs are now declared as peer dependencies on @lowdefy/blocks-aggrid (de-facto required by the existing ag-grid-antd.module.css token mapping).
Also fixes the long-standing cell vertical-centering drift: .ag-cell is now a flex container via the antd theme CSS module, which also benefits users' existing renderHtml cells.
feat(blocks-aggrid): Declare tooltip properties in block schemas.
Packages: @lowdefy/blocks-aggrid
All six AgGrid variants (Alpine/Balham/Material for display and input) now declare enableBrowserTooltips, tooltipShowDelay, and tooltipHideDelay at the grid level and tooltipField, tooltipValueGetter, and tooltipComponent at the column level. These AG Grid props already worked — they were passed through via property spreading — but were not documented in the block schemas. Users can now discover and configure tooltips directly from the schema.
feat(blocks-antd): DataDiff gains sideBySide, timeline, and gitDiff modes, plus depth-aware nested rendering.
Packages: @lowdefy/blocks-antd
mode: sideBySide— two aligned antdDescriptionspanels (Before / After) that respond to breakpoints.mode: timeline— antdTimelinewith one item per change, colour-coded and breadcrumb-labelled for context.mode: gitDiff— unified-diff YAML patch rendering with +/− line markers; for technical users who want to see every line of change. Ignores the structured-rendering props (labels,format,maxDepth) but still honourshide/show.- list mode now sub-groups array-of-objects changes into per-item sections with their own summary chips, and breadcrumbs deeply-nested row labels (
Order 1 › Customer › Nameinstead of justName). - New
maxDepthproperty (default4) collapses changes deeper than the cap into a single JSON-rendered row at the cap, keeping deep payloads legible.
feat(blocks-antd): Add DataDiff block for rendering user-friendly diffs.
Packages: @lowdefy/blocks-antd
DataDiff compares two objects (before / after) and renders the differences using antd primitives (Descriptions, Collapse, Tag, Empty). v1 ships a polished list mode: grouped by top-level key, with +N / −N / ~N summary chips per group, icon- and color-coded change rows, and a collapsed-JSON fallback for entirely-new nested objects. All colors come from antd semantic tokens (colorSuccess, colorError, colorWarning) so the block respects dark mode and theme overrides automatically.
Per-path value formatters — date, datetime, boolean (Yes/No tag), currency (Intl.NumberFormat), json (pretty inside a subtle collapse), code, and enum (value → { label, color } map) — turn raw field values into something end-users can read. labels maps dotted paths to display names; hide / show accept exact paths, prefix.*, or *.leaf patterns. Built on microdiff (~1 kB, zero deps).
- id: order_audit
type: DataDiff
properties:
before: _state.original
after: _state.current
labels:
status: Order status
total: Total
format:
total: { type: currency, currency: USD }
status:
type: enum
map:
pending: { label: Pending, color: warning }
paid: { label: Paid, color: success }
hide:
- 'internal.*'
feat(blocks-antd): Expose selector cssKey on Select-based blocks.
Packages: @lowdefy/blocks-antd
Selector, MultipleSelector, and AutoComplete now expose a selector cssKey that targets the inner tag/value container (antd v6's content semantic slot, rendered as .ant-select-content in the DOM). Use it to cap the tag container height and enable internal scroll on multi-select blocks:
style:
.selector:
maxHeight: 96px
overflowY: auto
Before this change, users had to reach for Tailwind arbitrary variants or global CSS (e.g. [&_.ant-select-selector]:max-h-24) to style the inner container, which leaked antd internals into app YAML and was brittle across antd upgrades.
feat(blocks-diff): New package. DataDiff extracted from blocks-antd and split
Packages: @lowdefy/blocks-antd, @lowdefy/blocks-diff
into DiffList, DiffSideBySide, DiffTimeline, and DiffGit blocks.
BREAKING: The DataDiff block has been removed from @lowdefy/blocks-antd.
Migrate to the per-mode blocks in @lowdefy/blocks-diff:
mode: list→Diff.DiffListmode: sideBySide→Diff.DiffSideBySidemode: timeline→Diff.DiffTimelinemode: gitDiff→Diff.DiffGit
The diff, yaml, pluralize, and microdiff dependencies have been moved
from @lowdefy/blocks-antd to @lowdefy/blocks-diff along with the block.
feat: Add PageSidebarLayout block
Packages: @lowdefy/blocks-antd
New full-page layout block with a full-height sidebar, no top-level header, and mobile drawer navigation. The sider spans the entire viewport height with the logo pinned at the bottom.
PageSidebarLayout
- Full-height collapsible sider with inline menu
- Sider collapse state persists in localStorage (configurable key via
siderStorageKey) - Dark mode via app-level ConfigProvider — all components adapt automatically via CSS variables
darkModeToggle,notifications, andprofileproperties shown in the sider on desktop and the mobile header on small screens, matching PageHeaderMenu and PageSiderMenuthemeproperty accepts an Ant Design design token object for fine-grained color customization via ConfigProvider- Responsive logo: full logo when sider is expanded, square logo when collapsed, auto-swaps between light and dark variants based on dark mode
- 8 content slots: content, footer, header, siderOpen, siderClosed, mobileExtra, mobileDrawerContent, mobileDrawerFooter
Drawer
- Added
footercontent slot andstyles.footerpassthrough
MobileMenu
- Added
logoproperty for drawer header branding with dark mode-aware logo switching - Added
drawerContentanddrawerFootercontent slots - Changed category from
displaytocontainerto support slot resolution
feat: Persist PageSiderMenu sider open state to localStorage
Packages: @lowdefy/blocks-antd
PageSiderMenu now persists its sider collapsed/expanded state across page navigations and reloads, matching PageSidebarLayout behavior.
- Sider open state reads from and writes to
lf-{siderStorageKey}-openin localStorage - New
siderStorageKeyproperty (default'sider') — shares the same key as PageSidebarLayout by default, so the user's preference survives swapping between layouts - New
sider.initialCollapsedproperty used as the fallback when no persisted value exists - Gracefully handles SSR and privacy-mode localStorage unavailability
Fixes & Improvements
-
fix(server): Prevent white flash on page navigation in dark mode. (
@lowdefy/build,@lowdefy/client,@lowdefy/server,@lowdefy/server-dev)Pages no longer flash white when navigating between pages in dark mode. A synchronous inline script now sets the correct background color before the page paints, matching the user's dark mode preference from config, localStorage, or system settings.
-
feat(operators-js): Add
_user.hasRole,_user.hasSomeRoles, and_user.hasAllRolesmethods to check user roles against theuser.rolesarray.hasRoletakes a single role string;hasSomeRolesandhasAllRolestake an array of role strings. All return a boolean. (@lowdefy/docs,@lowdefy/operators-js) -
fix(blocks-aggrid): React to
loadingprop changes on AgGrid. (@lowdefy/blocks-aggrid)The
loadingblock flag now toggles AG Grid's nativeshowLoadingOverlay/hideOverlayat runtime. Previously the overlay calls were inside auseEffectwith an empty dependency array, so they only ran once on mount and never reacted to subsequentloadingchanges. The effect has been split in two — method registration still runs once, overlay toggling now runs wheneverloadingchanges — and anonGridReadycallback applies the initial overlay state safely after the grid api is attached. -
fix(blocks-aggrid): Safari loading overlay stuck. (
@lowdefy/blocks-aggrid)AgGridandAgGridInputno longer use ag-grid's internalshowLoadingOverlay/hideOverlayAPI to reflect the block'sloadingprop. On Safari / WebKit, a microtask race between ourhideOverlay()call and ag-grid's own lateshowOverlaytick left the "Loading…" box stuck on screen even after data had rendered (ag-grid issues #4421, #1665, #8358). Chromium happened to win the race the other way, which hid the symptom.Both blocks now wrap
AgGridReactin aposition: relativediv, setsuppressLoadingOverlayon the grid, and render a small themed overlay component (LoadingOverlay.js) when the Lowdefyloadingprop istrue. The overlay is styled via antd CSS custom properties, so it follows the active theme. -
fix(blocks-antd): PageSiderMenu state sync, setSiderOpen bug, and menu auto-popup. (
@lowdefy/blocks-antd)Three related fixes so the sider persists correctly and the inline menu doesn't pop open flyouts on page load.
- Persistence on hard-refresh / new tab: PageSiderMenu now feeds its computed
openSiderState(read fromlocalStorage['lf-{siderStorageKey}-open']) into the inner Sider block asinitialCollapsed. Previously the Sider re-read the media-query-computedinitialCollapsedindependently and ignored the persisted value, so the sider always started collapsed on desktop regardless of the user's preference. setSiderOpen({ open })bug: the action called the Sider's_toggleSiderOpen(a no-arg toggle) with an{ open }argument that was silently ignored. Fixed to call_setSiderOpen({ open })so the sider lands in the requested state.toggleSiderOpennow also uses the explicit setter with the computed next value for symmetry.- Menu auto-popup when sider is collapsed: the Menu block's
defaultOpenKeysguard checkedproperties.collapsed !== true, but PageSiderMenu never passescollapsedas a prop — antd derives the collapsed state fromSiderContext. As a result the current page's parent group was added todefaultOpenKeys, and antd's Menu auto-popped the flyout for that group on mount. Menu now readssiderCollapsedfromLayout._InternalSiderContext(the same channel antd's own Menu uses) so the group auto-expansion only applies when the sider is expanded.
- Persistence on hard-refresh / new tab: PageSiderMenu now feeds its computed
-
fix(server): Escape theme values embedded in the pre-hydration inline script. (
@lowdefy/server,@lowdefy/server-dev)_document.jsinterpolatesconfigColorMode,darkToken.colorBgLayout, andlightToken.colorBgLayoutfromtheme.jsoninto a synchronous<script>block to set the<html>background before hydration. Previously the values went throughJSON.stringifyonly — enough to escape JS-string-context characters, but not enough to prevent a value containing</script>(or U+2028 / U+2029 line separators) from breaking out of the enclosing<script>tag.Added a
safeScriptJsonhelper that additionally escapes<,>, control chars, and U+2028 / U+2029 to\uXXXXsequences afterJSON.stringify. For every valid color value (#1e293b,rgb(...),slategray,oklch(...), etc.) the output is byte-identical to the previous behavior; only payloads that would have tripped<script>breakout or JS-line-terminator injection are now neutralized.Closes the six
js/bad-code-sanitizationCodeQL alerts (89 – 94) opened against the per-mode-theme PR.
- Migrate all _moment usages to _dayjs; update format strings if necessary
- Review date picker configurations for day.js compatibility
- Check Google Sheets connections and AG Grid cell renderers for date handling changes
- _moment operator removed; use _dayjs instead
- @lowdefy/operators-moment package removed
- Nunjucks `date` filter now uses day.js (format strings must be day.js‑compatible)
- Theme tokens at runtime via _theme operator and custom token config
- Keyboard shortcuts on block events with platform‑aware bindings
- System dark mode support through theme.darkMode config
Full changelog
Highlights
- Theme tokens at runtime: Access Ant Design v6 design tokens (colors, spacing, typography) with the new
_themeoperator. Configure custom tokens viatheme.antd.tokenandtheme.antd.algorithminlowdefy.yamlfor theme-aware component styling. - Keyboard shortcuts on events: Add
shortcutproperty to block events for platform-aware keyboard bindings (e.g.,mod+K→ Cmd+K on Mac, Ctrl+K on Windows). Supports key sequences and multiple bindings. Built-in validation warns about duplicates and browser conflicts. Button, Anchor, Tag, and Search blocks display shortcut badges. - System dark mode support: New
theme.darkMode: 'system' | 'light' | 'dark'config auto-follows OS preference.SetDarkModeaction accepts string params to control mode._media: darkModePreferencereturns user preference;_media: darkModecontinues returning the boolean state. - Search command palette block: New
Searchdisplay block provides full-text search with MiniSearch. Supports static JSON index viaindexUrlor runtime indexing withdocuments. Features grouped results, keyboard navigation, term highlighting, and search history. Replaces Algolia DocSearch with self-hosted alternative. lowdefy upgradecommand: New CLI tool guides version migrations with prompt-based codemods. Use--to,--plan, or--resumeto manage upgrades. Each prompt can be copied to clipboard, viewed as a manual guide, or skipped. Upgrade state persists for interrupted migrations. v5.0 codemods cover antd v6, layout grid, and dayjs migrations.- Better build error messages: Schema validation errors now include property names. CSS errors suggest dot-prefixed slot keys. YAML parse errors surface immediately instead of crashing on null entries.
- AG Grid automatic theming: All six grid blocks now follow Ant Design theme automatically, responding to light/dark mode and custom colors without configuration. Removed separate dark variant blocks. Override individual
--ag-*variables via the block'sstyleproperty. - Dark mode toggle in headers: New
darkModeToggle: trueproperty on PageHeaderMenu and PageSiderMenu renders a built-in sun/moon toggle. Preference persists to localStorage and respects OS default.toggleDarkModemethod available for programmatic control. - Header customization: Add
colorproperty to set header background (defaults to--ant-color-bg-container). NewiconsColorproperty controls notification, profile, and dark mode toggle icon colors — useful with dark backgrounds. - PhoneNumberInput formatting: Phone number values now strip leading zeros and non-digits.
0821234567with +27 selected produces+27821234567instead of+270821234567. - S3 upload improvements: New
onBeforeUploadevent fires before each file upload; throw in the handler to cancel. XHR uploads are now Promise-based with proper error propagation. CORS/network failures throwServiceErrorwith diagnostic message. File metadata serialized to plain objects to preserve_eventreferences. - Modulo operator: Added
_math.modfor remainder calculations. Use_math.mod: [10, 3]or_math.mod: { dividend: 10, divisor: 3 }. _dateoperator flexibility: Now accepts Date objects in addition to numbers and strings.- Anchor icon spacing: Fixed icon margin issue so icons no longer sit flush against title text.
- Error serialization robustness: Fixed crashes on circular references in error objects (e.g., Axios responses with Node.js request cycles). Errors now log properly instead of crashing the logger.
- ⚠️ BREAKING: Block metadata restructure — Move block definitions from
schema.jstometa.js. Block packages export./metasinstead of./schemas. Components no longer have.metastatic property. Metadata loaded fromblockMetas.jsonbuild artifact at runtime. UsebuildBlockSchema(meta)from@lowdefy/block-utilsto generate JSON Schema. - ⚠️ BREAKING: Moment.js removed — Replace
_momentoperator with_dayjs. Apps using_momentmust migrate.@lowdefy/operators-momentpackage removed. Date picker blocks use day.js internally. Nunjucksdatefilter uses day.js.humanizeDurationthresholds parameter is now ignored. - ⚠️ BREAKING: Component theme properties removed — Remove
header.theme,sider.theme,menu.themeproperties from PageSiderMenu, PageHeaderMenu, Header, and Sider blocks. Dark mode now automatic via CSS variables. Also removedheader.style,sider.style, etc. — usestyle: { .header },style: { .sider }instead. RemovedonProfileClickandonNotificationClickevents; usenotifications.linkproperty instead.collapsibleandinitialCollapsedproperties removed from PageSiderMenu. - ⚠️ BREAKING: Algolia blocks removed — Use the
Searchblock in@lowdefy/blocks-antdinstead of@lowdefy/blocks-algolia.
What's New
Add theme token system. Use _theme operator to access Ant Design v6 design tokens (colors, spacing, typography) at runtime. Theme is configured via theme.antd.token and theme.antd.algorithm in lowdefy.yaml. The _theme operator resolves the full computed token set including antd defaults.
Packages: @lowdefy/api, @lowdefy/build, @lowdefy/build, @lowdefy/build, @lowdefy/build, @lowdefy/build, @lowdefy/build, @lowdefy/client, @lowdefy/client, @lowdefy/client, @lowdefy/client, @lowdefy/client, @lowdefy/docs, @lowdefy/engine, @lowdefy/engine, @lowdefy/layout, @lowdefy/layout, @lowdefy/layout, @lowdefy/actions-core, @lowdefy/actions-pdf-make, @lowdefy/blocks-aggrid, @lowdefy/blocks-aggrid, @lowdefy/blocks-aggrid, @lowdefy/blocks-antd, @lowdefy/blocks-antd, @lowdefy/blocks-antd, @lowdefy/blocks-antd, @lowdefy/blocks-antd, @lowdefy/blocks-antd, @lowdefy/blocks-antd, @lowdefy/blocks-antd, @lowdefy/blocks-antd, @lowdefy/blocks-antd, @lowdefy/blocks-antd, @lowdefy/blocks-antd, @lowdefy/blocks-antd, @lowdefy/blocks-antd, @lowdefy/blocks-antd, @lowdefy/blocks-basic, @lowdefy/blocks-basic, @lowdefy/blocks-basic, @lowdefy/blocks-echarts, @lowdefy/blocks-echarts, @lowdefy/blocks-echarts, @lowdefy/blocks-echarts, @lowdefy/blocks-google-maps, @lowdefy/blocks-google-maps, @lowdefy/blocks-google-maps, @lowdefy/blocks-loaders, @lowdefy/blocks-loaders, @lowdefy/blocks-loaders, @lowdefy/blocks-markdown, @lowdefy/blocks-markdown, @lowdefy/blocks-markdown, @lowdefy/blocks-qr, @lowdefy/blocks-qr, @lowdefy/blocks-qr, @lowdefy/connection-axios-http, @lowdefy/connection-elasticsearch, @lowdefy/connection-google-sheets, @lowdefy/connection-knex, @lowdefy/connection-redis, @lowdefy/connection-sendgrid, @lowdefy/connection-stripe, @lowdefy/connection-test, @lowdefy/operators-change-case, @lowdefy/operators-diff, @lowdefy/operators-js, @lowdefy/operators-js, @lowdefy/operators-jsonata, @lowdefy/operators-mql, @lowdefy/operators-nunjucks, @lowdefy/operators-uuid, @lowdefy/operators-yaml, @lowdefy/plugin-auth0, @lowdefy/plugin-aws, @lowdefy/plugin-aws, @lowdefy/plugin-aws, @lowdefy/plugin-csv, @lowdefy/plugin-next-auth, @lowdefy/server, @lowdefy/server, @lowdefy/server-dev, @lowdefy/server-dev, @lowdefy/server-dev, @lowdefy/server-dev, @lowdefy/block-dev, @lowdefy/block-dev, @lowdefy/block-utils, @lowdefy/block-utils, @lowdefy/e2e-utils, @lowdefy/node-utils
Restructure block metadata from component static properties to dedicated meta.js files.
Packages: @lowdefy/build, @lowdefy/client, @lowdefy/engine, @lowdefy/blocks-aggrid, @lowdefy/blocks-antd, @lowdefy/blocks-basic, @lowdefy/blocks-echarts, @lowdefy/blocks-google-maps, @lowdefy/blocks-loaders, @lowdefy/blocks-markdown, @lowdefy/blocks-qr, @lowdefy/plugin-aws, @lowdefy/server-dev, @lowdefy/block-utils
Breaking Changes
schema.jsrenamed tometa.js: Block definitions moved fromschema.jstometa.js. Themeta.jsfiles exportcategory,icons,valueType,cssKeys,events, andproperties(JSON Schema).schemas.jsbarrel renamed tometas.js: Block packages export./metasinstead of./schemas..metaremoved from components: Block components no longer have a.metastatic property. Metadata is loaded from theblockMetas.jsonbuild artifact at runtime.blockMetas.jsonbuild artifact: The build pipeline writesplugins/blockMetas.jsoncontaining category, valueType, and initValue for each block type.buildBlockSchema(meta): New function in@lowdefy/block-utilsgenerates complete JSON Schema from meta objects with operator support and CSS slot key validation.
Replace moment.js with day.js across the monorepo.
Packages: @lowdefy/build, @lowdefy/blocks-antd, @lowdefy/connection-google-sheets, @lowdefy/operators-dayjs, @lowdefy/nunjucks
Breaking Changes
_momentoperator removed: Use_dayjsinstead. The new@lowdefy/operators-dayjspackage provides the_dayjsoperator with the same API patterns.@lowdefy/operators-momentpackage removed: Apps using_momentmust migrate to_dayjs.- Nunjucks
datefilter: Now uses day.js internally. Format strings are day.js compatible (mostly identical to moment). - Date picker blocks: All date/time picker blocks use day.js instead of moment for value parsing and formatting.
- Google Sheets connection: Date serialization uses day.js internally.
humanizeDurationthresholds: Thethresholdsparameter on_dayjs.humanizeDurationis silently ignored (day.js does not support it).- AgGrid cell renderers: Update
__momentto__dayjsin custom AG Grid cell renderer references. - Date selector UTC handling: Antd v6 bundles its own dayjs without the UTC plugin. Date selector blocks wrap antd's dayjs instances with the extended dayjs before calling
.utc()— this is handled internally and requires no user action.
Add keyboard shortcut support for block events.
Packages: @lowdefy/build, @lowdefy/client, @lowdefy/engine, @lowdefy/blocks-antd, @lowdefy/blocks-basic, @lowdefy/block-utils
Blocks can now define keyboard shortcuts on events using the shortcut property in the event long-form object. Shortcuts are platform-aware (mod+K maps to Cmd+K on Mac, Ctrl+K on Windows), support sequences (g i), and can be arrays for multiple bindings.
- Build validation warns on duplicate shortcuts within a page and conflicts with browser defaults (e.g.
mod+N) - ShortcutManager registers a single global keydown listener via tinykeys with visibility gating and input field suppression
- ShortcutBadge component renders platform-appropriate key symbols (e.g.
⌘ K) and is available to all blocks viacomponents.ShortcutBadge - ShortcutBadge in blocks: Button, Anchor, Tag, and Search blocks display a platform-aware keyboard shortcut badge (e.g.
⌘S/Ctrl+S) next to the title when the event has ashortcutdefined
Add theme.darkMode config with system preference support.
Packages: @lowdefy/build, @lowdefy/client, @lowdefy/actions-core, @lowdefy/blocks-antd, @lowdefy/operators-js, @lowdefy/server, @lowdefy/server-dev
System Dark Mode (theme.darkMode)
- New
theme.darkModeconfig key accepts'system'(default),'light', or'dark' - When set to
'system', the app follows the OS dark mode preference and updates live when it changes - When set to
'light'or'dark', the developer locks the mode — user preferences are stored but not applied
SetDarkMode Action
- Now accepts string params:
darkMode: 'system' | 'light' | 'dark' - Without params, cycles through light, dark, and system preferences
_media Operator
- New
_media: darkModePreferencereturns the user's preference ('system','light', or'dark') _media: darkModecontinues to return the effective boolean state
Dark Mode Rendering
- Notification, Message, and ConfirmModal render with correct dark mode colors via
App.useApp()hooks - Loader blocks (Skeleton, Spinner) use antd design tokens instead of hardcoded colors
- 404 page and loading states use theme-aware backgrounds
- Mobile menu drawer background matches the active theme
feat: Add lowdefy upgrade command with prompt-based codemod system
Packages: lowdefy, @lowdefy/codemods
New CLI command that guides version migrations using markdown prompts. Resolves a chain of upgrade phases from current to target version, presents migration prompts in order, and tracks progress for --resume support.
CLI (lowdefy)
lowdefy upgradecommand with--to,--plan,--resumeoptions- Version chain resolver computes ordered upgrade phases from semver ranges
- Fetches
@lowdefy/codemodspackage from npm, presents migration prompts - Each prompt can be copied to clipboard for AI tools, viewed as a manual guide, or skipped
- Upgrade state persistence in
.lowdefy/upgrade-state.jsonfor interrupted upgrades - Build-time warning when skipped codemods are detected
Codemods (@lowdefy/codemods)
- v5.0 entry with 20 migration prompts
- Covers antd v6 upgrade (14 prompts), layout grid migration (4 prompts), dayjs migration (2 prompts)
- Self-contained markdown prompts with context, examples, edge cases, and verification steps
AG Grid blocks now follow the Ant Design theme automatically. All six grid blocks (AgGridAlpine, AgGridBalham, AgGridMaterial, and their Input variants) map ag-grid CSS variables to antd design tokens, so they respond to light/dark mode and custom theme colors without any configuration. Override individual --ag-* variables via the block's style property for per-instance customization. The explicit dark variant blocks (AgGridAlpineDark, AgGridBalhamDark, AgGridInputAlpineDark, AgGridInputBalhamDark) have been removed.
Packages: @lowdefy/blocks-aggrid
Remove per-component header.theme, sider.theme, and menu.theme string properties from PageSiderMenu, PageHeaderMenu, Header, and Sider blocks. Dark mode now works automatically via CSS variables from the root ConfigProvider — no manual theme switching needed.
Packages: @lowdefy/blocks-antd
Removed properties:
| Block | Removed Property |
| -------------- | ------------------------- |
| PageSiderMenu | properties.header.theme |
| PageSiderMenu | properties.sider.theme |
| PageSiderMenu | properties.menu.theme |
| PageHeaderMenu | properties.header.theme |
| PageHeaderMenu | properties.menu.theme |
| Header | properties.theme |
| Sider | properties.theme |
Migration: Simply remove these properties. Dark mode is handled automatically by the global ConfigProvider. Use darkModeToggle: true on page blocks or SetDarkMode action for user-facing toggle. Use properties.theme (design token object) for fine-grained color customization.
Also removed:
| Block | Removed Property | Replacement |
| -------------- | -------------------------------- | --------------------------- |
| PageSiderMenu | properties.header.style | style: { .header } |
| PageSiderMenu | properties.header.contentStyle | style: { .headerContent } |
| PageSiderMenu | properties.sider.style | style: { .sider } |
| PageSiderMenu | properties.footer.style | style: { .footer } |
| PageSiderMenu | properties.content.style | style: { .content } |
| PageSiderMenu | properties.logo.style | style: { .logo } |
| PageHeaderMenu | properties.header.style | style: { .header } |
| PageHeaderMenu | properties.header.contentStyle | style: { .headerContent } |
| PageHeaderMenu | properties.footer.style | style: { .footer } |
| PageHeaderMenu | properties.content.style | style: { .content } |
| PageHeaderMenu | properties.logo.style | style: { .logo } |
Events removed:
| Block | Removed Event | Replacement |
| -------------- | --------------------- | ----------------------------------------- |
| PageSiderMenu | onNotificationClick | Use notifications.link property instead |
| PageSiderMenu | onProfileClick | Removed |
| PageHeaderMenu | onNotificationClick | Use notifications.link property instead |
| PageHeaderMenu | onProfileClick | Removed |
Other removals:
collapsibleandinitialCollapsedproperties removed from PageSiderMenu sider- Horizontal menu border removed from PageHeaderMenu header
Added:
notifications.linkproperty for notification item navigation
Add darkModeToggle property to PageHeaderMenu and PageSiderMenu. Set darkModeToggle: true to render a built-in sun/moon toggle button in the header that switches between light and dark Ant Design themes. The preference is persisted to localStorage and respects the OS dark mode setting as default. A toggleDarkMode method is also registered for programmatic control.
Packages: @lowdefy/blocks-antd
feat(plugin-aws): Add onBeforeUpload event and improve S3 upload error handling.
Packages: @lowdefy/plugin-aws
S3UploadButton and S3UploadDragger now fire an onBeforeUpload event before each file upload begins. If any action in the event handler throws, the upload is cancelled — useful for file validation, size checks, or confirmation prompts.
Upload error handling has been rewritten: XHR uploads are now Promise-based with proper error propagation, CORS/network failures throw a ServiceError with a diagnostic message, and file metadata is serialized into a plain object so _event resolution no longer destroys File/Blob references.
Fixes & Improvements
-
Improve build error messages: schema validation errors include the property name, style/class errors suggest dot-prefixed CSS slot keys, and YAML parse errors surface immediately instead of crashing on null entries. (
@lowdefy/build,@lowdefy/operators-js) -
feat(blocks-antd): Add Search command palette block with MiniSearch. (
@lowdefy/build,@lowdefy/blocks-antd)New
Searchdisplay block provides a full-text search command palette (Cmd+K / Ctrl+K) using MiniSearch (~6KB) and antd Modal.- Pre-built index support: Load a static JSON index via
indexUrlfor zero-config search on static sites - Runtime indexing: Pass
documentsarray withfieldsandstoreFieldsfor client-side indexing - Grouped results: Results auto-grouped by configurable field with section headers
- Keyboard navigation: Arrow keys, Enter to select, Escape to close
- Term highlighting: Matched search terms highlighted in results
- Recent searches: localStorage-backed search history with configurable count
- 14 CSS slots: Full style customization via
styles/classNames(trigger, modal, input, results, groups, highlights) - Analytics-friendly events:
onSelectpasses the result item, searchquery, andresultCountfor click-through tracking;onSearchpasses the search term and result count on each query change
Docs app integration
- New search index transformer (
generateSiteAssets.js) builds a MiniSearch index at build time from page content - Replaces Algolia DocSearch with the self-hosted Search block — removes external CDN dependency
Removed
@lowdefy/blocks-algoliapackage has been removed. Use theSearchblock in@lowdefy/blocks-antdinstead.
- Pre-built index support: Load a static JSON index via
-
fix(blocks-antd): Format PhoneNumberInput phone_number value. (
@lowdefy/blocks-antd)PhoneNumberInput now strips leading zeros and non-digit characters from user input when building the
phone_numbervalue. Typing0821234567with +27 selected now produces+27821234567instead of+270821234567. Empty input produces an empty string instead of just the dial code. -
Add
colorandiconsColorproperties to Header block. (@lowdefy/blocks-antd)Set
colorto change the header background color (defaults to--ant-color-bg-container). SeticonsColorto control the color of notification, profile, and dark mode toggle icons — useful when using a dark background color. TheiconsColorproperty is also available on PageHeaderMenu and PageSiderMenu. -
Fix Anchor block icon spacing by adding
marginRight: 4to the icon style so the icon doesn't sit flush against the title text. (@lowdefy/blocks-basic) -
feat(operators-js): Add
_math.modmodulo operator. (@lowdefy/operators-js)Added
_math.modoperator for modulo (remainder) calculations. Supports both array and named argument forms:_math.mod: [10, 3]or_math.mod: { dividend: 10, divisor: 3 }. -
fix(operators-js): The
_dateoperator now accepts Date objects as input, in addition to numbers and strings. (@lowdefy/operators-js) -
refactor(plugin-aws): Migrate S3 presigned URL operations from deprecated
aws-sdkv2 to modular@aws-sdkv3. (@lowdefy/plugin-aws)Replaced the monolithic
aws-sdkpackage with the modular v3 packages (@aws-sdk/client-s3,@aws-sdk/s3-request-presigner,@aws-sdk/s3-presigned-post). No changes to the request/connection API — existing configs work without modification. -
fix(helpers): Fix error serialization crash on circular structures. (
@lowdefy/helpers)Errors with circular references in nested objects (e.g., Axios HTTP error responses containing Node.js request/response cycles) crashed the logger with "Converting circular structure to JSON" instead of logging the actual error.
extractErrorPropsnow deep-cleans plain objects, arrays, and non-Error causes — stripping class instances, detecting circular references, and capping object depth.
- Validate session.user.roles as an array of strings to prevent silent authorization bypasses caused by misconfigured auth.userFields mappings
- Dev server skeleton rebuild detection uses build's ref map for accuracy
- Runtime operators _date, _intl, and _number.toLocaleString evaluate at runtime
Full changelog
Highlights
-
Session role validation: Session roles are now validated as arrays during session assembly, catching misconfigured
auth.userFieldsmappings early with a clear error message. Prevents silent authorization bypasses from incorrect role configuration. -
Dev server skeleton rebuild detection: Fixed dev server to more accurately detect which file changes require skeleton rebuilds, using the build's ref map instead of file paths. Prevents unnecessary rebuilds when modifying page templates and catches changes to API endpoints.
-
Runtime operators no longer frozen at build time: Fixed
_date,_intl, and_number.toLocaleStringoperators from being evaluated during build, which was freezing their values instead of evaluating them at runtime with the correct context.
Fixes & Improvements
-
fix(api): Validate session.user.roles is an array of strings. (
@lowdefy/api)Misconfigured
auth.userFieldsmapping roles to a non-array provider field (e.g., a string) caused silent authorization bypasses viaString.prototype.includessubstring matching. Session roles are now validated after session assembly, throwing a clearConfigErrorpointing to the auth configuration. Added a defense-in-depth guard increateAuthorizefor the same check. -
fix(build,server-dev): Improved accuracy of dev server skeleton rebuild detection. (
@lowdefy/build,@lowdefy/server-dev)The dev server previously used a path-based heuristic to decide which file changes required a skeleton rebuild. This could miss changes to API endpoints referenced from page directories, and unnecessarily rebuild for non-skeleton page templates. Skeleton rebuild classification now uses the build's ref map as the source of truth, ensuring only the correct file changes trigger skeleton rebuilds.
-
fix: Prevent _date, _intl, and _number.toLocaleString operators from being evaluated at build time. (
@lowdefy/operators-js)These operators depend on runtime context (current date/time, locale) and were incorrectly marked as static, causing them to be evaluated during the build and freezing their values.
Fixed dev server hang when a page YAML file's entire content was a `_ref`.
Full changelog
Highlights
- Fixed dev server hang that occurred when a page's entire YAML content was a
_ref— the server could get stuck indefinitely at "Building config..." - Improved custom plugin type map generation to handle missing
lowdefy.yamlfiles
Fixes & Improvements
-
fix: Fix dev server build hang when page files contain top-level _ref. (
@lowdefy/build,@lowdefy/server,@lowdefy/server-dev)The dev server could hang indefinitely at "Building config..." when a page YAML file's entire content was a
_ref. This caused a self-referencing parent in the ref map, leading to an infinite loop during page source resolution. Also fixed nulllowdefy.yamlhandling in custom plugin type map generation.
- Rename MDB_E2E_URI to LOWDEFY_E2E_MONGODB_URI in scaffold templates and run dependency install manually after init
- Page navigation now waits for 'domcontentloaded' instead of full 'load' event
- Dev server dynamically discovers and serves icons referenced in page blocks without rebuild or restart
- Date picker e2e helpers (do.select, do.fill) added with time selection for DateTimeSelector
- E2E secrets can be overridden via LOWDEFY_E2E_SECRET_* env vars
Full changelog
Highlights
-
Dev server: Icons referenced in page blocks are now dynamically discovered and served without requiring a rebuild or server restart.
-
Date picker e2e testing: All date picker blocks now support
do.select()for calendar interaction anddo.fill()for typing dates directly. DateTimeSelector also supports time selection. -
Operators: Fixed
_functioncallback mutation inevaluateOperatorsthat was causing_build.array.mapand similar operators to produce duplicate results on repeated invocations. -
E2E testing: Environment variable
LOWDEFY_E2E_SECRET_*can now overrideLOWDEFY_SECRET_*values, allowing test infrastructure (e.g., MongoMemoryServer) to coexist with secret managers. -
E2E navigation: Page navigation now waits for
domcontentloadedinstead of the fullloadevent, preventing hangs on pages with WebSocket connections or slow resources. -
E2E utils: Renamed
MDB_E2E_URItoLOWDEFY_E2E_MONGODB_URIin scaffold templates. Init script no longer auto-installs dependencies — they're added topackage.jsonand the user is prompted to install. -
Build performance: Sibling refs are now resolved in parallel to interleave CPU and I/O operations.
Fixes & Improvements
-
fix(build): Dev server dynamically loads icons discovered during JIT page builds. (
@lowdefy/build,@lowdefy/server-dev)Icons referenced only inside page blocks (e.g.,
icon: FiApertureon a Button) were not available in the dev server's static bundle, causing a fallback icon to render. The JIT page builder now detects missing icons when a page is compiled, extracts their SVG data from react-icons, and serves it via a dynamic API endpoint. The client fetches and merges these icons at runtime without triggering a Next.js rebuild or server restart. -
Resolve sibling refs in parallel using Promise.all to interleave CPU and I/O during build. (
@lowdefy/build) -
feat(blocks-antd): Add do.select() and do.fill() to date picker e2e helpers. (
@lowdefy/blocks-antd)All five date picker e2e helpers (DateSelector, DateTimeSelector,
DateRangeSelector, MonthSelector, WeekSelector) now support
do.select()for calendar UI interaction anddo.fill()for
typing dates directly. DateTimeSelector also supports time
selection via the time panel. -
Fix
_functioncallback template being mutated in-place byevaluateOperators, causing_build.array.mapand similar operators to produce duplicate results from repeated callback invocations. (@lowdefy/operators-js) -
feat(server-e2e): Add LOWDEFYE2E_SECRET* override support. (
@lowdefy/server-e2e)Secrets can now be overridden in e2e tests using
LOWDEFY_E2E_SECRET_*environment variables. These take precedence overLOWDEFY_SECRET_*values, allowing test infrastructure (e.g. MongoMemoryServer) to coexist with secret managers injected viacommandPrefix. -
fix(e2e-utils): Use domcontentloaded for page navigation. (
@lowdefy/e2e-utils)Page navigation now uses
waitUntil: 'domcontentloaded'instead of the defaultloadevent. This prevents hangs on pages with WebSocket connections or slow-loading resources, since the Lowdefy client readiness check is already a stronger signal. -
refactor(e2e-utils): Update scaffold env vars and simplify init. (
@lowdefy/e2e-utils)Renamed
MDB_E2E_URItoLOWDEFY_E2E_MONGODB_URIin scaffold templates to align with the newLOWDEFY_E2E_SECRET_*override pattern. The init script no longer runs install automatically — dependencies are added topackage.jsonand the user is prompted to install.
- Single-pass async walker (`walker` module) replaces multi‑pass JSON serialization in `@lowdefy/build`, reducing serializer.copy calls per ref.
- New e2e API assertions via `ldf.api()` with methods `expect.toFinish()`, `expect.toHaveResponse()`, `expect.toHavePayload()`, `response()`, and `state()`.
Full changelog
Highlights
- Build Performance: Single-pass async walker replaces multi-pass JSON serialization for ref resolution, eliminating 5+
serializer.copycalls per ref - Build Errors: Collect and report all ref errors at once instead of stopping on the first failure — faster fix-rebuild cycles when multiple config files have issues
- Inline Pages: Fixed inline page content being stripped during JIT builds with no recovery path
- Template Errors: YAML errors in
.yaml.njktemplates now show "Nunjucks template produced invalid YAML" instead of misleading line numbers - CLI: Build now properly exits with
process.exit(1)on errors instead of hanging with a spinner - CLI Start: Fixed port availability check when no
--portflag is passed (now defaults to port 3000) - Build Logs: Removed spurious
print: warnfields from build logger output - E2E Testing: New
ldf.api()assertions for API endpoint testing —expect.toFinish(),expect.toHaveResponse(),expect.toHavePayload(),response(),state() - E2E Developer Experience: New scaffold scripts (
e2e:headed,e2e:server),SLOW_MOenv var support, and fixed template defaults (appDir, health check, fixtures) - E2E Block IDs: Dotted block IDs (e.g.,
form.field.name) now work correctly in CSS selectors with newescapeId()utility - Helpers: Fixed
makeReplacermutating object marker enumerability, preventing internal markers from leaking to plugin components
What's New
feat: Single-pass async walker for ref resolution
Packages: @lowdefy/build, @lowdefy/operators, @lowdefy/helpers
Single-Pass Walker (@lowdefy/build)
- New
walkermodule replaces the multi-pass JSON round-trip architecture inbuildRefswith a single async tree walk - Resolves
_refmarkers, evaluates_build.*operators, and tags~rprovenance in one pass instead of 5+serializer.copycalls per ref - Wired into both
buildRefs(production) andbuildPageJit(dev server) - Added
isPageContentPathfor semantic shallow build matching, replacing brittle path-index checks - Deleted redundant code replaced by walker:
getRefsFromFile,populateRefs,createRefReviver, and theevaluateStaticOperatorswrapper
In-Place Operator Evaluation (@lowdefy/operators)
- New
evaluateOperatorsfunction walks a tree in-place and evaluates operator nodes, avoiding JSON serialization round-trips - Used by the walker module to evaluate
_build.*operators inline during ref resolution
Serializer Fix (@lowdefy/helpers)
- Added
skipMarkersoption toserializer.serializeToStringto exclude internal markers (~k,~r,~l,~arr) from serialized output
feat(e2e-utils): Add ldf.api() assertions for API endpoint testing
Packages: @lowdefy/e2e-utils
- New
api.jscore module withgetApiState,getApiResponse,expectApifunctions - Reads from
window.lowdefy.apiResponses[endpointId][0](mirrors request pattern) ldf.api(endpointId).expect.toFinish()— wait for API call completionldf.api(endpointId).expect.toHaveResponse(response)— assert responseldf.api(endpointId).expect.toHavePayload(payload)— assert sent payloadldf.api(endpointId).response()— get raw response valueldf.api(endpointId).state()— get full API state objectldf.mock.api()now captures payloads for assertionldf.mock.getCapturedApi(endpointId)— retrieve captured API data
Fixes & Improvements
-
fix(build): Report all ref errors at once instead of stopping on the first one. (
@lowdefy/build)When multiple referenced files have errors (missing files, YAML parse errors, invalid refs), the build now collects and reports all errors at once instead of stopping on the first failure. This reduces the fix-rebuild-fix cycle when multiple config files have issues.
-
fix(build): Preserve inline page content in JIT builds (
@lowdefy/build,lowdefy,@lowdefy/docs)Pages declared inline in
lowdefy.yaml(not via_ref) had their content stripped during shallow builds with no way to recover at JIT time, resulting in empty page shells. Detect inline pages by checking refId matches root ref with no sourceRef, and skip stripping. Set refId to null for inline pages increatePageRegistrysobuildPageJitreads the pre-built artifact instead of attempting JIT resolution. -
fix(build): Improve error message for YAML errors in njk templates (
@lowdefy/build)When a .yaml.njk nunjucks template produces invalid YAML, the error now says "Nunjucks template produced invalid YAML" instead of showing a misleading line number from the generated output.
-
fix(cli): Exit process and stop spinner on build errors. (
lowdefy)The CLI error handler logged errors but never called
process.exit(1), so the process continued running with a spinning indicator after a build failure. Addedprocess.exit(1)torunCommandafter error handling, and added{ spin: 'fail' }to stop the spinner inrunLowdefyBuild,runNextBuild, andinstallServercatch blocks. -
fix(cli): Fix port availability check for start command (
lowdefy,@lowdefy/server-dev)The CLI's
checkPortAvailablewas called withundefinedport when no--portflag was passed, causingnet.listen(undefined)to bind a random port instead of checking port 3000. Added defaultport: 3000ingetOptions. Removed redundantcheckPortAvailablefrom server-dev manager since the CLI now catches port conflicts before the server starts. -
fix(e2e-utils): Escape dotted block IDs in e2e CSS selectors. (
@lowdefy/blocks-antd,@lowdefy/blocks-basic,@lowdefy/e2e-utils)Block IDs containing dots (e.g.,
form.field.name) now work correctly in e2e test locators. AddedescapeId()utility to@lowdefy/e2e-utilsthat escapes CSS special characters, and updated all block e2e helpers and test specs to use it. -
fix(server): Remove unused print mixin from build logger (
@lowdefy/server)Removed the pino
mixinthat added aprintfield to every build log entry. This field was a leftover from a previous CLI display system and caused spuriousprint: warnlines in build output. -
feat(e2e-utils): Improved e2e scaffold with new scripts and SLOW_MO support. (
@lowdefy/e2e-utils)New scaffold scripts:
e2e:headed— Run tests with a visible browser in slow motion (SLOW_MO=500,--workers=1)e2e:server— Start the e2e server once, then rerun tests without rebuilding
SLOW_MO env var:
createConfignow reads theSLOW_MOenvironment variable and passes it to Playwright'slaunchOptions.slowMo- No manual config extension needed — just set
SLOW_MO=500in your npm script
Scaffold template fixes:
- Fixed
appDirfrom'../'to'./'—path.resolveresolves relative to cwd, not the config file - Fixed
fixtures.jstemplate to usemdbFixtures(plural) from/fixturessubpath withmergeTests - Simplified
example.spec.jsto use/api/auth/sessionhealth check — works on auth-protected apps - Fixed README template with correct
appDirvalues, "Faster Test Runs" section, and "Common Patterns" section
-
fix(helpers): Prevent makeReplacer from mutating original object marker enumerability. (
@lowdefy/helpers)makeReplacer used Object.defineProperty to make ~k, ~r, ~l enumerable for JSON.stringify, but operated on the original object reference instead of a copy. This permanently mutated the original, causing internal markers to leak to plugin components via Object.entries/Object.keys.
- `~ignoreBuildCheck` has been renamed to `~ignoreBuildChecks`; update config files accordingly.
- Sentry integration requires setting the environment variable `SENTRY_DSN` if usage is desired.
- Renamed `~ignoreBuildCheck` (singular) to `~ignoreBuildChecks` (plural); using the old name throws a migration error.
- Config-aware error tracing shows exact YAML file:line locations with clickable VSCode links.
- Zero‑config Sentry integration captures client and server errors with Lowdefy context (pageId, blockId).
- JIT page building in dev server builds pages on demand for faster iteration.
Full changelog
Highlights
Looking at the release notes file and the changelog entries you've provided, here are the developer-friendly bullet points organized by theme:
Error Handling & Debugging
- Config-aware error tracing shows exact YAML file:line locations with clickable VSCode links in terminal and browser
- Unified error system in
@lowdefy/errorspackage with standardized TC39 constructor signatures (new MyError(message, { cause, ...options })) - Build-time validation provides "Did you mean?" suggestions for typos and catches NEXTAUTH_SECRET configuration issues
- Plugin errors simplified — operators, actions, and connections throw plain messages; interface layer adds context (received value, location)
- Sentry integration (zero-config: just set
SENTRY_DSN) captures errors on client and server with Lowdefy context (pageId, blockId, config location) - New
UserErrorclass for expected user-facing errors (validation, intentional throws) — logs to browser console only, never to server terminal
Build & Performance
- JIT page building for dev server — pages build on-demand when requested instead of all at once, speeding up development
- Shallow
_refresolution leaves on-demand markers for faster initial builds with file dependency tracking for targeted rebuilds - New
~ignoreBuildChecksproperty suppresses specific build validation errors/warnings, with cascade support to suppress entire page subtrees - Build now collects all errors before stopping instead of failing on first error, showing developers all issues at once
- Schema validation errors stop the build immediately to prevent cascading failures
Developer Experience
- New
@lowdefy/loggerpackage with environment-specific variants (Node, CLI, browser) and standardized logging interface - Dev server now accepts
LOWDEFY_DEV_USERenv var orauth.dev.mockUserconfig for mock user support in e2e testing - Port-in-use check displays clear error message before starting server
- Build-time validation no longer warns about
_statereferences set by SetState actions
Testing
- New
@lowdefy/e2e-utilspackage for Playwright e2e testing with locator-first API (ldf.block('id').do.*), request mocking, and state assertions - Comprehensive e2e tests for blocks-antd (~700 tests covering 63 blocks) and blocks-basic (~40 tests)
npx @lowdefy/e2e-utilsscaffold command sets up e2e testing in your project
Bug Fixes
- Fixed env vars not being passed to Next.js build subprocess (
NEXT_TELEMETRY_DISABLEDwas being ignored) - Array line number metadata (
~l) now preserved throughserializer.copy()— schema validation errors show correct line numbers - Error cause chains properly preserved across plugin boundaries and CLI
- Input block onChange events now pass the input value
What's New
feat: Config-aware error tracing and Sentry integration
Packages: @lowdefy/api, @lowdefy/build, lowdefy, @lowdefy/client, @lowdefy/engine, @lowdefy/operators, @lowdefy/actions-core, @lowdefy/blocks-basic, @lowdefy/connection-axios-http, @lowdefy/connection-knex, @lowdefy/connection-redis, @lowdefy/connection-sendgrid, @lowdefy/operators-change-case, @lowdefy/operators-diff, @lowdefy/operators-js, @lowdefy/operators-jsonata, @lowdefy/operators-moment, @lowdefy/operators-mql, @lowdefy/operators-nunjucks, @lowdefy/operators-uuid, @lowdefy/operators-yaml, @lowdefy/server, @lowdefy/server, @lowdefy/server-dev, @lowdefy/server-dev, @lowdefy/block-utils, @lowdefy/errors, @lowdefy/helpers, @lowdefy/node-utils
Config-Aware Error Tracing (#1940)
- Errors now trace back to exact YAML config locations with file:line
- Clickable VSCode links in terminal and browser
- Build-time validation catches typos with "Did you mean?" suggestions
- Service vs Config error classification
Plugin Error Refactoring
- Operators throw simple error messages without formatting
- Parsers (WebParser, ServerParser, BuildParser) format errors with received value and location
- Removed redundant "Operator Error:" prefix from error messages
- Consistent error format: "{message} Received: {params} at {location}."
- Actions and connections also simplified: removed inline
receivedfrom error messages (interface layer adds it) - Connection plugins (axios-http, knex, redis, sendgrid) no longer expose raw response data in errors
Error Class Hierarchy
- Unified error system in
@lowdefy/errorswith all error classes@lowdefy/errors/build- Build-time classes with sync location resolution
- Error classes:
LowdefyError,ConfigError,ConfigWarning,PluginError,ServiceError ConfigWarningsupportsprodErrorflag to throw in production buildsServiceError.isServiceError()detects network/timeout/5xx errors~ignoreBuildCheckscascades through descendants to suppress warnings/errors
Build Error Collection
- Errors collected in
context.errors[]instead of throwing immediately tryBuildStep()wrapper catches and collects errors from build steps- All errors logged together before summary message for proper ordering
Sentry Integration (#1945)
- Zero-config Sentry support - just set SENTRY_DSN
- Client and server error capture with Lowdefy context (pageId, blockId, config location)
- Configurable sampling rates, session replay, user feedback
- Graceful no-op when DSN not set
feat(server-dev): Add mock user support for e2e testing
Packages: @lowdefy/api, @lowdefy/build, @lowdefy/server-dev
Set LOWDEFY_DEV_USER env var or auth.dev.mockUser in config to bypass login in dev server.
Collect all build errors before stopping
Packages: @lowdefy/build, @lowdefy/build, @lowdefy/helpers
feat: JIT page building for dev server
Packages: @lowdefy/build, @lowdefy/operators-js, @lowdefy/server, @lowdefy/server-dev
Shallow Refs and JIT Build (@lowdefy/build)
- Shallow
_refresolution stops at configured JSON paths, leaving~shallowmarkers for on-demand resolution shallowBuildproduces a page registry with dependency tracking instead of fully built pagesbuildPageJitfully resolves a single page on demand using the shallow build output- File dependency map tracks which config files affect which pages for targeted rebuilds
- Build package reorganized:
jit/folder for dev-server-only files,full/folder for production-only files
JIT Page Building (@lowdefy/server-dev)
- Pages are built on-demand when requested instead of all at once during initial build
- Page cache with file-watcher invalidation for fast rebuilds
/api/page/[pageId]endpoint triggers JIT build if page not cached/api/js/[env]endpoint serves operator JS maps- Build error page component displays errors inline in the browser
Operator JS Hash Check (@lowdefy/operators-js)
- Added hash validation for jsMap to detect stale operator definitions
Add build-time validation for NEXTAUTH_SECRET environment variable when auth providers are configured
Packages: @lowdefy/build
feat(build): Add ~ignoreBuildChecks property to suppress build validation
Packages: @lowdefy/build, @lowdefy/docs
Build Validation Suppression (#1949, #1963)
- New
~ignoreBuildChecksproperty suppresses build-time validation errors and warnings - Supports
true(suppress all) or array of specific check slugs (e.g.,['state-refs', 'types']) - Cascades to all descendant config objects - set on a page to suppress for all child blocks
- Silent suppression - no log output when validation is skipped (visible with
--log-level debug)
Renamed: Previously
~ignoreBuildCheck(singular) - using the old name throws a helpful migration error.
Available Check Slugs:
state-refs,payload-refs,step-refs- Reference validation warningslink-refs,request-refs,connection-refs- Action reference validationtypes- All type validation (blocks, operators, actions, etc.)schema- JSON schema validation errors
Use Cases:
- Dynamic state references created at runtime by custom blocks
- Multi-app monorepos with conditional configurations
- Work-in-progress features during development
- Plugin development with custom types not yet registered
Example:
# Suppress all checks for this page and descendants
pages:
- id: dynamic-page
type: Box
~ignoreBuildChecks: true
blocks:
- id: block1
type: TextInput
properties:
value:
_state: dynamicField # No warning
# Suppress only specific checks
blocks:
- id: custom_block
type: CustomBlock
~ignoreBuildChecks:
- state-refs
- types
properties:
onClick:
_state: dynamicState # No warning (state-refs suppressed)
Add e2e testing package for Lowdefy apps
Packages: lowdefy, @lowdefy/client, @lowdefy/blocks-antd, @lowdefy/block-dev-e2e, @lowdefy/e2e-utils
@lowdefy/e2e-utils (new package)
- Locator-first API via
ldfPlaywright fixture:ldf.block('id').do.*,ldf.block('id').expect.* - Request mocking with static YAML files (
mocks.yaml) and inline per-test overrides - Request assertion API:
ldf.request('id').expect.toFinish(),.toHaveResponse(),.toHavePayload() - State and URL assertions:
ldf.state('key').expect.toBe(),ldf.url().expect.toBe() - Manifest generation from build artifacts for block type resolution and helper loading
createConfig()andcreateMultiAppConfig()for Playwright config with automatic build/server management- Scaffold command (
npx @lowdefy/e2e-utils) for project setup with templates and dependency management - Block helper factory with auto-provided expect methods (visible, hidden, disabled, validation)
@lowdefy/cli
- Add
--serveroption tolowdefy buildfor server variant selection (e.g.,--server e2e)
@lowdefy/client
- Expose
window.lowdefywhenstage="e2e"for e2e state/validation access
@lowdefy/blocks-antd
- Flatten e2e helper APIs for polymorphic proxy compatibility
- Add TextArea e2e helper
@lowdefy/block-dev-e2e
- Remove unused srcDir variable
feat(logger): Add centralized @lowdefy/logger package and standardize logging
Packages: lowdefy, @lowdefy/server-dev, @lowdefy/logger
New @lowdefy/logger Package
- Centralized logging with environment-specific subpaths:
/node,/cli,/browser createNodeLogger— pino factory with custom error serializer preserving Lowdefy error metadata (source, configKey, isServiceError)createCliLogger— wrapscreatePrint(ora spinners, colored output) with standard logger interfacecreateBrowserLogger— maps toconsole.*with error formattingwrapErrorLogger— formats Lowdefy errors, emits source as separate{ print: 'link' }line for blue clickable links
Standardized .ui Interface
All logger variants expose logger.ui with consistent methods: log, dim, info, warn, error, debug, link, spin, succeed. This allows any component to emit structured output without knowing the runtime environment.
dimrenders as dimmed text in the CLI — useful for low-priority trace lines (e.g., request logs) that shouldn't compete visually with build output
CLI Logger Migration
- CLI now uses
createCliLoggerinstead of rawcreatePrint context.printreplaced withcontext.logger/context.logger.uicreatePrintandcreateStdOutLineHandlermoved from CLI to@lowdefy/logger/cli
Server-Dev stdio:inherit
- Server process spawned with
stdio: ['ignore', 'inherit', 'pipe'] - Server pino JSON flows directly to manager stdout (inherited by CLI) — eliminates dev stdout line handler
- Only stderr piped for error formatting through manager logger
- Server
createLoggerincludesprintmixin so CLI can render each line correctly
feat(errors): Add UserError class and thread actionId through request pipeline
Packages: @lowdefy/engine, @lowdefy/actions-core, @lowdefy/connection-test, @lowdefy/errors
UserError Class
- New
UserErrorin@lowdefy/errorsfor expected user-facing errors (validation failures, intentional throws) - UserError logs to browser console only — never sent to the server terminal
Throwaction now throwsUserErrorinstead of customThrowActionError
Engine Error Routing
Actions.logActionError()routes errors by type:UserError→console.error(), all others →logError()(terminal)- Deduplication by error message + action ID prevents repeated logging
actionId Threading
actionIdthreaded fromcallActionthroughcreateRequesttoRequests.callRequests- Server-dev request handler logs request trace via
logger.ui.dim()for dimmed output - Enables request logs to include the triggering action for better debugging context
test(blocks): Add comprehensive Playwright e2e tests for blocks-antd and blocks-basic
Packages: @lowdefy/blocks-antd, @lowdefy/blocks-basic, @lowdefy/block-dev-e2e
@lowdefy/block-dev-e2e (new package)
- Shared test utilities for block e2e testing in the monorepo
createPlaywrightConfigfor consistent Playwright setupgetBlockhelper using framework wrapper ID pattern (#bl-{blockId})navigateToTestPagefor test page navigation
@lowdefy/blocks-antd
- ~700 e2e tests covering all 63 blocks
- Test coverage for input, display, layout, navigation, and overlay blocks
- Block-specific e2e helpers (Button, TextInput, Selector)
@lowdefy/blocks-basic
- ~40 e2e tests covering core blocks (Box, Span, Anchor, Html, etc.)
Fixes & Improvements
-
refactor: Consolidate error classes into @lowdefy/errors package with environment-specific subpaths (
@lowdefy/api,@lowdefy/build,@lowdefy/client,@lowdefy/engine,@lowdefy/operators,@lowdefy/errors,@lowdefy/helpers,@lowdefy/logger)Error Package Restructure
- New
@lowdefy/errorspackage with all error classes (ConfigError,PluginError,ServiceError,UserError,LowdefyInternalError,ConfigWarning)@lowdefy/errors/build- Build-time errors with sync resolution via keyMap/refMap
- Moved ConfigMessage, resolveConfigLocation from node-utils to errors/build
TC39 Standard Constructor Signatures
- All error constructors standardized to
new MyError(message, { cause, ...options }):new ConfigError('Property must be a string.', { configKey }); new OperatorError(e.message, { cause: e, typeName: '_if', received: params }); new ServiceError(undefined, { cause: error, service: 'MongoDB', configKey }); - Plugins throw simple errors without knowing about configKey
- Interface layer adds configKey before re-throwing
configKey Added to ALL Errors
- Interface layer now adds configKey to ALL error types (not just PluginError):
- ConfigError: adds configKey if not present, re-throws
- ServiceError: created via
new ServiceError(undefined, { cause: error, service, configKey }) - Plain Error: wraps in PluginError with configKey
- Helps developers trace any error back to its config source, including service/network errors
Cause Chain Support
- All error classes use TC39
error.causeinstead of custom stack copying - CLI logger walks cause chain displaying
Caused by:lines extractErrorPropsrecursively serializes Error causes for pino JSON logs- ConfigError and PluginError extract
receivedandconfigKeyfromcause:new ConfigError(undefined, { cause: plainError }); // extracts cause.received and cause.configKey new PluginError(undefined, { cause: plainError }); // same extraction
Error Display
errorToDisplayString()formats errors for display, appendingReceived: <JSON>whenerror.receivedis definedrawMessagestores the original unformatted message on PluginError
- New
-
fix(build): Eliminate false positive warnings for _state references set by SetState actions (
@lowdefy/build)The validateStateReferences validator now recognizes state keys initialized by SetState actions in page or block events, eliminating false positive warnings when _state references legitimate state that's set programmatically rather than from input blocks.
-
Improve build error handling and test infrastructure: (
@lowdefy/build,@lowdefy/engine)- Stop build after schema validation errors to prevent cascading failures
- Convert makeId to class with reset() method for reliable test isolation
- Add parseTestYaml helper for realistic YAML-based test fixtures
- Simplify buildConnections by removing duplicate validations handled by schema
- Fix addKeys to not store undefined values in keyMap
- Menu link to missing page is warning in dev, error in prod
- Handle areas with no blocks gracefully - render as empty page instead of crashing
- Filter out anyOf/oneOf cascade errors in schema validation - only show the specific error
-
fix(helpers): Preserve ~l line numbers on arrays in serializer.copy (
@lowdefy/build,@lowdefy/helpers)Fixed an issue where line number metadata (
~l) on arrays was lost duringserializer.copy(), causing schema validation errors to show incorrect line numbers.Problem:
- Schema errors for properties like
requests:at line 7 were showing:1instead of:7 - The
~lproperty on arrays was stripped during JSON round-trip inevaluateBuildOperators
Solution:
- Arrays with
~lare now wrapped in a marker object{ '~arr': [...], '~l': N }during serialization - The reviver restores the array with
~lpreserved as a non-enumerable property - Custom revivers now receive the restored array instead of the wrapper object
Result:
Before: lowdefy.yaml:1 at root After: lowdefy.yaml:7 at root - Schema errors for properties like
-
fix(errors): Preserve error cause chains in catch-and-rethrow blocks across plugins and CLI (
lowdefy,@lowdefy/connection-axios-http,@lowdefy/connection-sendgrid,@lowdefy/operators-js,@lowdefy/operators-jsonata,@lowdefy/operators-nunjucks) -
Fix env vars not being passed to Next.js build subprocess. The
envobject was passed as a separate parameter tospawnProcessinstead of insideprocessOptions, soNEXT_TELEMETRY_DISABLEDwas silently ignored duringnext build. (lowdefy) -
Add port-in-use check with clear error message before starting server.
-
fix(errors): Remove redundant try/catch in operator runners, add cause chains to remaining throws (
@lowdefy/actions-core,@lowdefy/server-dev,@lowdefy/block-dev) -
Pass value of inputs to onChange event. (
@lowdefy/blocks-antd,@lowdefy/blocks-color-selectors)