fix: align reply threading refs

This commit is contained in:
Peter Steinberger
2026-01-12 23:41:00 +00:00
parent ca98f87b2f
commit bb9a9633a8
5 changed files with 189 additions and 38 deletions

View File

@@ -0,0 +1,56 @@
import { describe, expect, it } from "vitest";
import { createReplyReferencePlanner } from "./reply-reference.js";
describe("createReplyReferencePlanner", () => {
it("disables references when mode is off", () => {
const planner = createReplyReferencePlanner({
replyToMode: "off",
startId: "parent",
});
expect(planner.use()).toBeUndefined();
expect(planner.hasReplied()).toBe(false);
});
it("uses startId once when mode is first", () => {
const planner = createReplyReferencePlanner({
replyToMode: "first",
startId: "parent",
});
expect(planner.use()).toBe("parent");
expect(planner.hasReplied()).toBe(true);
planner.markSent();
expect(planner.use()).toBeUndefined();
});
it("returns startId for every call when mode is all", () => {
const planner = createReplyReferencePlanner({
replyToMode: "all",
startId: "parent",
});
expect(planner.use()).toBe("parent");
expect(planner.use()).toBe("parent");
});
it("prefers existing thread id regardless of mode", () => {
const planner = createReplyReferencePlanner({
replyToMode: "off",
existingId: "thread-1",
startId: "parent",
});
expect(planner.use()).toBe("thread-1");
expect(planner.hasReplied()).toBe(true);
});
it("honors allowReference=false", () => {
const planner = createReplyReferencePlanner({
replyToMode: "all",
startId: "parent",
allowReference: false,
});
expect(planner.use()).toBeUndefined();
expect(planner.hasReplied()).toBe(false);
planner.markSent();
expect(planner.hasReplied()).toBe(true);
});
});

View File

@@ -0,0 +1,56 @@
import type { ReplyToMode } from "../../config/types.js";
export type ReplyReferencePlanner = {
/** Returns the effective reply/thread id for the next send and updates state. */
use(): string | undefined;
/** Mark that a reply was sent (needed when no reference is used). */
markSent(): void;
/** Whether a reply has been sent in this flow. */
hasReplied(): boolean;
};
export function createReplyReferencePlanner(options: {
replyToMode: ReplyToMode;
/** Existing thread/reference id (always used when present). */
existingId?: string;
/** Id to start a new thread/reference when allowed (e.g., parent message id). */
startId?: string;
/** Disable reply references entirely (e.g., when posting inside a new thread). */
allowReference?: boolean;
/** Seed the planner with prior reply state. */
hasReplied?: boolean;
}): ReplyReferencePlanner {
let hasReplied = options.hasReplied ?? false;
const allowReference = options.allowReference !== false;
const existingId = options.existingId?.trim();
const startId = options.startId?.trim();
const use = (): string | undefined => {
if (!allowReference) return undefined;
if (existingId) {
hasReplied = true;
return existingId;
}
if (!startId) return undefined;
if (options.replyToMode === "off") return undefined;
if (options.replyToMode === "all") {
hasReplied = true;
return startId;
}
if (!hasReplied) {
hasReplied = true;
return startId;
}
return undefined;
};
const markSent = () => {
hasReplied = true;
};
return {
use,
markSent,
hasReplied: () => hasReplied,
};
}