feat(cli): gate dev broadcasts by --path and push currentLastEdit on connect

When --path (or auto-detected flow path) is set, drop file events for
any other path so the dev page stays locked to the requested resource
and currentLastEdit can never reflect an unrelated edit. The connection
handler proactively pushes currentLastEdit so the page renders without
waiting for the first file change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Guilhem Lemouel
2026-04-23 12:59:40 +02:00
co-authored by Claude Opus 4.7
parent b57ec80fd1
commit 3c2d5155e1
+28 -9
View File
@@ -237,6 +237,15 @@ export async function dev(opts: GlobalOptions & SyncOptions & DevOpts) {
} }
} }
if (!localPath) return; if (!localPath) return;
// Strip whichever flow suffix is present (dotted or non-dotted)
let wmFlowPath = localPath.replace(/\/$/, "");
if (wmFlowPath.endsWith(".flow")) {
wmFlowPath = wmFlowPath.slice(0, -".flow".length);
} else if (wmFlowPath.endsWith("__flow")) {
wmFlowPath = wmFlowPath.slice(0, -"__flow".length);
}
// Skip work entirely when --path is set and this change is for a different path
if (opts.path && wmFlowPath !== opts.path) return;
const localFlow = (await yamlParseFile( const localFlow = (await yamlParseFile(
localPath + "flow.yaml" localPath + "flow.yaml"
)) as FlowFile; )) as FlowFile;
@@ -257,13 +266,6 @@ export async function dev(opts: GlobalOptions & SyncOptions & DevOpts) {
}); });
await replaceAllPathScriptsWithLocal(localFlow.value, localScriptReader, log); await replaceAllPathScriptsWithLocal(localFlow.value, localScriptReader, log);
tagReplacedPathScripts(localFlow); tagReplacedPathScripts(localFlow);
// Strip whichever flow suffix is present (dotted or non-dotted)
let wmFlowPath = localPath.replace(/\/$/, "");
if (wmFlowPath.endsWith(".flow")) {
wmFlowPath = wmFlowPath.slice(0, -".flow".length);
} else if (wmFlowPath.endsWith("__flow")) {
wmFlowPath = wmFlowPath.slice(0, -"__flow".length);
}
currentLastEdit = { currentLastEdit = {
type: "flow", type: "flow",
flow: localFlow, flow: localFlow,
@@ -273,9 +275,11 @@ export async function dev(opts: GlobalOptions & SyncOptions & DevOpts) {
log.info("Updated " + wmFlowPath); log.info("Updated " + wmFlowPath);
broadcastChanges(currentLastEdit); broadcastChanges(currentLastEdit);
} else if (typ == "script") { } else if (typ == "script") {
const content = await readFile(cpath, "utf-8");
const splitted = cpath.split("."); const splitted = cpath.split(".");
const wmPath = splitted[0]; const wmPath = splitted[0];
// Skip work entirely when --path is set and this change is for a different path
if (opts.path && wmPath !== opts.path) return;
const content = await readFile(cpath, "utf-8");
const lang = inferContentTypeFromFilePath(cpath, opts.defaultTs); const lang = inferContentTypeFromFilePath(cpath, opts.defaultTs);
const typed = const typed =
(await parseMetadataFile( (await parseMetadataFile(
@@ -470,8 +474,13 @@ export async function dev(opts: GlobalOptions & SyncOptions & DevOpts) {
const connectedClients: Set<WebSocket> = new Set(); const connectedClients: Set<WebSocket> = new Set();
// Function to send a message to all connected clients // Function to send a message to all connected clients.
// When --path (or auto-detected flow path) is set, drop edits for any other
// path so the dev page stays locked to the requested resource.
function broadcastChanges(lastEdit: LastEditScript | LastEditFlow) { function broadcastChanges(lastEdit: LastEditScript | LastEditFlow) {
if (opts.path && lastEdit.path !== opts.path) {
return;
}
for (const client of connectedClients.values()) { for (const client of connectedClients.values()) {
client.send(JSON.stringify(lastEdit)); client.send(JSON.stringify(lastEdit));
} }
@@ -482,6 +491,16 @@ export async function dev(opts: GlobalOptions & SyncOptions & DevOpts) {
connectedClients.add(ws); connectedClients.add(ws);
console.log("New dev client connected"); console.log("New dev client connected");
// Push the currently loaded edit so the page renders immediately on
// page load, without waiting for a file change to trigger a broadcast.
if (currentLastEdit) {
try {
ws.send(JSON.stringify(currentLastEdit));
} catch (e) {
console.error("Failed to push initial state to new client:", e);
}
}
ws.on("close", () => { ws.on("close", () => {
connectedClients.delete(ws); connectedClients.delete(ws);
console.log("Dev client disconnected"); console.log("Dev client disconnected");