kaizen-agent 1.44.0 → 1.45.0
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/cli/settings.test.ts +8 -0
- package/cli/settings.ts +2 -0
- package/commands/kaizen-lite.md +1 -1
- package/package.json +1 -1
- package/skills/kaizen/SKILL.md +10 -5
- package/skills/kaizen/config.default.yml +8 -0
- package/skills/kaizen/settings.md +3 -1
- package/skills/kaizen/spec-main.md +1 -1
- package/skills/kaizen/spec.md +1 -0
- package/skills/kaizen-help/SKILL.md +2 -1
- package/web/board/terms.ts +36 -4
- package/web/board.html +5 -1
- package/web/board.js +25 -25
- package/web/notebook.js +1 -1
- package/web/term.js +1 -1
package/cli/settings.test.ts
CHANGED
|
@@ -86,3 +86,11 @@ test("a config still holding git.branch_before_implement keeps its preset", () =
|
|
|
86
86
|
expect(detectPreset(low, text)).toBe("low");
|
|
87
87
|
expect(detectPreset(text, text)).toBe("medium");
|
|
88
88
|
});
|
|
89
|
+
|
|
90
|
+
test("a low-preset config saved before review.executor existed still reads as low", () => {
|
|
91
|
+
const text = readFileSync(defaults, "utf8");
|
|
92
|
+
const low = applyPreset("", "low", text);
|
|
93
|
+
const old = low.replace(/(review:\n(?:\s*#.*\n)*)\s*executor: inline\n/, "$1");
|
|
94
|
+
expect(old).not.toContain("executor: inline\n # Let");
|
|
95
|
+
expect(detectPreset(old, text)).toBe("low");
|
|
96
|
+
});
|
package/cli/settings.ts
CHANGED
|
@@ -25,6 +25,7 @@ export const PRESETS: Record<PresetName, {
|
|
|
25
25
|
mode: "approve",
|
|
26
26
|
runner: "lite",
|
|
27
27
|
"build.executor": "inline",
|
|
28
|
+
"review.executor": "inline",
|
|
28
29
|
"approvals.plan": "true",
|
|
29
30
|
"approvals.review": "true",
|
|
30
31
|
"approvals.each_file": "false",
|
|
@@ -105,6 +106,7 @@ const SETTINGS: Setting[] = [
|
|
|
105
106
|
{ key: "approvals.each_file", label: "Confirm every file edit", group: "Approvals", values: ["true", "false"], help: "", when: ["mode", "approve"] },
|
|
106
107
|
{ key: "runner", label: "Stage isolation", group: "Agents", values: ["full", "lite"], help: "full: separate agent per stage. lite: one session" },
|
|
107
108
|
{ key: "build.executor", label: "Who builds", group: "Agents", values: ["subagent", "inline", "ask"], help: "subagent: its own agent · inline: this session · ask: pick at plan approval", when: ["runner", "full"] },
|
|
109
|
+
{ key: "review.executor", label: "Reviewer", group: "Agents", values: ["inline", "subagent", "ask"], help: "subagent: its own cold agent · inline: this session · ask: pick before review", when: ["runner", "lite"] },
|
|
108
110
|
{ key: "agent.default", label: "Tool the board launches", group: "Agents", values: ["auto", "claude", "codex", "agy", "opencode", "gemini"], help: "" },
|
|
109
111
|
{ key: "agent.launch", label: "Where the board runs agents", group: "Agents", values: ["card", "terminal"], help: "" },
|
|
110
112
|
{ key: "subagents.model", label: "Model for subagents", group: "Agents", values: ["inherit", "opus", "sonnet", "haiku"], help: "Planner, builder, reviewer", when: ["runner", "full"] },
|
package/commands/kaizen-lite.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Run kaizen with every stage in this session — cheaper
|
|
2
|
+
description: Run kaizen with every stage in this session — cheaper; the reviewer has seen the work unless review.executor is subagent
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
Invoke the `kaizen` skill with argument `lite $ARGUMENTS`.
|
package/package.json
CHANGED
package/skills/kaizen/SKILL.md
CHANGED
|
@@ -42,7 +42,7 @@ Porting the workflow to Codex or Antigravity is covered in [`adapters.md`](adapt
|
|
|
42
42
|
| `/kaizen <request>` | Start a new run in the configured mode (default: `approve`) |
|
|
43
43
|
| `/kaizen plan <request>` | Start a run and stop after the plan (`plan-only`) |
|
|
44
44
|
| `/kaizen auto <request>` | Start a run in `auto` mode: every stage back to back, no approval stops |
|
|
45
|
-
| `/kaizen lite <request>` | Run every stage in this session — cheap, and the reviewer has seen the work |
|
|
45
|
+
| `/kaizen lite <request>` | Run every stage in this session — cheap, and the reviewer has seen the work unless `review.executor` is `subagent` |
|
|
46
46
|
| `/kaizen full <request>` | Dispatch every stage as its own agent — the reviewer starts cold |
|
|
47
47
|
| `/kaizen run` | Resume the current run and execute the approved plan |
|
|
48
48
|
| `/kaizen review [target]` | Review-only: audit existing code, no plan, no implementation (`review-only`) |
|
|
@@ -210,11 +210,13 @@ would cost in one session and several times that. Set it for the project with
|
|
|
210
210
|
The reviewer has never seen how the work was done, so it has nothing to defend.
|
|
211
211
|
That is the entire reason its findings are worth reading, and it is what catches the
|
|
212
212
|
bug in a fix rather than the bug in the plan.
|
|
213
|
-
- **`lite`** — this session runs every stage itself, in order, as
|
|
213
|
+
- **`lite`** — this session runs every stage itself (the review too, unless `review.executor` says otherwise), in order, as
|
|
214
214
|
[`adapters.md`](adapters.md) already describes for tools that have no subagents.
|
|
215
215
|
Read only the stage's declared inputs, write its artifact before starting the next,
|
|
216
216
|
and when reviewing, judge the work on its own terms rather than defending the
|
|
217
217
|
reasoning you used producing it.
|
|
218
|
+
`review.executor: subagent` hands just the review to a cold agent, which buys
|
|
219
|
+
back the independence for the price of one stage.
|
|
218
220
|
|
|
219
221
|
Everything else holds in both: the plan, the approval stop, the numbered gate answered
|
|
220
222
|
with evidence, numbered findings, the files, and a run another tool can pick up.
|
|
@@ -227,7 +229,7 @@ produced it — and a fix that is worse than the finding it closes is exactly th
|
|
|
227
229
|
|
|
228
230
|
So: `lite` where being wrong is cheap and obvious, which is also where the short path
|
|
229
231
|
above applies. `full` where being wrong is expensive. When a run is going to
|
|
230
|
-
touch money, a migration, or anything hard to undo, say which runner it used at the
|
|
232
|
+
touch money, a migration, or anything hard to undo, say which runner (and, for `lite`, which reviewer) it used at the
|
|
231
233
|
final report, so nobody reads a same-session review as an independent one.
|
|
232
234
|
|
|
233
235
|
## Running a stage
|
|
@@ -241,7 +243,7 @@ it. Everything else in this file holds unchanged.
|
|
|
241
243
|
|
|
242
244
|
**Check `runner` first.**
|
|
243
245
|
|
|
244
|
-
- `runner: lite` — do not call the Agent tool. You run plan, build and review
|
|
246
|
+
- `runner: lite` — do not call the Agent tool, except for the review as below. You run plan, build and review
|
|
245
247
|
yourself, in that order, reading only each stage's declared inputs and writing its
|
|
246
248
|
artifact before starting the next. The stage table below still applies; you are the
|
|
247
249
|
agent for every row.
|
|
@@ -251,6 +253,9 @@ it. Everything else in this file holds unchanged.
|
|
|
251
253
|
`runs/<id>/attachments/` embedded in `## Preview`, taken by driving a headless browser
|
|
252
254
|
against the running app; "no browser session" or "same renderer as before" is not a
|
|
253
255
|
reason, it is the gate failing. Short path or not, no screenshot means not built.
|
|
256
|
+
The review stage follows `review.executor`: `inline` (default) reviews in this session; `subagent` dispatches
|
|
257
|
+
`kaizen-reviewer` cold, as under `full`, for the review and every recheck; `ask` puts the choice to the user when the build is done.
|
|
258
|
+
Record the answer as `review_executor` in `state.json` so a resumed run keeps it.
|
|
254
259
|
- `runner: full` — dispatch each stage as its own subagent, as follows.
|
|
255
260
|
|
|
256
261
|
Under `full`, each stage runs as a subagent via the Agent tool, with the agent type
|
|
@@ -271,7 +276,7 @@ the choice at the plan approval. Building inline does not make the review less
|
|
|
271
276
|
independent: the reviewer is a separate agent in every case, and never sees the
|
|
272
277
|
conversation the work happened in.
|
|
273
278
|
|
|
274
|
-
`subagents.model` and `subagents.effort` apply to every stage dispatched this way.
|
|
279
|
+
`subagents.model` and `subagents.effort` apply to every stage dispatched this way, including a lite run's reviewer under `review.executor: subagent`.
|
|
275
280
|
Pass `model` on the Agent call when it is not `inherit`. Effort has no call-time
|
|
276
281
|
knob: when it is not `inherit`, make sure the frontmatter of the agent's definition
|
|
277
282
|
(`~/.claude/agents/kaizen-<stage>.md`, or the project's `.claude/agents/` copy)
|
|
@@ -59,6 +59,14 @@ auto_fix:
|
|
|
59
59
|
max_iterations: 2
|
|
60
60
|
|
|
61
61
|
review:
|
|
62
|
+
# Who reviews a lite run. Only read when runner is lite; under full the reviewer is
|
|
63
|
+
# always its own agent.
|
|
64
|
+
# inline - this session reviews its own work. Cheapest; expect it to miss what
|
|
65
|
+
# it just did. The default, so a lite run costs what it always has.
|
|
66
|
+
# subagent - plan and build stay in this session, the review (and every recheck in
|
|
67
|
+
# the fix loop) goes to a cold reviewer agent that never saw the work.
|
|
68
|
+
# ask - offer the choice when the build is done, before the review.
|
|
69
|
+
executor: inline
|
|
62
70
|
# Let the reviewer append durable lessons to .kaizen/memory.md.
|
|
63
71
|
write_memory: true
|
|
64
72
|
# Every stage of every future run reads memory.md, so cap it. Over this, the reviewer
|
|
@@ -15,6 +15,7 @@ nearest first, so a change lands globally unless this project keeps its own conf
|
|
|
15
15
|
| `mode` | `approve`, `auto`, `plan-only`, `review-only` | Where a run stops |
|
|
16
16
|
| `runner` | `full`, `lite` | Whether each stage is its own cold agent, or this session runs them all |
|
|
17
17
|
| `build.executor` | `subagent`, `inline`, `ask` | Who carries out the approved plan |
|
|
18
|
+
| `review.executor` | `inline`, `subagent`, `ask` | Who reviews a lite run: this session, a cold reviewer agent, or ask before the review |
|
|
18
19
|
| `approvals.plan` | `true`, `false` | Stop and show the plan before anything is built |
|
|
19
20
|
| `approvals.review` | `true`, `false` | Stop after the review, before the run is called done |
|
|
20
21
|
| `approvals.each_file` | `true`, `false` | Confirm every individual edit |
|
|
@@ -34,7 +35,8 @@ nearest first, so a change lands globally unless this project keeps its own conf
|
|
|
34
35
|
| `agent.default` | `auto`, `claude`, `codex`, `agy`, `opencode`, `gemini` | Coding agent to launch from dashboard backlog |
|
|
35
36
|
|
|
36
37
|
Some settings only have an effect while another stands a certain way: the approvals
|
|
37
|
-
while `mode` is `approve`, `build.executor` and `subagents.*` while `runner` is `full
|
|
38
|
+
while `mode` is `approve`, `build.executor` and `subagents.*` while `runner` is `full` (`subagents.*` also reaches a lite
|
|
39
|
+
run's reviewer under `review.executor: subagent`), `review.executor` while `runner` is `lite`,
|
|
38
40
|
`auto_fix.min_severity` and `auto_fix.max_iterations` while `auto_fix.enabled` is
|
|
39
41
|
`true`, `sources.model` and `sources.effort` while `sources.agent` is `chatgpt`. When
|
|
40
42
|
printing one of these that has no effect now, say so beside its value. Still offer it:
|
|
@@ -138,7 +138,7 @@ Controlled by `approvals.review`, skipped in `auto`.
|
|
|
138
138
|
Report: what was built (for a code run, show its Preview proof), verification results, findings fixed, findings still open,
|
|
139
139
|
every criterion the reviewer answered `CANNOT VERIFY` with the check it says a human
|
|
140
140
|
must make, anything the builder noted as out of scope, and the notes the *Docs gate*
|
|
141
|
-
touched. Where the run used `runner: lite
|
|
141
|
+
touched. Where the run used `runner: lite` and reviewed inline (`review.executor: inline`), say
|
|
142
142
|
so in that report — the reviewer saw the work, and nobody should read a same-session
|
|
143
143
|
review as an independent one because the output looked the same.
|
|
144
144
|
Later build requests in the same session follow *Follow-ups are runs too* in [`spec.md`](spec.md): a revise of this run until it is `done`, then a new linked run.
|
package/skills/kaizen/spec.md
CHANGED
|
@@ -111,6 +111,7 @@ Write into `00-request.md`: the verbatim request, the deliverable, what done mea
|
|
|
111
111
|
and what wrong means. Record in `state.json`: `id`, `mode`, `runner`, `agent`, `track`,
|
|
112
112
|
`stage: "plan"`, `awaiting: null`, `iteration: 0`, and `updated` — the time of this
|
|
113
113
|
write, refreshed on every later one.
|
|
114
|
+
A `lite` run also records `review_executor` (`inline` or `subagent`, from `review.executor`, `ask` resolved) once it is known, and a resumed run keeps it.
|
|
114
115
|
Where the environment has `CLAUDE_CODE_SESSION_ID`, also record it as `session`: the
|
|
115
116
|
board keeps the run on the card it was launched from by that id.
|
|
116
117
|
|
|
@@ -23,7 +23,7 @@ anything.
|
|
|
23
23
|
| `/kaizen <request>` | Start a new run in the configured mode (default: `approve`) |
|
|
24
24
|
| `/kaizen plan <request>` | Start a run and stop after the plan (`plan-only`) |
|
|
25
25
|
| `/kaizen auto <request>` | Start a run in `auto` mode: every stage back to back, no approval stops |
|
|
26
|
-
| `/kaizen lite <request>` | Run the stages in this session — cheap, reviewer has seen the work |
|
|
26
|
+
| `/kaizen lite <request>` | Run the stages in this session — cheap, reviewer has seen the work (unless `review.executor: subagent`) |
|
|
27
27
|
| `/kaizen full <request>` | Dispatch each stage as its own cold agent |
|
|
28
28
|
| `/kaizen run` | Resume the current run and execute the approved plan |
|
|
29
29
|
| `/kaizen review [target]` | Review-only: audit existing code, no plan, no implementation |
|
|
@@ -85,6 +85,7 @@ Each iteration is written to `runs/<id>/05-iterations/`.
|
|
|
85
85
|
| `approvals.review` | `false` | Stop after the review |
|
|
86
86
|
| `approvals.each_file` | `false` | Confirm every file edit |
|
|
87
87
|
| `build.executor` | `subagent` | Who builds: `subagent`, `inline` (main thread), or `ask` at the approval |
|
|
88
|
+
| `review.executor` | `inline` | Lite only — who reviews: `inline` (this session), `subagent` (cold agent), or `ask` |
|
|
88
89
|
| `auto_fix.enabled` | `true` | Auto-fix findings vs. escalate everything |
|
|
89
90
|
| `auto_fix.min_severity` | `high` | Threshold for auto-fix (`critical\|high\|medium\|low`) |
|
|
90
91
|
| `auto_fix.max_iterations` | `2` | Fix/recheck rounds before escalating remaining findings |
|
package/web/board/terms.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { resume } from "./approvals.ts";
|
|
|
13
13
|
// ponytail: a √n grid with draggable borders, not an i3-style split tree; add one if a grid falls short.
|
|
14
14
|
// mode: grid (√n), cols (side by side) or rows (stacked, so borders drag up and down).
|
|
15
15
|
type Lay = { mode: string; order: string[]; hidden: string[]; max: string | null; sizes: Record<string, { c: number[]; r: number[] }> };
|
|
16
|
-
const MODES = [["grid", "Grid"], ["cols", "Side by side"], ["rows", "Stacked"]];
|
|
16
|
+
const MODES = [["grid", "Grid", "lgrid"], ["cols", "Side by side", "lcols"], ["rows", "Stacked", "lrows"]];
|
|
17
17
|
const GAP = 6;
|
|
18
18
|
let lay: Lay = { mode: "grid", order: [], hidden: [], max: null, sizes: {} };
|
|
19
19
|
try {
|
|
@@ -51,11 +51,32 @@ function sessions() {
|
|
|
51
51
|
|
|
52
52
|
export function termsStop() {
|
|
53
53
|
showing = false;
|
|
54
|
+
document.body.classList.remove("tmax");
|
|
54
55
|
for (const t of tiles.values()) t.off?.();
|
|
55
56
|
tiles.clear();
|
|
56
57
|
$("terms").innerHTML = "";
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
// A header button took focus off the terminal; give it back so typing carries on.
|
|
61
|
+
const focusTerm = (k: string) => tiles.get(k)?.el.querySelector<HTMLElement>(".xterm-helper-textarea")?.focus({ preventScroll: true });
|
|
62
|
+
// Ctrl+Shift+M maximises or restores the tile being typed in. Escape belongs to the agent,
|
|
63
|
+
// so the key is read in capture, before xterm sends it on.
|
|
64
|
+
if (typeof window !== "undefined") addEventListener(
|
|
65
|
+
"keydown",
|
|
66
|
+
(e) => {
|
|
67
|
+
if (!showing || !e.ctrlKey || !e.shiftKey || e.altKey || e.metaKey || e.code !== "KeyM" || phone?.matches) return;
|
|
68
|
+
const k = (e.target as HTMLElement).closest?.<HTMLElement>(".tile")?.dataset.k || lay.max;
|
|
69
|
+
if (!k) return;
|
|
70
|
+
e.preventDefault();
|
|
71
|
+
e.stopPropagation();
|
|
72
|
+
lay.max = lay.max === k ? null : k;
|
|
73
|
+
save();
|
|
74
|
+
openTerms();
|
|
75
|
+
focusTerm(k);
|
|
76
|
+
},
|
|
77
|
+
true,
|
|
78
|
+
);
|
|
79
|
+
|
|
59
80
|
export function openTerms() {
|
|
60
81
|
showing = true;
|
|
61
82
|
if (!phone) (phone = matchMedia("(max-width:640px)")).onchange = () => showing && openTerms();
|
|
@@ -87,7 +108,7 @@ export function openTerms() {
|
|
|
87
108
|
// An ended session closed with × has nothing to come back to: no chip for it.
|
|
88
109
|
const hid = [...all.keys()].filter((k) => lay.hidden.includes(k) && all.get(k).pty.alive);
|
|
89
110
|
// The layout switch sits at the right of the page header.
|
|
90
|
-
$("tmodes").innerHTML = html`${MODES.map(([v, l]) => html`<button data-mode="${v}" class="${lay.mode === v ? "on" : ""}" aria-pressed="${String(lay.mode === v)}"
|
|
111
|
+
$("tmodes").innerHTML = html`${MODES.map(([v, l, i]) => html`<button data-mode="${v}" class="${lay.mode === v ? "on" : ""}" aria-pressed="${String(lay.mode === v)}" title="${l}" aria-label="${l}">${ico(i)}</button>`)}`.s;
|
|
91
112
|
bar!.innerHTML = html`${hid.length ? html`<span class="dim">Hidden</span>${hid.map((k) => html`<button class="chip" data-show="${k}" title="Show this terminal">${ico("plus")}${nameOf(all.get(k))} - ${projOf(all.get(k))}</button>`)}` : ""}${phone.matches && keys.length > 1 ? html`<span class="ttabs" role="group" aria-label="Terminals">${keys.map((k) => html`<button class="${k === one ? "on" : ""}" data-pick="${k}" aria-pressed="${String(k === one)}"><span class="tdot ${tst(all.get(k))}"></span>${nameOf(all.get(k))} - ${projOf(all.get(k))}</button>`)}</span>` : ""}`.s;
|
|
92
113
|
bar!.querySelectorAll<HTMLElement>("[data-pick]").forEach(
|
|
93
114
|
(b) =>
|
|
@@ -124,6 +145,8 @@ export function openTerms() {
|
|
|
124
145
|
);
|
|
125
146
|
return;
|
|
126
147
|
}
|
|
148
|
+
const had = document.activeElement;
|
|
149
|
+
let i = 0;
|
|
127
150
|
for (const k of keys) {
|
|
128
151
|
const c = all.get(k);
|
|
129
152
|
let t = tiles.get(k);
|
|
@@ -142,12 +165,19 @@ export function openTerms() {
|
|
|
142
165
|
const hd = t.el.querySelector("header")!;
|
|
143
166
|
hd.title = (c.kind === "run" ? c.id : c.text) + " · " + c.where;
|
|
144
167
|
// An ended run can be resumed from here; an idea whose agent ended has no run to resume.
|
|
145
|
-
hd.innerHTML = html`<span class="tdot ${st}" role="img" title="${st}" aria-label="${st}"></span><b>${nameOf(c)} - ${projOf(c)}</b>${off ? html`<span class="tend">ended</span>` : ""}<span class="sp"></span>${off && c.kind === "run" ? html`<button class="btn sm" data-a="resume" title="Continue this run's agent">Resume</button>` : ""}<button class="ib sm" data-a="card" title="Open card" aria-label="Open card">${ico("open")}</button><button class="ib sm" data-a="max" title="${lay.max === k ? "Restore" : "Maximise"}" aria-label="${lay.max === k ? "Restore" : "Maximise"}">${ico(lay.max === k ? "min" : "max")}</button>${off ? "" : html`<button class="ib sm" data-a="hide" title="Hide" aria-label="Hide">${ico("dash")}</button>`}<button class="ib sm" data-a="${off ? "hide" : "exit"}" title="${off ? "Close" : "Exit session"}" aria-label="${off ? "Close" : "Exit session"}">${ico("close")}</button>`.s;
|
|
168
|
+
hd.innerHTML = html`<span class="tdot ${st}" role="img" title="${st}" aria-label="${st}"></span><b>${nameOf(c)} - ${projOf(c)}</b>${off ? html`<span class="tend">ended</span>` : ""}<span class="sp"></span>${off && c.kind === "run" ? html`<button class="btn sm" data-a="resume" title="Continue this run's agent">Resume</button>` : ""}<button class="ib sm" data-a="card" title="Open card" aria-label="Open card">${ico("open")}</button><button class="ib sm" data-a="max" title="${lay.max === k ? "Restore" : "Maximise"} (Ctrl+Shift+M)" aria-label="${lay.max === k ? "Restore" : "Maximise"}">${ico(lay.max === k ? "min" : "max")}</button>${off ? "" : html`<button class="ib sm" data-a="hide" title="Hide" aria-label="Hide">${ico("dash")}</button>`}<button class="ib sm" data-a="${off ? "hide" : "exit"}" title="${off ? "Close" : "Exit session"}" aria-label="${off ? "Close" : "Exit session"}">${ico("close")}</button>`.s;
|
|
146
169
|
t.el.classList.toggle("ended", off);
|
|
147
170
|
t.el.classList.toggle("off", !!one && one !== k);
|
|
171
|
+
t.el.classList.toggle("max", !phone.matches && lay.max === k);
|
|
148
172
|
t.el.style.gridColumn = "";
|
|
149
|
-
|
|
173
|
+
// In order, moving only what is out of place: a moved tile loses its terminal's focus,
|
|
174
|
+
// and every /state tick came here, so typed keys fell to the page (Firefox's find bar).
|
|
175
|
+
if (grid.children[i] !== t.el) grid.insertBefore(t.el, grid.children[i] || null);
|
|
176
|
+
i++;
|
|
150
177
|
}
|
|
178
|
+
if (had instanceof HTMLElement && had !== document.activeElement && had.isConnected) had.focus({ preventScroll: true });
|
|
179
|
+
// Maximised fills the window, over the sidebar and header: with one terminal it was already the whole grid.
|
|
180
|
+
document.body.classList.toggle("tmax", !!lay.max && !phone.matches);
|
|
151
181
|
// Grid is √n, the last tile spanning what its row leaves empty; side by side is one row, stacked one column.
|
|
152
182
|
const n = one ? 1 : keys.length,
|
|
153
183
|
cols = lay.mode === "cols" ? n : lay.mode === "rows" ? 1 : Math.ceil(Math.sqrt(n)),
|
|
@@ -198,6 +228,7 @@ function wire(el: HTMLElement) {
|
|
|
198
228
|
} else return;
|
|
199
229
|
save();
|
|
200
230
|
openTerms();
|
|
231
|
+
focusTerm(k);
|
|
201
232
|
};
|
|
202
233
|
el.querySelector("header")!.ondblclick = (e) => {
|
|
203
234
|
// A phone shows one tile anyway: a double-tap there would only leave desktop maximised.
|
|
@@ -205,6 +236,7 @@ function wire(el: HTMLElement) {
|
|
|
205
236
|
lay.max = lay.max === k ? null : k;
|
|
206
237
|
save();
|
|
207
238
|
openTerms();
|
|
239
|
+
focusTerm(k);
|
|
208
240
|
};
|
|
209
241
|
// Drag a header onto another tile: the two swap places.
|
|
210
242
|
el.querySelector("header")!.ondragstart = (e) => {
|
package/web/board.html
CHANGED
|
@@ -272,7 +272,7 @@ body.paged:not(.ice) .hdr,body.ice aside.docked>header{display:none}body.ice #ne
|
|
|
272
272
|
/* The Terminal page: the project bar stays (it picks whose terminals show), the board gives way to the tiles */
|
|
273
273
|
#terms{display:none}body.termv.paged .hdr{display:flex}body.termv #newtop,body.termv #fbtn{display:none!important}
|
|
274
274
|
body.termv #terms{flex:1;min-height:0;display:flex;flex-direction:column;gap:8px;padding:10px 20px 14px 28px}
|
|
275
|
-
.tbar{display:flex;flex-wrap:wrap;align-items:center;gap:6px;font-size:12px}#tmodes{display:none}body.termv #tmodes{display:inline-flex}#tmodes button{padding:0
|
|
275
|
+
.tbar{display:flex;flex-wrap:wrap;align-items:center;gap:6px;font-size:12px}#tmodes{display:none}body.termv #tmodes{display:inline-flex}#tmodes button{display:inline-grid;place-items:center;padding:0 8px}#tmodes svg{width:16px;height:16px}.tbar .chip{cursor:pointer;background:none;border:0;font:inherit;font-size:11px}.tbar .chip:hover{color:var(--ink)}.tbar svg{width:12px;height:12px}
|
|
276
276
|
.tgrid{position:relative;flex:1;min-height:0;display:grid;gap:6px}
|
|
277
277
|
.tile{display:flex;flex-direction:column;min-width:0;min-height:0;border:1px solid var(--line);border-radius:var(--r-s);background:var(--bg);overflow:hidden}.tile.off{display:none}.tile.drop{outline:2px solid var(--accent);outline-offset:-2px}
|
|
278
278
|
.tile header{display:flex;align-items:center;gap:8px;min-height:30px;padding:0 4px 0 10px;border-bottom:1px solid var(--line);background:var(--bg-2);cursor:grab;user-select:none}
|
|
@@ -281,6 +281,7 @@ body.termv #terms{flex:1;min-height:0;display:flex;flex-direction:column;gap:8px
|
|
|
281
281
|
.tdot{flex:none;width:8px;height:8px;border-radius:50%;background:var(--run)}.tdot.asking{background:var(--warn)}.tdot.ended{background:var(--dim);opacity:.6}
|
|
282
282
|
.tile .term{flex:1;min-height:0}.tile .term .xterm{height:100%;padding:4px}.tile .term .xterm-viewport{background:transparent!important}
|
|
283
283
|
.tsplit{position:absolute;z-index:2;touch-action:none}.tsplit.c{top:0;width:10px;margin-left:-5px;cursor:col-resize}.tsplit.r{left:0;right:0;height:10px;margin-top:-5px;cursor:row-resize}.tsplit:hover,.tdrag .tsplit:active{background:color-mix(in srgb,var(--accent) 35%,transparent)}
|
|
284
|
+
.tile.max{position:fixed;inset:8px;z-index:10;box-shadow:var(--shadow-1)}body.tmax .tsplit{display:none}
|
|
284
285
|
body.tdrag{user-select:none}body.tdrag .tile{pointer-events:none}
|
|
285
286
|
@media(max-width:900px){body.termv #terms{padding-inline:16px}}
|
|
286
287
|
/* A phone has no room for a grid or a stack: one terminal fills the page, and tabs above pick which */
|
|
@@ -535,6 +536,9 @@ a.schip:hover{color:var(--ink);border-color:color-mix(in srgb,var(--line) 60%,va
|
|
|
535
536
|
<symbol id="i-term" viewBox="0 0 24 24"><path d="M4 17l6-5-6-5M12 19h8"/></symbol>
|
|
536
537
|
<symbol id="i-max" viewBox="0 0 24 24"><path d="M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5"/></symbol>
|
|
537
538
|
<symbol id="i-min" viewBox="0 0 24 24"><path d="M9 4v5H4M15 4v5h5M9 20v-5H4M15 20v-5h5"/></symbol>
|
|
539
|
+
<symbol id="i-lgrid" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></symbol>
|
|
540
|
+
<symbol id="i-lcols" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M12 3v18"/></symbol>
|
|
541
|
+
<symbol id="i-lrows" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 12h18"/></symbol>
|
|
538
542
|
<symbol id="i-dash" viewBox="0 0 24 24"><path d="M5 12h14"/></symbol>
|
|
539
543
|
<symbol id="i-close" viewBox="0 0 24 24"><path d="M18 6L6 18M6 6l12 12"/></symbol>
|
|
540
544
|
<symbol id="i-more" viewBox="0 0 24 24"><circle cx="5" cy="12" r="1.2" fill="currentColor"/><circle cx="12" cy="12" r="1.2" fill="currentColor"/><circle cx="19" cy="12" r="1.2" fill="currentColor"/></symbol>
|