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.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKL7sUgNWhukDEz95vSMQv
This commit is contained in:
2026-08-20 14:52:51 +02:00
co-authored by Claude Opus 5
parent 26833d63ee
commit 8d5dde003b
4 changed files with 12 additions and 8 deletions
-3
View File
@@ -30,7 +30,6 @@ should reopen it.
- FEAT/UI/MOBILE: a rank of many nodes — a connector feeding eight dashboard tiles — is thousands of pixels wide however the graph is turned, so on a phone the fit shrinks it past reading. The layout is right and the flow is simply too big for the screen; a "one rank at a time" reading mode, or wrapping a wide rank, is what would make it legible. - FEAT/UI/MOBILE: a rank of many nodes — a connector feeding eight dashboard tiles — is thousands of pixels wide however the graph is turned, so on a phone the fit shrinks it past reading. The layout is right and the flow is simply too big for the screen; a "one rank at a time" reading mode, or wrapping a wide rank, is what would make it legible.
- CHORE/UI: an edge's value chip sits at the bezier midpoint while the layout reserves its room at dagre's label rank. The two agree closely enough today; if chips ever pile up, take the position from the layout instead. - CHORE/UI: an edge's value chip sits at the bezier midpoint while the layout reserves its room at dagre's label rank. The two agree closely enough today; if chips ever pile up, take the position from the layout instead.
- CHORE/UI: the bar widget's nested fill is `--chart-5` against a `--primary` outer fill, which measures 2.53:1 in light mode — under the 3:1 guideline for non-text. The `inset-y-1` gutter supplies the edge, and every other slot of the ramp collapses in dark mode instead. - CHORE/UI: the bar widget's nested fill is `--chart-5` against a `--primary` outer fill, which measures 2.53:1 in light mode — under the 3:1 guideline for non-text. The `inset-y-1` gutter supplies the edge, and every other slot of the ramp collapses in dark mode instead.
- CHORE/UI: the Home flow-activity table's name cell is `max-w-0` with nothing setting a floor, so `demo_training` reads as "demo⋯" at 1440px while the row has slack to spare. The cap is what keeps the table from widening the page; it wants a minimum beside it.
- FEAT/UI (deferred until MCP lands): add a "bot" icon button to the home view (graph panel) which opens a chat window (reuse general concept of a side panel like in flows/nodes to make it a chat panel which can open on any screen (stacks below any other existing panel -> introduce stacking) to give support on errors/write code, generate dashboards etc) to explain the error(s) - FEAT/UI (deferred until MCP lands): add a "bot" icon button to the home view (graph panel) which opens a chat window (reuse general concept of a side panel like in flows/nodes to make it a chat panel which can open on any screen (stacks below any other existing panel -> introduce stacking) to give support on errors/write code, generate dashboards etc) to explain the error(s)
- FEAT/UI make the header (Fluksio - YEAR) and the logo in the sidebar link to the main page (fluksio.com) - FEAT/UI make the header (Fluksio - YEAR) and the logo in the sidebar link to the main page (fluksio.com)
- FEAT/UI consider adding a diagram to the Home view which shows a histogram of the different classes of nodes and which time it takes to execute (logarithmic scale); this should give a hint on the load and help to detect bottle necks/hotspots - FEAT/UI consider adding a diagram to the Home view which shows a histogram of the different classes of nodes and which time it takes to execute (logarithmic scale); this should give a hint on the load and help to detect bottle necks/hotspots
@@ -118,7 +117,6 @@ Decisions taken up front, because most items below depend on them:
- Nothing e-ink-specific in the widgets. Panel access is a credential problem - Nothing e-ink-specific in the widgets. Panel access is a credential problem
(see below); the display's demands are a rendering profile, deferred. (see below); the display's demands are a rendering profile, deferred.
- CHORE/UI: `MarkdownWidget`'s docstring claims "headings, bold, code, links, list items"; only headings and bullets are implemented. Either the inline spans or the docstring.
- CHORE/UI: identical in-flight chart requests are deduplicated per browser tab, - CHORE/UI: identical in-flight chart requests are deduplicated per browser tab,
so two wall panels showing the same tile still run the query twice. An so two wall panels showing the same tile still run the query twice. An
`interval` on the request port is the backstop, and it belongs to the flow `interval` on the request port is the backstop, and it belongs to the flow
@@ -150,7 +148,6 @@ as an em dash.
### Dashboard follow-ups ### Dashboard follow-ups
- BUG/UI: ensure dashboard wallpanel (read-only) links hot reload automatically on dashboard changes - BUG/UI: ensure dashboard wallpanel (read-only) links hot reload automatically on dashboard changes
- CHORE/UI: `BarWidget`'s caption names the nested reading by its raw message name, so a wall panel reads "home.self_use_kw 1.14 kW". The widget title is what the rest of the panel shows; the caption wants a label of its own, or the port's name rather than the qualified one.
- FEAT/NODE: the hosted demo covers thirteen of the fifteen built-in node types. `switch` and `delay` are unplaced — a `switch` branch needs either a dead-end port or trivial nodes to turn a branch back into a label, and neither read as something a person would hang. Worth revisiting when the demo grows a second page. - FEAT/NODE: the hosted demo covers thirteen of the fifteen built-in node types. `switch` and `delay` are unplaced — a `switch` branch needs either a dead-end port or trivial nodes to turn a branch back into a label, and neither read as something a person would hang. Worth revisiting when the demo grows a second page.
- CHORE/UI: an icon rule stores a `label` — the widget draws it under the glyph and uses it as the glyph's accessible name — but the mapping editor offers no field for it, so it can only be set through the API. - CHORE/UI: an icon rule stores a `label` — the widget draws it under the glyph and uses it as the glyph's accessible name — but the mapping editor offers no field for it, so it can only be set through the API.
- BUG/UI: shrinking the canvas silently clips whatever now falls past its bottom edge. `maxRows` only constrains a new drag, not a stored placement, so nothing warns and nothing offers to reflow. - BUG/UI: shrinking the canvas silently clips whatever now falls past its bottom edge. `maxRows` only constrains a new drag, not a stored placement, so nothing warns and nothing offers to reflow.
@@ -1,3 +1,4 @@
import { displayName, flowOf } from "@/components/Flow/deriveEdges"
import { useLiveValue } from "@/components/Flow/liveStore" import { useLiveValue } from "@/components/Flow/liveStore"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import type { WidgetProps } from "./widgets" import type { WidgetProps } from "./widgets"
@@ -28,6 +29,9 @@ export function BarWidget({ widget }: WidgetProps) {
const cfg = config(widget) const cfg = config(widget)
const message = text(cfg.message) const message = text(cfg.message)
const nested = text(cfg.inner) const nested = text(cfg.inner)
// The panel already carries the widget's title, so the caption names the
// reading by its port rather than repeating the flow it comes from.
const innerName = nested ? displayName(flowOf(nested), nested) : ""
const outer = useLiveValue(message || undefined) const outer = useLiveValue(message || undefined)
const inner = useLiveValue(nested || undefined) const inner = useLiveValue(nested || undefined)
if (!message) if (!message)
@@ -56,7 +60,7 @@ export function BarWidget({ widget }: WidgetProps) {
role="img" role="img"
aria-label={ aria-label={
nested nested
? `${write(value)} of ${max}${unit}, ${write(innerValue)} of it from ${nested}` ? `${write(value)} of ${max}${unit}, ${write(innerValue)} of it from ${innerName}`
: `${write(value)} of ${max}${unit}` : `${write(value)} of ${max}${unit}`
} }
> >
@@ -89,7 +93,7 @@ export function BarWidget({ widget }: WidgetProps) {
</div> </div>
{innerValue === null ? null : ( {innerValue === null ? null : (
<p className="truncate text-xs text-muted-foreground"> <p className="truncate text-xs text-muted-foreground">
{nested} {write(innerValue)} {innerName} {write(innerValue)}
</p> </p>
)} )}
</div> </div>
@@ -382,7 +382,8 @@ function GaugeWidget({ widget }: WidgetProps) {
} }
/** /**
* A very small markdown subset: headings, bold, code, links, list items. * A very small markdown subset, read a line at a time: headings and list items.
* Inline spans — bold, code, links — are not parsed and read as written.
* *
* Enough for the labels and notes a dashboard carries, and not worth a parser. * Enough for the labels and notes a dashboard carries, and not worth a parser.
*/ */
@@ -185,10 +185,12 @@ export function HealthOverview({
<tbody> <tbody>
{(flows ?? []).map((row: FlowRollup) => ( {(flows ?? []).map((row: FlowRollup) => (
<tr key={row.flow} className="border-t border-border"> <tr key={row.flow} className="border-t border-border">
<td className="max-w-0 py-2 pr-3"> <td className="max-w-0 py-2 pr-3 sm:min-w-32">
{/* `max-w-0` is what lets a cell truncate at all: without {/* `max-w-0` is what lets a cell truncate at all: without
it the table sizes to the longest name and pushes the it the table sizes to the longest name and pushes the
page sideways. */} page sideways. The floor beside it keeps an ordinary
name readable where there is room for one; a phone has
none to spare, so it starts at `sm` like the trend. */}
<Link <Link
to="/flows/$flowName" to="/flows/$flowName"
params={{ flowName: row.flow }} params={{ flowName: row.flow }}