feat(portal,server): custom blocks authoring, canvas integration and queued execution - #259
Merged
07prajwal2000 merged 2 commits intoAug 17, 2026
Conversation
…queued execution Closes Fluxify-rest#258. Portal - Input params editor and icon picker, wired into a 4-step create page and a settings modal, so a custom block can finally declare its input contract. - List page reworked: grid of the real canvas node, search, empty state, and a click straight into the block's canvas. - Custom blocks now load from the DB into the block picker under their own category (plugin-sourced ones get "Built-in", ready for the shipped set), and a placed block renders its own label, description and icon on the canvas and in the settings panel. - A custom block's `integration_selector` params are injected into the integration picker on its own canvas: picking one writes `param:<name>` and the real integration is chosen by the caller, so no test/open action is shown. Engine - `params` is bound alongside `input` in a custom block's JS: configuration is `params.<name>` at any depth, `input` is always the previous block's output. - Nine db/log emitters take their connection through `node.value`, so a `param:` integration resolves at call time. - New `queued` invoke mode: durable background execution over a single shared JetStream work queue (`fluxify.jobs.<projectId>.<kind>`), kind-routed so crons, workflows and schedules can reuse the same stream and worker. Fixes - The compiler never registered custom blocks into its own library, so any route calling one failed with "No codegen for block type". It now registers its own output and resolves dependencies before compiling, and unregisters on rename or delete. - Recursion is refused: a block cannot call itself, directly or through a chain, checked on every canvas save and reflected in the picker. - Handle shape rules no longer depend on stylesheet order. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One custom block, two input params, three routes that configure it differently — signing, lenient verification, and strict verification that ends on the block's own response block. The harness gained what custom blocks need: fixtures declare the blocks they call in `uses`, and the runner registers them before compiling the route, the same order the real compiler works in. Custom blocks live in `blocks/` in the shape the portal saves them, input contract included. Also adds `engine: "none"` for graphs that touch no database, so a pure-JS fixture no longer starts a Postgres container. The block's secret is hardcoded — custom block params cannot reference app config yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #258.
Why
Custom blocks worked end to end on the server, but the portal exposed a thin slice: a card grid, a three-field create modal, and an "Open canvas" link. The panel that renders a placed block's settings already read
inputParams— nothing could write it, so every custom block on a route was unconfigurable. Authoring a block also gave no way to reach it: it never appeared in the block picker.What
Authoring
namestays immutable and the UI says why.Canvas
integration_selectorparams are injected into the integration picker. Picking one writesparam:<name>; the concrete integration is chosen by the caller, so no test/open action is offered for it. Route canvases are untouched — the behaviour is gated on the custom-block route'sblockId.Engine
paramsis bound alongsideinputin a custom block's JS. Configuration isparams.<name>at any depth;inputis always the previous block's output. Previously the first block saw config asinput.fooand the caller's value atinput.input, and the meaning changed further down the graph.Nine db/log emitters take their connection through
node.value, so aparam:integration resolves at call time rather than being baked in.New
queuedinvoke mode: durable background execution.asyncis a floating promise on the request worker — it dies with the worker, competes with traffic, and cannot retry.queuedpublishes to a JetStream work queue and another worker picks it up.One stream for all background work (
fluxify.jobs.<projectId>.<kind>), kind-routed through a handler registry, so crons, workflows and scheduled jobs reuse the same stream, consumer and worker rather than each adding a mostly-idle stream. Transport (acks, redelivery, concurrency) lives where NATS is; handlers live where user code is, connected over the existing supervisor↔execution IPC. Tunable viaJOBS_CONCURRENCY,JOBS_ACK_WAIT_MS,JOBS_MAX_DELIVER,JOBS_RETRY_DELAY_MS.Fixes
No codegen for block type. It now registers its own output and resolves dependencies before compiling, and unregisters on rename or delete — a rename previously left the old name resolving to a stale body forever.Notes
apps/portalonly; no hardcoded colors (semantic tokens throughout); shared components reused.input.fooneedparams.foo. Nothing warns — the value silently becomesundefined. Cheaper to land now than after people build on it.invokeat all.Testing
turbo run lintclean across all 10 packages.packages/blocks145 pass,apps/server355 pass,apps/portal82 pass.🤖 Generated with Claude Code