Refactored to use the unified handler output format from setMessages():

Removed always_enabled: true — plugin is opt-in, not mandatory for A0 operation Eliminated manual DOM stamping (data-log-no) — log number now available via context.results[].args.no Eliminated full-document querySelectorAll scan — action bar accessed directly via context.results[].result.element No changes to backend API handler (branch_chat.py)

keyboardstaff committed Feb 25, 2026 at 07:22 UTC f3c517ee45998f0da9299d2e592373da7af7dc28
2 files changed +10 -30
plugins/chat_branching/extensions/webui/set_messages_after_loop/inject-branch-buttons.js
+9 -28
@@ -1,41 +1,22 @@
1 // Chat Branching Plugin — injects a "branch" button into every message's action bar.
2 // Runs as a set_messages_after_loop JS extension.
3 +//
4 +// Uses the unified handler output: context.results is an array of
5 +// { args: { no, id, type, … }, result: { element, … } } from each setMessage() call.
6
7 import { createActionButton } from "/components/messages/action-buttons/simple-action-buttons.js";
8 import { callJsonApi } from "/js/api.js";
9
7 -const LOG_NO_ATTR = "data-log-no";
8 -
9 -/**
10 - * default export called by callJsExtensions("set_messages_after_loop", context)
11 - * context.messages is the raw log items array with { no, id, type, ... }
12 - */
10 export default async function injectBranchButtons(context) {
14 - if (!context?.messages?.length) return;
15 -
16 - // 1. Stamp every rendered element with its log "no" so the button can read it.
17 - for (const msg of context.messages) {
18 - const domId = msg.id || msg.no;
19 - // message containers use id="message-{id}", process steps use id="process-step-{id}"
20 - const el =
21 - document.getElementById(`message-${domId}`) ||
22 - document.getElementById(`process-step-${domId}`);
23 - if (el) el.setAttribute(LOG_NO_ATTR, String(msg.no));
24 - }
11 + if (!context?.results?.length) return;
12
26 - // 2. Inject branch button into every action bar.
27 - // Core renderers clear and rebuild action-button children on each update,
28 - // so we check for the actual button element instead of a marker attribute.
29 - const bars = document.querySelectorAll(".step-action-buttons");
30 - for (const bar of bars) {
31 - if (bar.querySelector(".action-fork_right")) continue;
13 + for (const { args, result } of context.results) {
14 + if (!result?.element || args.no == null) continue;
15
33 - // Resolve the log no from the nearest stamped ancestor
34 - const stamped = bar.closest(`[${LOG_NO_ATTR}]`);
35 - if (!stamped) continue;
36 - const logNo = Number(stamped.getAttribute(LOG_NO_ATTR));
37 - if (Number.isNaN(logNo)) continue;
16 + const bar = result.element.querySelector(".step-action-buttons");
17 + if (!bar || bar.querySelector(".action-fork_right")) continue;
18
19 + const logNo = args.no;
20 const btn = createActionButton("fork_right", "Branch chat", async () => {
21 const ctxid = globalThis.getContext?.();
22 if (!ctxid) throw new Error("No active chat");
plugins/chat_branching/plugin.yaml
+1 -2
@@ -1,4 +1,3 @@
1 name: Chat Branching
2 description: Branch a chat from any message, creating a new chat with history up to that point.
3 -version: 1.0.0
4 -always_enabled: true
\ No newline at end of file
3 +version: 1.0.0
\ No newline at end of file