Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: a todo switched from clear to upsert drops its prefix flag

This commit is contained in:
iris 2026-10-24 14:05:02 +02:00 • committed by mara
commit fb8aa23e66
5 changed files with 34 additions and 12 deletions

View file

@ -258,9 +258,12 @@ Edits stay in a draft until **save**, which sends the whole flows document with
the revision the editor read. **discard edits** goes back to the saved flow and
**delete flow** removes it, after a confirmation. If someone changed the flows
elsewhere in the meantime, the controller turns the save down as stale, and the
editor offers to reload and drop your edits, or to keep them on top of the
latest. For any other refusal the editor shows the controller's own message.
The router runs a saved flow from its next event.
editor offers to reload and drop your edits, or to keep them and replace this
flow's latest version with them. For any other refusal the editor shows the
controller's own message. The router runs a saved flow from its next event.
Closing or reloading the tab with unsaved edits asks first. Leaving through a
link in the app, or the browser's back button, drops the draft without asking.
### Sending a message to an agent

View file

@ -200,6 +200,20 @@ export function reparent(
};
}
// A todo with its op switched. A clear carries no text, and only a clear may
// be a prefix, so each is reset where the new op has no place for it.
export function withOp(
node: Extract<FlowNode, { kind: "todo" }>,
op: "upsert" | "clear",
): Extract<FlowNode, { kind: "todo" }> {
return {
...node,
op,
text: op === "clear" ? "" : node.text,
prefix: op === "clear" ? node.prefix : false,
};
}
// The node with `next.id` swapped for `next`. A node keeps its place and its
// kind-class: the source stays the source, and nothing with children becomes
// a todo.

View file

@ -185,7 +185,7 @@ export function FlowEditorPage({
reload and drop my edits
</Button>
<Button onClick={() => void load(true)}>
keep my edits on top of the latest
keep my edits (replaces this flow's latest version)
</Button>
</div>
) : null}

View file

@ -16,6 +16,7 @@ import {
replaceNode,
type Edit,
type Limits,
withOp,
} from "./FlowDraft.js";
import {
kindValues,
@ -237,14 +238,7 @@ function TodoFields({
label="op"
value={node.op}
options={opList}
onChange={(op) =>
onChange({
...node,
op: op as TodoNode["op"],
// A clear carries no text.
text: op === "clear" ? "" : node.text,
})
}
onChange={(op) => onChange(withOp(node, op as TodoNode["op"]))}
/>
<SelectField
id={`${keyPrefix}-to-kind`}

View file

@ -21,6 +21,7 @@ import {
subtreeIds,
withFlow,
withoutFlow,
withOp,
} from "../src/pages/notifications/FlowDraft.ts";
const todo = (id) => ({
@ -156,6 +157,16 @@ test("replaceNode keeps the place and the kind class", () => {
assert.equal(replaceNode(f, todo("nope")).ok, false);
});
test("withOp drops what the new op has no place for", () => {
const clearing = { ...todo("t"), op: "clear", prefix: true, text: "" };
const up = withOp(clearing, "upsert");
assert.equal(up.op, "upsert");
assert.equal(up.prefix, false, "only a clear may be a prefix");
const back = withOp({ ...todo("t"), prefix: false }, "clear");
assert.equal(back.text, "", "a clear carries no text");
assert.equal(withOp(clearing, "clear").prefix, true, "kept on a clear");
});
test("new ids do not collide", () => {
const f = flow();
assert.equal(newNodeId(f, "match"), "match1");