Move the remaining panel explanations into tooltips

A node setting's description comes off the backend JSON schema, so every
node type's panel still printed a paragraph under each field. It moves to
the field label, which is the change that makes the node panel read as a
list rather than a page.

Alerts, Modules and Remote access follow the same shape. A page keeps a
one-line description under its h1 — a page is arrived at rather than
scanned — and the rest goes behind the heading's tooltip.

Verified on the running stack: the tooltip opens on tap in the phone sheet
and on hover on the desktop panel.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YrQnKV3bnQd4K342y8tKj
This commit is contained in:
2026-08-31 10:38:16 +02:00
co-authored by Claude Opus 5
parent 954d90fbf7
commit 2422a9b22b
4 changed files with 66 additions and 39 deletions
+22 -15
View File
@@ -49,7 +49,13 @@ import {
secretsQueryOptions, secretsQueryOptions,
useParamSuggestions, useParamSuggestions,
} from "./queries" } from "./queries"
import { InfoTip, PANEL_SECTION, PanelTitle, SidePanel } from "./SidePanel" import {
FieldLabel,
InfoTip,
PANEL_SECTION,
PanelTitle,
SidePanel,
} from "./SidePanel"
const NodeEditor = lazy(() => import("./NodeEditor")) const NodeEditor = lazy(() => import("./NodeEditor"))
@@ -782,7 +788,14 @@ function ParamsForm({
const reference = (params[key] ?? null) as { $secret?: string } | null const reference = (params[key] ?? null) as { $secret?: string } | null
return ( return (
<div key={key} className="grid gap-1.5"> <div key={key} className="grid gap-1.5">
<Label className="text-sm font-normal">{label}</Label> <FieldLabel
help={
property.description ||
"Stored in the secrets store, never in the flow file."
}
>
{label}
</FieldLabel>
<Select <Select
value={reference?.$secret ?? ""} value={reference?.$secret ?? ""}
onValueChange={(name) => onValueChange={(name) =>
@@ -801,12 +814,11 @@ function ParamsForm({
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
<p className="text-xs text-muted-foreground"> {secrets.length ? null : (
{secrets.length <p className="text-xs text-muted-foreground">
? property.description || No secrets stored yet.
"Stored in the secrets store, never in the flow file." </p>
: "No secrets stored yet."} )}
</p>
</div> </div>
) )
} }
@@ -819,9 +831,9 @@ function ParamsForm({
property.type === "integer" || property.type === "number" property.type === "integer" || property.type === "number"
return ( return (
<div key={key} className="grid gap-1.5"> <div key={key} className="grid gap-1.5">
<Label htmlFor={`param-${key}`} className="text-sm font-normal"> <FieldLabel htmlFor={`param-${key}`} help={property.description}>
{label} {label}
</Label> </FieldLabel>
<SuggestInput <SuggestInput
id={`param-${key}`} id={`param-${key}`}
className="h-8 text-sm" className="h-8 text-sm"
@@ -831,11 +843,6 @@ function ParamsForm({
empty="Nothing like that in use yet." empty="Nothing like that in use yet."
onChange={(next) => set(key, numeric ? Number(next) : next)} onChange={(next) => set(key, numeric ? Number(next) : next)}
/> />
{property.description ? (
<p className="text-xs text-muted-foreground">
{property.description}
</p>
) : null}
{key === "cron" ? ( {key === "cron" ? (
<CronHelp <CronHelp
params={params} params={params}
@@ -2,6 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { useState } from "react" import { useState } from "react"
import { CloudService } from "@/client" import { CloudService } from "@/client"
import { InfoTip } from "@/components/Flow/SidePanel"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { import {
Card, Card,
@@ -154,15 +155,25 @@ export function RemoteAccess() {
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div> <div>
<h3 className="font-medium">Remote users</h3> <div className="flex items-center gap-1">
<h3 className="font-medium">Remote users</h3>
<InfoTip label="Remote users">
They get a user of their own here, not yours, and never a
superuser, so they cannot pass access on.
</InfoTip>
</div>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Let someone else reach this instance through the portal. They Let someone else reach this instance through the portal.
get a user of their own here — not yours, and never a
superuser, so they cannot pass access on.
</p> </p>
</div> </div>
<div className="grid gap-2"> <div className="grid gap-2">
<Label htmlFor="join-code">Code</Label> <div className="flex items-center gap-1">
<Label htmlFor="join-code">Code</Label>
<InfoTip label="Code">
Added users appear under Admin → Users; deleting them there
ends their access.
</InfoTip>
</div>
<Input <Input
id="join-code" id="join-code"
value={joinCode} value={joinCode}
@@ -174,8 +185,6 @@ export function RemoteAccess() {
/> />
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
They get a code at fluksio.com → Instances → Join an instance. They get a code at fluksio.com → Instances → Join an instance.
Added users appear under Admin → Users; deleting them there
ends their access.
</p> </p>
</div> </div>
<div> <div>
+17 -12
View File
@@ -4,6 +4,7 @@ import { Bell, BellOff, Plus, Send, Trash2 } from "lucide-react"
import { useState } from "react" import { useState } from "react"
import { type AlertsConfig, AlertsService, type Channel } from "@/client" import { type AlertsConfig, AlertsService, type Channel } from "@/client"
import { InfoTip } from "@/components/Flow/SidePanel"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox" import { Checkbox } from "@/components/ui/checkbox"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
@@ -161,9 +162,7 @@ function Alerts() {
<div className="grid gap-1"> <div className="grid gap-1">
<h1 className="text-2xl">Alerts</h1> <h1 className="text-2xl">Alerts</h1>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Where the engine goes when something breaks. A channel is somewhere to Where the engine goes when something breaks.
send to; a rule says which failures go to which channels. The same
fault repeating is held back until its cooldown passes.
</p> </p>
</div> </div>
{data ? <AlertsForm initial={data} /> : null} {data ? <AlertsForm initial={data} /> : null}
@@ -229,7 +228,14 @@ function AlertsForm({ initial }: { initial: AlertsConfig }) {
<section className="grid gap-3"> <section className="grid gap-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h2 className={SECTION}>Channels</h2> <div className="flex items-center gap-1">
<h2 className={SECTION}>Channels</h2>
<InfoTip label="Channels">
Somewhere to send to. A setting can hold{" "}
<code className="font-mono">{`{"$secret": "name"}`}</code> instead
of the value itself, and the stored secret is used.
</InfoTip>
</div>
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
@@ -359,18 +365,17 @@ function AlertsForm({ initial }: { initial: AlertsConfig }) {
</div> </div>
)) ))
)} )}
{channels.length > 0 ? (
<p className="text-xs text-muted-foreground">
A setting can hold{" "}
<code className="font-mono">{`{"$secret": "name"}`}</code> instead
of the value itself, and the stored secret is used.
</p>
) : null}
</section> </section>
<section className="grid gap-3"> <section className="grid gap-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h2 className={SECTION}>Rules</h2> <div className="flex items-center gap-1">
<h2 className={SECTION}>Rules</h2>
<InfoTip label="Rules">
Which failures go to which channels. The same fault repeating is
held back until its cooldown passes.
</InfoTip>
</div>
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
+11 -5
View File
@@ -4,6 +4,7 @@ import { Package } from "lucide-react"
import { useState } from "react" import { useState } from "react"
import { type ApiError, ModulesService } from "@/client" import { type ApiError, ModulesService } from "@/client"
import { InfoTip } from "@/components/Flow/SidePanel"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import useCustomToast from "@/hooks/useCustomToast" import useCustomToast from "@/hooks/useCustomToast"
import { handleError } from "@/utils" import { handleError } from "@/utils"
@@ -70,11 +71,16 @@ function Modules() {
return ( return (
<div className="grid gap-6"> <div className="grid gap-6">
<div className="grid gap-1"> <div className="grid gap-1">
<h1 className="text-2xl">Modules</h1> <div className="flex items-center gap-1">
<h1 className="text-2xl">Modules</h1>
<InfoTip label="Modules">
{adopted
? "Fluksio was installed into an environment you already had, so that is the one your nodes run on. Change it with pip or uv rather than from here."
: "Packages install into an environment of their own, separate from the engine's, so a version pinned here is the one your code gets. The list is kept with your flows, so a rebuilt deployment installs the same set again."}
</InfoTip>
</div>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
{adopted The Python packages your function nodes can import.
? "The Python packages your function nodes can import. Fluksio was installed into an environment you already had, so that is the one your nodes run on — and it is yours to change, with pip or uv rather than from here."
: "The Python packages your function nodes can import. They are installed into an environment of their own, separate from the engine's, so a version you pin here is the one your code gets. The list is kept with your flows, so a rebuilt deployment installs the same set again."}
</p> </p>
</div> </div>
@@ -139,7 +145,7 @@ function Modules() {
) : null} ) : null}
{packages.length === 0 ? ( {packages.length === 0 ? (
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Nothing installed yet node code has the standard library. Nothing installed yet. Node code has the standard library.
</p> </p>
) : ( ) : (
<div className="grid gap-2"> <div className="grid gap-2">