Commit Graph
545 Commits
Author SHA1 Message Date
stroblmeandClaude Opus 5.5 19efb3a15d Use a placeholder password in the docs; ignore *.pem and *.key
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-06 12:30:57 +02:00
stroblmeandClaude Opus 5.5 6c0ab2049b Send a CSP and security headers from the frontend's nginx
script-src admits the bundle and the pre-paint theme script by hash, with no
unsafe-inline or unsafe-eval; style-src keeps unsafe-inline for the <style>
elements Monaco, Radix and the toasts insert. frame-ancestors 'self',
nosniff and strict-origin-when-cross-origin go with it, and the Dockerfile
adds the API origin to connect-src when the bundle calls one elsewhere. Zod
runs jitless so its eval probe is not reported on every page. The embed
widget keeps allow-same-origin: the demo's weather map does not render
without it; the comment now says so and what it costs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-06 12:30:47 +02:00
stroblmeandClaude Opus 5.5 b0c601c6ed Enrol over http only to loopback; serve the API docs to this machine
The enrolment exemption covered any *.local host, which mDNS answers with
whoever claims it; it is now localhost, 127.0.0.0/8 and ::1. /docs, /redoc
and openapi.json stay closed in production and otherwise answer only a
loopback caller unless API_DOCS_ENABLED is set, which the dev compose does,
so `fluksio serve --host 0.0.0.0` no longer hands the schema to the network.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-06 12:20:13 +02:00
stroblmeandClaude Opus 5.5 cf5329e3a8 Harden the remote worker: TLS off loopback, full digests, filtered env
fluksio-worker now refuses ws:// and http:// engine and artifact URLs unless
they are loopback or --insecure is given; the engine still accepts both.
An artifact reference must be sha256: plus 64 hex digits before it is joined
into a path, and node code gets the host environment through the engine's
credential denylist instead of all of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-06 12:18:30 +02:00
stroblmeandClaude Opus 5.5 17c2f4ff70 Quote every path parameter the MCP tools put into a URL
httpx resolves `..` before sending, so save_flow("../ai/config") reached
PUT /ai/config. Each segment is now percent-encoded, dots included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-06 12:16:48 +02:00
stroblmeandClaude Opus 5.5 8cfe5c4e6e Count per-address limits on the hop a trusted proxy added; limit signup
The limits read the leftmost X-Forwarded-For entry, which the client writes,
and the LAN nginx appended to it. The address now comes from uvicorn's
proxy-headers middleware (rightmost hop not in FORWARDED_ALLOW_IPS), applied
in the app too so tunnel-replayed calls resolve the same way; nginx sets the
header to $remote_addr and the compose backend trusts the Docker networks.
/users/signup gets a per-address limit of 10 an hour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-06 12:15:58 +02:00
stroblmeandClaude Opus 5.5 7f06260c1a Read secret names in the node panel only as a superuser
GET /secrets/ is a superuser's, so every node a non-superuser opened
showed "You do not have permission to do that". The query now runs only
for a superuser; anyone else gets no secret suggestions, silently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-06 12:01:53 +02:00
stroblmeandClaude Opus 5.5 7ffdb1a3bf Refuse names that are not names at the store boundary
DELETE /flows/%2e%2e reached FlowStore.delete_flow(".."), which removed
the data directory, and /flows/_lib removed every shared node. Every
path the flow and dashboard stores build from a flow, node, library or
dashboard name now goes through checked(), which raises InvalidName
(400) for anything _validate_name refuses. _validate_name uses
fullmatch, so a trailing newline is refused too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-06 11:59:25 +02:00
stroblmeandClaude Opus 5.5 48b87cc16e Upgrade dependencies flagged by pip-audit and bun audit
Backend: starlette 1.7.0, pyjwt 2.15.1, python-multipart 0.0.32,
aiohttp 3.14.4, anyio, idna, urllib3, click, lxml, mako, multidict,
pydantic-settings, pygments, python-dotenv, requests. pip-audit goes
from 121 findings in 16 packages to pytest alone (dev, needs 9.x).

Frontend: axios 1.20.0, form-data 4.0.6, dompurify 3.4.16 through an
override (monaco-editor pins 3.4.8), vite 7.3.7, the router packages
and the vite and router build-time dependencies within their ranges.
bun audit goes from 97 to 21, all through @hey-api/openapi-ts 0.73.0,
whose fix is a breaking 0.97 upgrade. The new router plugin reorders
routeTree.gen.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-06 11:50:35 +02:00
stroblmeandClaude Opus 5.5 7370105cd8 Make changing engine-run code superuser-only
Writing node source, installing modules and any flow save that adds,
retypes or repoints a code node (Function, Command), edits a Command
node's settings or changes a node's resources.env now answer 403 for
everyone but a superuser. MCP tools and the assistant reach the same
routes, so they inherit the gate. Node types report runs_code.

The editor shows code read-only with a note, disables a Command node's
settings, leaves code nodes and reusable nodes out of the palette and
paste, and the Modules page offers no install.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-06 11:36:30 +02:00
stroblmeandClaude Opus 5.5 de7ee759f9 Make self-service signup opt-in via SIGNUP_ENABLED
/users/signup answers 403 unless SIGNUP_ENABLED is set. A public
GET /utils/config/ tells the login page, which hides the sign-up link,
and the signup page explains that an administrator creates accounts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-06 11:24:27 +02:00
stroblmeandClaude Opus 5.5 6e18ab96ce Explain the terminal chart colours by the terminal theme
The docstring still justified the ANSI colours by the old one-hue web ramp.
The web now draws from the categorical --chart-1…8; the terminal keeps named
ANSI colours so it follows the user's terminal theme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-06 07:24:54 +02:00
stroblmeandClaude Opus 5.5 4d63f58806 Scroll the current dashboard to the middle of the phone rail
On mount and on every switch the horizontal rail scrolls itself (not the
page) so the active entry is centred, instantly under reduced motion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-06 07:24:53 +02:00
stroblmeandClaude Opus 5.5 c7e91ef234 Make dialog close and footer buttons 44px below md
The close button grows to a 44px target centred on where its icon already
sits, and every footer button is 44px tall, on a phone only; desktop keeps
its sizes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-06 07:24:53 +02:00
stroblmeandClaude Opus 5.5 457b88ed14 Edit json, record and list values in the JSON dialog
An inject's payload and a flow input's starting value of those types use
JsonField instead of a one-line text field. The field can now hold no value
(shown as the placeholder; an emptied editor saves null) and checks the shape:
a record must be an object, a list an array.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-06 07:24:43 +02:00
stroblmeandClaude Opus 5.5 4f4de1fd9d Draw charts from one categorical palette, --chart-1…8
The one-hue ramp told lines apart by lightness alone. --chart-1 is now
--primary and slots 2-8 are distinct hues (orange, violet, green, orchid,
ochre, teal, magenta) that clear 3:1 on --card and stay apart under
protanopia and deuteranopia; red and sage are left out as the status
colours. A chart takes the slots in order instead of spreading a ramp,
and a dashboard's own palette continues after its primary and listed
colours with slots 2-8 rather than tints of its scheme. Line charts stay
at five series. Tokens mirror index's, as design-check requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-06 07:05:09 +02:00
stroblmeandClaude Opus 5.5 d7ca933478 Float the dashboard rail along the bottom on a phone
Below md a dashboard stacks to one column and dropped its rail. The rail is
now a horizontal row in the dashboard's own look, floated over the bottom
of the screen on /panel/{id} and above the dock in the app's view, with
44px items, sideways scrolling when it overflows, and padding so the last
tile clears it; a panel's read-only notice moves up out of its way. The
three looks share the geometry from ui/core.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-06 00:22:28 +02:00
stroblmeandClaude Opus 5.5 2c1ad395de Edit JSON node settings in a dialog from a one-line preview
A free setting typed json and a declared object or array setting (a Switch
node's rules, HTTP headers) show one line of their value; clicking opens a
Monaco JSON editor in a dialog that saves only once the text parses.
Declared object settings were not shown at all before. Escape in a dialog
opened from a side panel no longer closes the panel too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-05 23:49:42 +02:00
stroblmeandClaude Opus 5.5 5b67dab415 Name a port apart from its message in the node panel, two lines per port
The port name follows the message until it is typed by hand, and clearing
it follows again. The second line holds the message, so a list port's item
type no longer squeezes the name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-05 23:28:04 +02:00
stroblmeandClaude Opus 5.5 e8536e7c1c Document the wren node and the chat's Control tab
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-05 20:59:23 +02:00
stroblmeandClaude Opus 5.5 673061d9fb Talk to wren nodes from the chat's Control tab, opened from Home
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-05 20:59:11 +02:00
stroblmeandClaude Opus 5.5 d8edce55f9 Add the wren node: a typed command into values for the devices wired to it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-05 20:58:59 +02:00
stroblmeandClaude Opus 5.5 550185b73c Document the icon and colour names a forecast can carry
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-05 18:57:58 +02:00
stroblmeandClaude Opus 5.5 ae6e51b82a Centre a node arrived on from another screen
The viewport effect marked the selection as centred before the layout
had placed anything, so an arrival at ?node= fitted the whole flow.
It is recorded once the fit has run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-05 18:19:38 +02:00
stroblmeandClaude Opus 5.5 c3031a9960 Find a flow's declared inputs and outputs in the global search
/search lists each input and batch output under its qualified message,
and picking one opens the flow on the label's id, which the editor now
treats like a click on the label: it opens the flow's settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-05 18:15:35 +02:00
stroblmeandClaude Opus 5.5 32a0d23dcb Word the soak harness's host-safety note host-neutrally
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-05 18:01:34 +02:00
stroblmeandClaude Opus 5.5 2563f14bc5 Spell the API host as api.${DOMAIN} in the worker and login examples
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-05 16:54:09 +02:00
stroblmeandClaude Opus 5.5 a8495c023c Make narrowed backend test runs and the quarantine test order- and load-proof
The session db fixture now disposes the engine before deleting the file, since a
module under a no-op db (tests/sdk, tests/flow) may have connected first; the
sdk import tests forget the study packages they import; the supervision tests
keep the real quarantine length so it cannot expire mid-assertion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-05 16:54:00 +02:00
stroblmeandClaude Opus 5.5 4e5f0d0654 Read a key a run lacks as its flow's declared value in "What differs"
Runs from before the declared values were folded into params lack them, so
every such key read as varied next to a newer run with the same value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-05 16:34:48 +02:00
stroblmeandClaude Opus 5.5 190754e398 Show one seed column when the flow declares a seed input
The run-level seed is that parameter then, so the comparison table drew it
twice and counted it twice in "N of M varied".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-05 15:11:01 +02:00
stroblmeandClaude Opus 5.5 8e655c842f Record which inputs a run's caller passed, and mark defaults from it
`Run.passed` (migration d5a3c8e1f246) keeps the names the caller chose, so
the run detail and the comparison table can mark a value as the flow's own
again; rows from before it fall back to the names their params lack. Retries
hand on their parent's list, imports record their entry's.

Also: a cancel of a queued run no longer leaves its id in `_cancelled`, a
linked comparison no longer crashes before the list loads, and the pre-commit
failures are fixed (stale client, `text-[10px]`, worker formatting).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-05 15:01:33 +02:00
stroblmeandClaude Opus 5.5 84e054cdd1 Keep a run's lease while its pipeline builds; allow cancelling it then
A claimed run only joined the leased set once its pipeline was built, so a
build waiting for a busy worker pool got the run swept as abandoned. Cancel
now also stops such a run, and a terminal status being overwritten is logged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-05 11:23:54 +02:00
stroblme f3adee7b56 Back to development
Docs / docs (push) Successful in 17s
Playwright Tests / test-playwright (1, 2) (push) Failing after 18s
Playwright Tests / test-playwright (2, 2) (push) Failing after 11s
pre-commit / pre-commit (push) Failing after 1m58s
Test Backend / test-backend (push) Failing after 2m45s
Compose Smoke Test / test-compose (push) Failing after 10s
Playwright Tests / merge-reports (push) Failing after 3m24s
2026-10-03 11:55:17 +02:00
stroblme 6057f85ec1 Release v0.1.6
Docs / docs (push) Successful in 31s
Playwright Tests / test-playwright (1, 2) (push) Failing after 36s
Playwright Tests / test-playwright (2, 2) (push) Failing after 10s
pre-commit / pre-commit (push) Failing after 1m59s
Playwright Tests / merge-reports (push) Canceled after 0s
Test Backend / test-backend (push) Canceled after 35s
Compose Smoke Test / test-compose (push) Canceled after 0s
Publish / publish (push) Successful in 44s
2026-10-03 11:51:21 +02:00
stroblmeandClaude Opus 5.5 dafdcb76c4 Bound warm workers' host memory and account local RAM
Docs / docs (push) Successful in 19s
Playwright Tests / test-playwright (1, 2) (push) Failing after 12s
Playwright Tests / test-playwright (2, 2) (push) Failing after 9s
pre-commit / pre-commit (push) Failing after 1m56s
Test Backend / test-backend (push) Failing after 2m50s
Compose Smoke Test / test-compose (push) Failing after 9s
Playwright Tests / merge-reports (push) Failing after 2m16s
A worker over FLOW_WORKER_MAX_RSS_MB (--worker-max-rss) when its node
returns is retired instead of kept warm, in the shared pool and in every
declared one; 0, the default, keeps them all. The engine now accounts its
own memory (FLOW_RAM / --ram, else the box or the cgroup limit less 1 GB),
so a node's `ram` declaration holds locally. A declared environment the
shared pool already has is that pool rather than a duplicate set of warm
workers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 16:11:13 +02:00
stroblmeandClaude Fable 5.1 cc4091166e Add the assistant: a chat that drives the MCP tools in-process
Docs / docs (push) Successful in 42s
Playwright Tests / test-playwright (1, 2) (push) Failing after 2m42s
Playwright Tests / test-playwright (2, 2) (push) Failing after 10s
pre-commit / pre-commit (push) Failing after 2m33s
Test Backend / test-backend (push) Failing after 2m48s
Compose Smoke Test / test-compose (push) Failing after 9s
Playwright Tests / merge-reports (push) Failing after 2m17s
A provider set under Settings (OpenAI-compatible; key encrypted in
flow-data/ai.json) turns on an Assistant panel in the sidebar. Each turn runs
the FastMCP tools in-process as the person chatting, streams the rounds over
SSE and keeps one transcript per user. The panel floats on any screen and
shares the editor's lane: a settings panel keeps the top half, the chat the
bottom. The MCP client is now built once, for the endpoint and the chat.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 23:28:23 +02:00
stroblmeandClaude Opus 5 d9630f66f5 Add POST /runs/import for runs executed elsewhere
A history recorded before the move (MLflow experiments) had nowhere to
go, since runs were only made by running a flow. Each entry is stored as
it ended: parameters completed and checked like a submit, cause
"import", empty commit and code digest, metrics and artifacts written
as given, nothing queued or announced. Run.external_id (new migration)
makes a second import a no-op and is an export column so a row joins
back. The request is all or nothing and capped at 200 entries;
Client.import_runs pages a longer history.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QgG6TJw5qBZzaLghQSRVrq
2026-09-16 17:01:07 +02:00
stroblmeandClaude Opus 5 eeaa46d4ae Drop an unused fixture from the ignore-walk test
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QgG6TJw5qBZzaLghQSRVrq
2026-09-16 17:01:07 +02:00
stroblmeandClaude Opus 5 fccf89f849 Sync only the declaring module before a run; skip git-ignored paths
`fluksio run` walked the working directory and imported every module in
it, which ran a research repository's analysis scripts before the run was
submitted. A flow's origin now records the file and module name that
declared it, and run/sweep import just that, under the same name so the
generated node bodies do not change. A flow with no record still walks,
now skipping what .gitignore covers.

Every code-defined flow reports `flow` changed once on its next sync,
since the origin gained two fields.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QgG6TJw5qBZzaLghQSRVrq
2026-09-16 16:55:02 +02:00
stroblmeandClaude Opus 5 7dfe646e2b Bind serve's socket before writing client.json
A serve that lost the port race had already signed in, so client.json
named a port another engine held and every client talked to that one.
The socket is now bound first and handed to uvicorn.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QgG6TJw5qBZzaLghQSRVrq
2026-09-16 16:47:11 +02:00
stroblmeandClaude Opus 5 3dc43be6d0 Export FLOWS and build from the top-level fluksio package
A test of the declarations had to import them from fluksio.sdk while the
guide teaches `from fluksio import ...`. The worker's inert module gets the
same two names so a module importing them still loads inside a node.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QgG6TJw5qBZzaLghQSRVrq
2026-09-16 16:44:47 +02:00
stroblmeandClaude Opus 5 96fe2e0bc3 Leave a dotted key out of the runs export header
_leaves named a column for a key like `-1.0` that _dig can never read
back, so the export showed an always-empty column. Skip such a key.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QgG6TJw5qBZzaLghQSRVrq
2026-09-16 16:43:52 +02:00
stroblmeandClaude Opus 5 81fbd1ee54 Sweep stale e2e fixtures before a run, not after one
An aborted Playwright run never reaches `afterAll`, so its flows and
dashboards stayed in the store until somebody deleted them by hand. A
setup-project test now removes them before the next run, which is the
only point that is reached even when the run before it died.

The suite runs against whatever stack is up — somebody's own instance
with real flows in it — so the predicate matches only the shape every
fixture has, `test_<spec>_<base36 Date.now()>` with an optional suffix,
and only once that timestamp is an hour old.

Also correct the coverage-html fallback hint: `make clean` cannot clear
a root-owned htmlcov either, for the same reason `coverage html` cannot
write it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VfgHWr7AQDiLsTQVu7hpKB
2026-09-14 16:27:36 +02:00
stroblmeandClaude Opus 5 6ccdabb520 Surface a node's health when the engine's floor expires
`node_health` fires once at the transition, while `HEALTH_FLOOR` still
hides the node, and nothing is published when the floor expires 60 s
later. Schedule a per-node re-read for that moment, cancelled when the
node recovers and on socket teardown. Health now also invalidates the
brain graph, whose key is a sibling of the flow's rather than a child,
so Home's fault rings never moved on a health flip at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VfgHWr7AQDiLsTQVu7hpKB
2026-09-14 15:50:33 +02:00
stroblmeandClaude Opus 5 79cb3f881a Collapse the button-variant fork and the canvas's duplicate work
LoadingButton kept its own copy of buttonVariants, which had drifted past
`brand` and the icon sizes; it imports Button's now, so the Connect /
Re-pair button can show the loader instead of swapping its own label.

DashboardView mounted a second LookProvider inside the one CanvasSurface
already gives it and dropped `touch` on the way, so every widget on a
touch panel read `useLook().touch` as false. The provider is now mounted
once, by whatever owns the canvas root.

useCanvasRoot resolved the palette twice per render — once for the theme
class, once for the tokens — so useDashboardTheme takes the roles the
caller already has.

The bar assertions in widgets.spec.ts are scoped to the Load tile rather
than the page, which held only because the unbound spare bar draws no
rows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VfgHWr7AQDiLsTQVu7hpKB
2026-09-14 15:29:40 +02:00
stroblmeandClaude Opus 5 853fd8f720 Measure the bar where it settles, not mid-spring
`widgets.spec.ts` took a one-shot `boundingBox()` of a fill that is still
moving. Two ways that reads wrong. "a bar draws a row per reading" compared
two widths that are both zero until the socket has delivered the readings,
and a plain `expect` over a captured number does not retry. "a reading over
its scale fills the track and no more" allowed the fill one pixel past the
track, but the house spring is underdamped: `motion`'s own generator puts the
fluksio look at 100.66% of the track 276ms in (material 100.91%, glass
103.58%), which on this tile's ~240px track is ~1.6px — over the tolerance,
and reached only if the sample happens to land near the peak.

Neither is the widget. `fractionOf` clamps the reading to [0, 1] and
`.dui-bar-track` is `overflow: hidden`, so nothing past a full track is ever
drawn; a bounding box just is not clipped by an ancestor's overflow. So both
now poll: one until the shorter reading is drawn shorter, the other until the
fill is flush with its track — which is a stricter reading of "and no more"
than the pixel of slack was.

The live-socket test gains the other half of what that spin cost: an ASGI
task that never returns is one uvicorn's graceful shutdown waits on forever,
which is why `fluksio serve` could only be SIGKILLed and left `serve.pid`
behind.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VfgHWr7AQDiLsTQVu7hpKB
2026-09-14 15:02:42 +02:00
stroblmeandClaude Opus 5 dc08fa132b Spin the Add remote user button; stop the search prefilter dropping typos
"Add remote user" was the one call site swapping its label instead of
using LoadingButton, so it alone had no spinner.

Global search's couldMatch was a strict subsequence test while cmdk's
scorer steps over one query character for a transposition, so a typo it
would have scored never reached it. The prefilter now carries that one
skip, which recovers every single-transposition query in a sweep of the
index shapes and admits nothing cmdk scores zero.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VfgHWr7AQDiLsTQVu7hpKB
2026-09-14 14:40:17 +02:00
stroblmeandClaude Opus 5 aa3e2e8b81 Stop a bound dashboard setting rebuilding every tile
A setting driven by a flow re-renders whatever reads it on every message.
`LookProvider` and `PaletteProvider` built a fresh context value each time,
and `CanvasSurface` called its render prop again, so a value on `theme`,
`palette`, `look`, `background`, `touch` or `locked` reconciled the whole
widget tree and re-ran every chart's `setData`.

The palette is now read as roles once per set of colours, the tokens and
both context values are memoised on it, and the canvas holds the tiles it
built while the callback and the scale stay put. `WidgetBody` is memoised
too: its two props are stable while the document is, so a grid rendered for
its own reasons no longer rebuilds every chart with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VfgHWr7AQDiLsTQVu7hpKB
2026-09-14 14:38:59 +02:00
stroblme 265cea38d2 Merge branch 'main' of git.stroblme.de:Fluksio/app
Docs / docs (push) Successful in 21s
Playwright Tests / test-playwright (1, 2) (push) Failing after 2m10s
Playwright Tests / test-playwright (2, 2) (push) Failing after 11s
pre-commit / pre-commit (push) Failing after 1m56s
Test Backend / test-backend (push) Failing after 2m35s
Compose Smoke Test / test-compose (push) Failing after 11s
Playwright Tests / merge-reports (push) Failing after 2m19s
# Conflicts:
#	frontend/src/client/sdk.gen.ts
2026-09-07 08:41:45 +02:00
stroblmeandClaude Opus 5 7b67eb5a8e Hold a node down for a minute before calling it down
A connector that misses one poll turned the health badge amber and the node
on the canvas red until it recovered. `LoadedNode.health_since` marks when the
status last actually changed — a rotating error detail under an unchanged
status does not move it, or a connector retrying with a different errno each
poll would never debounce — and `unhealthy_nodes` applies a 60 s floor to it.

The floor is on the surfacing, not on the transition: the stored health is the
truth the moment a node reports it, so a flow reading node health is never
told a stale story. Only going down is held back; recovery clears at once.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CL9zvnnvcp1mvA8o7impxk
2026-09-07 07:54:10 +02:00