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
parent b57ec80fd1
commit 3c2d5155e1
+28 -9
View File
@@ -237,6 +237,15 @@ export async function dev(opts: GlobalOptions & SyncOptions & DevOpts) {
}
}
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(
localPath + "flow.yaml"
)) as FlowFile;
@@ -257,13 +266,6 @@ export async function dev(opts: GlobalOptions & SyncOptions & DevOpts) {
});
await replaceAllPathScriptsWithLocal(localFlow.value, localScriptReader, log);
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 = {
type: "flow",
flow: localFlow,
@@ -273,9 +275,11 @@ export async function dev(opts: GlobalOptions & SyncOptions & DevOpts) {
log.info("Updated " + wmFlowPath);
broadcastChanges(currentLastEdit);
} else if (typ == "script") {
const content = await readFile(cpath, "utf-8");
const splitted = cpath.split(".");
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 typed =
(await parseMetadataFile(
@@ -470,8 +474,13 @@ export async function dev(opts: GlobalOptions & SyncOptions & DevOpts) {
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) {
if (opts.path && lastEdit.path !== opts.path) {
return;
}
for (const client of connectedClients.values()) {
client.send(JSON.stringify(lastEdit));
}
@@ -482,6 +491,16 @@ export async function dev(opts: GlobalOptions & SyncOptions & DevOpts) {
connectedClients.add(ws);
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", () => {
connectedClients.delete(ws);
console.log("Dev client disconnected");