Move panel explanations into tooltips

The node, flow, widget and dashboard panels explained themselves in
paragraphs under every control. They now carry a caption and an info
tooltip, so a panel reads as a list of settings.

InfoTip, PanelSection and FieldLabel live beside PANEL_SECTION in
SidePanel.tsx, which the panels already share. InfoTip holds its own open
state because a Radix tooltip ignores a touch pointer, and below md the
whole panel is a full-screen sheet — hover-only help would leave a phone
with the caption and nothing else.

Dashboard/panels.tsx drops from 30 helper paragraphs to 5; the ones left
are empty states and status, not explanation. Em-dashes are out of the
user-facing strings, except where one separates the halves of a select
item.
This commit is contained in:
2026-08-31 10:24:26 +02:00
parent 6c8a101d24
commit 4e98bf30bf
6 changed files with 303 additions and 224 deletions
+37 -30
View File
@@ -49,7 +49,7 @@ import {
secretsQueryOptions,
useParamSuggestions,
} from "./queries"
import { PANEL_SECTION, PanelTitle, SidePanel } from "./SidePanel"
import { InfoTip, PANEL_SECTION, PanelTitle, SidePanel } from "./SidePanel"
const NodeEditor = lazy(() => import("./NodeEditor"))
@@ -368,8 +368,8 @@ function PortList({
aria-label="Streaming output"
aria-pressed={spec.stream ?? false}
title={
"Published repeatedly while the node runs — a curve rather " +
"than a result. A run keeps every value it takes."
"Published repeatedly while the node runs: a curve rather than " +
"a result. A run keeps every value it takes."
}
className={cn(
"shrink-0",
@@ -613,15 +613,15 @@ function CronHelp({
return (
<>
<p className="text-xs text-muted-foreground">
Five fields —{" "}
<InfoTip label="Cron">
Five fields:{" "}
<span className="font-mono">
minute hour day-of-month month weekday
</span>
. <span className="font-mono">*</span> is every,{" "}
<span className="font-mono">*/5</span> every fifth,{" "}
<span className="font-mono">1-5</span> a range.
</p>
</InfoTip>
{derivable && derived && params.cron !== derived ? (
<Button
variant="ghost"
@@ -886,9 +886,17 @@ function ResourcesSection({
return (
<div className="grid gap-1.5">
<Label htmlFor="node-resources" className={SECTION}>
Resources
</Label>
<div className="flex items-center gap-1">
<Label htmlFor="node-resources" className={SECTION}>
Resources
</Label>
<InfoTip label="Resources">
What is held for the whole execution, and what the node's own
libraries are told they may use. The duration is a planning figure,
not a limit. Held nothing, the node shares the pool with every other
node that asks for nothing.
</InfoTip>
</div>
<Select value={choice} onValueChange={pick}>
<SelectTrigger id="node-resources" className="h-8 text-sm">
<SelectValue />
@@ -946,7 +954,7 @@ function ResourcesSection({
{resources ? (
<Input
className="h-8 text-sm"
placeholder="expected duration — 30m, 2h"
placeholder="expected duration, e.g. 30m or 2h"
defaultValue={formatDuration(resources.duration_s)}
key={`${choice}-${resources.duration_s ?? ""}`}
onBlur={(event) => {
@@ -962,12 +970,6 @@ function ResourcesSection({
}}
/>
) : null}
<p className="text-xs text-muted-foreground">
{resources
? "Held for the whole execution, and what the node's own libraries are told they may use. The duration is a planning fact, not a limit."
: "Nothing held: the node shares the pool with every other node that says nothing."}
</p>
</div>
)
}
@@ -1047,10 +1049,13 @@ function SharingSection({
return (
<div className="grid gap-3">
<span className={SECTION}>Reuse</span>
<p className="text-sm text-muted-foreground">
Move this node's code to the library so other flows can run it too.
</p>
<div className="flex items-center gap-1">
<span className={SECTION}>Reuse</span>
<InfoTip label="Reuse">
Moves this node's code to the library so other flows can run it too.
Editing a shared node changes it everywhere at once.
</InfoTip>
</div>
<div className="flex items-center gap-1.5">
<Input
value={name}
@@ -1325,9 +1330,16 @@ function PanelBody({
) : null}
{hasSource ? (
<div className="grid gap-1.5">
<Label htmlFor="node-timeout" className={SECTION}>
Timeout
</Label>
<div className="flex items-center gap-1">
<Label htmlFor="node-timeout" className={SECTION}>
Timeout
</Label>
<InfoTip label="Timeout">
Seconds this code may stay silent before it is stopped. A yield
or an emit resets the clock. 0 disables the limit; empty uses
the engine default.
</InfoTip>
</div>
<Input
id="node-timeout"
type="number"
@@ -1346,11 +1358,6 @@ function PanelBody({
})
}
/>
<p className="text-xs text-muted-foreground">
Seconds this code may be silent before it is stopped — a yield or
an emit resets the clock. 0 disables the limit; empty uses the
engine default.
</p>
</div>
) : null}
{hasSource ? (
@@ -1394,8 +1401,8 @@ function PanelBody({
data-testid="node-source-generated"
>
Generated by <span className="font-mono">fluksio sync</span> from{" "}
<span className="font-mono">{origin.repo || "a repository"}</span>{" "}
— edit it there and sync again.
<span className="font-mono">{origin.repo || "a repository"}</span>
. Edit it there and sync again.
</p>
) : null}
<div className="min-h-0 flex-1 overflow-hidden rounded-md border border-border">