buttons
This commit is contained in:
Vendored
+25
-9
@@ -87,15 +87,32 @@ export function mountTranscript(root) {
|
||||
}
|
||||
const sess = currentSession(task.id);
|
||||
if (task.status !== "active") {
|
||||
replace(head, h("div", { class: "meta" }, h("code", {}, task.branch)), h("div", { class: "head-actions" }, task.status));
|
||||
replace(head, h("div", { class: "head-actions" }, task.status));
|
||||
return;
|
||||
}
|
||||
const target = state.projects.find((p) => p.id === task.project_id)?.default_branch ?? "the default branch";
|
||||
const merge = h("button", { title: `Squash into one commit on ${target}; nothing is pushed` }, "Merge");
|
||||
merge.onclick = () => closeTask(task, (u) => `Merge "${task.title}" into ${target}? ${changesText(u)} become one commit on ${target}. Nothing is pushed.`, "/api/tasks/{taskId}/merge");
|
||||
const discard = h("button", { class: "danger", title: "Delete the worktree and branch without merging" }, "Discard");
|
||||
discard.onclick = () => closeTask(task, (u) => `Discard "${task.title}"? ${changesText(u)} that aren't on ${target} will be deleted. This can't be undone.`, "/api/tasks/{taskId}/discard");
|
||||
replace(head, h("div", { class: "meta" }, h("code", {}, task.branch)), h("div", { class: "head-actions" }, sess && contextMeter(context.get(sess.id)), sess && compactButton(sess.id, sess.state), merge, discard));
|
||||
const busy = sess?.state === "running" || sess?.state === "queued";
|
||||
const merge = h("button", { disabled: busy, title: `The agent merges ${target} in, then byre squashes into one commit on ${target}; nothing is pushed` }, "Merge");
|
||||
merge.onclick = () => confirmChanges(task, (u) => `Merge "${task.title}" into ${target}? The agent first merges ${target} in and resolves any conflicts; when that turn ends, ${changesText(u)} become one commit on ${target} and the task closes. Nothing is pushed.`, async () => {
|
||||
await post("/api/tasks/{taskId}/merge", { path: { taskId: task.id } });
|
||||
});
|
||||
const mergeIn = h("button", { disabled: busy, title: `Ask the agent to merge ${target} into this branch and resolve any conflicts` }, `Merge ${target} in`);
|
||||
mergeIn.onclick = async () => {
|
||||
mergeIn.disabled = true;
|
||||
try {
|
||||
await post("/api/tasks/{taskId}/merge-in", { path: { taskId: task.id } });
|
||||
}
|
||||
catch (err) {
|
||||
alert(errorText(err));
|
||||
mergeIn.disabled = false;
|
||||
}
|
||||
};
|
||||
const discard = h("button", { class: "danger", disabled: busy, title: "Delete the worktree and branch without merging" }, "Discard");
|
||||
discard.onclick = () => confirmChanges(task, (u) => `Discard "${task.title}"? ${changesText(u)} that aren't on ${target} will be deleted. This can't be undone.`, async () => {
|
||||
await post("/api/tasks/{taskId}/discard", { path: { taskId: task.id } });
|
||||
location.hash = "#/";
|
||||
});
|
||||
replace(head, h("div", { class: "head-actions session-actions" }, sess && contextMeter(context.get(sess.id)), sess && compactButton(sess.id, sess.state), mergeIn), h("div", { class: "head-actions" }, merge, discard));
|
||||
};
|
||||
let composerModel = "";
|
||||
const renderComposer = () => {
|
||||
@@ -610,14 +627,13 @@ function messageImages(ids) {
|
||||
return h("a", { href: src, target: "_blank", rel: "noopener" }, h("img", { src, alt: "attached image", loading: "lazy" }));
|
||||
}));
|
||||
}
|
||||
async function closeTask(task, prompt, path) {
|
||||
async function confirmChanges(task, prompt, act) {
|
||||
try {
|
||||
const u = await get("/api/tasks/{taskId}/unmerged", { path: { taskId: task.id } });
|
||||
if (!confirm(prompt(u))) {
|
||||
return;
|
||||
}
|
||||
await post(path, { path: { taskId: task.id } });
|
||||
location.hash = "#/";
|
||||
await act();
|
||||
}
|
||||
catch (err) {
|
||||
alert(errorText(err));
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+48
-4
@@ -121,7 +121,7 @@ export interface paths {
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** @description Squashes the task's worktree, uncommitted changes included, into one commit on the project's default branch, fast-forwards the project's checkout to it, then removes the worktree and branch. Nothing is pushed. Refused while a turn is running or queued, and on merge conflicts. */
|
||||
/** @description Starts the same agent turn as merge-in. When that turn ends normally, byre squashes the task's worktree into one commit on the project's default branch, fast-forwards the project's checkout to it, then removes the worktree and branch and marks the task merged. Nothing is pushed. Refused while a turn is running or queued. */
|
||||
post: operations["mergeTask"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
@@ -129,6 +129,25 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/tasks/{taskId}/merge-in": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
taskId: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** @description Asks the task's agent to merge the project's default branch into the task branch, resolving any conflicts and regenerating generated files. Sent to the task's idle session, or starts a session when it has none. Refused while a turn is running or queued. */
|
||||
post: operations["mergeIn"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/tasks/{taskId}/discard": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -893,13 +912,38 @@ export interface operations {
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description The merged task. */
|
||||
200: {
|
||||
/** @description The merge turn was started or queued. */
|
||||
202: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["Task"];
|
||||
"application/json": components["schemas"]["AgentSession"];
|
||||
};
|
||||
};
|
||||
400: components["responses"]["Error"];
|
||||
404: components["responses"]["Error"];
|
||||
409: components["responses"]["Error"];
|
||||
};
|
||||
};
|
||||
mergeIn: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
taskId: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description The merge turn was started or queued. */
|
||||
202: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["AgentSession"];
|
||||
};
|
||||
};
|
||||
400: components["responses"]["Error"];
|
||||
|
||||
+34
-10
@@ -111,20 +111,45 @@ export function mountTranscript(root: HTMLElement): () => void {
|
||||
}
|
||||
const sess = currentSession(task.id);
|
||||
if (task.status !== "active") {
|
||||
replace(head, h("div", { class: "meta" }, h("code", {}, task.branch)), h("div", { class: "head-actions" }, task.status));
|
||||
replace(head, h("div", { class: "head-actions" }, task.status));
|
||||
return;
|
||||
}
|
||||
const target = state.projects.find((p) => p.id === task.project_id)?.default_branch ?? "the default branch";
|
||||
const merge = h("button", { title: `Squash into one commit on ${target}; nothing is pushed` }, "Merge");
|
||||
const busy = sess?.state === "running" || sess?.state === "queued";
|
||||
const merge = h(
|
||||
"button",
|
||||
{ disabled: busy, title: `The agent merges ${target} in, then byre squashes into one commit on ${target}; nothing is pushed` },
|
||||
"Merge",
|
||||
);
|
||||
merge.onclick = () =>
|
||||
closeTask(task, (u) => `Merge "${task.title}" into ${target}? ${changesText(u)} become one commit on ${target}. Nothing is pushed.`, "/api/tasks/{taskId}/merge");
|
||||
const discard = h("button", { class: "danger", title: "Delete the worktree and branch without merging" }, "Discard");
|
||||
confirmChanges(
|
||||
task,
|
||||
(u) =>
|
||||
`Merge "${task.title}" into ${target}? The agent first merges ${target} in and resolves any conflicts; when that turn ends, ${changesText(u)} become one commit on ${target} and the task closes. Nothing is pushed.`,
|
||||
async () => {
|
||||
await post("/api/tasks/{taskId}/merge", { path: { taskId: task.id } });
|
||||
},
|
||||
);
|
||||
const mergeIn = h("button", { disabled: busy, title: `Ask the agent to merge ${target} into this branch and resolve any conflicts` }, `Merge ${target} in`);
|
||||
mergeIn.onclick = async () => {
|
||||
mergeIn.disabled = true;
|
||||
try {
|
||||
await post("/api/tasks/{taskId}/merge-in", { path: { taskId: task.id } });
|
||||
} catch (err) {
|
||||
alert(errorText(err));
|
||||
mergeIn.disabled = false;
|
||||
}
|
||||
};
|
||||
const discard = h("button", { class: "danger", disabled: busy, title: "Delete the worktree and branch without merging" }, "Discard");
|
||||
discard.onclick = () =>
|
||||
closeTask(task, (u) => `Discard "${task.title}"? ${changesText(u)} that aren't on ${target} will be deleted. This can't be undone.`, "/api/tasks/{taskId}/discard");
|
||||
confirmChanges(task, (u) => `Discard "${task.title}"? ${changesText(u)} that aren't on ${target} will be deleted. This can't be undone.`, async () => {
|
||||
await post("/api/tasks/{taskId}/discard", { path: { taskId: task.id } });
|
||||
location.hash = "#/";
|
||||
});
|
||||
replace(
|
||||
head,
|
||||
h("div", { class: "meta" }, h("code", {}, task.branch)),
|
||||
h("div", { class: "head-actions" }, sess && contextMeter(context.get(sess.id)), sess && compactButton(sess.id, sess.state), merge, discard),
|
||||
h("div", { class: "head-actions session-actions" }, sess && contextMeter(context.get(sess.id)), sess && compactButton(sess.id, sess.state), mergeIn),
|
||||
h("div", { class: "head-actions" }, merge, discard),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -706,14 +731,13 @@ function messageImages(ids: unknown): HTMLElement | null {
|
||||
);
|
||||
}
|
||||
|
||||
async function closeTask(task: Task, prompt: (u: Unmerged) => string, path: "/api/tasks/{taskId}/merge" | "/api/tasks/{taskId}/discard") {
|
||||
async function confirmChanges(task: Task, prompt: (u: Unmerged) => string, act: () => Promise<void>) {
|
||||
try {
|
||||
const u = await get("/api/tasks/{taskId}/unmerged", { path: { taskId: task.id } });
|
||||
if (!confirm(prompt(u))) {
|
||||
return;
|
||||
}
|
||||
await post(path, { path: { taskId: task.id } });
|
||||
location.hash = "#/";
|
||||
await act();
|
||||
} catch (err) {
|
||||
alert(errorText(err));
|
||||
}
|
||||
|
||||
+4
-14
@@ -432,20 +432,6 @@ textarea {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
font-size: 11px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.meta code {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.head-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
@@ -453,6 +439,10 @@ textarea {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.session-actions {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.context-meter {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user