kaizen-agent 1.34.4 → 1.34.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/web/term.ts CHANGED
@@ -41,45 +41,80 @@ function mountTerm(el: HTMLElement, key: string, onEnd?: () => void): () => void
41
41
  term.write(`\r\n\x1b[2m[${e.reason === "ended" ? "agent ended" : "connection to the board lost"}]\x1b[0m\r\n`);
42
42
  onEnd?.();
43
43
  };
44
- // Phone/tablet: a bar of the keys a soft keyboard lacks. Ctrl and Alt are sticky —
45
- // they modify the next key typed, from the soft keyboard or the bar. The ⌨ button
46
- // hides the native keyboard (inputmode none) so the bar alone drives the terminal.
47
- const mods = { ctrl: false, alt: false };
44
+ // Phone/tablet: a bar of the keys a soft keyboard lacks. Alt is sticky for the next key.
45
+ // Ctrl opens its own a-z pad instead of waiting on the native keyboard, whose IME can
46
+ // deliver the letter without the modifier. The handle above the bar hides it.
47
+ const mods = { alt: false };
48
48
  const typed = term.onData((d) => {
49
- if (d.length === 1 && mods.ctrl && /[a-z@[\\\]^_]/i.test(d)) d = String.fromCharCode(d.toUpperCase().charCodeAt(0) & 31);
50
49
  if (mods.alt) d = "\x1b" + d;
51
- mods.ctrl = mods.alt = false;
52
- bar?.querySelectorAll(".on").forEach((b) => b.classList.remove("on"));
50
+ mods.alt = false;
51
+ bar?.querySelector(".alt")?.classList.remove("on");
53
52
  send({ t: "i", d });
54
53
  });
55
54
  let bar: HTMLElement | null = null;
56
55
  if (matchMedia("(pointer: coarse)").matches) {
57
56
  const arrow = (c: string) => () => `\x1b${term.modes.applicationCursorKeysMode ? "O" : "["}${c}`;
58
- const keys: [string, string | (() => string) | "ctrl" | "alt" | "kbd"][] = [
59
- ["Esc", "\x1b"], ["Tab", "\t"], ["↵", "\r"], ["⇧⇥", "\x1b[Z"], ["Ctrl", "ctrl"], ["Alt", "alt"],
60
- ["←", arrow("D")], ["↑", arrow("A")], ["↓", arrow("B")], ["→", arrow("C")], ["^C", "\x03"], ["⌨", "kbd"],
57
+ const paste = () => navigator.clipboard?.readText().then((t) => t && term.paste(t), () => {});
58
+ // inverted T: Up above Down, Left and Right beside it
59
+ const keys: [string, string | (() => unknown) | "alt" | "ctrl" | ""][] = [
60
+ ["Esc", "\x1b"], ["Paste", paste], ["↵", "\r"], ["⇧⇥", "\x1b[Z"], ["↑", arrow("A")], ["", ""],
61
+ ["Ctrl", "ctrl"], ["Alt", "alt"], ["^C", "\x03"], ["←", arrow("D")], ["↓", arrow("B")], ["→", arrow("C")],
61
62
  ];
62
63
  bar = document.createElement("div");
63
64
  bar.className = "termkeys";
64
- for (const [label, v] of keys) {
65
+ // pointerdown + preventDefault: a tap must not move focus off the terminal.
66
+ const key = (label: string, fn: () => void) => {
65
67
  const b = document.createElement("button");
66
68
  b.type = "button";
67
69
  b.textContent = label;
68
- // pointerdown + preventDefault: the tap must not move focus off the terminal, or the keyboard closes.
69
- b.addEventListener("pointerdown", (e) => {
70
- e.preventDefault();
71
- if (v === "ctrl" || v === "alt") b.classList.toggle("on", (mods[v] = !mods[v]));
72
- else if (v === "kbd") {
73
- const ta = el.querySelector<HTMLTextAreaElement>(".xterm-helper-textarea");
74
- if (ta) ta.inputMode = ta.inputMode === "none" ? "text" : "none";
75
- b.classList.toggle("on", ta?.inputMode === "none");
76
- ta?.blur();
77
- ta?.focus();
78
- } else send({ t: "i", d: (typeof v === "function" ? v() : v) as string });
79
- term.focus();
70
+ b.addEventListener("pointerdown", (e) => { e.preventDefault(); fn(); term.focus(); });
71
+ return b;
72
+ };
73
+ const grid = document.createElement("div");
74
+ grid.className = "keygrid";
75
+ const setPad = (on: boolean) => { pad.hidden = !on; grid.hidden = on; };
76
+ const pad = document.createElement("div");
77
+ pad.className = "ctrlpad";
78
+ pad.hidden = true;
79
+ // qwerty rows; the last row ends with ✕ to leave the pad
80
+ for (const row of ["qwertyuiop", "asdfghjkl", "zxcvbnm✕"]) {
81
+ const r = document.createElement("div");
82
+ for (const c of row) r.append(key(c, () => {
83
+ if (c !== "✕") send({ t: "i", d: String.fromCharCode(c.charCodeAt(0) & 31) });
84
+ setPad(false);
85
+ }));
86
+ pad.append(r);
87
+ }
88
+ for (const [label, v] of keys) {
89
+ if (!label) { grid.append(document.createElement("i")); continue; }
90
+ const b = key(label, () => {
91
+ if (v === "alt") b.classList.toggle("on", (mods.alt = !mods.alt));
92
+ else if (v === "ctrl") setPad(true);
93
+ else send({ t: "i", d: (typeof v === "function" ? v() : v) as string });
80
94
  });
81
- bar.append(b);
95
+ if (v === "alt") b.classList.add(v);
96
+ grid.append(b);
82
97
  }
98
+ const hide = key("Keys ▾", () => {
99
+ const off = hide.textContent === "Keys ▾";
100
+ grid.hidden = pad.hidden = true;
101
+ if (!off) grid.hidden = false;
102
+ hide.textContent = off ? "Keys ▴" : "Keys ▾";
103
+ });
104
+ // The native keyboard stays shut (inputmode none) until this button asks for it, so
105
+ // tapping the terminal or a key never pops it over the Ctrl pad and Paste.
106
+ const ta = term.textarea;
107
+ if (ta) ta.inputMode = "none";
108
+ const kbd = key("⌨", () => {
109
+ if (!ta) return;
110
+ const on = kbd.classList.toggle("on");
111
+ ta.inputMode = on ? "text" : "none";
112
+ ta.blur(); // inputmode is read on focus; the key's term.focus() reopens it
113
+ });
114
+ const top = document.createElement("div");
115
+ top.className = "keytop";
116
+ top.append(hide, kbd);
117
+ bar.append(top, pad, grid);
83
118
  el.after(bar);
84
119
  }
85
120
  // xterm.js has no touch scrolling, so on a phone a swipe did nothing. Each line's