Scope lead notes by project and share them with task agents
This commit is contained in:
Vendored
+45
-21
@@ -7,6 +7,7 @@ const kinds = [
|
||||
{ key: "errors", label: "Session errors" },
|
||||
{ key: "rate_limits", label: "Rate limits" },
|
||||
];
|
||||
let notesProject = "";
|
||||
export function mountSettings(root) {
|
||||
const device = h("div", { class: "push-device" });
|
||||
const prefs = h("fieldset", { class: "push-prefs" });
|
||||
@@ -52,12 +53,51 @@ export function mountSettings(root) {
|
||||
replace(device, h("p", {}, "This browser doesn't show notifications."), button);
|
||||
};
|
||||
const notes = h("div", { class: "lead-notes" });
|
||||
let list;
|
||||
let loads = 0;
|
||||
const noteItem = (note) => {
|
||||
const remove = h("button", {}, "delete");
|
||||
remove.onclick = async () => {
|
||||
if (!(await showConfirm([h("p", {}, "Delete this note? Your lead stops applying it."), h("p", {}, note.text)], "Delete", true, remove))) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await del("/api/me/lead/notes/{noteId}", { path: { noteId: note.id } });
|
||||
}
|
||||
catch (err) {
|
||||
void showAlert(errorText(err));
|
||||
}
|
||||
};
|
||||
return h("li", {}, h("span", {}, note.text), remove);
|
||||
};
|
||||
const renderNotes = () => {
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
if (list.length === 0) {
|
||||
replace(notes, h("p", { class: "hint" }, "Your lead hasn't noted anything about how you work yet."));
|
||||
return;
|
||||
}
|
||||
const all = list;
|
||||
const scoped = (id) => all.filter((note) => (note.project_id ?? "") === id);
|
||||
const groups = [{ id: "", name: "Every project" }, ...state.projects.map((p) => ({ id: p.id, name: p.name }))];
|
||||
if (!groups.some((g) => g.id === notesProject)) {
|
||||
notesProject = "";
|
||||
}
|
||||
const picker = h("select", { title: "Which notes to show" }, ...groups.map((g) => h("option", { value: g.id }, `${g.name} (${scoped(g.id).length})`)));
|
||||
picker.value = notesProject;
|
||||
picker.onchange = () => {
|
||||
notesProject = picker.value;
|
||||
renderNotes();
|
||||
};
|
||||
const shown = scoped(notesProject);
|
||||
replace(notes, picker, shown.length === 0 ? h("p", { class: "hint" }, "No notes here yet.") : h("ul", {}, ...shown.map(noteItem)));
|
||||
};
|
||||
const loadNotes = async () => {
|
||||
const n = ++loads;
|
||||
let list;
|
||||
let loaded;
|
||||
try {
|
||||
list = await get("/api/me/lead/notes");
|
||||
loaded = await get("/api/me/lead/notes");
|
||||
}
|
||||
catch (err) {
|
||||
replace(notes, h("p", { class: "hint" }, errorText(err)));
|
||||
@@ -66,29 +106,13 @@ export function mountSettings(root) {
|
||||
if (n !== loads) {
|
||||
return;
|
||||
}
|
||||
if (list.length === 0) {
|
||||
replace(notes, h("p", { class: "hint" }, "Your lead hasn't noted anything about how you work yet."));
|
||||
return;
|
||||
}
|
||||
replace(notes, h("ul", {}, ...list.map((note) => {
|
||||
const remove = h("button", {}, "delete");
|
||||
remove.onclick = async () => {
|
||||
if (!(await showConfirm([h("p", {}, "Delete this note? Your lead stops applying it."), h("p", {}, note.text)], "Delete", true, remove))) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await del("/api/me/lead/notes/{noteId}", { path: { noteId: note.id } });
|
||||
}
|
||||
catch (err) {
|
||||
void showAlert(errorText(err));
|
||||
}
|
||||
};
|
||||
return h("li", {}, h("span", {}, note.text), remove);
|
||||
})));
|
||||
list = loaded;
|
||||
renderNotes();
|
||||
};
|
||||
replace(root, h("div", { class: "settings-view" }, h("h2", {}, "Notifications"), device, prefs, h("h2", {}, "What your lead has learned"), h("p", { class: "hint" }, "Your lead notes your priorities and preferences as it works with you, and starts every session with them."), notes));
|
||||
const offs = [
|
||||
on("me", renderPrefs),
|
||||
on("projects", renderNotes),
|
||||
on("event", (detail) => {
|
||||
const ev = detail;
|
||||
if (ev.kind === "lead.note_saved" || ev.kind === "lead.note_deleted") {
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -1433,6 +1433,8 @@ export interface components {
|
||||
LeadNote: {
|
||||
id: string;
|
||||
text: string;
|
||||
/** @description The project the note applies to; absent for a note that applies to every project. */
|
||||
project_id?: string;
|
||||
/** Format: date-time */
|
||||
created_at: string;
|
||||
/** Format: date-time */
|
||||
|
||||
+45
-27
@@ -9,6 +9,8 @@ const kinds: { key: keyof NotificationPrefs; label: string }[] = [
|
||||
{ key: "rate_limits", label: "Rate limits" },
|
||||
];
|
||||
|
||||
let notesProject = "";
|
||||
|
||||
export function mountSettings(root: HTMLElement): () => void {
|
||||
const device = h("div", { class: "push-device" });
|
||||
const prefs = h("fieldset", { class: "push-prefs" });
|
||||
@@ -61,12 +63,50 @@ export function mountSettings(root: HTMLElement): () => void {
|
||||
};
|
||||
|
||||
const notes = h("div", { class: "lead-notes" });
|
||||
let list: LeadNote[] | undefined;
|
||||
let loads = 0;
|
||||
const noteItem = (note: LeadNote) => {
|
||||
const remove = h("button", {}, "delete");
|
||||
remove.onclick = async () => {
|
||||
if (!(await showConfirm([h("p", {}, "Delete this note? Your lead stops applying it."), h("p", {}, note.text)], "Delete", true, remove))) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await del("/api/me/lead/notes/{noteId}", { path: { noteId: note.id } });
|
||||
} catch (err) {
|
||||
void showAlert(errorText(err));
|
||||
}
|
||||
};
|
||||
return h("li", {}, h("span", {}, note.text), remove);
|
||||
};
|
||||
const renderNotes = () => {
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
if (list.length === 0) {
|
||||
replace(notes, h("p", { class: "hint" }, "Your lead hasn't noted anything about how you work yet."));
|
||||
return;
|
||||
}
|
||||
const all = list;
|
||||
const scoped = (id: string) => all.filter((note) => (note.project_id ?? "") === id);
|
||||
const groups = [{ id: "", name: "Every project" }, ...state.projects.map((p) => ({ id: p.id, name: p.name }))];
|
||||
if (!groups.some((g) => g.id === notesProject)) {
|
||||
notesProject = "";
|
||||
}
|
||||
const picker = h("select", { title: "Which notes to show" }, ...groups.map((g) => h("option", { value: g.id }, `${g.name} (${scoped(g.id).length})`)));
|
||||
picker.value = notesProject;
|
||||
picker.onchange = () => {
|
||||
notesProject = picker.value;
|
||||
renderNotes();
|
||||
};
|
||||
const shown = scoped(notesProject);
|
||||
replace(notes, picker, shown.length === 0 ? h("p", { class: "hint" }, "No notes here yet.") : h("ul", {}, ...shown.map(noteItem)));
|
||||
};
|
||||
const loadNotes = async () => {
|
||||
const n = ++loads;
|
||||
let list: LeadNote[];
|
||||
let loaded: LeadNote[];
|
||||
try {
|
||||
list = await get("/api/me/lead/notes");
|
||||
loaded = await get("/api/me/lead/notes");
|
||||
} catch (err) {
|
||||
replace(notes, h("p", { class: "hint" }, errorText(err)));
|
||||
return;
|
||||
@@ -74,31 +114,8 @@ export function mountSettings(root: HTMLElement): () => void {
|
||||
if (n !== loads) {
|
||||
return;
|
||||
}
|
||||
if (list.length === 0) {
|
||||
replace(notes, h("p", { class: "hint" }, "Your lead hasn't noted anything about how you work yet."));
|
||||
return;
|
||||
}
|
||||
replace(
|
||||
notes,
|
||||
h(
|
||||
"ul",
|
||||
{},
|
||||
...list.map((note) => {
|
||||
const remove = h("button", {}, "delete");
|
||||
remove.onclick = async () => {
|
||||
if (!(await showConfirm([h("p", {}, "Delete this note? Your lead stops applying it."), h("p", {}, note.text)], "Delete", true, remove))) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await del("/api/me/lead/notes/{noteId}", { path: { noteId: note.id } });
|
||||
} catch (err) {
|
||||
void showAlert(errorText(err));
|
||||
}
|
||||
};
|
||||
return h("li", {}, h("span", {}, note.text), remove);
|
||||
}),
|
||||
),
|
||||
);
|
||||
list = loaded;
|
||||
renderNotes();
|
||||
};
|
||||
|
||||
replace(
|
||||
@@ -116,6 +133,7 @@ export function mountSettings(root: HTMLElement): () => void {
|
||||
);
|
||||
const offs = [
|
||||
on("me", renderPrefs),
|
||||
on("projects", renderNotes),
|
||||
on("event", (detail) => {
|
||||
const ev = detail as ByreEvent;
|
||||
if (ev.kind === "lead.note_saved" || ev.kind === "lead.note_deleted") {
|
||||
|
||||
+5
-10
@@ -1040,7 +1040,8 @@ button.compact.danger {
|
||||
.snap-row button,
|
||||
.todo-link .todo-action button,
|
||||
.finding-actions button,
|
||||
.note button.merge-after-review-cancel {
|
||||
.note button.merge-after-review-cancel,
|
||||
.lead-notes button {
|
||||
padding: 0 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
@@ -2418,15 +2419,9 @@ button.danger-primary {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.lead-notes button {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.lead-notes button:hover {
|
||||
text-decoration: underline;
|
||||
.lead-notes select {
|
||||
width: auto;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.fatal {
|
||||
|
||||
Reference in New Issue
Block a user