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 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 **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 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 editor offers to reload and drop your edits, or to keep them and replace this
latest. For any other refusal the editor shows the controller's own message. flow's latest version with them. For any other refusal the editor shows the
The router runs a saved flow from its next event. 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 ### 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 // 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 // kind-class: the source stays the source, and nothing with children becomes
// a todo. // a todo.

View file

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

View file

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

View file

@ -21,6 +21,7 @@ import {
subtreeIds, subtreeIds,
withFlow, withFlow,
withoutFlow, withoutFlow,
withOp,
} from "../src/pages/notifications/FlowDraft.ts"; } from "../src/pages/notifications/FlowDraft.ts";
const todo = (id) => ({ const todo = (id) => ({
@ -156,6 +157,16 @@ test("replaceNode keeps the place and the kind class", () => {
assert.equal(replaceNode(f, todo("nope")).ok, false); 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", () => { test("new ids do not collide", () => {
const f = flow(); const f = flow();
assert.equal(newNodeId(f, "match"), "match1"); assert.equal(newNodeId(f, "match"), "match1");