Commit Graph
101 Commits
Author SHA1 Message Date
stroblme 1ff7811b62 Merge branch 'main' of git.stroblme.de:Fluksio/app 2026-08-23 13:01:26 +02:00
stroblme 83e1a0a2ba NOTEPAD: close the CubeLoader bug, note the loader the portal now draws
The invisible loader lived in the index repo only and is fixed there. What is
left for this repo is whether its lucide spinners should become the same brand
mark.
2026-08-23 12:51:15 +02:00
stroblme 24782c8a06 Notepad: close the wave-1 items, record what they left behind
Eleven "to be sorted" entries are done. Three that survive are amended
rather than removed: the house panel's chart budget (the range picker
gave its row back), layoutGraph (height is derived now, width is still
guessed), and the dark:-variant scope, which turned out to share a root
with the React Flow colorMode bug. Adds the portal's missing
not-reporting state, and drops the frontend-origin entry the rebuild
settled.
2026-08-23 11:16:50 +02:00
stroblme e8cb8b92f1 up
Signed-off-by: stroblme <stroblme@posteo.de>
2026-08-23 11:02:08 +02:00
stroblme 0fddec7616 up
Signed-off-by: stroblme <stroblme@posteo.de>
2026-08-23 00:11:08 +02:00
stroblme a69d24c1c5 Panels for a ten-inch screen, and a motor button that says where it is
Both screens the house is looked at on are 1280x800, so that is what the three
dashboards are laid out for: twelve columns of 96px, twelve rows of 51px, and
nothing past the bottom, because a panel does not scroll.

The motors are one control each instead of three buttons. A button could only
publish; a segmented control reads back as well — so the motor writes what it
is doing to the same message the control sets, and the segment that is held is
the direction it actually went. Up, Stop, Down for the shutters; Close/Open for
the window and In/Out for the awning, which is what those two are for.

A run stopped part way now leaves the position unknown rather than claiming the
target it never reached, so the next command in either direction moves it.

The preflight gained the two checks this needed. One runs each sample shape
past the port that would receive it. The other is arithmetic: every tile inside
the panel and none on top of another — both silent failures on a screen with no
scrollbar, and both caught before anything is written.

Sizes were settled by looking. A slider needs three rows or its tick labels
fall off; a status icon needs three or it loses the word under the glyph; a
gauge in two rows has no arc worth reading, so the battery is a bar on Home and
a gauge on Energy where there is height for one. A chart spends eighty pixels
on its chrome whatever it is given, so two of them read on this panel and three
did not — the temperature history is the one that went, and `history` still
answers for it.

`capture-panels.mjs` is how that was checked: the three panels at the screen's
own pixels, in both themes, reporting whether anything spilled.
2026-08-22 18:50:03 +02:00
stroblme c9c02c2741 NOTEPAD: the house-control rig is deleted, not running 2026-08-22 16:38:25 +02:00
stroblme e46a55fe98 history: one Flux script per chart, and rows are json
Two things the installation found that the checks did not.

A script with three `from()` statements in it produces three results all
called `_result`, and InfluxDB refuses that outright — so the measurements go
into one filter and the rows come back tagged with which one they are.

And the answer a database node hands back holds a *list* of rows, which a
record may not: a record is flat scalars. It was declared one, so every chart
failed on the type check the moment a real answer arrived.

The second one is now caught before anything is pushed: the preflight runs
each sample shape past the port that would receive it, which is what turns
"expected record, got dict" from a runtime surprise into a line of output.

Also records the two engine faults this seeding session surfaced.
2026-08-22 16:24:38 +02:00
stroblme 84e4bd7d2c NOTEPAD: what the house port settled, and what it left 2026-08-22 14:46:08 +02:00
stroblme 8009a13e00 NOTEPAD: clear what wave 4 closed, record what it left 2026-08-22 13:19:41 +02:00
stroblme bd93b8f182 NOTEPAD: clear what wave 3 closed, record what it left 2026-08-22 12:52:31 +02:00
stroblme d75c581b91 NOTEPAD: clear what wave 2 closed, record what it left 2026-08-22 12:24:56 +02:00
stroblme cb513e531a NOTEPAD: clear what wave 1 closed, record what it left 2026-08-22 12:05:08 +02:00
stroblme ba8dbe5815 NOTEPAD: CubeLoader references a CSS rule that exists in neither repo 2026-08-22 11:44:54 +02:00
stroblme b71ebcc68d up
Signed-off-by: stroblme <stroblme@posteo.de>
2026-08-22 10:22:51 +02:00
stroblme 1737d1b931 Merge branch 'main' of git.stroblme.de:Fluksio/app 2026-08-22 09:43:12 +02:00
stroblme deeefc0b5a up
Signed-off-by: stroblme <stroblme@posteo.de>
2026-08-22 09:43:09 +02:00
stroblme 3a88182384 Add make dev-lan: the app on a host port, API proxied on the same origin 2026-08-22 09:25:02 +02:00
stroblme d3badd5014 Note that make update removes the local Traefik proxy 2026-08-22 08:55:49 +02:00
stroblme 7db3e304ff Note what the docs site left open 2026-08-22 05:57:29 +02:00
stroblme 2038a39192 Record the packaging work and what it left open 2026-08-21 23:01:25 +02:00
stroblme 78dcadc7ce NOTEPAD: drop what wave two closed, file what it turned up
Twenty-three items are done: the dashboard draft/publish split and panel
freshness, the canvas clipping warning, the inject node's per-port payloads,
the sticky failure and the brain click-through, and the lint and DOMAIN
tooling fixes.

The ChartWidget entry is rewritten rather than closed. Its ring buffer would
only remove the cheap half of the cost, and it is the shape most likely to
tempt someone into re-adding the dependency array that made a full window of
chart data go stale.
2026-08-21 14:35:24 +02:00
stroblme 4da12bf7e6 NOTEPAD: drop what this wave closed, file what it turned up
Twenty-one items are done: the canvas viewport and logs panel, the widget
overflow and stacking work, the backup and queue-namespace changes, and the
observability fold.

What replaced them is mostly the residue of doing them — a legend styled in the
wrong CSS chunk, two overflow floors that nothing would notice regressing, and
artifact blobs that outlive the rows pointing at them.
2026-08-21 10:14:06 +02:00
stroblme b60fc3706a Bound a panel credential where the route check cannot reach
Three things the security pass on the portal pairing turned up. The first two
were already true of a screen on the local network; what changed is that a
panel credential is now presentable from the internet, which is what makes
them worth closing rather than recording.

The artifact endpoint authenticates for itself, because a worker's credential
has to open it and that token is no use anywhere else. It resolved the caller
without handing over the request, so the one credential that is scoped by
route was judged by no route at all — a panel could read and write the store
as whoever approved it. It passes the request it already holds now.

The websocket has no route to judge either, and there the bound has to be on
what is sent: a panel is given the values its own dashboards draw and nothing
else — no node status, no logs, no shape of the graph. The keys stay in the
message, emptied, because a screen on a wall runs the bundle it was paired
with. `messages_for` reads that set off the published dashboards, and is the
walk the `/messages/` allowlist has wanted for a while.

And locality is no longer a header anyone can type. The marker the connector
stamps is a value minted per process, so reaching this API directly cannot buy
a device the credential meant for one that cannot reach it at all.
2026-08-21 00:09:18 +02:00
stroblme 184ef8b00c Pair a wall panel through the portal
A screen somewhere this installation is not reachable from asks the portal for
a code instead, and the portal mints its credential — because a token signed
here is one such a device could never present.

Where it was minted changes nothing about what it may do. The panel gate moved
off the branch that decodes a local panel token and onto whatever claims name
a panel, so the portal's and this installation's are bounded by the same check
against the same panel's dashboards. A token of that scope naming no panel is
refused rather than left holding the account it borrows.

The connector marks what arrives on its socket, since that is the only thing
that makes it true, and the approval screen now names what is holding a code —
approving adopts whatever answers, so it is worth a look first.
2026-08-20 23:42:58 +02:00
stroblme e6bc4d6a4d Record what the aircon actually did
The mode-reads-unknown-while-off note is narrower than it looked: a unit switched off by a command keeps its last mode in those bits.
2026-08-20 23:02:04 +02:00
stroblme 0fe544fab2 Seed the aircon write-path rig
Four controls and the unit's own answer beside them. Two catches, both on:
the flow is seeded stopped and the node's commands setting is off. The initial
values are read off the unit when the script runs, so starting the flow asks
for what it was already doing rather than commanding it to something else.
2026-08-20 22:43:03 +02:00
stroblme 9d6c757156 Merge branch 'main' of git.stroblme.de:Fluksio/app 2026-08-20 22:05:21 +02:00
stroblme 848e1dc8ba up
Signed-off-by: stroblme <stroblme@posteo.de>
2026-08-20 22:05:18 +02:00
stroblme c216659703 Let a connector write, and publish strings bare
Two things stopped the engine commanding this house. ConnectorNode hardwired
its node function to a no-op, so an input message reaching a connector was
discarded and Art-Net's packet builder was unreachable; write() now carries
the input ports, which is additive so the contract version holds. And the MQTT
publisher JSON-encoded every payload, so "ON" went on the wire quoted and the
devices on a shared broker, which speak bare values, ignored it.

seed_house_control.py is the rig: a flow that drives the washing machine plug,
a dimmer and a colour fixture over MQTT, carries the same two as DMX on an
Art-Net node with transmit still off, and a dashboard to drive it by hand.
2026-08-20 21:57:27 +02:00
stroblme d7cd091bda up
Signed-off-by: stroblme <stroblme@posteo.de>
2026-08-20 21:04:35 +02:00
stroblme 93e1b55529 The e2e suite names its own origins, and refuses a live instance
`tests/utils/api.ts` took the API origin from `VITE_API_URL`, which
`tests/config.ts` loads out of `app/.env`. In a checkout configured for a
deployment that names the deployment — so the browser went to the local stack
while every setup and teardown call, `deleteAll` included, went to the live
one. `privateApi.ts` had the same reading, and it creates users.

Both origins now come from one place: `PLAYWRIGHT_BASE_URL`, with the API
derived from it (`app.<domain>` → `api.<domain>`) or named outright by
`PLAYWRIGHT_API_URL`, which is what CI and the compose service set. Nothing in
the suite reads `VITE_API_URL` any more.

Belt and braces, since a stack served under a real domain answers to the same
names its production instance does: a global setup resolves both origins and
refuses anything that is not loopback or a private range, before a test runs.
`PLAYWRIGHT_ALLOW_PUBLIC=1` says you meant it.

`make test-frontend` is now that safe run — the Playwright image on the proxy
network with both names mapped onto Traefik by address, as the host user so it
does not leave root-owned results behind.
2026-08-20 20:46:04 +02:00
stroblme d708231b26 NOTEPAD: drop what is done, correct what was never true
A sweep against the code. Removed as resolved: the worker pool's `_running`
is keyed by (run, node) now, `WorkItem.kind` documents only the three kinds
that exist, `routeTree.gen.ts` matches its generator, and the host's Node is
well past the version the Vite 7 note was about. Removed as disproved: the
collector's redelivery branch is reachable — Redis reclaims an entry this
process is still running.

Corrected: the demo places six of the fifteen node types, not thirteen — the
count was the widget types. Narrowed to what is left: the header/config sync
now covers settings but only on an untouched scaffold and only one way; a
declared flow input is what the dashboard-input node asked for; the flow
boundary already refits the canvas where node ports do not.

The Playwright hazard is not resolved — both names still resolve to
production — and the fix as written is incomplete: `VITE_API_URL` has to be
overridden to `http://` or every setup call fails the TLS handshake.

Also: one `asText` instead of two, and the queue docstring no longer refers
to a node item that never existed.
2026-08-20 19:06:48 +02:00
stroblme bec8e89459 Flow inputs and outputs are visible and editable in the UI
A flow's inputs are the messages it takes from outside — a dashboard control,
a run, the API — and its outputs are what a batch run reports. Both existed in
the document and in the engine, and neither had any UI: the values looked
hard-coded on the canvas and the Run button always used the declared defaults.

The canvas now draws each as a labelled endpoint, the way it already draws a
dashboard tile or another flow, skipping an input something else already
accounts for. The flow panel edits them — mode, name, type, starting value,
and for a live flow the value it currently holds with a way to put a new one
in. Pressing Run on a batch flow asks for its parameters first.
2026-08-20 18:10:16 +02:00
stroblme 508172ad79 Node settings arrive as keyword arguments, not a params dict
A python node's settings are constants of its own function, so they are passed
the way its ports are: by name. The controller binds them to the compiled
function, the `params` field is gone from the worker and remote protocols, and
a setting sharing a port's name is reported as a node error rather than
shadowing it. The panel's scaffold follows suit and keeps the header in step
with both ports and settings.

The demo's `pace` moves from a flow input to a setting of the training node,
which is what it always was.
2026-08-20 17:47:45 +02:00
stroblme ae77018732 Panels: per-device dashboard sets, paired by code
A panel is one screen and the ordered set of whole dashboards it shows, so a
hallway tablet and a workshop tablet carry different sets without either
dashboard knowing about the other. More than one and the device draws a rail
to switch between them — the same rail the editor puts on screen, because the
wall has it and it takes room off the canvas.

A screen has no keyboard, so it pairs rather than logs in: it shows a
six-character code, somebody approves it against a panel from the dashboards
overview, and the credential that mints reaches that panel's published
dashboards and the message endpoints its widgets speak, and nothing else.
Deleting the panel revokes it.

Closes the per-device view and the kiosk credential; supersedes the
multi-page/multi-section UI, since a page is now a dashboard of its own.
2026-08-20 16:23:36 +02:00
stroblme 5821d33ebf NOTEPAD: record the edge popover's duplicate value and the chart's point-count gate
Two findings from fixing the value overflow and the empty figures: the edge
popover renders ValuePreview twice, and UplotChart only pushes readings when
the point count changes, so a saturated rolling window stops following.
2026-08-20 15:11:18 +02:00
stroblme b7c7f07a1d Read a flow name, a port name and an honest docstring
Give the Home flow-activity name cell a `sm:min-w-32` floor beside its
`max-w-0` cap, so an ordinary name reads in full where there is room
while the phone keeps today's truncation. Name the bar widget's nested
reading by its port rather than its qualified message. Correct
`MarkdownWidget`'s docstring to the subset it actually renders.
2026-08-20 14:52:51 +02:00
stroblme dc15735335 Keep the theme redraw from wiping a chart's x scale
Reaching Home from another page builds its charts in the same commit their
theme effect first fires in, with the readings already cached. uPlot ranges its
scales in a microtask, so `redraw()` re-set the x scale from the chart's own —
still empty — bounds before that ran, and the pending range taken from the data
was lost: axes without ticks, no lines, and no way back but the range control,
which rebuilds the chart. Redrawing without the paths is all a colour swap
needs and leaves the scales alone.

The cards also carried "Nothing has run yet." while the first readings were
still on their way; they now carry the skeleton the rest of Home uses.
2026-08-20 14:33:17 +02:00
stroblme 4a7a4e73d8 Draw the dashboard selection ring inwards
The canvas is clipped to the panel's exact pixel size, so an outward ring
on a widget in column 0 or row 0 was cut off on that side.
2026-08-20 13:51:35 +02:00
stroblme 9595e5f46b Gate the flow node pulse on a real emit 2026-08-20 13:38:59 +02:00
stroblme d5830a5d1e Show the streaming dash on dashboard edges and without motion 2026-08-20 13:28:40 +02:00
stroblme 3a417cdf5d Select the edge you click in the flow editor
React Flow's selection changes had nowhere to go: the canvas passed
edges but no onEdgesChange, so no edge ever carried .selected and both
the selected-edge stroke and its rest colour were unreachable. The edges
are derived from the bindings, so the selected ids are held on their own
and marked on after the layout has had the array — a rebuild cannot drop
the selection, and selecting cannot make the graph lay itself out again.
2026-08-20 13:18:13 +02:00
stroblme a8052e873a Anchor the Playwright login helper on the health heading
The greeting it waited for was removed from Home in b9437b0.
2026-08-20 12:56:37 +02:00
stroblme 563671b41f NOTEPAD: close what this session finished
The brain now opens sorted (the snapshot carries emit counts, and values
always carried timestamps), the hosted demo is the range-aware dashboard
that was missing, and the demo is hosted-only — so a server-side route
seeding one per signed-in user is not wanted rather than not done.
2026-08-20 12:30:24 +02:00
stroblme a8c1fa04c8 NOTEPAD: what the socket and demo round left behind 2026-08-20 12:18:44 +02:00
stroblme 840d731b9d Send a node's emit pulse round the rim instead of scaling it out
The ring hung off the node itself, and `inset` on an absolute child
resolves against the padding box — so on the brain's thick status ring it
landed inside the outer edge and painted over the status the ring carries.
Both node shapes now hang it off a border-less wrapper, where the same
offsets clear the rim whatever border the node draws (measured: 4px on all
four sides, on a 1px card border and a 7px neuron ring alike).

The shape is a conic-gradient arc masked to the padding band, running one
lap in `--duration-pulse` and going out on the way past.

Note: BrainNode.tsx also carries an unrelated in-flight change from a
concurrent session (the `seen` ref gating the pulse on a real emit); the
two could not be separated in one file.
2026-08-20 11:51:21 +02:00
stroblme a147484cd6 Widget panel: settings for the bar, icon, forecast and chart axis title
The four widget types added this round had renderers but no way to configure
them. The panel now offers a bar's nested reading, unit and range (no step —
that is a slider's), the forecast's item count on the agenda's field, a chart's
y axis title beside its y range, and the icon's mapping editor: a value, a
glyph and a colour per row, first match wins, with a fallback glyph below.

Adds tests/widgets.spec.ts, which asserts each of them on /view: the nested bar
inside its outer fill, the glyph following the message, five forecast columns
fading outwards, a clock that reads the wall without being flagged unbound,
a segmented control and a latching button reading back what they published,
and an unbound tile that says so instead of taking the page down. The axis
title is drawn into uPlot's canvas, so it is checked by panel round-trip.
mobile.spec.ts grows a bar and a forecast so the width check covers them.
2026-08-20 09:48:42 +02:00
stroblme ec54084fb1 up
Signed-off-by: stroblme <stroblme@posteo.de>
2026-08-19 13:08:31 +02:00
stroblme 9fce427ffa up
Signed-off-by: stroblme <stroblme@posteo.de>
2026-08-18 22:26:17 +02:00