@fayz-ai/plugin-tasks 0.1.7 → 0.8.3

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.
Files changed (37) hide show
  1. package/README.md +2 -0
  2. package/dist/components/TaskDetail.d.ts.map +1 -1
  3. package/dist/components/TasksDrawer.d.ts +1 -0
  4. package/dist/components/TasksDrawer.d.ts.map +1 -1
  5. package/dist/components/TasksGeneralSettings.d.ts.map +1 -1
  6. package/dist/components/TasksPanel.d.ts +7 -0
  7. package/dist/components/TasksPanel.d.ts.map +1 -0
  8. package/dist/components/TasksTopbarButton.d.ts.map +1 -1
  9. package/dist/data/supabase.d.ts.map +1 -1
  10. package/dist/data/tables.d.ts +5 -0
  11. package/dist/data/tables.d.ts.map +1 -0
  12. package/dist/index.cjs +317 -162
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +320 -165
  16. package/dist/index.js.map +1 -1
  17. package/dist/locales/en.d.ts.map +1 -1
  18. package/dist/locales/pt-BR.d.ts.map +1 -1
  19. package/dist/migrations/index.d.ts +7 -0
  20. package/dist/migrations/index.d.ts.map +1 -0
  21. package/dist/registries.d.ts.map +1 -1
  22. package/package.json +10 -4
  23. package/src/components/TaskDetail.tsx +71 -46
  24. package/src/components/TasksDrawer.tsx +5 -67
  25. package/src/components/TasksGeneralSettings.tsx +14 -2
  26. package/src/components/TasksPanel.tsx +94 -0
  27. package/src/components/TasksTopbarButton.tsx +49 -4
  28. package/src/data/capability.test.ts +1 -1
  29. package/src/data/supabase.ts +11 -10
  30. package/src/data/tables.ts +6 -0
  31. package/src/index.ts +41 -56
  32. package/src/locales/en.ts +2 -0
  33. package/src/locales/pt-BR.ts +2 -0
  34. package/src/migrations/000_plg_rename.sql +12 -0
  35. package/src/migrations/001_tasks_base.sql +34 -34
  36. package/src/migrations/index.ts +89 -0
  37. package/src/registries.ts +2 -1
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import React4, { useEffect, useState, useCallback } from 'react';
2
- import { createPluginContext, dedup } from '@fayz-ai/saas';
2
+ import { createPluginContext, registerNotesTasksAdapter, NOTE_LABEL_PREFIX, dedup, useRightRailStore, useRightRailPanel, useTenantPluginSettings, useDelegateToAssistant } from '@fayz-ai/saas';
3
3
  import { registerTranslations, createSafeDataProvider, useTranslation, getActiveTenantId, getSupabaseClientOptional } from '@fayz-ai/core';
4
4
  import { createStore } from 'zustand/vanilla';
5
- import { jsxs, jsx } from 'react/jsx-runtime';
6
- import { CheckSquare, ClipboardList, Plus, ArrowLeft, ChevronDown, Calendar, Trash2, ListChecks, ChevronRight, AlertTriangle, ArrowUp, Minus, ArrowDown } from 'lucide-react';
5
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
+ import { CheckSquare, ClipboardList, Plus, ArrowLeft, Trash2, ChevronDown, Calendar, Sparkles, ListChecks, ChevronRight, AlertTriangle, ArrowUp, Minus, ArrowDown } from 'lucide-react';
7
7
  import { Button, cn, Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetBody, Checkbox } from '@fayz-ai/ui';
8
8
  import { useStore } from 'zustand';
9
9
 
@@ -146,6 +146,12 @@ function createMockTasksProvider() {
146
146
  };
147
147
  }
148
148
 
149
+ // src/data/tables.ts
150
+ var T = {
151
+ tasks: "plg_tasks_tasks",
152
+ labels: "plg_tasks_labels"
153
+ };
154
+
149
155
  // src/data/supabase.ts
150
156
  function getTenantId() {
151
157
  return getActiveTenantId();
@@ -172,7 +178,7 @@ function createSupabaseTasksProvider() {
172
178
  async getTasks(query) {
173
179
  const tenantId = getTenantId();
174
180
  if (!tenantId) return [];
175
- let q = getClient().from("tsk_tasks").select("*").eq("tenant_id", tenantId);
181
+ let q = getClient().from(T.tasks).select("*").eq("tenant_id", tenantId);
176
182
  if (query.parentId === null) {
177
183
  q = q.is("parent_id", null);
178
184
  } else if (query.parentId) {
@@ -200,19 +206,19 @@ function createSupabaseTasksProvider() {
200
206
  return (data ?? []).map(toTask);
201
207
  },
202
208
  async getTaskById(id) {
203
- const { data } = await getClient().from("tsk_tasks").select("*").eq("id", id).maybeSingle();
209
+ const { data } = await getClient().from(T.tasks).select("*").eq("id", id).maybeSingle();
204
210
  return data ? toTask(data) : null;
205
211
  },
206
212
  async getSubtasks(parentId) {
207
- const { data } = await getClient().from("tsk_tasks").select("*").eq("parent_id", parentId).order("position", { ascending: true });
213
+ const { data } = await getClient().from(T.tasks).select("*").eq("parent_id", parentId).order("position", { ascending: true });
208
214
  return (data ?? []).map(toTask);
209
215
  },
210
216
  async createTask(input) {
211
217
  const tenantId = getTenantId();
212
218
  if (!tenantId) throw new Error("No active tenant");
213
- const { data: maxData } = await getClient().from("tsk_tasks").select("position").eq("tenant_id", tenantId).eq("status", input.status ?? "todo").is("parent_id", input.parentId ?? null).order("position", { ascending: false }).limit(1);
219
+ const { data: maxData } = await getClient().from(T.tasks).select("position").eq("tenant_id", tenantId).eq("status", input.status ?? "todo").is("parent_id", input.parentId ?? null).order("position", { ascending: false }).limit(1);
214
220
  const maxPos = maxData?.[0]?.position ?? -1;
215
- const { data, error } = await getClient().from("tsk_tasks").insert({
221
+ const { data, error } = await getClient().from(T.tasks).insert({
216
222
  tenant_id: tenantId,
217
223
  title: input.title,
218
224
  description: input.description ?? null,
@@ -237,26 +243,26 @@ function createSupabaseTasksProvider() {
237
243
  if (input.assignedToId !== void 0) payload.assigned_to_id = input.assignedToId;
238
244
  if (input.labels !== void 0) payload.labels = input.labels;
239
245
  if (input.position !== void 0) payload.position = input.position;
240
- const { data, error } = await getClient().from("tsk_tasks").update(payload).eq("id", id).select("*").single();
246
+ const { data, error } = await getClient().from(T.tasks).update(payload).eq("id", id).select("*").single();
241
247
  if (error) throw error;
242
248
  return toTask(data);
243
249
  },
244
250
  async deleteTask(id) {
245
- await getClient().from("tsk_tasks").delete().eq("id", id);
251
+ await getClient().from(T.tasks).delete().eq("id", id);
246
252
  },
247
253
  async reorderTask(id, newPosition, status) {
248
- await getClient().from("tsk_tasks").update({ position: newPosition, status, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
254
+ await getClient().from(T.tasks).update({ position: newPosition, status, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
249
255
  },
250
256
  async getLabels() {
251
257
  const tenantId = getTenantId();
252
258
  if (!tenantId) return [];
253
- const { data } = await getClient().from("tsk_labels").select("*").eq("tenant_id", tenantId).eq("is_active", true).order("name");
259
+ const { data } = await getClient().from(T.labels).select("*").eq("tenant_id", tenantId).eq("is_active", true).order("name");
254
260
  return (data ?? []).map((r) => snakeToCamel(r));
255
261
  },
256
262
  async getSummary() {
257
263
  const tenantId = getTenantId();
258
264
  if (!tenantId) return { total: 0, todo: 0, inProgress: 0, done: 0, overdue: 0, dueToday: 0, highPriority: 0 };
259
- const { data } = await getClient().from("tsk_tasks").select("status, priority, due_date").eq("tenant_id", tenantId).is("parent_id", null);
265
+ const { data } = await getClient().from(T.tasks).select("status, priority, due_date").eq("tenant_id", tenantId).is("parent_id", null);
260
266
  const tasks = data ?? [];
261
267
  const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
262
268
  return {
@@ -394,6 +400,8 @@ var en = {
394
400
  "tasks.detail.delete": "Delete Task",
395
401
  "tasks.detail.deleteConfirm": "Are you sure you want to delete this task?",
396
402
  "tasks.detail.created": "Created",
403
+ "tasks.detail.chatWithAi": "Chat with AI",
404
+ "tasks.detail.chatWithAi.prompt": "Take over this task and help me resolve it now, using your tools when needed:",
397
405
  "tasks.detail.updated": "Updated",
398
406
  "tasks.detail.addSubtask": "Add subtask...",
399
407
  "tasks.detail.back": "Back to list",
@@ -436,6 +444,8 @@ var ptBR = {
436
444
  "tasks.detail.delete": "Excluir Tarefa",
437
445
  "tasks.detail.deleteConfirm": "Tem certeza que deseja excluir esta tarefa?",
438
446
  "tasks.detail.created": "Criada em",
447
+ "tasks.detail.chatWithAi": "Conversar com IA",
448
+ "tasks.detail.chatWithAi.prompt": "Assuma esta tarefa e me ajude a resolver agora, usando suas ferramentas quando precisar:",
439
449
  "tasks.detail.updated": "Atualizada em",
440
450
  "tasks.detail.addSubtask": "Adicionar subtarefa...",
441
451
  "tasks.detail.back": "Voltar \xE0 lista",
@@ -467,7 +477,7 @@ var taskLabelEntity = {
467
477
  { key: "color", label: "Color", type: "text", showInTable: true, defaultValue: "#6366f1" },
468
478
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
469
479
  ],
470
- data: { table: "tsk_labels", tenantScoped: true }
480
+ data: { table: T.labels, tenantScoped: true }
471
481
  };
472
482
  var tasksRegistries = [
473
483
  {
@@ -484,8 +494,12 @@ var tasksRegistries = [
484
494
  ]
485
495
  }
486
496
  ];
497
+ var TASKS_DEFAULTS = {
498
+ defaultPriority: "medium"
499
+ };
487
500
  function TasksGeneralSettings() {
488
501
  const t = useTranslation();
502
+ const s = useTenantPluginSettings("tasks", TASKS_DEFAULTS);
489
503
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
490
504
  /* @__PURE__ */ jsxs("div", { children: [
491
505
  /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold mb-1", children: t("tasks.settings.general") }),
@@ -493,12 +507,20 @@ function TasksGeneralSettings() {
493
507
  ] }),
494
508
  /* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
495
509
  /* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: t("tasks.settings.defaultPriority") }),
496
- /* @__PURE__ */ jsxs("select", { className: "w-full max-w-xs rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm", children: [
497
- /* @__PURE__ */ jsx("option", { value: "low", children: t("tasks.priority.low") }),
498
- /* @__PURE__ */ jsx("option", { value: "medium", selected: true, children: t("tasks.priority.medium") }),
499
- /* @__PURE__ */ jsx("option", { value: "high", children: t("tasks.priority.high") }),
500
- /* @__PURE__ */ jsx("option", { value: "urgent", children: t("tasks.priority.urgent") })
501
- ] })
510
+ /* @__PURE__ */ jsxs(
511
+ "select",
512
+ {
513
+ className: "w-full max-w-xs rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm",
514
+ value: s.get("defaultPriority"),
515
+ onChange: (e) => s.set("defaultPriority", e.target.value),
516
+ children: [
517
+ /* @__PURE__ */ jsx("option", { value: "low", children: t("tasks.priority.low") }),
518
+ /* @__PURE__ */ jsx("option", { value: "medium", children: t("tasks.priority.medium") }),
519
+ /* @__PURE__ */ jsx("option", { value: "high", children: t("tasks.priority.high") }),
520
+ /* @__PURE__ */ jsx("option", { value: "urgent", children: t("tasks.priority.urgent") })
521
+ ]
522
+ }
523
+ )
502
524
  ] }) })
503
525
  ] });
504
526
  }
@@ -718,8 +740,10 @@ var STATUS_DOT = {
718
740
  done: "bg-success",
719
741
  cancelled: "bg-muted-foreground/60"
720
742
  };
721
- function formatDate(iso) {
722
- return new Date(iso).toLocaleDateString(void 0, { month: "short", day: "numeric" });
743
+ function formatDateTime(iso) {
744
+ const d = new Date(iso);
745
+ const sameDay = d.toDateString() === (/* @__PURE__ */ new Date()).toDateString();
746
+ return sameDay ? d.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" }) : d.toLocaleDateString(void 0, { day: "2-digit", month: "2-digit", year: "2-digit" });
723
747
  }
724
748
  function TaskDetail({ taskId }) {
725
749
  const t = useTranslation();
@@ -728,6 +752,7 @@ function TaskDetail({ taskId }) {
728
752
  const updateTask = useTasksStore((s) => s.updateTask);
729
753
  const deleteTask = useTasksStore((s) => s.deleteTask);
730
754
  const task = tasks.find((tt) => tt.id === taskId);
755
+ const delegate = useDelegateToAssistant();
731
756
  const [title, setTitle] = useState("");
732
757
  const [description, setDescription] = useState("");
733
758
  const [subtasks, setSubtasks] = useState([]);
@@ -761,17 +786,36 @@ function TaskDetail({ taskId }) {
761
786
  /* @__PURE__ */ jsx("button", { type: "button", onClick: () => selectTask(null), className: "mt-2 text-xs text-primary hover:underline", children: t("tasks.detail.back") })
762
787
  ] });
763
788
  }
764
- return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
765
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
766
- /* @__PURE__ */ jsx(
767
- "button",
768
- {
769
- type: "button",
770
- onClick: () => selectTask(null),
771
- className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground transition-colors",
772
- children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" })
773
- }
774
- ),
789
+ return (
790
+ // Same frame as the Notes editor: header row with back + icon actions,
791
+ // full-bleed px-4 content, sectioned by hairlines, thin timestamp footer.
792
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-full flex-col", children: [
793
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 border-b border-border/40 px-2 py-1.5", children: [
794
+ /* @__PURE__ */ jsxs(
795
+ "button",
796
+ {
797
+ type: "button",
798
+ onClick: () => selectTask(null),
799
+ className: "inline-flex items-center gap-1 rounded-lg px-1.5 py-1 text-[11.5px] text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
800
+ children: [
801
+ /* @__PURE__ */ jsx(ArrowLeft, { className: "h-3.5 w-3.5" }),
802
+ t("tasks.drawer.title")
803
+ ]
804
+ }
805
+ ),
806
+ /* @__PURE__ */ jsx("span", { className: "flex-1" }),
807
+ /* @__PURE__ */ jsx(
808
+ "button",
809
+ {
810
+ type: "button",
811
+ onClick: handleDelete,
812
+ "aria-label": t("tasks.detail.delete"),
813
+ title: t("tasks.detail.delete"),
814
+ className: "inline-flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-destructive",
815
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
816
+ }
817
+ )
818
+ ] }),
775
819
  /* @__PURE__ */ jsx(
776
820
  "input",
777
821
  {
@@ -782,101 +826,112 @@ function TaskDetail({ taskId }) {
782
826
  onKeyDown: (e) => {
783
827
  if (e.key === "Enter") e.currentTarget.blur();
784
828
  },
785
- className: "flex-1 bg-transparent text-sm font-semibold outline-none"
829
+ className: "bg-transparent px-4 pt-3 text-sm font-semibold text-foreground outline-none"
786
830
  }
787
- )
788
- ] }),
789
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [
790
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
791
- /* @__PURE__ */ jsx(
792
- "select",
793
- {
794
- value: task.status,
795
- onChange: (e) => updateTask(task.id, { status: e.target.value }),
796
- className: "appearance-none rounded-full border bg-background pl-5 pr-6 py-1 text-xs font-medium cursor-pointer hover:border-primary/40 transition-colors",
797
- children: TASK_STATUSES.map((s) => /* @__PURE__ */ jsx("option", { value: s, children: t(`tasks.status.${s}`) }, s))
798
- }
799
- ),
800
- /* @__PURE__ */ jsx("span", { className: `absolute left-2 top-1/2 -translate-y-1/2 h-2 w-2 rounded-full ${STATUS_DOT[task.status]}` }),
801
- /* @__PURE__ */ jsx(ChevronDown, { className: "absolute right-1.5 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground pointer-events-none" })
802
- ] }),
803
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
804
- /* @__PURE__ */ jsx(
805
- "select",
806
- {
807
- value: task.priority,
808
- onChange: (e) => updateTask(task.id, { priority: e.target.value }),
809
- className: "appearance-none rounded-full border bg-background pl-2 pr-6 py-1 text-xs font-medium cursor-pointer hover:border-primary/40 transition-colors",
810
- children: TASK_PRIORITIES.map((p) => /* @__PURE__ */ jsx("option", { value: p, children: t(`tasks.priority.${p}`) }, p))
811
- }
812
- ),
813
- /* @__PURE__ */ jsx(ChevronDown, { className: "absolute right-1.5 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground pointer-events-none" })
814
- ] }),
815
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
816
- /* @__PURE__ */ jsx(
817
- "input",
818
- {
819
- type: "date",
820
- value: task.dueDate ?? "",
821
- onChange: (e) => updateTask(task.id, { dueDate: e.target.value || null }),
822
- className: "appearance-none rounded-full border bg-background pl-6 pr-2 py-1 text-xs font-medium cursor-pointer hover:border-primary/40 transition-colors w-[120px]"
823
- }
824
- ),
825
- /* @__PURE__ */ jsx(Calendar, { className: "absolute left-2 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground pointer-events-none" })
826
- ] }),
827
- task.labels.length > 0 && /* @__PURE__ */ jsx(TaskLabelBadges, { labelIds: task.labels, max: 3 })
828
- ] }),
829
- /* @__PURE__ */ jsx(
830
- "textarea",
831
- {
832
- value: description,
833
- onChange: (e) => setDescription(e.target.value),
834
- onBlur: handleDescriptionBlur,
835
- placeholder: t("tasks.detail.descriptionPlaceholder"),
836
- rows: 3,
837
- className: "w-full rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-xs outline-none resize-none placeholder:text-muted-foreground/50 leading-relaxed"
838
- }
839
- ),
840
- /* @__PURE__ */ jsxs("div", { children: [
841
- /* @__PURE__ */ jsx("p", { className: "text-[10px] font-semibold text-muted-foreground uppercase tracking-wider mb-1", children: t("tasks.detail.subtasks") }),
842
- /* @__PURE__ */ jsxs("div", { className: "rounded-md border overflow-hidden", children: [
843
- /* @__PURE__ */ jsx(TaskQuickAdd, { parentId: task.id }),
844
- subtasks.length > 0 && /* @__PURE__ */ jsx("div", { className: "divide-y", children: subtasks.map((st) => /* @__PURE__ */ jsx(TaskCard, { task: st }, st.id)) })
845
- ] })
846
- ] }),
847
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pt-2 border-t", children: [
848
- /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
849
- formatDate(task.createdAt),
850
- " \xB7 ",
851
- t("tasks.detail.updated"),
852
- " ",
853
- formatDate(task.updatedAt)
831
+ ),
832
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5 px-4 pt-2", children: [
833
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
834
+ /* @__PURE__ */ jsx(
835
+ "select",
836
+ {
837
+ value: task.status,
838
+ onChange: (e) => updateTask(task.id, { status: e.target.value }),
839
+ className: "appearance-none rounded-full border bg-background pl-5 pr-6 py-1 text-xs font-medium cursor-pointer hover:border-primary/40 transition-colors",
840
+ children: TASK_STATUSES.map((s) => /* @__PURE__ */ jsx("option", { value: s, children: t(`tasks.status.${s}`) }, s))
841
+ }
842
+ ),
843
+ /* @__PURE__ */ jsx("span", { className: `absolute left-2 top-1/2 -translate-y-1/2 h-2 w-2 rounded-full ${STATUS_DOT[task.status]}` }),
844
+ /* @__PURE__ */ jsx(ChevronDown, { className: "absolute right-1.5 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground pointer-events-none" })
845
+ ] }),
846
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
847
+ /* @__PURE__ */ jsx(
848
+ "select",
849
+ {
850
+ value: task.priority,
851
+ onChange: (e) => updateTask(task.id, { priority: e.target.value }),
852
+ className: "appearance-none rounded-full border bg-background pl-2 pr-6 py-1 text-xs font-medium cursor-pointer hover:border-primary/40 transition-colors",
853
+ children: TASK_PRIORITIES.map((p) => /* @__PURE__ */ jsx("option", { value: p, children: t(`tasks.priority.${p}`) }, p))
854
+ }
855
+ ),
856
+ /* @__PURE__ */ jsx(ChevronDown, { className: "absolute right-1.5 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground pointer-events-none" })
857
+ ] }),
858
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
859
+ /* @__PURE__ */ jsx(
860
+ "input",
861
+ {
862
+ type: "date",
863
+ value: task.dueDate ?? "",
864
+ onChange: (e) => updateTask(task.id, { dueDate: e.target.value || null }),
865
+ className: "appearance-none rounded-full border bg-background pl-6 pr-2 py-1 text-xs font-medium cursor-pointer hover:border-primary/40 transition-colors w-[120px]"
866
+ }
867
+ ),
868
+ /* @__PURE__ */ jsx(Calendar, { className: "absolute left-2 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground pointer-events-none" })
869
+ ] }),
870
+ task.labels.length > 0 && /* @__PURE__ */ jsx(TaskLabelBadges, { labelIds: task.labels, max: 3 })
854
871
  ] }),
855
- /* @__PURE__ */ jsxs(
872
+ /* @__PURE__ */ jsx(
873
+ "textarea",
874
+ {
875
+ value: description,
876
+ onChange: (e) => setDescription(e.target.value),
877
+ onBlur: handleDescriptionBlur,
878
+ placeholder: t("tasks.detail.descriptionPlaceholder"),
879
+ className: "min-h-[72px] flex-1 resize-none bg-transparent px-4 py-3 text-sm leading-relaxed text-foreground outline-none placeholder:text-muted-foreground/60"
880
+ }
881
+ ),
882
+ /* @__PURE__ */ jsx("div", { className: "px-4 pb-2", children: /* @__PURE__ */ jsxs(
856
883
  "button",
857
884
  {
858
885
  type: "button",
859
- onClick: handleDelete,
860
- className: "inline-flex items-center gap-1 text-[11px] text-muted-foreground hover:text-destructive transition-colors",
886
+ onClick: () => delegate(
887
+ `${t("tasks.detail.chatWithAi.prompt")}
888
+
889
+ "${task.title}"${task.description ? `
890
+ ${task.description}` : ""}${task.dueDate ? `
891
+ (${t("tasks.detail.dueDate")}: ${task.dueDate})` : ""}`
892
+ ),
893
+ className: "group flex w-full items-center justify-center gap-2 rounded-xl bg-gradient-to-r from-primary via-primary/80 to-primary bg-[length:200%_100%] bg-left px-3 py-2 text-[13px] font-semibold text-primary-foreground shadow-sm transition-all duration-500 hover:bg-right hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
861
894
  children: [
862
- /* @__PURE__ */ jsx(Trash2, { className: "h-3 w-3" }),
863
- t("tasks.detail.delete")
895
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-4 w-4 transition-transform duration-300 group-hover:rotate-12" }),
896
+ t("tasks.detail.chatWithAi")
864
897
  ]
865
898
  }
866
- )
899
+ ) }),
900
+ /* @__PURE__ */ jsxs("div", { className: "border-t border-border/40 px-4 py-2.5", children: [
901
+ /* @__PURE__ */ jsxs("p", { className: "pb-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60", children: [
902
+ t("tasks.detail.subtasks"),
903
+ subtasks.length > 0 ? ` \xB7 ${subtasks.length}` : ""
904
+ ] }),
905
+ /* @__PURE__ */ jsx("div", { className: "-mx-3", children: /* @__PURE__ */ jsx(TaskQuickAdd, { parentId: task.id }) }),
906
+ subtasks.length > 0 && /* @__PURE__ */ jsx("div", { className: "divide-y divide-border/40", children: subtasks.map((st) => /* @__PURE__ */ jsx(TaskCard, { task: st }, st.id)) })
907
+ ] }),
908
+ /* @__PURE__ */ jsxs("p", { className: "border-t border-border/40 px-4 py-1.5 text-[10px] text-muted-foreground/60", children: [
909
+ t("tasks.detail.created"),
910
+ " ",
911
+ formatDateTime(task.createdAt),
912
+ " \xB7 ",
913
+ t("tasks.detail.updated"),
914
+ " ",
915
+ formatDateTime(task.updatedAt)
916
+ ] })
867
917
  ] })
868
- ] });
918
+ );
869
919
  }
870
920
  var STATUS_ORDER = ["todo", "in_progress", "done", "cancelled"];
871
- function TasksDrawer() {
921
+ function TasksPanel() {
872
922
  const t = useTranslation();
873
- const drawerOpen = useTasksStore((s) => s.drawerOpen);
874
- const closeDrawer = useTasksStore((s) => s.closeDrawer);
875
923
  const selectedTaskId = useTasksStore((s) => s.selectedTaskId);
876
924
  const tasks = useTasksStore((s) => s.tasks);
877
925
  const tasksLoading = useTasksStore((s) => s.tasksLoading);
878
- const summary = useTasksStore((s) => s.summary);
879
926
  const activeFilter = useTasksStore((s) => s.activeFilter);
927
+ const fetchTasks = useTasksStore((s) => s.fetchTasks);
928
+ const fetchLabels = useTasksStore((s) => s.fetchLabels);
929
+ const fetchSummary = useTasksStore((s) => s.fetchSummary);
930
+ React4.useEffect(() => {
931
+ void fetchTasks();
932
+ void fetchLabels();
933
+ void fetchSummary();
934
+ }, [fetchTasks, fetchLabels, fetchSummary]);
880
935
  const grouped = React4.useMemo(() => {
881
936
  const map = /* @__PURE__ */ new Map();
882
937
  for (const s of STATUS_ORDER) map.set(s, []);
@@ -886,13 +941,32 @@ function TasksDrawer() {
886
941
  }
887
942
  return map;
888
943
  }, [tasks]);
889
- const summaryLine = React4.useMemo(() => {
944
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
945
+ !selectedTaskId && /* @__PURE__ */ jsx(TasksFilterBar, {}),
946
+ !selectedTaskId && /* @__PURE__ */ jsx(TaskQuickAdd, {}),
947
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: selectedTaskId ? /* @__PURE__ */ jsx(TaskDetail, { taskId: selectedTaskId }) : tasksLoading && tasks.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-16", children: /* @__PURE__ */ jsx("div", { className: "h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent" }) }) : tasks.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-muted-foreground", children: [
948
+ /* @__PURE__ */ jsx(ClipboardList, { className: "mb-3 h-10 w-10 opacity-40" }),
949
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t("tasks.empty") }),
950
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs", children: t("tasks.empty.description") })
951
+ ] }) : activeFilter !== "all" ? /* @__PURE__ */ jsx("div", { className: "py-1", children: tasks.map((task) => /* @__PURE__ */ jsx("div", { className: "px-2", children: /* @__PURE__ */ jsx(TaskCard, { task }) }, task.id)) }) : /* @__PURE__ */ jsx("div", { className: "space-y-2 py-2", children: STATUS_ORDER.filter((s) => s !== "cancelled" || (grouped.get(s)?.length ?? 0) > 0).map((status) => /* @__PURE__ */ jsx(TaskStatusGroup, { status, tasks: grouped.get(status) ?? [] }, status)) }) })
952
+ ] });
953
+ }
954
+ function useTasksSummaryLine() {
955
+ const t = useTranslation();
956
+ const summary = useTasksStore((s) => s.summary);
957
+ return React4.useMemo(() => {
890
958
  if (!summary) return "";
891
959
  const parts = [];
892
960
  if (summary.overdue > 0) parts.push(`${summary.overdue} ${t("tasks.summary.overdue")}`);
893
961
  if (summary.dueToday > 0) parts.push(`${summary.dueToday} ${t("tasks.summary.dueToday")}`);
894
962
  return parts.join(" \xB7 ");
895
963
  }, [summary, t]);
964
+ }
965
+ function TasksDrawer() {
966
+ const t = useTranslation();
967
+ const drawerOpen = useTasksStore((s) => s.drawerOpen);
968
+ const closeDrawer = useTasksStore((s) => s.closeDrawer);
969
+ const summaryLine = useTasksSummaryLine();
896
970
  return /* @__PURE__ */ jsx(Sheet, { open: drawerOpen, onOpenChange: (v) => {
897
971
  if (!v) closeDrawer();
898
972
  }, children: /* @__PURE__ */ jsxs(SheetContent, { width: "max-w-lg", overlay: "dim", children: [
@@ -900,26 +974,46 @@ function TasksDrawer() {
900
974
  /* @__PURE__ */ jsx(SheetTitle, { children: t("tasks.drawer.title") }),
901
975
  /* @__PURE__ */ jsx(SheetDescription, { children: summaryLine || t("tasks.drawer.subtitle") })
902
976
  ] }),
903
- !selectedTaskId && /* @__PURE__ */ jsx(TasksFilterBar, {}),
904
- !selectedTaskId && /* @__PURE__ */ jsx(TaskQuickAdd, {}),
905
- /* @__PURE__ */ jsx(SheetBody, { className: "px-0 py-0", children: selectedTaskId ? /* @__PURE__ */ jsx("div", { className: "px-5 py-4", children: /* @__PURE__ */ jsx(TaskDetail, { taskId: selectedTaskId }) }) : tasksLoading && tasks.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-16", children: /* @__PURE__ */ jsx("div", { className: "h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent" }) }) : tasks.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-muted-foreground", children: [
906
- /* @__PURE__ */ jsx(ClipboardList, { className: "h-10 w-10 mb-3 opacity-40" }),
907
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t("tasks.empty") }),
908
- /* @__PURE__ */ jsx("p", { className: "text-xs mt-1", children: t("tasks.empty.description") })
909
- ] }) : activeFilter !== "all" ? /* @__PURE__ */ jsx("div", { className: "py-1", children: tasks.map((task) => /* @__PURE__ */ jsx("div", { className: "px-2", children: /* @__PURE__ */ jsx(TaskCard, { task }) }, task.id)) }) : /* @__PURE__ */ jsx("div", { className: "py-2 space-y-2", children: STATUS_ORDER.filter((s) => s !== "cancelled" || (grouped.get(s)?.length ?? 0) > 0).map((status) => /* @__PURE__ */ jsx(TaskStatusGroup, { status, tasks: grouped.get(status) ?? [] }, status)) }) })
977
+ /* @__PURE__ */ jsx(SheetBody, { className: "flex flex-col px-0 py-0", children: /* @__PURE__ */ jsx(TasksPanel, {}) })
910
978
  ] }) });
911
979
  }
912
980
  function TasksTopbarButton({ config }) {
913
981
  const { tasksConfig, tasksProvider, tasksStore } = config;
982
+ const t = useTranslation();
983
+ const railMounted = useRightRailStore((s) => s.mounted);
984
+ const summary = useStore(tasksStore, (s) => s.summary);
985
+ const openCount = summary ? summary.todo + summary.inProgress : 0;
986
+ const Panel = React4.useMemo(() => {
987
+ const RailPanel = () => /* @__PURE__ */ jsx(TasksContextProvider, { config: tasksConfig, provider: tasksProvider, store: tasksStore, children: /* @__PURE__ */ jsx(TasksPanel, {}) });
988
+ RailPanel.displayName = "TasksRailPanel";
989
+ return RailPanel;
990
+ }, [tasksConfig, tasksProvider, tasksStore]);
991
+ useRightRailPanel(
992
+ React4.useMemo(
993
+ () => railMounted ? {
994
+ id: "tasks",
995
+ label: t("tasks.drawer.title") || "Tasks",
996
+ icon: CheckSquare,
997
+ order: 20,
998
+ badge: { count: openCount, tone: "destructive" },
999
+ Component: Panel
1000
+ } : null,
1001
+ [railMounted, t, Panel, openCount]
1002
+ )
1003
+ );
914
1004
  return /* @__PURE__ */ jsxs(TasksContextProvider, { config: tasksConfig, provider: tasksProvider, store: tasksStore, children: [
915
- /* @__PURE__ */ jsx(TasksTopbarButtonInner, { store: tasksStore }),
916
- /* @__PURE__ */ jsx(TasksDrawer, {})
1005
+ /* @__PURE__ */ jsx(TasksTopbarButtonInner, { store: tasksStore, railMounted }),
1006
+ !railMounted && /* @__PURE__ */ jsx(TasksDrawer, {})
917
1007
  ] });
918
1008
  }
919
- function TasksTopbarButtonInner({ store }) {
1009
+ function TasksTopbarButtonInner({
1010
+ store,
1011
+ railMounted
1012
+ }) {
920
1013
  const toggleDrawer = useStore(store, (s) => s.toggleDrawer);
921
1014
  const fetchSummary = useStore(store, (s) => s.fetchSummary);
922
1015
  const summary = useStore(store, (s) => s.summary);
1016
+ const togglePanel = useRightRailStore((s) => s.togglePanel);
923
1017
  useEffect(() => {
924
1018
  fetchSummary();
925
1019
  }, [fetchSummary]);
@@ -930,7 +1024,7 @@ function TasksTopbarButtonInner({ store }) {
930
1024
  variant: "ghost",
931
1025
  size: "icon",
932
1026
  className: cn("relative text-sidebar-foreground/70 hover:bg-sidebar/30 hover:text-sidebar-foreground"),
933
- onClick: toggleDrawer,
1027
+ onClick: () => railMounted ? togglePanel("tasks") : toggleDrawer(),
934
1028
  "aria-label": "Tasks",
935
1029
  children: [
936
1030
  /* @__PURE__ */ jsx(CheckSquare, { className: "h-5 w-5" }),
@@ -949,40 +1043,49 @@ function TasksTopbarButtonInner({ store }) {
949
1043
  );
950
1044
  }
951
1045
 
952
- // src/index.ts
953
- var DEFAULT_LABELS = {
954
- drawerTitle: "Tasks",
955
- settingsTitle: "Tasks",
956
- quickAddPlaceholder: "Add a task..."
957
- };
958
- function resolveConfig(options) {
959
- return {
960
- labels: { ...DEFAULT_LABELS, ...options?.labels }
961
- };
962
- }
963
- var MIGRATION_001 = `-- Tasks Plugin: Base Tables
964
- CREATE TABLE IF NOT EXISTS public.tsk_labels (
1046
+ // src/migrations/index.ts
1047
+ var MIGRATION_000_PLG_RENAME = `-- 000_plg_rename.sql \u2014 rename legacy tasks tables to plg_tasks_* for pools
1048
+ -- provisioned before the industry-pool rename. Guarded: fires only when legacy
1049
+ -- name exists and target does not, so fresh pools skip every branch.
1050
+ DO $$
1051
+ BEGIN
1052
+ IF to_regclass('public.tsk_tasks') IS NOT NULL AND to_regclass('public.plg_tasks_tasks') IS NULL THEN
1053
+ ALTER TABLE public.tsk_tasks RENAME TO plg_tasks_tasks;
1054
+ END IF;
1055
+ IF to_regclass('public.tsk_labels') IS NOT NULL AND to_regclass('public.plg_tasks_labels') IS NULL THEN
1056
+ ALTER TABLE public.tsk_labels RENAME TO plg_tasks_labels;
1057
+ END IF;
1058
+ END $$;
1059
+ `;
1060
+ var MIGRATION_001_TASKS_BASE = `-- Tasks Plugin: Base Tables
1061
+ -- Prefix: plg_tasks_
1062
+
1063
+ -- Label definitions for categorizing tasks
1064
+ CREATE TABLE IF NOT EXISTS public.plg_tasks_labels (
965
1065
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
966
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
1066
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
967
1067
  name text NOT NULL,
968
1068
  color text NOT NULL DEFAULT '#6366f1',
969
1069
  is_active boolean NOT NULL DEFAULT true,
970
1070
  created_at timestamptz NOT NULL DEFAULT now()
971
1071
  );
972
- ALTER TABLE public.tsk_labels ENABLE ROW LEVEL SECURITY;
973
- CREATE INDEX IF NOT EXISTS idx_tsk_labels_tenant ON public.tsk_labels(tenant_id);
1072
+ ALTER TABLE public.plg_tasks_labels ENABLE ROW LEVEL SECURITY;
1073
+ CREATE INDEX IF NOT EXISTS idx_plg_tasks_labels_tenant ON public.plg_tasks_labels(tenant_id);
974
1074
 
975
- CREATE TABLE IF NOT EXISTS public.tsk_tasks (
1075
+ -- Main tasks table (subtasks via parent_id self-reference)
1076
+ CREATE TABLE IF NOT EXISTS public.plg_tasks_tasks (
976
1077
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
977
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
1078
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
978
1079
  title text NOT NULL,
979
1080
  description text,
980
- status text NOT NULL DEFAULT 'todo' CHECK (status IN ('todo','in_progress','done','cancelled')),
981
- priority text NOT NULL DEFAULT 'medium' CHECK (priority IN ('low','medium','high','urgent')),
1081
+ status text NOT NULL DEFAULT 'todo'
1082
+ CHECK (status IN ('todo', 'in_progress', 'done', 'cancelled')),
1083
+ priority text NOT NULL DEFAULT 'medium'
1084
+ CHECK (priority IN ('low', 'medium', 'high', 'urgent')),
982
1085
  due_date date,
983
1086
  assigned_to_id uuid,
984
1087
  assigned_to_name text,
985
- parent_id uuid REFERENCES public.tsk_tasks(id) ON DELETE CASCADE,
1088
+ parent_id uuid REFERENCES public.plg_tasks_tasks(id) ON DELETE CASCADE,
986
1089
  labels jsonb NOT NULL DEFAULT '[]'::jsonb,
987
1090
  position integer NOT NULL DEFAULT 0,
988
1091
  created_by_id uuid,
@@ -990,26 +1093,48 @@ CREATE TABLE IF NOT EXISTS public.tsk_tasks (
990
1093
  created_at timestamptz NOT NULL DEFAULT now(),
991
1094
  updated_at timestamptz NOT NULL DEFAULT now()
992
1095
  );
993
- ALTER TABLE public.tsk_tasks ENABLE ROW LEVEL SECURITY;
994
- CREATE INDEX IF NOT EXISTS idx_tsk_tasks_tenant ON public.tsk_tasks(tenant_id);
995
- CREATE INDEX IF NOT EXISTS idx_tsk_tasks_status ON public.tsk_tasks(tenant_id, status);
996
- CREATE INDEX IF NOT EXISTS idx_tsk_tasks_parent ON public.tsk_tasks(parent_id);
997
- CREATE INDEX IF NOT EXISTS idx_tsk_tasks_assigned ON public.tsk_tasks(assigned_to_id);
998
- CREATE INDEX IF NOT EXISTS idx_tsk_tasks_due ON public.tsk_tasks(tenant_id, due_date) WHERE due_date IS NOT NULL;
1096
+ ALTER TABLE public.plg_tasks_tasks ENABLE ROW LEVEL SECURITY;
1097
+ CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_tenant ON public.plg_tasks_tasks(tenant_id);
1098
+ CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_status ON public.plg_tasks_tasks(tenant_id, status);
1099
+ CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_parent ON public.plg_tasks_tasks(parent_id);
1100
+ CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_assigned ON public.plg_tasks_tasks(assigned_to_id);
1101
+ CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_due ON public.plg_tasks_tasks(tenant_id, due_date)
1102
+ WHERE due_date IS NOT NULL;
1103
+
1104
+ -- RLS policies for plg_tasks_tasks (idempotent \u2014 DROP IF EXISTS before CREATE)
1105
+ DROP POLICY IF EXISTS plg_tasks_tasks_select ON public.plg_tasks_tasks;
1106
+ CREATE POLICY plg_tasks_tasks_select ON public.plg_tasks_tasks FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
1107
+ DROP POLICY IF EXISTS plg_tasks_tasks_insert ON public.plg_tasks_tasks;
1108
+ CREATE POLICY plg_tasks_tasks_insert ON public.plg_tasks_tasks FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
1109
+ DROP POLICY IF EXISTS plg_tasks_tasks_update ON public.plg_tasks_tasks;
1110
+ CREATE POLICY plg_tasks_tasks_update ON public.plg_tasks_tasks FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
1111
+ DROP POLICY IF EXISTS plg_tasks_tasks_delete ON public.plg_tasks_tasks;
1112
+ CREATE POLICY plg_tasks_tasks_delete ON public.plg_tasks_tasks FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
1113
+ GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_tasks_tasks TO authenticated;
999
1114
 
1000
- -- RLS policies for tsk_tasks
1001
- CREATE POLICY tsk_tasks_select ON public.tsk_tasks FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
1002
- CREATE POLICY tsk_tasks_insert ON public.tsk_tasks FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
1003
- CREATE POLICY tsk_tasks_update ON public.tsk_tasks FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
1004
- CREATE POLICY tsk_tasks_delete ON public.tsk_tasks FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
1005
- GRANT SELECT, INSERT, UPDATE, DELETE ON public.tsk_tasks TO authenticated;
1115
+ -- RLS policies for plg_tasks_labels (idempotent)
1116
+ DROP POLICY IF EXISTS plg_tasks_labels_select ON public.plg_tasks_labels;
1117
+ CREATE POLICY plg_tasks_labels_select ON public.plg_tasks_labels FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
1118
+ DROP POLICY IF EXISTS plg_tasks_labels_insert ON public.plg_tasks_labels;
1119
+ CREATE POLICY plg_tasks_labels_insert ON public.plg_tasks_labels FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
1120
+ DROP POLICY IF EXISTS plg_tasks_labels_update ON public.plg_tasks_labels;
1121
+ CREATE POLICY plg_tasks_labels_update ON public.plg_tasks_labels FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
1122
+ DROP POLICY IF EXISTS plg_tasks_labels_delete ON public.plg_tasks_labels;
1123
+ CREATE POLICY plg_tasks_labels_delete ON public.plg_tasks_labels FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
1124
+ GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_tasks_labels TO authenticated;
1125
+ `;
1006
1126
 
1007
- -- RLS policies for tsk_labels
1008
- CREATE POLICY tsk_labels_select ON public.tsk_labels FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
1009
- CREATE POLICY tsk_labels_insert ON public.tsk_labels FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
1010
- CREATE POLICY tsk_labels_update ON public.tsk_labels FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
1011
- CREATE POLICY tsk_labels_delete ON public.tsk_labels FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
1012
- GRANT SELECT, INSERT, UPDATE, DELETE ON public.tsk_labels TO authenticated;`;
1127
+ // src/index.ts
1128
+ var DEFAULT_LABELS = {
1129
+ drawerTitle: "Tasks",
1130
+ settingsTitle: "Tasks",
1131
+ quickAddPlaceholder: "Add a task..."
1132
+ };
1133
+ function resolveConfig(options) {
1134
+ return {
1135
+ labels: { ...DEFAULT_LABELS, ...options?.labels }
1136
+ };
1137
+ }
1013
1138
  function createTasksPlugin(options) {
1014
1139
  const config = resolveConfig(options);
1015
1140
  registerTranslations(tasksLocales);
@@ -1018,6 +1143,30 @@ function createTasksPlugin(options) {
1018
1143
  () => createMockTasksProvider()
1019
1144
  );
1020
1145
  const store = createTasksStore(provider);
1146
+ registerNotesTasksAdapter({
1147
+ async createTodo({ title, noteId, noteTitle }) {
1148
+ const task = await provider.createTask({
1149
+ title,
1150
+ description: noteTitle ? `Nota: ${noteTitle}` : void 0,
1151
+ labels: [`${NOTE_LABEL_PREFIX}${noteId}`]
1152
+ });
1153
+ void store.getState().fetchSummary();
1154
+ return { id: task.id, title: task.title, done: task.status === "done" };
1155
+ },
1156
+ async listTodos(noteId) {
1157
+ const tasks = await provider.getTasks({});
1158
+ const label = `${NOTE_LABEL_PREFIX}${noteId}`;
1159
+ return tasks.filter((t) => t.labels.includes(label)).sort((a, b) => (a.status === "done" ? 1 : 0) - (b.status === "done" ? 1 : 0)).map((t) => ({ id: t.id, title: t.title, done: t.status === "done" }));
1160
+ },
1161
+ async toggleTodo(id, done) {
1162
+ await provider.updateTask(id, { status: done ? "done" : "todo" });
1163
+ void store.getState().fetchSummary();
1164
+ },
1165
+ async removeTodo(id) {
1166
+ await provider.deleteTask(id);
1167
+ void store.getState().fetchSummary();
1168
+ }
1169
+ });
1021
1170
  return {
1022
1171
  id: "tasks",
1023
1172
  name: config.labels.drawerTitle,
@@ -1086,11 +1235,17 @@ function createTasksPlugin(options) {
1086
1235
  }
1087
1236
  ],
1088
1237
  migrations: [
1238
+ {
1239
+ id: "tasks-000-plg-rename",
1240
+ version: "1.0.0",
1241
+ sql: MIGRATION_000_PLG_RENAME,
1242
+ description: "Rename legacy tsk_* tables to plg_tasks_* in-place for pools provisioned before the industry-pool rename (guarded no-op on fresh pools)"
1243
+ },
1089
1244
  {
1090
1245
  id: "tasks-001-base-tables",
1091
1246
  version: "1.0.0",
1092
- sql: MIGRATION_001,
1093
- description: "Create tsk_tasks and tsk_labels tables"
1247
+ sql: MIGRATION_001_TASKS_BASE,
1248
+ description: "Create plg_tasks_tasks and plg_tasks_labels"
1094
1249
  }
1095
1250
  ],
1096
1251
  locales: tasksLocales