Give every task a bot: drop shared workspace tasks

A workspace task with no bot was seen by every bot working there. Now a
task can't be saved without a bot, a worker reaches only its own (the
workspace admin and the coordinator still reach all), the workspace page
picks who has a new task, and a bot leaving a workspace or deleted passes
its tasks there to the workspace's admin bot, else the coordinator (also
at boot for rows without a bot).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-28 06:26:44 +01:00
co-authored by Claude Opus 5.5
parent 3e78d51b56
commit 623112fd19
30 changed files with 239 additions and 106 deletions
@@ -15,7 +15,7 @@ type Params = {
/**
* A conversation's tasks: a bot's own list, or in a workspace the ones it
* has there plus the shared ones nobody has yet. Loaded on open and refreshed after every change,
* has there. Loaded on open and refreshed after every change,
* never part of the page props.
*/
export default function useBotTasks({ bot_id, workspace_id, enabled = true }: Params) {
@@ -31,7 +31,7 @@ type Props = {
/**
* The tasks this conversation works on: a bot's own list, or in a workspace
* the ones it has there plus the shared ones nobody has yet. Open it from the header menu; the list loads
* the ones it has there. Open it from the header menu; the list loads
* then, so the page's props stay small. A crew manager adds, moves, edits
* and deletes; a card opens the task in a modal of its own with everything
* about it.
@@ -157,7 +157,7 @@ export default function BotTasksModal({ bot, workspace, menu, on_open }: Props)
</Row>
<Span className="text-[13px] leading-[18px] text-ink-secondary">
{workspace?.id
? `What ${name} has in ${workspace.name || "this workspace"}, and the shared tasks there nobody has taken up yet. The workspace shows every bot's.`
? `What ${name} has in ${workspace.name || "this workspace"}. The workspace shows every bot's.`
: `What ${name} is working through, and what it's waiting on.`}
</Span>
</Stack>
@@ -22,14 +22,19 @@ type Props = {
* Its tasks, open first.
*/
tasks: TaskWithNames[];
/**
* The bots working there, to pick who has a new task.
*/
bots: { id?: number | string | null; name?: string | null }[];
};
/**
* Every task in a workspace, whichever bot has it (or shared, nobody's
* yet). Each bot there sees its own and the shared ones. Add and change one by hand here; the bots
* Every task in a workspace, whichever bot has it: every task belongs to
* one bot, which sees only its own (the workspace's admin bot sees all).
* Add and change one by hand here; the bots
* keep it up to date themselves as they work.
*/
export default function WorkspaceTasksGroup({ workspace_id, tasks }: Props) {
export default function WorkspaceTasksGroup({ workspace_id, tasks, bots }: Props) {
const { pageProps } = useContext(AppContext);
const [items, setItems] = useState(tasks);
const [busy, setBusy] = useState(false);
@@ -89,18 +94,19 @@ export default function WorkspaceTasksGroup({ workspace_id, tasks }: Props) {
<SettingsGroup
id="tasks"
title="Tasks"
description="Every task in this workspace, whichever bot has it. A bot sees its own here and the shared ones nobody has taken up yet."
description="Every task in this workspace, whichever bot has it. Each bot sees only its own; the workspace's admin bot and the coordinator see them all."
>
<Stack className="w-full gap-2! items-stretch">
{can_manage ? (
<Row className="justify-end">
<Button
title="Add a task"
title={bots.length ? "Add a task" : "Add a bot to this workspace first"}
type="button"
variant="outlined"
color="gray"
size="small"
onClick={() => setAdding(true)}
disabled={!bots.length}
>
Add task
</Button>
@@ -138,10 +144,11 @@ export default function WorkspaceTasksGroup({ workspace_id, tasks }: Props) {
open={adding}
setOpen={setAdding}
title="Add a task"
description="It's added shared: whichever bot here takes it up has it. Hand it over to give it to one."
description="Pick the bot that has it. Hand it over later to give it to another."
>
<TaskForm
workspace_id={workspace_id}
bots={bots}
onCancel={() => setAdding(false)}
onSaved={(task, msg) => {
replace({ task });
@@ -42,6 +42,7 @@ export default function WorkspaceDashboard() {
<WorkspaceTasksGroup
workspace_id={workspace_id}
tasks={pageProps?.workspace_tasks || []}
bots={pageProps?.workspace_bots || []}
/>
<WorkspaceActivityGroup
workspace_id={workspace_id}