Files
app/ROADMAP.md
T
stroblmeandClaude Fable 5 4bcd38354b Draw every flow as one graph, merged on what it talks to
A node type can now say which outside thing its parameters point at, and
nodes sharing one — a broker topic, a URL, a bucket — are drawn as a single
neuron on a new /brain canvas. That makes the wiring which runs between
flows through a broker visible for the first time; no single flow's canvas
can show it. The key is read off stored parameters, so a credential
reference never reaches an id.

Layout is a d3 force simulation settled once and then frozen, lit by the
socket the editor already listens to: a neuron pulses when any node behind
it publishes, and its connections light as values pass.

Fixes the message pulse while here: interpolating the stroke against the
edge's `color-mix()` resting colour went through oklab and left the gamut,
which turned every pulse on both canvases fluorescent yellow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017MeiWk3Yq12n2pTvnQWYvt
2026-08-16 22:59:31 +02:00

13 KiB
Raw Blame History

Roadmap

Component-level breakdown. The milestone-level master (M1M5, with the vision decisions behind it) is docs/private/roadmap.md in the docs submodule.

Implementation strategy and record of existing/planned features. Completed items are terse checklists — the requirement detail lives in docs/private/vision.md (goals, requirements, decisions) and docs/architecture/structure.canvas (the four-way component split). Remaining tasks keep enough scope to be actionable.

Legend: [x] done · [ ] planned · sub-lists split done vs. remaining for partial items.

Within each phase, remaining [ ] items are listed in rough priority order: making the existing flow engine reachable and persistent precedes new feature breadth.

Phase 0 — Workspace and platform

  • Root orchestrator repo with app, index and docs as submodules
  • make init bootstrap: secrets generation, per-stack .env propagation, shared proxy docker network
  • Layered compose (compose.ymlcompose.dev.ymlcompose.local.yml) for both stacks, one Traefik serving ${DOMAIN}, app.${DOMAIN}, api.${DOMAIN}
  • Design token contract: root DESIGN-GUIDELINES.md, per-repo DESIGN.md, byte-identical token blocks verified by make design-check
  • CI on Codeberg (Forgejo Actions): pre-commit, backend tests, Playwright, compose smoke
  • Soak and chaos harness: sustained load with the state backend, the broker and the engine itself taken away underneath it. The reliability layers were verified by hand against a live instance; keeping them verified needs a harness

Phase 1 — Backend: management

Python, optimised for development speed. Owns the graph structure, persistence and the external interfaces. See docs/architecture/structure.canvasBackend Management.

  • FastAPI + SQLModel + Alembic + Postgres base with JWT auth and user management
  • Flow engine in backend/app/flow/: Node / Pipeline / StateBackend (memory + Redis) / FlowController
  • Node types: HTTP, MQTT, InfluxDB, Delay, MLP
  • Flow-logic vocabulary as node types rather than repeated code: inject (manual, interval, cron or at startup), switch, change, filter-unchanged, join, trigger, command, file and ntfy. Each is configured by filling in a form the editor generates from its parameter schema
  • app/flow is an importable package with absolute app.flow.* imports
  • Typed, serializable node I/O: every port declares a DType, messages are JSON on the wire and in Redis, no pickle anywhere. Binary codecs are still open — DType.JSON carries everything non-scalar for now
  • Message namespacing per flow (flow.message), with several producers per message resolving to real fan-in
  • Secrets/credentials store for node integrations managed via the API/UI (encrypted at rest, referenced from node params as {"$secret": "name"}); .env bootstrap-only
  • Python modules for node code, managed from the UI: a pip manifest versioned with the flows, installed with uv pip sync into a venv of the user's own on the data volume. The worker processes run that interpreter, so an install takes effect without restarting the engine and can never shadow the app's own packages
  • Connector node contract: ConnectorNode with a declared contract version, a polling coordinator that deduplicates, x-secret parameters the editor renders as a secret picker, and health reporting. Connectors are installed packages found through the fluksio.node_types entry point group; the contract is documented in docs/connectors/ with a working skeleton at connector-skeleton/. The registry follows later
  • First real connectors written against that contract from outside the engine: WF-RAC aircon, calendar, UniFi presence and Art-Net, in connectors/. Built by make connectors and installed into the image. Reading only for now — the aircon package cannot produce a command and Art-Net keeps its packets off the wire until transmit is switched on
  • Node lifecycle as a protocol (start/stop/report_health on Node), replacing the controller's per-type isinstance chains — the same hooks a connector implements, validated on the built-in nodes first
  • Flow persistence: flow.json plus node sources per flow, replacing the watch-directory prototype
  • REST + WebSocket API over the engine: create/read/update flows, edit node source, run, and stream values, node status and execution events
  • Dependency-loop detection and graph validation surfaced as API errors
  • Per-flow start/stop, stored in a runtime.json beside the flow so it survives a restart and stays out of the autosaved document; pause/resume holds a flow's nodes while its values keep arriving
  • Node log streaming: what a node prints, and the traceback of one that fails, reach the editor as node_log events
  • MQTT broker / InfluxDB compose services for local development
  • Git-based versioning of the flow store (one commit per saved change)
  • Draft/publish split: edits autosave to flow.draft.json / nodes.draft/, the engine runs only the published files, and publishing promotes the draft. Saves carry the version they were based on, so a second client editing the same flow is refused rather than overwritten
  • Import/export of a flow as human-readable code plus a JSON structure
  • Per-input/-output discretization interval setting: a port publishes, or wakes its node, at most every n seconds. State keeps the latest value, so only the delivery is skipped
  • Alert / notification handler: engine failures — a node raising, a connection dropping, a flow quarantined, the queue gone — reach ntfy, email or a webhook. Mostly it declines to send: the same fault repeating is one alert with a count, a flapping connection is muted, and there is a ceiling per hour. Configured through the API at /alerts/config, with a test send per channel
  • Deep health check (GET /utils/health/): reports event-loop lag and state-backend reachability and fails the container healthcheck, so a wedged engine is restarted rather than counted as up. One engine per deployment — the API image runs a single worker, because a second one would be a second engine
  • Supervised background tasks: a node's subscription, schedule or poll loop is restarted with growing delay when it dies, and a flow that spends its failure budget is quarantined and surfaced rather than left crash-looping. The loops themselves no longer carry private retry logic
  • Durable work queue: every external trigger is journaled to Redis Streams before anything runs and acknowledged once its cascade finishes, so an engine that dies mid-cascade picks the work up again instead of losing it. A reaper reclaims what a dead consumer never acknowledged; nodes that reach outside are skipped on a redelivery they already ran. Long-lived worker pools replace the per-wave executors, and a delay now waits in the queue rather than on a worker thread
  • Engine history in Postgres: a second bus subscriber folds executions, errors, timings and queue lag into per-minute rollups, keeps failures with their traceback and an audit trail of who published what, and records one row per cascade — including the manual runs and previews that never went through the queue. Read back through /observability/*, which always answers 200 so a degraded engine still renders, and pruned on a retention window
  • Test nodes: a small node dragged onto an existing one, smoke or unit, blocking deployment on failure
  • User management scoped per flow and per data set
  • MCP server over the same API: agents authenticate through a built-in OAuth 2.1 authorization server (dynamic registration, PKCE, rotating refresh tokens) and drive the flow API through 20 tools. Tokens are RS256, signed with their own keypair, so the set can be revoked on its own — and an additional issuer is one branch in deps.decode_token, which is the seam remote access needs later
  • LLM interface for natural-language flow authoring beyond the MCP tools

Phase 2 — Backend: processing

Rust, optimised for throughput. Executes nodes and distributes them across workers. See docs/architecture/structure.canvasBackend Processing.

  • Parallel invocation of stateless nodes over independent input sets, to keep I/O delay minimal (stateful I/O nodes keep serializing via the synchronous mechanism)
  • Run user Python nodes out of process: a pool of persistent worker subprocesses speaking one JSON object per line, entered through a proxy the controller installs as the node's function, so every execution path funnels through it unchanged. A crash costs one subprocess, a per-node timeout is a kill, and cancelling from the canvas is that same kill on request
  • Extract node execution from the Python prototype into a Rust engine
  • Worker distribution and load balancing across capable devices
  • Input/output validation at the node boundary
  • Data aggregation and discretization

Phase 3 — Frontend: admin view

React + Vite, primarily desktop but usable on mobile. See docs/architecture/structure.canvasFrontend Admin View.

  • Dashboard SPA shell: TanStack Router, floating frosted sidebar, auth flows, generated OpenAPI SDK
  • Node canvas (@xyflow/react) showing nodes and their connections, which are derived from message names rather than stored
  • Tab-style view of atomic flows, with a floating dock
  • Embedded code editor (Monaco) for node source
  • Live values on the edges, with the last payload and its time on click
  • Provenance: every value says what caused it, so an edge pulses for the producer that actually published rather than every producer of that message. A dashboard control, another flow or an API caller is drawn as a label on the canvas instead of being invisible — which also gives cross-flow wiring the link in/out it lacked
  • Validation shown on the node it belongs to, and summarised in the dock
  • Publish control and draft markers in the flow bar, discard in the flow panel, and a conflict dialog when another client got there first
  • Marking a node reusable, and placing a shared one from the palette
  • Secret picker for credential parameters, so a password never lands in flow.json
  • Dashboard showing which flows run, which are stopped and which have errors, with a switch per flow
  • Logs panel in the canvas dock, pause/resume beside Run, and replaying an edge's last message from the inspector
  • Device assignment per node, selectable from compatible devices
  • Test-node affordance on the canvas
  • User management screens
  • Screens for what the API used to own alone: the secrets store and the alert channels/rules each get a sidebar page, and the OAuth clients an agent registers are listed and revocable under Admin — which needed its management endpoints written first
  • Health screen: how the engine is doing now (nodes, flows, queue, loop lag) over what it has been doing all day — throughput and failure charts, a per-flow table, the recent cascades, failures that expand to their traceback, dead-lettered work and the audit trail
  • Brain graph: every published flow on one canvas, with nodes that talk to the same outside thing — a broker topic, a URL, a bucket — drawn as a single neuron, so the wiring that runs between flows through a broker is visible at all. Laid out by a force simulation settled once and then frozen, lit by the same socket the editor listens to, and read-only: a neuron leads back to the flow it came from
  • Mobile-friendly canvas: touch connect, full-screen node panel
  • Installable as a PWA (vite-plugin-pwa)

Phase 4 — Frontend: dashboard view

Shares components with the admin view. See docs/architecture/structure.canvasFrontend Dashboard View.

  • User-defined dashboard layout with edit and view modes: dashboards are their own documents, widgets bind to message names, and the input ones publish back. View mode is plain CSS grid, so a panel that only displays loads no editing code
  • Chart widget drawing a message's history through uPlot, with --chart-1…5 as one lightness ramp of the brand hue; a widget bound to the wrong dtype, or to nothing, is flagged the way a failing node is
  • Layout by dragging and resizing (react-grid-layout), a grid size per dashboard, and /view/{name} — a full-bleed route that loads neither the editor nor the grid library, which is what a wall panel is pointed at
  • Per-device view

Phase 5 — Website and docs

  • Marketing site with a live node-graph demo, shared design system
  • Published documentation site fed from the docs submodule
  • Umami analytics configured (the site still ships the placeholder script)