@mulmoclaude/todo-plugin 0.1.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.
Files changed (42) hide show
  1. package/dist/Preview.vue.d.ts +8 -0
  2. package/dist/View.vue.d.ts +12 -0
  3. package/dist/composables/index.d.ts +2 -0
  4. package/dist/composables/useTodos.d.ts +26 -0
  5. package/dist/composables.js +101 -0
  6. package/dist/composables.js.map +1 -0
  7. package/dist/definition-mymex4HE.js +55 -0
  8. package/dist/definition-mymex4HE.js.map +1 -0
  9. package/dist/definition.d.ts +43 -0
  10. package/dist/handlers/columns.d.ts +31 -0
  11. package/dist/handlers/items.d.ts +36 -0
  12. package/dist/handlers/llm.d.ts +30 -0
  13. package/dist/handlers/priority-notifier.d.ts +55 -0
  14. package/dist/index.d.ts +71 -0
  15. package/dist/index.js +1223 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/internal/utils.d.ts +4 -0
  18. package/dist/io.d.ts +6 -0
  19. package/dist/labels-C4z7FMoE.js +85 -0
  20. package/dist/labels-C4z7FMoE.js.map +1 -0
  21. package/dist/labels.d.ts +15 -0
  22. package/dist/lang/de.d.ts +25 -0
  23. package/dist/lang/en.d.ts +25 -0
  24. package/dist/lang/es.d.ts +25 -0
  25. package/dist/lang/fr.d.ts +25 -0
  26. package/dist/lang/index.d.ts +28 -0
  27. package/dist/lang/ja.d.ts +25 -0
  28. package/dist/lang/ko.d.ts +25 -0
  29. package/dist/lang/pt-BR.d.ts +25 -0
  30. package/dist/lang/zh.d.ts +25 -0
  31. package/dist/lang-D72AIF9U.js +215 -0
  32. package/dist/lang-D72AIF9U.js.map +1 -0
  33. package/dist/priority.d.ts +10 -0
  34. package/dist/shared.d.ts +13 -0
  35. package/dist/shared.js +89 -0
  36. package/dist/shared.js.map +1 -0
  37. package/dist/types.d.ts +22 -0
  38. package/dist/viewModes.d.ts +11 -0
  39. package/dist/vue.d.ts +59 -0
  40. package/dist/vue.js +422 -0
  41. package/dist/vue.js.map +1 -0
  42. package/package.json +48 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../node_modules/gui-chat-protocol/dist/index.js","../src/internal/utils.ts","../src/handlers/columns.ts","../src/handlers/items.ts","../src/io.ts","../src/handlers/llm.ts","../src/handlers/priority-notifier.ts","../src/index.ts"],"sourcesContent":["function definePlugin(setup) {\n return setup;\n}\nfunction isPluginFactory(value) {\n return typeof value === \"function\";\n}\nexport {\n definePlugin,\n isPluginFactory\n};\n//# sourceMappingURL=index.js.map\n","// Tiny utilities the plugin needs that the runtime doesn't provide.\n// Kept minimal (no host imports) so the plugin stays self-contained.\n\n// `crypto.randomUUID()` is on globalThis in modern browsers and\n// Node 20+. Avoiding `import { randomUUID } from \"node:crypto\"`\n// because the plugin's vue.ts entry transitively pulls\n// index.ts → handlers/items.ts → here during the browser bundle,\n// and Vite externalises `node:*` for browsers — which would leave\n// the import unresolved at runtime.\nexport function makeId(prefix: string): string {\n return `${prefix}_${globalThis.crypto.randomUUID()}`;\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n// Minimal slugify for column ids. Lowercase alphanumeric + hyphens,\n// trimmed, with a fallback when the input has no usable characters.\n// The host's `server/utils/slug.ts` is more sophisticated (handles\n// non-ASCII via base64-encoded hash); for column labels which are\n// typed in by the user in the kanban UI a simple pass is plenty.\nconst MAX_SLUG_LEN = 60;\n\nexport function slugify(label: string, fallback: string): string {\n const normalised = label\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, MAX_SLUG_LEN);\n return normalised.length > 0 ? normalised : fallback;\n}\n\nexport function disambiguateSlug(base: string, existingIds: ReadonlySet<string>): string {\n if (!existingIds.has(base)) return base;\n let n = 2;\n while (existingIds.has(`${base}-${n}`)) n += 1;\n return `${base}-${n}`;\n}\n","// Pure handlers for status columns used by the file-explorer todo\n// view. Same shape as todosHandlers / schedulerHandlers: each function\n// takes the current state + an input record and returns either an\n// error or the next state. The Express route is responsible for\n// loading / saving the underlying JSON files.\n//\n// Storage layout:\n// workspace/todos/columns.json ← StatusColumn[]\n//\n// At least one column must always carry `isDone: true` so completed\n// items have somewhere to live and the legacy `completed` boolean has\n// something to map to.\n\nimport { slugify as slugifyCanonical, disambiguateSlug, isRecord } from \"../internal/utils\";\nimport type { TodoItem, StatusColumn } from \"../types\";\n\nexport type { StatusColumn } from \"../types\";\n\nexport const DEFAULT_COLUMNS: StatusColumn[] = [\n { id: \"backlog\", label: \"Backlog\" },\n { id: \"todo\", label: \"Todo\" },\n { id: \"in-progress\", label: \"In Progress\" },\n { id: \"done\", label: \"Done\", isDone: true },\n];\n\n// ── Result types ──────────────────────────────────────────────────\n\nexport type ColumnsActionResult =\n | { kind: \"error\"; status: number; error: string }\n | {\n kind: \"success\";\n columns: StatusColumn[];\n // Some operations also need to mutate items (e.g. removing a\n // column reassigns its items to another column). When set, the\n // route persists this items array as well.\n items?: TodoItem[];\n };\n\n// ── id slug generation ────────────────────────────────────────────\n\n// Convert a free-text label into a URL-safe column id. Thin wrapper\n// around the canonical slugify (server/utils/slug.ts) with the\n// \"column\" fallback. Hyphen-separated to match wiki / files / journal\n// — the previous underscore-separated rule was the only divergent\n// slug producer in the codebase. See #732.\nfunction slugify(label: string): string {\n return slugifyCanonical(label, \"column\");\n}\n\n// Pick an id that doesn't collide with `existingIds`. Thin wrapper\n// around the shared `disambiguateSlug` helper so the e2e dispatcher\n// (which mocks this server-side flow) can mirror the truncation\n// boundary without duplicating the logic — Codex iter-2 #732.\nfunction uniqueId(base: string, existingIds: ReadonlySet<string>): string {\n return disambiguateSlug(base, existingIds);\n}\n\n// ── Validation helpers ────────────────────────────────────────────\n\nfunction findColumn(columns: StatusColumn[], columnId: string): StatusColumn | undefined {\n return columns.find((column) => column.id === columnId);\n}\n\nfunction ensureColumnsValid(columns: StatusColumn[]): StatusColumn[] {\n // Guarantee invariants when reading: at least one column, exactly\n // one isDone column, ids are unique. If anything is off, fall back\n // to DEFAULT_COLUMNS rather than try to repair partial state.\n if (columns.length === 0) return [...DEFAULT_COLUMNS];\n const seen = new Set<string>();\n for (const column of columns) {\n if (seen.has(column.id)) return [...DEFAULT_COLUMNS];\n seen.add(column.id);\n }\n const doneCount = columns.filter((column) => column.isDone).length;\n if (doneCount === 0) {\n // Promote the last column to done so the invariant holds.\n const fixed = columns.map((column, i) => (i === columns.length - 1 ? { ...column, isDone: true } : column));\n return fixed;\n }\n if (doneCount > 1) {\n // Keep only the first done flag.\n let kept = false;\n return columns.map((column) => {\n if (!column.isDone) return column;\n if (kept) {\n const next: StatusColumn = { id: column.id, label: column.label };\n return next;\n }\n kept = true;\n return column;\n });\n }\n return columns;\n}\n\n// Public: load-time normaliser. Use this when parsing columns.json so\n// the rest of the system never has to think about invalid shapes.\nexport function normalizeColumns(raw: unknown): StatusColumn[] {\n if (!Array.isArray(raw)) return [...DEFAULT_COLUMNS];\n const cleaned: StatusColumn[] = [];\n for (const entry of raw) {\n if (!isRecord(entry)) continue;\n if (typeof entry[\"id\"] !== \"string\" || typeof entry[\"label\"] !== \"string\") continue;\n const col: StatusColumn = { id: entry[\"id\"], label: entry[\"label\"] };\n if (entry[\"isDone\"] === true) col.isDone = true;\n cleaned.push(col);\n }\n return ensureColumnsValid(cleaned);\n}\n\n// ── Item helpers tied to columns ──────────────────────────────────\n\n// id of the first column flagged isDone. Guaranteed to exist after\n// normalizeColumns.\nexport function doneColumnId(columns: StatusColumn[]): string {\n const done = columns.find((column) => column.isDone);\n if (done) return done.id;\n const last = columns[columns.length - 1];\n if (!last) throw new Error(\"doneColumnId: empty columns array (normalizeColumns invariant violated)\");\n return last.id;\n}\n\n// id of the first non-done column, used as the default status when\n// adding new items. Falls back to the done column if everything is\n// somehow flagged done.\nexport function defaultStatusId(columns: StatusColumn[]): string {\n const open = columns.find((column) => !column.isDone);\n return open ? open.id : doneColumnId(columns);\n}\n\n// Reconcile each item's `completed` boolean with the new done-column\n// id. Items in the new done column are completed=true, items\n// elsewhere are completed=false. Returns [updatedItems, changed]\n// where `changed` says whether any item was actually rewritten —\n// callers use that to decide whether to persist items along with\n// the column change.\n//\n// This is the *only* place that mass-mutates `completed` based on\n// status. The migration on read deliberately does NOT do this any\n// more (so the legacy MCP `check` action's plain boolean flips keep\n// working). Column operations are explicit user intent so it's safe\n// to sync at that point.\nexport function resyncDoneMembership(items: TodoItem[], newDoneId: string): { items: TodoItem[]; changed: boolean } {\n let changed = false;\n const next = items.map((item): TodoItem => {\n const shouldBeDone = item.status === newDoneId;\n if (item.completed === shouldBeDone) return item;\n changed = true;\n return { ...item, completed: shouldBeDone };\n });\n return { items: next, changed };\n}\n\n// Re-stripe order values for every item in `columnId`. Items already\n// sorted by their existing `order` get reassigned to 1000, 2000, ...\n// so two columns being merged together (handleDeleteColumn's refuge\n// case) end up with unique, contiguous orders rather than colliding\n// 1000s from each side.\nconst ORDER_STEP = 1000;\n\nfunction rebuildColumnOrder(items: TodoItem[], columnId: string): TodoItem[] {\n const inColumn = items.filter((item) => item.status === columnId).sort((left, right) => (left.order ?? 0) - (right.order ?? 0));\n const newOrders = new Map<string, number>();\n inColumn.forEach((item, index) => newOrders.set(item.id, (index + 1) * ORDER_STEP));\n return items.map((item): TodoItem => {\n const newOrder = newOrders.get(item.id);\n if (newOrder === undefined) return item;\n return { ...item, order: newOrder };\n });\n}\n\n// ── Action handlers ───────────────────────────────────────────────\n\nexport interface AddColumnInput {\n label?: string;\n isDone?: boolean;\n}\n\nexport function handleAddColumn(columns: StatusColumn[], items: TodoItem[], input: AddColumnInput): ColumnsActionResult {\n if (!input.label || input.label.trim().length === 0) {\n return { kind: \"error\", status: 400, error: \"label required\" };\n }\n const baseId = slugify(input.label);\n const columnId = uniqueId(baseId, new Set(columns.map((column) => column.id)));\n const columnToAdd: StatusColumn = { id: columnId, label: input.label.trim() };\n if (input.isDone === true) columnToAdd.isDone = true;\n // If the new column is flagged done, demote any existing done\n // columns (only one is allowed at a time) and resync items so the\n // old done column's items are no longer marked completed. The new\n // column itself is empty so there's nothing on its side to sync.\n if (input.isDone === true) {\n const nextColumns = [...columns.map((column) => ({ ...column, isDone: false })), columnToAdd];\n const { items: nextItems, changed } = resyncDoneMembership(items, columnId);\n return {\n kind: \"success\",\n columns: nextColumns,\n ...(changed ? { items: nextItems } : {}),\n };\n }\n return { kind: \"success\", columns: [...columns, columnToAdd] };\n}\n\nexport interface PatchColumnInput {\n label?: string;\n isDone?: boolean;\n}\n\nexport function handlePatchColumn(columns: StatusColumn[], columnId: string, input: PatchColumnInput, items: TodoItem[]): ColumnsActionResult {\n const target = findColumn(columns, columnId);\n if (!target) {\n return { kind: \"error\", status: 404, error: `column not found: ${columnId}` };\n }\n const patched: StatusColumn = { id: target.id, label: target.label };\n if (target.isDone) patched.isDone = true;\n if (typeof input.label === \"string\" && input.label.trim().length > 0) {\n patched.label = input.label.trim();\n }\n let nextColumns = columns.map((column) => (column.id === columnId ? patched : column));\n // Toggling done flag is non-trivial: only one column may be done.\n let itemsChanged = false;\n let nextItems = items;\n if (input.isDone === true && !target.isDone) {\n // Promote this column to done; demote everyone else.\n nextColumns = nextColumns.map((column) => (column.id === columnId ? { ...column, isDone: true } : { id: column.id, label: column.label }));\n // Resync `completed` across all items: the new done column's\n // items become true, the old done column's items become false.\n // Doing this with the helper rather than a one-sided pass means\n // both ends of the swap stay consistent.\n const synced = resyncDoneMembership(items, columnId);\n nextItems = synced.items;\n itemsChanged = synced.changed;\n } else if (input.isDone === false && target.isDone) {\n // Refuse to demote the only done column — there must always be one.\n return {\n kind: \"error\",\n status: 400,\n error: \"at least one column must be marked as done\",\n };\n }\n return {\n kind: \"success\",\n columns: nextColumns,\n ...(itemsChanged ? { items: nextItems } : {}),\n };\n}\n\nexport function handleDeleteColumn(columns: StatusColumn[], columnId: string, items: TodoItem[]): ColumnsActionResult {\n if (columns.length <= 1) {\n return {\n kind: \"error\",\n status: 400,\n error: \"cannot delete the last remaining column\",\n };\n }\n const target = findColumn(columns, columnId);\n if (!target) {\n return { kind: \"error\", status: 404, error: `column not found: ${columnId}` };\n }\n const remaining = columns.filter((column) => column.id !== columnId);\n // If we just removed the done column, promote the new last column.\n let nextColumns = remaining;\n if (target.isDone) {\n nextColumns = remaining.map((column, i) => (i === remaining.length - 1 ? { ...column, isDone: true } : column));\n }\n const newDoneId = doneColumnId(nextColumns);\n // Reassign orphaned items to the (possibly new) done column if the\n // deleted column was done; otherwise to the new default open column.\n const refugeId = target.isDone ? newDoneId : defaultStatusId(nextColumns);\n let itemsChanged = false;\n let nextItems = items.map((item): TodoItem => {\n if (item.status !== columnId) return item;\n itemsChanged = true;\n return { ...item, status: refugeId };\n });\n if (itemsChanged) {\n // The refuge column might have already had items in it; the ones\n // we just merged in came with their original order values from\n // the deleted column, which can collide with the refuge's\n // existing orders. Re-stripe the whole refuge column to 1000,\n // 2000, ... so the kanban sort stays unique and stable.\n nextItems = rebuildColumnOrder(nextItems, refugeId);\n }\n // Resync the done flag across the result. Necessary in two\n // scenarios: (a) we deleted the done column, so the new last column\n // is now done and its existing items should flip to completed=true;\n // (b) we deleted any other column whose items happened to be the\n // refuge target — already handled by the migration above, but\n // running the helper unconditionally keeps the rest of the items\n // consistent too.\n if (target.isDone) {\n const synced = resyncDoneMembership(nextItems, newDoneId);\n nextItems = synced.items;\n itemsChanged = itemsChanged || synced.changed;\n }\n return {\n kind: \"success\",\n columns: nextColumns,\n ...(itemsChanged ? { items: nextItems } : {}),\n };\n}\n\nexport function handleReorderColumns(columns: StatusColumn[], ids: string[]): ColumnsActionResult {\n if (!Array.isArray(ids)) {\n return { kind: \"error\", status: 400, error: \"ids array required\" };\n }\n if (ids.length !== columns.length) {\n return {\n kind: \"error\",\n status: 400,\n error: \"ids must contain every existing column id exactly once\",\n };\n }\n const known = new Set(columns.map((column) => column.id));\n const seen = new Set<string>();\n for (const columnId of ids) {\n if (!known.has(columnId) || seen.has(columnId)) {\n return {\n kind: \"error\",\n status: 400,\n error: \"ids must contain every existing column id exactly once\",\n };\n }\n seen.add(columnId);\n }\n const byId = new Map(columns.map((column) => [column.id, column]));\n const next = ids.map((columnId) => {\n const column = byId.get(columnId);\n if (!column) throw new Error(`reorderColumns: missing column for id \"${columnId}\" (set-equality check above missed it)`);\n return column;\n });\n return { kind: \"success\", columns: next };\n}\n","// Pure handlers for the id-based REST routes used by the file-explorer\n// todo view (TodoExplorer.vue). The MCP `manageTodoList` action route\n// continues to live in todosHandlers.ts; these handlers are intended\n// for the web UI which knows item ids directly and doesn't need the\n// substring-match contract that the MCP handlers use.\n//\n// Each function takes the current items + columns + an input record\n// and returns a result. The Express route is responsible for loading\n// and saving JSON to disk.\n\nimport type { TodoItem, TodoPriority, StatusColumn } from \"../types\";\nimport { defaultStatusId, doneColumnId } from \"./columns\";\nimport { mergeLabels } from \"../labels\";\nimport { makeId } from \"../internal/utils\";\n\nconst ORDER_STEP = 1000;\nconst PRIORITIES: readonly TodoPriority[] = [\"low\", \"medium\", \"high\", \"urgent\"];\n\n// ── Result type ───────────────────────────────────────────────────\n\nexport type ItemsActionResult = { kind: \"error\"; status: number; error: string } | { kind: \"success\"; items: TodoItem[]; item?: TodoItem };\n\n// ── Migration ─────────────────────────────────────────────────────\n\n// Backfill `status` and `order` on items that pre-date the kanban\n// extension. Pure / idempotent: items that already have valid values\n// pass through unchanged. Done via a single forward pass that assigns\n// monotonically-increasing order values per status column so the\n// kanban view has a stable initial sort.\n//\n// Reasoning for the order assignment: legacy items only carry a\n// `createdAt`, and we want oldest-first within each column. We can't\n// just use createdAt as the order key because it's a milliseconds\n// number which makes hand-editing painful and conflicts with the\n// 1000-step convention drag-drop uses for new items.\nexport function migrateItems(rawItems: TodoItem[], columns: StatusColumn[]): TodoItem[] {\n const doneId = doneColumnId(columns);\n const openId = defaultStatusId(columns);\n const validStatusIds = new Set(columns.map((column) => column.id));\n\n // First pass: backfill status. Items pointing at a column that no\n // longer exists are reassigned to the default open or done column\n // depending on `completed`.\n //\n // Note: we deliberately do NOT re-sync `completed` to status on\n // every read. Earlier versions of this function did, but that\n // overrode the legacy MCP `check` / `uncheck` actions — those\n // actions only flip the boolean, never touch status, so a sync\n // pass kept reverting them on the next read. Treating the two\n // fields as independent at the storage layer leaves both the REST\n // PATCH path (which keeps them in sync explicitly) and the legacy\n // MCP actions (which only touch `completed`) working correctly.\n const withStatus = rawItems.map((item): TodoItem => {\n const hasValidStatus = typeof item.status === \"string\" && validStatusIds.has(item.status);\n if (hasValidStatus) return item;\n const status = item.completed ? doneId : openId;\n return { ...item, status };\n });\n\n // Second pass: backfill order per column. Items that already have\n // an order keep item untouched — only items missing order get one\n // assigned, and they go after the column's current max so they\n // sort to the bottom in createdAt order. This preserves any\n // hand-managed ordering even when a column is a mix of legacy\n // and kanban-aware items.\n const byStatus = new Map<string, TodoItem[]>();\n for (const item of withStatus) {\n const key = item.status ?? openId;\n let bucket = byStatus.get(key);\n if (!bucket) {\n bucket = [];\n byStatus.set(key, bucket);\n }\n bucket.push(item);\n }\n const orderById = new Map<string, number>();\n for (const [, group] of byStatus) {\n const missing = group.filter((item) => typeof item.order !== \"number\");\n if (missing.length === 0) continue;\n const existingMax = group.filter((item) => typeof item.order === \"number\").reduce((acc, item) => Math.max(acc, item.order ?? 0), 0);\n const sorted = [...missing].sort((left, right) => left.createdAt - right.createdAt);\n sorted.forEach((item, i) => {\n orderById.set(item.id, existingMax + (i + 1) * ORDER_STEP);\n });\n }\n return withStatus.map((item): TodoItem => {\n const next = orderById.get(item.id);\n if (next === undefined) return item;\n return { ...item, order: next };\n });\n}\n\n// ── Validators ────────────────────────────────────────────────────\n\nfunction isPriority(value: unknown): value is TodoPriority {\n return typeof value === \"string\" && PRIORITIES.includes(value as TodoPriority);\n}\n\n// YYYY-MM-DD only — keep item boring so the column is sortable as text.\nfunction isDueDate(value: unknown): value is string {\n return typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}$/.test(value);\n}\n\nfunction nextOrder(items: TodoItem[], statusId: string): number {\n const inColumn = items.filter((item) => item.status === statusId).map((item) => item.order ?? 0);\n if (inColumn.length === 0) return ORDER_STEP;\n return Math.max(...inColumn) + ORDER_STEP;\n}\n\n// ── Create ────────────────────────────────────────────────────────\n\nexport interface CreateInput {\n text?: string;\n note?: string;\n status?: string;\n priority?: string;\n dueDate?: string;\n labels?: string[];\n}\n\n// Resolve the status field from input, validating against known\n// columns. Returns the resolved column id or an error result.\ntype ResolveStatusResult = { kind: \"ok\"; status: string } | { kind: \"error\"; status: number; error: string };\n\nfunction resolveStatus(input: CreateInput, columns: StatusColumn[]): ResolveStatusResult {\n if (input.status === undefined || input.status === \"\") {\n return { kind: \"ok\", status: defaultStatusId(columns) };\n }\n const validStatusIds = new Set(columns.map((column) => column.id));\n if (validStatusIds.has(input.status)) {\n return { kind: \"ok\", status: input.status };\n }\n return {\n kind: \"error\",\n status: 400,\n error: `unknown status: ${input.status}`,\n };\n}\n\n// Apply optional priority + dueDate to an item, returning an error\n// result on validation failure.\nfunction applyOptionalFields(item: TodoItem, input: CreateInput): ItemsActionResult | null {\n if (input.priority !== undefined && input.priority !== \"\") {\n if (!isPriority(input.priority)) {\n return { kind: \"error\", status: 400, error: \"invalid priority\" };\n }\n item.priority = input.priority;\n }\n if (input.dueDate !== undefined && input.dueDate !== \"\") {\n if (!isDueDate(input.dueDate)) {\n return {\n kind: \"error\",\n status: 400,\n error: \"dueDate must be YYYY-MM-DD\",\n };\n }\n item.dueDate = input.dueDate;\n }\n return null;\n}\n\nexport function handleCreate(items: TodoItem[], columns: StatusColumn[], input: CreateInput): ItemsActionResult {\n if (!input.text || input.text.trim().length === 0) {\n return { kind: \"error\", status: 400, error: \"text required\" };\n }\n const resolved = resolveStatus(input, columns);\n if (resolved.kind === \"error\") return resolved;\n\n const { status } = resolved;\n const item: TodoItem = {\n id: makeId(\"todo\"),\n text: input.text.trim(),\n completed: status === doneColumnId(columns),\n createdAt: Date.now(),\n status,\n order: nextOrder(items, status),\n };\n if (input.note !== undefined && input.note !== \"\") item.note = input.note;\n const normalizedLabels = mergeLabels([], input.labels ?? []);\n if (normalizedLabels.length > 0) item.labels = normalizedLabels;\n\n const fieldError = applyOptionalFields(item, input);\n if (fieldError) return fieldError;\n\n return { kind: \"success\", items: [...items, item], item };\n}\n\n// ── Patch ─────────────────────────────────────────────────────────\n\nexport interface PatchInput {\n text?: string;\n note?: string | null;\n status?: string;\n priority?: string | null;\n dueDate?: string | null;\n labels?: string[];\n completed?: boolean;\n}\n\n// Each `applyXxx` helper mutates `updated` in place and returns either\n// `null` (success) or an error result. Splitting them out keeps the\n// top-level `handlePatch` linear so item stays under the cognitive\n// complexity threshold and so each field's edit semantics live in one\n// obvious place.\n\nfunction applyTextPatch(updated: TodoItem, input: PatchInput): ItemsActionResult | null {\n if (typeof input.text !== \"string\") return null;\n if (input.text.trim().length === 0) {\n return { kind: \"error\", status: 400, error: \"text cannot be empty\" };\n }\n updated.text = input.text.trim();\n return null;\n}\n\n// eslint-disable-next-line sonarjs/no-invariant-returns\nfunction applyNotePatch(updated: TodoItem, input: PatchInput): ItemsActionResult | null {\n if (input.note === null || input.note === \"\") {\n delete updated.note;\n return null;\n }\n if (typeof input.note === \"string\") updated.note = input.note;\n return null;\n}\n\n// eslint-disable-next-line sonarjs/no-invariant-returns\nfunction applyLabelsPatch(updated: TodoItem, input: PatchInput): ItemsActionResult | null {\n if (!Array.isArray(input.labels)) return null;\n const merged = mergeLabels([], input.labels);\n if (merged.length > 0) updated.labels = merged;\n else delete updated.labels;\n return null;\n}\n\nfunction applyPriorityPatch(updated: TodoItem, input: PatchInput): ItemsActionResult | null {\n if (input.priority === null || input.priority === \"\") {\n delete updated.priority;\n return null;\n }\n if (input.priority === undefined) return null;\n if (!isPriority(input.priority)) {\n return { kind: \"error\", status: 400, error: \"invalid priority\" };\n }\n updated.priority = input.priority;\n return null;\n}\n\nfunction applyDueDatePatch(updated: TodoItem, input: PatchInput): ItemsActionResult | null {\n if (input.dueDate === null || input.dueDate === \"\") {\n delete updated.dueDate;\n return null;\n }\n if (input.dueDate === undefined) return null;\n if (!isDueDate(input.dueDate)) {\n return { kind: \"error\", status: 400, error: \"dueDate must be YYYY-MM-DD\" };\n }\n updated.dueDate = input.dueDate;\n return null;\n}\n\nfunction applyStatusPatch(updated: TodoItem, target: TodoItem, items: TodoItem[], columns: StatusColumn[], input: PatchInput): ItemsActionResult | null {\n if (typeof input.status !== \"string\" || input.status === target.status) {\n return null;\n }\n const validStatusIds = new Set(columns.map((column) => column.id));\n if (!validStatusIds.has(input.status)) {\n return {\n kind: \"error\",\n status: 400,\n error: `unknown status: ${input.status}`,\n };\n }\n updated.status = input.status;\n updated.order = nextOrder(items, input.status);\n updated.completed = input.status === doneColumnId(columns);\n return null;\n}\n\n// Explicit `completed` toggle without changing status: lets the user\n// check / uncheck a card and have item move between the done column and\n// a default open column the obvious way.\n// eslint-disable-next-line sonarjs/no-invariant-returns\nfunction applyCompletedPatch(updated: TodoItem, items: TodoItem[], columns: StatusColumn[], input: PatchInput): ItemsActionResult | null {\n if (typeof input.completed !== \"boolean\") return null;\n if (input.completed === updated.completed) return null;\n updated.completed = input.completed;\n const targetStatus = input.completed ? doneColumnId(columns) : defaultStatusId(columns);\n if (targetStatus !== updated.status) {\n updated.status = targetStatus;\n updated.order = nextOrder(items, targetStatus);\n }\n return null;\n}\n\nexport function handlePatch(items: TodoItem[], columns: StatusColumn[], itemId: string, input: PatchInput): ItemsActionResult {\n const target = items.find((item) => item.id === itemId);\n if (!target) {\n return { kind: \"error\", status: 404, error: `item not found: ${itemId}` };\n }\n const updated: TodoItem = { ...target };\n\n // Each step short-circuits on validation failure. Order matters:\n // status changes happen before completed-toggling so an explicit\n // completed: true alongside a non-done status doesn't fight itself.\n const steps: (() => ItemsActionResult | null | undefined)[] = [\n () => applyTextPatch(updated, input),\n () => applyNotePatch(updated, input),\n () => applyLabelsPatch(updated, input),\n () => applyPriorityPatch(updated, input),\n () => applyDueDatePatch(updated, input),\n () => applyStatusPatch(updated, target, items, columns, input),\n () => applyCompletedPatch(updated, items, columns, input),\n ];\n for (const step of steps) {\n const err = step();\n if (err) return err;\n }\n\n const next = items.map((item) => (item.id === itemId ? updated : item));\n return { kind: \"success\", items: next, item: updated };\n}\n\n// ── Move (drag & drop) ────────────────────────────────────────────\n\n// Reorder + cross-column move in a single call. `position` is the\n// 0-based index the item should occupy in its target column AFTER\n// the move (with the moving item itself excluded from the count).\n//\n// We rebuild the entire target column's order field for simplicity:\n// it's O(n) per column, which for a kanban with hundreds of items is\n// negligible and makes the math obviously correct.\nexport interface MoveInput {\n status?: string;\n position?: number;\n}\n\nexport function handleMove(items: TodoItem[], columns: StatusColumn[], itemId: string, input: MoveInput): ItemsActionResult {\n const target = items.find((item) => item.id === itemId);\n if (!target) {\n return { kind: \"error\", status: 404, error: `item not found: ${itemId}` };\n }\n const validStatusIds = new Set(columns.map((column) => column.id));\n const newStatus = input.status ?? target.status ?? defaultStatusId(columns);\n if (!validStatusIds.has(newStatus)) {\n return {\n kind: \"error\",\n status: 400,\n error: `unknown status: ${newStatus}`,\n };\n }\n const isDone = newStatus === doneColumnId(columns);\n const updatedSelf: TodoItem = {\n ...target,\n status: newStatus,\n completed: isDone,\n };\n // Re-collect the items in the target column with the moving item\n // pulled out, then splice item back in at `position`.\n const others = items.filter((item) => item.id !== itemId && item.status === newStatus).sort((left, right) => (left.order ?? 0) - (right.order ?? 0));\n const insertAt = clampPosition(input.position, others.length);\n const reordered = [...others];\n reordered.splice(insertAt, 0, updatedSelf);\n // Reassign order values 1000 / 2000 / 3000 ...\n const reorderedById = new Map<string, number>();\n reordered.forEach((item, i) => reorderedById.set(item.id, (i + 1) * ORDER_STEP));\n const nextItems = items.map((item): TodoItem => {\n const newOrder = reorderedById.get(item.id);\n if (item.id === itemId) {\n const out: TodoItem = {\n ...updatedSelf,\n order: newOrder ?? updatedSelf.order ?? ORDER_STEP,\n };\n return out;\n }\n if (newOrder !== undefined) return { ...item, order: newOrder };\n return item;\n });\n const finalSelf = nextItems.find((item) => item.id === itemId);\n if (!finalSelf) throw new Error(`reorder result missing item ${itemId}`);\n return { kind: \"success\", items: nextItems, item: finalSelf };\n}\n\nfunction clampPosition(raw: number | undefined, max: number): number {\n if (typeof raw !== \"number\" || !Number.isFinite(raw)) return max;\n if (raw < 0) return 0;\n if (raw > max) return max;\n return Math.floor(raw);\n}\n\n// ── Delete ────────────────────────────────────────────────────────\n\nexport function handleDeleteItem(items: TodoItem[], itemId: string): ItemsActionResult {\n const target = items.find((item) => item.id === itemId);\n if (!target) {\n return { kind: \"error\", status: 404, error: `item not found: ${itemId}` };\n }\n return { kind: \"success\", items: items.filter((item) => item.id !== itemId) };\n}\n","// Persistence layer — reads/writes todos.json + columns.json under\n// the plugin's runtime.files.data scope root.\n//\n// The data shape mirrors what `server/utils/files/todos-io.ts` (now\n// removed) wrote: a JSON-serialised `TodoItem[]` and `StatusColumn[]`.\n// Pre-#1145 installs that still keep todos under\n// `data/todos/{todos,columns}.json` will need to move them under the\n// scope dir by hand (`data/plugins/%40mulmoclaude%2Ftodo-plugin/`) —\n// the plugin reads from the new path only.\n\nimport type { FileOps } from \"gui-chat-protocol\";\nimport type { TodoItem, StatusColumn } from \"./types\";\nimport { DEFAULT_COLUMNS, normalizeColumns } from \"./handlers/columns\";\nimport { migrateItems } from \"./handlers/items\";\n\nconst TODOS_FILE = \"todos.json\";\nconst COLUMNS_FILE = \"columns.json\";\n\nasync function readJson(files: FileOps, rel: string): Promise<unknown> {\n if (!(await files.exists(rel))) return undefined;\n try {\n return JSON.parse(await files.read(rel));\n } catch {\n return undefined;\n }\n}\n\n// Item-level shape narrowing — drops anything that isn't a usable\n// TodoItem. The four required fields gate the rest of the plugin's\n// invariants (id is the dispatch key, text is rendered, completed\n// drives status backfill, createdAt is the implicit secondary sort).\n// Optional fields (note / labels / status / priority / dueDate /\n// order) get whatever the on-disk JSON says without further\n// narrowing — `migrateItems` defends against bad `status` and\n// missing `order`; the others are presentation-layer.\nfunction isTodoItem(value: unknown): value is TodoItem {\n if (!value || typeof value !== \"object\") return false;\n const obj = value as Record<string, unknown>;\n return typeof obj.id === \"string\" && typeof obj.text === \"string\" && typeof obj.completed === \"boolean\" && typeof obj.createdAt === \"number\";\n}\n\nexport async function loadColumns(files: FileOps): Promise<StatusColumn[]> {\n const raw = await readJson(files, COLUMNS_FILE);\n return normalizeColumns(raw ?? DEFAULT_COLUMNS);\n}\n\nexport async function saveColumns(files: FileOps, columns: StatusColumn[]): Promise<void> {\n await files.write(COLUMNS_FILE, JSON.stringify(columns, null, 2));\n}\n\nexport async function loadTodos(files: FileOps): Promise<TodoItem[]> {\n // `todos.json` is user-editable + workspace-shared, so a corrupted\n // shape (manual edit, partial write from a kill -9, schema drift\n // from a future version) must degrade gracefully rather than\n // crash every dispatch with `rawItems.map is not a function`.\n // Codex review iter on PR #1149.\n const raw = await readJson(files, TODOS_FILE);\n const items = Array.isArray(raw) ? raw.filter(isTodoItem) : [];\n const columns = await loadColumns(files);\n return migrateItems(items, columns);\n}\n\nexport async function saveTodos(files: FileOps, items: TodoItem[]): Promise<void> {\n await files.write(TODOS_FILE, JSON.stringify(items, null, 2));\n}\n","// Pure action handlers for the todos POST route. Same shape as\n// schedulerHandlers.ts: each handler takes the current items + the\n// relevant body fields and returns a discriminated result describing\n// either an HTTP error or the next state. The route handler in\n// todos.ts dispatches to one of these and translates the result into\n// an HTTP response.\n//\n// Keeping the action logic pure (no I/O, no globals) makes every\n// case unit-testable in isolation, and brings the cognitive\n// complexity of the route handler under the lint threshold.\n\nimport type { TodoItem } from \"../types\";\nimport { filterByLabels, listLabelsWithCount, mergeLabels, subtractLabels } from \"../labels\";\nimport { makeId } from \"../internal/utils\";\n\nexport interface TodosActionInput {\n text?: string;\n newText?: string;\n note?: string;\n // For `add`: labels to tag the new item with.\n // For `add_label` / `remove_label`: labels to add to / remove from\n // the item matched by `text`.\n labels?: string[];\n // For `show`: OR-semantics filter that restricts the returned list\n // to items carrying at least one of these labels (case-insensitive).\n filterLabels?: string[];\n}\n\nexport type TodosActionResult =\n | { kind: \"error\"; status: number; error: string }\n | {\n kind: \"success\";\n items: TodoItem[];\n message: string;\n jsonData: Record<string, unknown>;\n };\n\n// Substring match (case-insensitive). Used by delete / update /\n// check / uncheck / add_label / remove_label — all share the same\n// lookup contract.\nexport function findTodoByText(items: TodoItem[], text: string): TodoItem | undefined {\n const needle = text.toLowerCase();\n return items.find((i) => i.text.toLowerCase().includes(needle));\n}\n\nexport function handleShow(items: TodoItem[], input: TodosActionInput): TodosActionResult {\n const filterLabels = input.filterLabels ?? [];\n const filtered = filterByLabels(items, filterLabels);\n const filtering = filterLabels.length > 0;\n const message = filtering\n ? `Showing ${filtered.length} of ${items.length} todo item(s) filtered by: ${filterLabels.join(\", \")}`\n : `Showing ${items.length} todo item(s)`;\n return {\n kind: \"success\",\n items: filtered,\n message,\n jsonData: {\n items: filtered.map((i) => ({\n text: i.text,\n completed: i.completed,\n ...(i.labels && i.labels.length > 0 && { labels: i.labels }),\n })),\n },\n };\n}\n\nexport function handleAdd(items: TodoItem[], input: TodosActionInput): TodosActionResult {\n if (!input.text) {\n return { kind: \"error\", status: 400, error: \"text required\" };\n }\n // Normalise incoming labels by routing them through\n // `mergeLabels([], labels ?? [])` — that handles trim / collapse /\n // case-insensitive dedup in one shot.\n const normalizedLabels = mergeLabels([], input.labels ?? []);\n const item: TodoItem = {\n id: makeId(\"todo\"),\n text: input.text,\n ...(input.note !== undefined && { note: input.note }),\n ...(normalizedLabels.length > 0 && { labels: normalizedLabels }),\n completed: false,\n createdAt: Date.now(),\n };\n return {\n kind: \"success\",\n items: [...items, item],\n message: normalizedLabels.length > 0 ? `Added: \"${input.text}\" [${normalizedLabels.join(\", \")}]` : `Added: \"${input.text}\"`,\n jsonData: { added: input.text, labels: normalizedLabels },\n };\n}\n\nexport function handleDelete(items: TodoItem[], input: TodosActionInput): TodosActionResult {\n if (!input.text) {\n return { kind: \"error\", status: 400, error: \"text required\" };\n }\n const needle = input.text.toLowerCase();\n const next = items.filter((i) => !i.text.toLowerCase().includes(needle));\n const found = next.length < items.length;\n return {\n kind: \"success\",\n items: next,\n message: found ? `Deleted: \"${input.text}\"` : `Item not found: \"${input.text}\"`,\n jsonData: { deleted: input.text },\n };\n}\n\nexport function handleUpdate(items: TodoItem[], input: TodosActionInput): TodosActionResult {\n if (!input.text || !input.newText) {\n return { kind: \"error\", status: 400, error: \"text and newText required\" };\n }\n const target = findTodoByText(items, input.text);\n if (!target) {\n return {\n kind: \"success\",\n items,\n message: `Item not found: \"${input.text}\"`,\n jsonData: {},\n };\n }\n const oldText = target.text;\n const updated: TodoItem = {\n ...target,\n text: input.newText,\n note: input.note !== undefined ? input.note || undefined : target.note,\n };\n const next = items.map((i) => (i.id === target.id ? updated : i));\n return {\n kind: \"success\",\n items: next,\n message: `Updated: \"${oldText}\" → \"${input.newText}\"`,\n jsonData: { oldText, newText: input.newText },\n };\n}\n\nfunction setCompleted(\n items: TodoItem[],\n input: TodosActionInput,\n completed: boolean,\n verb: \"Checked\" | \"Unchecked\",\n jsonKey: \"checkedItem\" | \"uncheckedItem\",\n): TodosActionResult {\n if (!input.text) {\n return { kind: \"error\", status: 400, error: \"text required\" };\n }\n const target = findTodoByText(items, input.text);\n if (!target) {\n return {\n kind: \"success\",\n items,\n message: `Item not found: \"${input.text}\"`,\n jsonData: {},\n };\n }\n const updated: TodoItem = { ...target, completed };\n const next = items.map((i) => (i.id === target.id ? updated : i));\n return {\n kind: \"success\",\n items: next,\n message: `${verb}: \"${target.text}\"`,\n jsonData: { [jsonKey]: target.text },\n };\n}\n\nexport function handleCheck(items: TodoItem[], input: TodosActionInput): TodosActionResult {\n return setCompleted(items, input, true, \"Checked\", \"checkedItem\");\n}\n\nexport function handleUncheck(items: TodoItem[], input: TodosActionInput): TodosActionResult {\n return setCompleted(items, input, false, \"Unchecked\", \"uncheckedItem\");\n}\n\nexport function handleClearCompleted(items: TodoItem[]): TodosActionResult {\n const count = items.filter((i) => i.completed).length;\n const next = items.filter((i) => !i.completed);\n return {\n kind: \"success\",\n items: next,\n message: `Cleared ${count} completed item(s)`,\n jsonData: { clearedCount: count },\n };\n}\n\nexport function handleAddLabel(items: TodoItem[], input: TodosActionInput): TodosActionResult {\n if (!input.text || !input.labels || input.labels.length === 0) {\n return {\n kind: \"error\",\n status: 400,\n error: \"text and a non-empty labels array required\",\n };\n }\n const target = findTodoByText(items, input.text);\n if (!target) {\n return {\n kind: \"success\",\n items,\n message: `Item not found: \"${input.text}\"`,\n jsonData: { notFound: input.text },\n };\n }\n const merged = mergeLabels(target.labels ?? [], input.labels);\n const updated: TodoItem = { ...target, labels: merged };\n const next = items.map((i) => (i.id === target.id ? updated : i));\n return {\n kind: \"success\",\n items: next,\n message: `Labels on \"${target.text}\": ${merged.join(\", \")}`,\n jsonData: { item: target.text, labels: merged },\n };\n}\n\nexport function handleRemoveLabel(items: TodoItem[], input: TodosActionInput): TodosActionResult {\n if (!input.text || !input.labels || input.labels.length === 0) {\n return {\n kind: \"error\",\n status: 400,\n error: \"text and a non-empty labels array required\",\n };\n }\n const target = findTodoByText(items, input.text);\n if (!target) {\n return {\n kind: \"success\",\n items,\n message: `Item not found: \"${input.text}\"`,\n jsonData: { notFound: input.text },\n };\n }\n const remaining = subtractLabels(target.labels ?? [], input.labels);\n const updated: TodoItem = { ...target };\n if (remaining.length > 0) {\n updated.labels = remaining;\n } else {\n delete updated.labels;\n }\n const next = items.map((i) => (i.id === target.id ? updated : i));\n return {\n kind: \"success\",\n items: next,\n message: remaining.length > 0 ? `Labels on \"${target.text}\": ${remaining.join(\", \")}` : `\"${target.text}\" now has no labels`,\n jsonData: { item: target.text, labels: remaining },\n };\n}\n\nexport function handleListLabels(items: TodoItem[]): TodosActionResult {\n const inventory = listLabelsWithCount(items);\n const summary = inventory.map((entry) => `${entry.label} (${entry.count})`).join(\", \");\n const message = inventory.length === 0 ? \"No labels in use\" : `${inventory.length} label(s) in use: ${summary}`;\n return {\n kind: \"success\",\n items,\n message,\n jsonData: { labels: inventory },\n };\n}\n\nconst HANDLERS: Record<string, (items: TodoItem[], input: TodosActionInput) => TodosActionResult> = {\n show: handleShow,\n add: handleAdd,\n delete: handleDelete,\n update: handleUpdate,\n check: handleCheck,\n uncheck: handleUncheck,\n clear_completed: handleClearCompleted,\n add_label: handleAddLabel,\n remove_label: handleRemoveLabel,\n list_labels: handleListLabels,\n};\n\nexport function dispatchTodos(action: string, items: TodoItem[], input: TodosActionInput): TodosActionResult {\n const handler = HANDLERS[action];\n if (!handler) {\n return { kind: \"error\", status: 400, error: `Unknown action: ${action}` };\n }\n return handler(items, input);\n}\n","// Reconcile active \"urgent / high unchecked\" notifications against\n// the current todo list. Single source of truth on the plugin side\n// is `urgent-tickets.json`: a JSON map `todoId → { notificationId,\n// priority, title, body }` written alongside `todos.json` in the\n// plugin's data dir. After every mutating dispatch (LLM action or\n// UI kind) the plugin runs `reconcilePriorityNotifications(...)` so\n// the host notifier's active set converges with the desired set.\n//\n// Design lifted from `server/encore/reconcile.ts` (post-update-API\n// migration):\n//\n// - Tickets-on-disk are the source of truth for \"what bells we\n// own\". The host's `engine.listFor` exists but is not exposed\n// on the plugin-facing `NotifierRuntimeApi`; we don't need it\n// once we keep our own ticket file.\n// - **Drift detection on the trim path.** For every ticket whose\n// item is still notifiable, we recompute the desired title /\n// body / severity from current item state. If anything has\n// drifted, we call `notifier.update(id, patch)` — same\n// notificationId, no `cleared` history record, no flicker. The\n// todo's rename/priority-shift bug (\"two notifications for one\n// item\") collapses to a single in-place edit on the existing\n// entry.\n// - \"No longer applicable\" path: item gone / completed / priority\n// dropped below the notifiable threshold — clear the bell, drop\n// the ticket. The clear DOES write to history, which is fine\n// here: the user resolved the obligation and the audit trail\n// reads as \"you completed this\".\n//\n// Ghost-ticket recovery: when the host bell is deleted out-of-band\n// while the ticket survives (user dismissed via bell UI, crash\n// truncated active.json), the reconciler calls `notifier.get` on\n// the recorded notificationId, finds nothing, drops the ticket,\n// and lets Phase 2 republish a fresh entry. Same pattern Encore\n// uses via `engine.get` (`server/encore/notifier.ts:bellExists`).\n\nimport type { FileOps } from \"gui-chat-protocol\";\nimport type { TodoItem, TodoPriority } from \"../types\";\n\n// ── Notifier surface this module needs ────────────────────────────\n//\n// Mirrors the relevant slice of the host's `NotifierRuntimeApi` —\n// duplicated here so the plugin doesn't import server-internal\n// types. The cast point is in `index.ts`.\n\nexport type NotifiablePriority = \"urgent\" | \"high\";\n\nexport interface PriorityAlertPluginData {\n kind: \"todo-priority\";\n todoId: string;\n /** Snapshot of the item's priority at the last publish / update.\n * Duplicated on the notifier entry so a future debugger reading\n * active.json can see which severity bucket the entry came from\n * without cross-referencing the plugin's ticket store. */\n priority: NotifiablePriority;\n}\n\nexport interface PriorityNotifierApi {\n publish(input: {\n severity: \"urgent\" | \"nudge\";\n lifecycle: \"action\";\n title: string;\n body?: string;\n navigateTarget: string;\n pluginData: PriorityAlertPluginData;\n }): Promise<{ id: string }>;\n update(\n id: string,\n patch: {\n severity?: \"urgent\" | \"nudge\";\n title?: string;\n body?: string;\n pluginData?: PriorityAlertPluginData;\n },\n ): Promise<void>;\n clear(id: string): Promise<void>;\n /** Returns a truthy entry when the bell still exists and belongs\n * to this plugin, otherwise undefined. Used for ghost-bell\n * detection: a ticket whose `notificationId` no longer resolves\n * here means the bell was wiped (user dismissed via UI, crash\n * truncated active.json) and reconcile should re-publish rather\n * than `update` (which would silently no-op). */\n get(id: string): Promise<unknown | undefined>;\n}\n\n// ── Constants ─────────────────────────────────────────────────────\n\nconst PLUGIN_DATA_KIND = \"todo-priority\" as const;\nconst NAVIGATE_TARGET = \"/todos\";\nconst TITLE_MAX = 60;\n// Matches the host notifier engine's `bodyMax` cap. Clamping in the\n// plugin (rather than letting publish/update reject) means\n// `safeUpdate` / `safePublish` can't be tricked into committing a\n// ticket-as-synced for a patch the engine silently no-op'd — the\n// only remaining engine no-op case is unknown id (ghost bell), and\n// that's the accepted limitation documented in the file header.\n// Codex CHANGES REQUESTED on PR #1451.\nconst BODY_MAX = 4000;\nconst TICKETS_FILE = \"urgent-tickets.json\";\n\n// ── Ticket store (the plugin's own source of truth) ───────────────\n\ninterface Ticket {\n todoId: string;\n notificationId: string;\n priority: NotifiablePriority;\n /** Title and body as last rendered to the bell — the drift\n * baseline. The reconciler's trim path compares these against\n * `buildTitle` / `buildBody` recomputed from current item state;\n * if either has drifted (most commonly via an item rename or a\n * note edit), the path calls `notifier.update` in place. Optional\n * on the wire so pre-update-API tickets load cleanly — they read\n * as \"always drifted\" on first sight, which triggers an\n * idempotent update + ticket rewrite that backfills the fields. */\n title?: string;\n body?: string;\n}\n\ninterface TicketsFile {\n tickets: Record<string, Ticket>;\n}\n\nfunction isTicket(value: unknown): value is Ticket {\n if (!value || typeof value !== \"object\") return false;\n const t = value as Record<string, unknown>;\n if (typeof t[\"todoId\"] !== \"string\") return false;\n if (typeof t[\"notificationId\"] !== \"string\") return false;\n if (t[\"priority\"] !== \"urgent\" && t[\"priority\"] !== \"high\") return false;\n // title / body are optional on the wire (legacy tickets) but if\n // present must be strings — otherwise we drop them on read.\n if (t[\"title\"] !== undefined && typeof t[\"title\"] !== \"string\") return false;\n if (t[\"body\"] !== undefined && typeof t[\"body\"] !== \"string\") return false;\n return true;\n}\n\nasync function loadTickets(files: FileOps, log?: ReconcileLog): Promise<TicketsFile> {\n if (!(await files.exists(TICKETS_FILE))) return { tickets: {} };\n let raw: unknown;\n try {\n raw = JSON.parse(await files.read(TICKETS_FILE));\n } catch (err) {\n // Treat malformed JSON as \"no tickets\" rather than crashing the\n // reconcile, but log a warning so this is diagnosable from logs\n // if it ever happens (e.g. partial write from a crash, manual\n // hand-edit that broke the syntax).\n log?.warn(\"priority reconcile: tickets file unparseable; treating as empty\", { file: TICKETS_FILE, error: String(err) });\n return { tickets: {} };\n }\n if (\n !raw ||\n typeof raw !== \"object\" ||\n !(\"tickets\" in raw) ||\n typeof (raw as { tickets?: unknown }).tickets !== \"object\" ||\n (raw as { tickets?: unknown }).tickets === null\n ) {\n log?.warn(\"priority reconcile: tickets file has unexpected shape; treating as empty\", { file: TICKETS_FILE });\n return { tickets: {} };\n }\n const rawTickets = (raw as { tickets: Record<string, unknown> }).tickets;\n const out: Record<string, Ticket> = {};\n for (const [key, value] of Object.entries(rawTickets)) {\n if (!isTicket(value)) continue;\n if (value.todoId !== key) continue;\n out[key] = value;\n }\n return { tickets: out };\n}\n\nasync function saveTickets(files: FileOps, file: TicketsFile): Promise<void> {\n await files.write(TICKETS_FILE, JSON.stringify(file, null, 2));\n}\n\n// ── Priority → notification mapping ───────────────────────────────\n\nfunction isNotifiablePriority(priority: TodoPriority | undefined): priority is NotifiablePriority {\n return priority === \"urgent\" || priority === \"high\";\n}\n\nfunction severityFor(priority: NotifiablePriority): \"urgent\" | \"nudge\" {\n return priority === \"urgent\" ? \"urgent\" : \"nudge\";\n}\n\n// ── Title / body formatting ───────────────────────────────────────\n\nfunction truncate(text: string, max: number): string {\n return text.length <= max ? text : `${text.slice(0, max - 1)}…`;\n}\n\n// Title is the todo text verbatim (truncated). Severity is already\n// signalled by the bell's color badge and on-disk `pluginData.priority`,\n// so adding \"Urgent: \" / \"High priority: \" to the title is redundant.\nfunction buildTitle(item: TodoItem): string {\n return truncate(item.text, TITLE_MAX);\n}\n\n// Returns the empty string (NOT undefined) when there is no note /\n// dueDate. The empty-string contract is what lets \"removed body\"\n// flow through the notifier's patch API: the engine treats an\n// absent patch field as \"leave alone\", so we need a concrete value\n// to push. Empty body renders identically to no body in the bell\n// UI's v-if templates, so this is a presentation-equivalent\n// representation we can compare and persist without ambiguity.\n//\n// Output is clamped to `BODY_MAX` so the engine's publish/update\n// validation can never reject for body length — see the BODY_MAX\n// rationale above.\nfunction buildBody(item: TodoItem): string {\n const note = item.note?.trim();\n let body = \"\";\n if (note) body = note;\n else if (item.dueDate) body = `Due ${item.dueDate}`;\n return body.length <= BODY_MAX ? body : `${body.slice(0, BODY_MAX - 1)}…`;\n}\n\n// ── Reconcile (the IO-bound entry point) ──────────────────────────\n\ninterface ReconcileLog {\n warn: (msg: string, data?: object) => void;\n}\n\nasync function safeClear(notifier: PriorityNotifierApi, notificationId: string, todoId: string, log?: ReconcileLog): Promise<boolean> {\n try {\n await notifier.clear(notificationId);\n return true;\n } catch (err) {\n // Caller MUST consult the return value before destructive ticket\n // cleanup — swallowing a clear failure and dropping the ticket\n // would orphan the bell forever (no ticket means the next\n // reconcile has nothing to retry).\n log?.warn(\"priority reconcile: clear failed\", { notificationId, todoId, error: String(err) });\n return false;\n }\n}\n\nasync function safeUpdate(\n notifier: PriorityNotifierApi,\n notificationId: string,\n patch: {\n severity?: \"urgent\" | \"nudge\";\n title?: string;\n body?: string;\n pluginData?: PriorityAlertPluginData;\n },\n todoId: string,\n log?: ReconcileLog,\n): Promise<boolean> {\n try {\n await notifier.update(notificationId, patch);\n // `notifier.update` is silent on three engine-level no-ops:\n // 1. unknown id (ghost bell),\n // 2. cross-plugin id,\n // 3. merged-shape validation failure.\n // Each is either caught upstream or structurally impossible by\n // the time we reach this call:\n // - (1) is filtered by the `notifier.get` ghost-check in\n // `reconcilePriorityNotifications` BEFORE this function is\n // invoked, so the entry was alive at check-time. A TOCTOU\n // race could in theory dismiss it between check and update;\n // the worst case is the next reconcile catches it and\n // drops the now-ghost ticket for Phase-2 republish.\n // - (2) can't happen by construction: every id we hold came\n // from our own `notifier.publish`.\n // - (3) can't happen either: title is clamped to TITLE_MAX,\n // body to BODY_MAX, severity is always urgent/nudge,\n // navigateTarget is \"/todos\", and lifecycle is action.\n // So a non-throwing call means the bell really did update.\n return true;\n } catch (err) {\n // Caller MUST consult the return value — committing a ticket\n // rewrite after a failed update would erase the drift signal,\n // so the next reconcile would think the bell is in sync while\n // it's actually still stale.\n log?.warn(\"priority reconcile: update failed\", { notificationId, todoId, error: String(err) });\n return false;\n }\n}\n\nasync function safePublish(notifier: PriorityNotifierApi, item: TodoItem, priority: NotifiablePriority, log?: ReconcileLog): Promise<string | null> {\n try {\n const { id } = await notifier.publish({\n severity: severityFor(priority),\n lifecycle: \"action\",\n title: buildTitle(item),\n body: buildBody(item),\n navigateTarget: NAVIGATE_TARGET,\n pluginData: { kind: PLUGIN_DATA_KIND, todoId: item.id, priority },\n });\n return id;\n } catch (err) {\n log?.warn(\"priority reconcile: publish failed\", { todoId: item.id, error: String(err) });\n return null;\n }\n}\n\n/** Reconcile the plugin's tickets and the host's bell entries with\n * the current item list. After this resolves:\n *\n * - every notifiable item has a ticket and a live bell, with the\n * bell's severity / title / body matching the item's current\n * state;\n * - no ticket references a non-notifiable item.\n *\n * Idempotent and tolerant of partial state. Drift is detected per-\n * ticket against the title / body / priority stored at last publish\n * or update; an item rename flows through `notifier.update` rather\n * than clear-then-publish, preserving the notificationId. */\nexport async function reconcilePriorityNotifications(items: TodoItem[], notifier: PriorityNotifierApi, files: FileOps, log?: ReconcileLog): Promise<void> {\n const ticketsFile = await loadTickets(files, log);\n const itemsById = new Map(items.map((item) => [item.id, item]));\n let dirty = false;\n\n // Phase 1: walk existing tickets — clear stale, update in place\n // on drift, leave alone on exact match. Each notifier op is gated\n // on success: a failed clear/update keeps the ticket as-is so the\n // next reconcile retries from the same drift signal.\n for (const [todoId, ticket] of Object.entries(ticketsFile.tickets)) {\n const item = itemsById.get(todoId);\n const stillNotifiable = item !== undefined && !item.completed && isNotifiablePriority(item.priority);\n\n if (!stillNotifiable) {\n const cleared = await safeClear(notifier, ticket.notificationId, todoId, log);\n if (cleared) {\n delete ticketsFile.tickets[todoId];\n dirty = true;\n }\n continue;\n }\n\n // Ghost-bell check: if the host no longer has this entry (user\n // dismissed via the bell UI, or a crash wiped active.json),\n // `notifier.update` would silently no-op and our ticket would\n // converge to a fake \"in-sync\" state — the bell would never\n // come back. Drop the ticket here so Phase 2's \"publish for\n // unticketed notifiable item\" branch re-fires fresh, same\n // shape Encore uses (`server/encore/reconcile.ts`'s\n // `refreshGhostTicket`). Codex CHANGES REQUESTED, PR #1451.\n const bellAlive = (await notifier.get(ticket.notificationId)) !== undefined;\n if (!bellAlive) {\n log?.warn(\"priority reconcile: ghost ticket dropped; Phase 2 will re-publish\", { notificationId: ticket.notificationId, todoId });\n delete ticketsFile.tickets[todoId];\n dirty = true;\n continue;\n }\n\n const currentPriority: NotifiablePriority = item.priority as NotifiablePriority;\n const desiredTitle = buildTitle(item);\n const desiredBody = buildBody(item);\n\n const priorityDrift = currentPriority !== ticket.priority;\n const titleDrift = ticket.title !== desiredTitle;\n const bodyDrift = ticket.body !== desiredBody;\n\n if (!priorityDrift && !titleDrift && !bodyDrift) continue;\n\n // In-place update: same notificationId, no flicker, no history\n // record. Body is sent on every drift (including the \"note\n // removed\" case): `buildBody` returns \"\" rather than undefined\n // so this branch can always push a concrete value through the\n // notifier's patch API.\n const updated = await safeUpdate(\n notifier,\n ticket.notificationId,\n {\n ...(priorityDrift ? { severity: severityFor(currentPriority) } : {}),\n ...(titleDrift ? { title: desiredTitle } : {}),\n ...(bodyDrift ? { body: desiredBody } : {}),\n ...(priorityDrift ? { pluginData: { kind: PLUGIN_DATA_KIND, todoId, priority: currentPriority } } : {}),\n },\n todoId,\n log,\n );\n if (updated) {\n ticketsFile.tickets[todoId] = { todoId, notificationId: ticket.notificationId, priority: currentPriority, title: desiredTitle, body: desiredBody };\n dirty = true;\n }\n }\n\n // Phase 2: publish bells for notifiable items that don't have a\n // ticket yet.\n for (const item of items) {\n if (item.completed || !isNotifiablePriority(item.priority)) continue;\n if (ticketsFile.tickets[item.id]) continue;\n const newId = await safePublish(notifier, item, item.priority, log);\n if (newId === null) continue;\n ticketsFile.tickets[item.id] = {\n todoId: item.id,\n notificationId: newId,\n priority: item.priority,\n title: buildTitle(item),\n body: buildBody(item),\n };\n dirty = true;\n }\n\n if (dirty) {\n try {\n await saveTickets(files, ticketsFile);\n } catch (err) {\n log?.warn(\"priority reconcile: tickets save failed\", { error: String(err) });\n }\n }\n}\n","// Todo plugin — server side. Migration from the built-in plugin\n// (#1145) is fully done at this point; this is the active handler\n// for `manageTodoList`.\n//\n// Two callers, two arg shapes:\n//\n// 1. **LLM (MCP) tool call** — args have an `action` field\n// (\"show\" | \"add\" | \"delete\" | \"update\" | \"check\" | ...).\n// Dispatched through `dispatchLlmAction` in handlers/llm.ts.\n// Response carries `message` + `jsonData` for the LLM and\n// `data.items` for the View.\n//\n// 2. **Frontend Vue View** — args have a `kind` field\n// (\"listAll\" | \"itemCreate\" | \"itemPatch\" | \"itemMove\" |\n// \"itemDelete\" | \"columnsAdd\" | \"columnPatch\" | \"columnDelete\"\n// | \"columnsOrder\"). Used by the kanban / file-explorer UI\n// via `runtime.dispatch({kind: ...})`. Response carries\n// `data.items` + `data.columns` + an optional `item`.\n//\n// Both paths share the same on-disk store (`todos.json` +\n// `columns.json` under `runtime.files.data`). All mutating actions\n// publish a `changed` pubsub event so multi-tab views auto-refresh.\n\nimport { definePlugin, type PluginRuntime } from \"gui-chat-protocol\";\nimport { TOOL_DEFINITION } from \"./definition\";\nimport { loadTodos, saveTodos, loadColumns, saveColumns } from \"./io\";\nimport { dispatchTodos, type TodosActionInput } from \"./handlers/llm\";\nimport { handleAddColumn, handleDeleteColumn, handlePatchColumn, handleReorderColumns } from \"./handlers/columns\";\nimport { handleCreate, handleDeleteItem, handleMove, handlePatch, type CreateInput, type MoveInput, type PatchInput } from \"./handlers/items\";\nimport { reconcilePriorityNotifications, type PriorityNotifierApi } from \"./handlers/priority-notifier\";\n\nexport { TOOL_DEFINITION };\nexport type { TodoItem, TodoPriority, StatusColumn, TodoData } from \"./types\";\n\nconst READ_ONLY_ACTIONS = new Set([\"show\", \"list_labels\"]);\n\ninterface UiKindMap {\n listAll: Record<string, never>;\n itemCreate: CreateInput;\n itemPatch: { id: string } & PatchInput;\n itemMove: { id: string } & MoveInput;\n itemDelete: { id: string };\n columnsAdd: { label?: string; isDone?: boolean };\n columnPatch: { id: string; label?: string; isDone?: boolean };\n columnDelete: { id: string };\n columnsOrder: { ids: string[] };\n}\n\ntype UiArgs = { [K in keyof UiKindMap]: { kind: K } & UiKindMap[K] }[keyof UiKindMap];\n\ninterface LlmArgs extends TodosActionInput {\n action: string;\n}\n\nfunction isLlmArgs(value: unknown): value is LlmArgs {\n return typeof value === \"object\" && value !== null && \"action\" in value && typeof (value as { action: unknown }).action === \"string\";\n}\n\nfunction isUiArgs(value: unknown): value is UiArgs {\n return typeof value === \"object\" && value !== null && \"kind\" in value && typeof (value as { kind: unknown }).kind === \"string\";\n}\n\n// Host extension: MulmoClaude attaches a per-plugin `notifier` to the\n// PluginRuntime (publish / clear). Type-only — the runtime object\n// the host hands us at boot already has the field; this cast just\n// teaches TypeScript about it without importing server-internal\n// types. Same pattern as debug-plugin.\ntype MulmoclaudeRuntime = PluginRuntime & {\n notifier: PriorityNotifierApi;\n};\n\nexport default definePlugin((runtime) => {\n const { pubsub, files, log } = runtime;\n const { notifier } = runtime as MulmoclaudeRuntime;\n\n // Single source of truth for the desired notification set. Called\n // after every save so the notifier converges on\n // `priority ∈ {urgent, high} && !completed`. Idempotent — extra\n // calls are cheap (load tickets + diff) and never produce\n // duplicates. The plugin's own `urgent-tickets.json` is the\n // canonical \"what bells we own\" record; we never query the host\n // notifier's active set.\n async function reconcileNotifications(items: Awaited<ReturnType<typeof loadTodos>>): Promise<void> {\n await reconcilePriorityNotifications(items, notifier, files.data, log);\n }\n\n // Boot reconcile: catches the post-crash case where active.json\n // outlived a mutation that should have cleared its entry, or where\n // todos.json was hand-edited while the host was down. Kicked off\n // here (not awaited at init — plugin init must stay non-blocking),\n // but each handler awaits the promise before its own reconcile so\n // there's no race between the boot pass and the first dispatch.\n const bootReconcile: Promise<void> = (async () => {\n try {\n const items = await loadTodos(files.data);\n await reconcileNotifications(items);\n } catch (err) {\n log.warn(\"boot reconcile failed\", { error: String(err) });\n }\n })();\n\n // ── LLM action path ───────────────────────────────────────────\n async function handleLlm(args: LlmArgs) {\n await bootReconcile;\n const { action, ...input } = args;\n log.info(\"dispatch llm\", { action });\n const items = await loadTodos(files.data);\n const result = dispatchTodos(action, items, input);\n if (result.kind === \"error\") {\n log.warn(\"dispatch llm error\", { action, error: result.error });\n return { error: result.error, status: result.status };\n }\n if (!READ_ONLY_ACTIONS.has(action)) {\n await saveTodos(files.data, result.items);\n await reconcileNotifications(result.items);\n pubsub.publish(\"changed\", { reason: \"llm-action\", action });\n }\n return {\n data: { items: result.items },\n message: result.message,\n jsonData: result.jsonData,\n instructions: \"Display the updated todo list to the user.\",\n };\n }\n\n // ── UI dispatch path ──────────────────────────────────────────\n // eslint-disable-next-line sonarjs/cognitive-complexity -- the\n // switch covers 9 disjoint UI actions; further extraction would\n // just spread one big case statement across helper files for no\n // readability win.\n async function handleUi(args: UiArgs) {\n await bootReconcile;\n log.info(\"dispatch ui\", { kind: args.kind });\n if (args.kind === \"listAll\") {\n const [items, columns] = await Promise.all([loadTodos(files.data), loadColumns(files.data)]);\n return { data: { items, columns } };\n }\n const [items, columns] = await Promise.all([loadTodos(files.data), loadColumns(files.data)]);\n if (args.kind === \"itemCreate\") {\n const result = handleCreate(items, columns, args);\n if (result.kind === \"error\") return { error: result.error, status: result.status };\n await saveTodos(files.data, result.items);\n await reconcileNotifications(result.items);\n pubsub.publish(\"changed\", { reason: \"item-create\" });\n return { data: { items: result.items, columns }, ...(result.item && { item: result.item }) };\n }\n if (args.kind === \"itemPatch\") {\n const result = handlePatch(items, columns, args.id, args);\n if (result.kind === \"error\") return { error: result.error, status: result.status };\n await saveTodos(files.data, result.items);\n await reconcileNotifications(result.items);\n pubsub.publish(\"changed\", { reason: \"item-patch\", id: args.id });\n return { data: { items: result.items, columns }, ...(result.item && { item: result.item }) };\n }\n if (args.kind === \"itemMove\") {\n const result = handleMove(items, columns, args.id, args);\n if (result.kind === \"error\") return { error: result.error, status: result.status };\n await saveTodos(files.data, result.items);\n await reconcileNotifications(result.items);\n pubsub.publish(\"changed\", { reason: \"item-move\", id: args.id });\n return { data: { items: result.items, columns }, ...(result.item && { item: result.item }) };\n }\n if (args.kind === \"itemDelete\") {\n const result = handleDeleteItem(items, args.id);\n if (result.kind === \"error\") return { error: result.error, status: result.status };\n await saveTodos(files.data, result.items);\n await reconcileNotifications(result.items);\n pubsub.publish(\"changed\", { reason: \"item-delete\", id: args.id });\n return { data: { items: result.items, columns } };\n }\n if (args.kind === \"columnsAdd\") {\n const result = handleAddColumn(columns, items, args);\n if (result.kind === \"error\") return { error: result.error, status: result.status };\n await saveColumns(files.data, result.columns);\n if (result.items) await saveTodos(files.data, result.items);\n if (result.items) await reconcileNotifications(result.items);\n pubsub.publish(\"changed\", { reason: \"column-add\" });\n return { data: { items: result.items ?? items, columns: result.columns } };\n }\n if (args.kind === \"columnPatch\") {\n const result = handlePatchColumn(columns, args.id, args, items);\n if (result.kind === \"error\") return { error: result.error, status: result.status };\n await saveColumns(files.data, result.columns);\n if (result.items) await saveTodos(files.data, result.items);\n if (result.items) await reconcileNotifications(result.items);\n pubsub.publish(\"changed\", { reason: \"column-patch\", id: args.id });\n return { data: { items: result.items ?? items, columns: result.columns } };\n }\n if (args.kind === \"columnDelete\") {\n const result = handleDeleteColumn(columns, args.id, items);\n if (result.kind === \"error\") return { error: result.error, status: result.status };\n await saveColumns(files.data, result.columns);\n if (result.items) await saveTodos(files.data, result.items);\n if (result.items) await reconcileNotifications(result.items);\n pubsub.publish(\"changed\", { reason: \"column-delete\", id: args.id });\n return { data: { items: result.items ?? items, columns: result.columns } };\n }\n if (args.kind === \"columnsOrder\") {\n const result = handleReorderColumns(columns, args.ids);\n if (result.kind === \"error\") return { error: result.error, status: result.status };\n await saveColumns(files.data, result.columns);\n pubsub.publish(\"changed\", { reason: \"columns-order\" });\n return { data: { items, columns: result.columns } };\n }\n const exhaustive: never = args;\n return { error: `unknown kind: ${JSON.stringify(exhaustive)}`, status: 400 };\n }\n\n return {\n TOOL_DEFINITION,\n async manageTodoList(rawArgs: unknown) {\n if (isLlmArgs(rawArgs)) return handleLlm(rawArgs);\n if (isUiArgs(rawArgs)) return handleUi(rawArgs);\n return { error: \"unknown args shape — expected { action: ... } or { kind: ... }\", status: 400 };\n },\n };\n});\n"],"x_google_ignoreList":[0],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare function makeId(prefix: string): string;
2
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
3
+ export declare function slugify(label: string, fallback: string): string;
4
+ export declare function disambiguateSlug(base: string, existingIds: ReadonlySet<string>): string;
package/dist/io.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { FileOps } from 'gui-chat-protocol';
2
+ import { TodoItem, StatusColumn } from './types';
3
+ export declare function loadColumns(files: FileOps): Promise<StatusColumn[]>;
4
+ export declare function saveColumns(files: FileOps, columns: StatusColumn[]): Promise<void>;
5
+ export declare function loadTodos(files: FileOps): Promise<TodoItem[]>;
6
+ export declare function saveTodos(files: FileOps, items: TodoItem[]): Promise<void>;
@@ -0,0 +1,85 @@
1
+ //#region src/labels.ts
2
+ function normalizeLabel(raw) {
3
+ const collapsed = raw.replace(/\s+/g, " ").trim();
4
+ return collapsed.length > 0 ? collapsed : null;
5
+ }
6
+ function labelsEqual(left, right) {
7
+ const normLeft = normalizeLabel(left);
8
+ const normRight = normalizeLabel(right);
9
+ if (normLeft === null || normRight === null) return false;
10
+ return normLeft.toLowerCase() === normRight.toLowerCase();
11
+ }
12
+ var LABEL_PALETTE = [
13
+ "bg-blue-100 text-blue-700",
14
+ "bg-green-100 text-green-700",
15
+ "bg-purple-100 text-purple-700",
16
+ "bg-yellow-100 text-yellow-700",
17
+ "bg-pink-100 text-pink-700",
18
+ "bg-indigo-100 text-indigo-700",
19
+ "bg-red-100 text-red-700",
20
+ "bg-teal-100 text-teal-700"
21
+ ];
22
+ function colorForLabel(label) {
23
+ const key = label.toLowerCase();
24
+ let hash = 0;
25
+ for (let i = 0; i < key.length; i++) hash = hash * 31 + key.charCodeAt(i) >>> 0;
26
+ return LABEL_PALETTE[hash % LABEL_PALETTE.length];
27
+ }
28
+ function filterByLabels(items, filterLabels) {
29
+ if (filterLabels.length === 0) return [...items];
30
+ const wanted = new Set(filterLabels.map(normalizeLabel).filter((label) => label !== null).map((label) => label.toLowerCase()));
31
+ if (wanted.size === 0) return [...items];
32
+ return items.filter((item) => {
33
+ return (item.labels ?? []).some((label) => {
34
+ const normalized = normalizeLabel(label);
35
+ return normalized !== null && wanted.has(normalized.toLowerCase());
36
+ });
37
+ });
38
+ }
39
+ function listLabelsWithCount(items) {
40
+ const groups = /* @__PURE__ */ new Map();
41
+ for (const item of items) {
42
+ const seenInItem = /* @__PURE__ */ new Set();
43
+ for (const raw of item.labels ?? []) {
44
+ const normalized = normalizeLabel(raw);
45
+ if (normalized === null) continue;
46
+ const key = normalized.toLowerCase();
47
+ if (seenInItem.has(key)) continue;
48
+ seenInItem.add(key);
49
+ const existing = groups.get(key);
50
+ if (existing) existing.count++;
51
+ else groups.set(key, {
52
+ label: normalized,
53
+ count: 1
54
+ });
55
+ }
56
+ }
57
+ return [...groups.values()].sort((left, right) => {
58
+ if (right.count !== left.count) return right.count - left.count;
59
+ return left.label.toLowerCase() < right.label.toLowerCase() ? -1 : left.label.toLowerCase() > right.label.toLowerCase() ? 1 : 0;
60
+ });
61
+ }
62
+ function mergeLabels(existing, adding) {
63
+ const result = [];
64
+ const seen = /* @__PURE__ */ new Set();
65
+ const push = (label) => {
66
+ const normalized = normalizeLabel(label);
67
+ if (normalized === null) return;
68
+ const key = normalized.toLowerCase();
69
+ if (seen.has(key)) return;
70
+ seen.add(key);
71
+ result.push(normalized);
72
+ };
73
+ for (const label of existing) push(label);
74
+ for (const label of adding) push(label);
75
+ return result;
76
+ }
77
+ function subtractLabels(existing, removing) {
78
+ const toRemove = new Set(removing.map(normalizeLabel).filter((label) => label !== null).map((label) => label.toLowerCase()));
79
+ if (toRemove.size === 0) return existing.map(normalizeLabel).filter((label) => label !== null);
80
+ return existing.map(normalizeLabel).filter((label) => label !== null).filter((label) => !toRemove.has(label.toLowerCase()));
81
+ }
82
+ //#endregion
83
+ export { listLabelsWithCount as a, subtractLabels as c, labelsEqual as i, colorForLabel as n, mergeLabels as o, filterByLabels as r, normalizeLabel as s, LABEL_PALETTE as t };
84
+
85
+ //# sourceMappingURL=labels-C4z7FMoE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels-C4z7FMoE.js","names":[],"sources":["../src/labels.ts"],"sourcesContent":["// Pure helpers for todo labels. Used by both the server route\n// (server/routes/todos.ts) and the Vue views (View.vue / Preview.vue),\n// so the file is kept free of Node-only and browser-only APIs.\n//\n// Storage contract:\n// - Labels are case-preserving strings (e.g. \"Work\", \"Groceries\")\n// - Matching / deduplication is case-insensitive\n// - Whitespace is trimmed and collapsed on normalise\n// - Empty / whitespace-only labels are rejected\n\n// ── Normalisation and comparison ──────────────────────────────────\n\n// Trim leading/trailing whitespace and collapse internal whitespace\n// runs to single spaces. Returns `null` for empty / whitespace-only\n// input so callers can filter out bad entries at the boundary.\nexport function normalizeLabel(raw: string): string | null {\n const collapsed = raw.replace(/\\s+/g, \" \").trim();\n return collapsed.length > 0 ? collapsed : null;\n}\n\n// Case-insensitive label equality. Both inputs are normalised first\n// so `\" Work \"` and `\"work\"` compare equal.\nexport function labelsEqual(left: string, right: string): boolean {\n const normLeft = normalizeLabel(left);\n const normRight = normalizeLabel(right);\n if (normLeft === null || normRight === null) return false;\n return normLeft.toLowerCase() === normRight.toLowerCase();\n}\n\n// ── Colour assignment ─────────────────────────────────────────────\n\n// Eight Tailwind pastel chip styles. Enough visual variety without\n// turning the UI into a rainbow. Every label maps deterministically\n// to one of these via `colorForLabel`.\nexport const LABEL_PALETTE: readonly string[] = [\n \"bg-blue-100 text-blue-700\",\n \"bg-green-100 text-green-700\",\n \"bg-purple-100 text-purple-700\",\n \"bg-yellow-100 text-yellow-700\",\n \"bg-pink-100 text-pink-700\",\n \"bg-indigo-100 text-indigo-700\",\n \"bg-red-100 text-red-700\",\n \"bg-teal-100 text-teal-700\",\n] as const;\n\n// Deterministic colour class for a label. Same label → same colour\n// across sessions and across clients. Case-insensitive so that\n// `\"Work\"` and `\"work\"` look identical even if they drift in\n// different items over time.\nexport function colorForLabel(label: string): string {\n const key = label.toLowerCase();\n let hash = 0;\n for (let i = 0; i < key.length; i++) {\n // Classic 31-multiplier string hash. Kept as unsigned via >>> 0\n // so the modulo at the end doesn't produce negatives.\n hash = (hash * 31 + key.charCodeAt(i)) >>> 0;\n }\n return LABEL_PALETTE[hash % LABEL_PALETTE.length];\n}\n\n// ── Filtering ─────────────────────────────────────────────────────\n\n// OR-semantics filter: return items that have at least one label in\n// `filterLabels`. Matching is case-insensitive. An empty filter list\n// is a pass-through — callers don't need to special-case \"no filter\"\n// themselves.\n//\n// Items whose `labels` is `undefined` or `[]` are excluded when\n// `filterLabels` is non-empty, since they have nothing to match.\nexport function filterByLabels<T extends { labels?: string[] }>(items: readonly T[], filterLabels: readonly string[]): T[] {\n if (filterLabels.length === 0) return [...items];\n const wanted = new Set(\n filterLabels\n .map(normalizeLabel)\n .filter((label): label is string => label !== null)\n .map((label) => label.toLowerCase()),\n );\n if (wanted.size === 0) return [...items];\n return items.filter((item) => {\n const itemLabels = item.labels ?? [];\n return itemLabels.some((label) => {\n const normalized = normalizeLabel(label);\n return normalized !== null && wanted.has(normalized.toLowerCase());\n });\n });\n}\n\n// ── Label inventory ──────────────────────────────────────────────\n\n// Distinct-label summary for the whole collection. Counts are\n// case-insensitive: `\"Work\"` and `\"work\"` merge into one row. The\n// displayed form is the first spelling encountered while scanning,\n// which is usually the most recently added item at the top of the\n// list — stable enough in practice and avoids a second full pass.\n//\n// Sorted by count desc, then by the displayed label asc (case-\n// insensitive) for deterministic output.\nexport function listLabelsWithCount(items: readonly { labels?: string[] }[]): { label: string; count: number }[] {\n const groups = new Map<string, { label: string; count: number }>();\n for (const item of items) {\n const seenInItem = new Set<string>();\n for (const raw of item.labels ?? []) {\n const normalized = normalizeLabel(raw);\n if (normalized === null) continue;\n const key = normalized.toLowerCase();\n // Guard against the same label appearing twice within one item\n // (shouldn't happen post-mergeLabels but be safe).\n if (seenInItem.has(key)) continue;\n seenInItem.add(key);\n const existing = groups.get(key);\n if (existing) {\n existing.count++;\n } else {\n groups.set(key, { label: normalized, count: 1 });\n }\n }\n }\n return [...groups.values()].sort((left, right) => {\n if (right.count !== left.count) return right.count - left.count;\n return left.label.toLowerCase() < right.label.toLowerCase() ? -1 : left.label.toLowerCase() > right.label.toLowerCase() ? 1 : 0;\n });\n}\n\n// ── Set operations (for add_label / remove_label) ────────────────\n\n// Union of `existing` and `adding`, de-duped case-insensitively.\n// Preserves the order of `existing`, then appends newly-introduced\n// labels in the order they appear in `adding`. Normalises both\n// sides (trim/collapse) before comparison and storage.\nexport function mergeLabels(existing: readonly string[], adding: readonly string[]): string[] {\n const result: string[] = [];\n const seen = new Set<string>();\n const push = (label: string): void => {\n const normalized = normalizeLabel(label);\n if (normalized === null) return;\n const key = normalized.toLowerCase();\n if (seen.has(key)) return;\n seen.add(key);\n result.push(normalized);\n };\n for (const label of existing) push(label);\n for (const label of adding) push(label);\n return result;\n}\n\n// Remove `removing` labels from `existing`, case-insensitively.\n// Removing a label that isn't present is a no-op. The order of\n// surviving labels is preserved. Normalises both sides first.\nexport function subtractLabels(existing: readonly string[], removing: readonly string[]): string[] {\n const toRemove = new Set(\n removing\n .map(normalizeLabel)\n .filter((label): label is string => label !== null)\n .map((label) => label.toLowerCase()),\n );\n if (toRemove.size === 0) {\n return existing.map(normalizeLabel).filter((label): label is string => label !== null);\n }\n return existing\n .map(normalizeLabel)\n .filter((label): label is string => label !== null)\n .filter((label) => !toRemove.has(label.toLowerCase()));\n}\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export declare function normalizeLabel(raw: string): string | null;
2
+ export declare function labelsEqual(left: string, right: string): boolean;
3
+ export declare const LABEL_PALETTE: readonly string[];
4
+ export declare function colorForLabel(label: string): string;
5
+ export declare function filterByLabels<T extends {
6
+ labels?: string[];
7
+ }>(items: readonly T[], filterLabels: readonly string[]): T[];
8
+ export declare function listLabelsWithCount(items: readonly {
9
+ labels?: string[];
10
+ }[]): {
11
+ label: string;
12
+ count: number;
13
+ }[];
14
+ export declare function mergeLabels(existing: readonly string[], adding: readonly string[]): string[];
15
+ export declare function subtractLabels(existing: readonly string[], removing: readonly string[]): string[];
@@ -0,0 +1,25 @@
1
+ declare const _default: {
2
+ clearFilters: string;
3
+ deleteItem: string;
4
+ apiError: string;
5
+ loadFailed: string;
6
+ heading: string;
7
+ completedRatio: string;
8
+ filter: string;
9
+ noItems: string;
10
+ noMatchingFilter: string;
11
+ update: string;
12
+ clearCompleted: string;
13
+ clearButton: string;
14
+ deleteSymbol: string;
15
+ cancel: string;
16
+ previewHeaderIcon: string;
17
+ previewDoneIcon: string;
18
+ previewPendingIcon: string;
19
+ previewMoreLabels: string;
20
+ previewMoreItems: string;
21
+ collapse: string;
22
+ expand: string;
23
+ yamlParseError: string;
24
+ };
25
+ export default _default;
@@ -0,0 +1,25 @@
1
+ declare const _default: {
2
+ clearFilters: string;
3
+ deleteItem: string;
4
+ apiError: string;
5
+ loadFailed: string;
6
+ heading: string;
7
+ completedRatio: string;
8
+ filter: string;
9
+ noItems: string;
10
+ noMatchingFilter: string;
11
+ update: string;
12
+ clearCompleted: string;
13
+ clearButton: string;
14
+ deleteSymbol: string;
15
+ cancel: string;
16
+ previewHeaderIcon: string;
17
+ previewDoneIcon: string;
18
+ previewPendingIcon: string;
19
+ previewMoreLabels: string;
20
+ previewMoreItems: string;
21
+ collapse: string;
22
+ expand: string;
23
+ yamlParseError: string;
24
+ };
25
+ export default _default;
@@ -0,0 +1,25 @@
1
+ declare const _default: {
2
+ clearFilters: string;
3
+ deleteItem: string;
4
+ apiError: string;
5
+ loadFailed: string;
6
+ heading: string;
7
+ completedRatio: string;
8
+ filter: string;
9
+ noItems: string;
10
+ noMatchingFilter: string;
11
+ update: string;
12
+ clearCompleted: string;
13
+ clearButton: string;
14
+ deleteSymbol: string;
15
+ cancel: string;
16
+ previewHeaderIcon: string;
17
+ previewDoneIcon: string;
18
+ previewPendingIcon: string;
19
+ previewMoreLabels: string;
20
+ previewMoreItems: string;
21
+ collapse: string;
22
+ expand: string;
23
+ yamlParseError: string;
24
+ };
25
+ export default _default;
@@ -0,0 +1,25 @@
1
+ declare const _default: {
2
+ clearFilters: string;
3
+ deleteItem: string;
4
+ apiError: string;
5
+ loadFailed: string;
6
+ heading: string;
7
+ completedRatio: string;
8
+ filter: string;
9
+ noItems: string;
10
+ noMatchingFilter: string;
11
+ update: string;
12
+ clearCompleted: string;
13
+ clearButton: string;
14
+ deleteSymbol: string;
15
+ cancel: string;
16
+ previewHeaderIcon: string;
17
+ previewDoneIcon: string;
18
+ previewPendingIcon: string;
19
+ previewMoreLabels: string;
20
+ previewMoreItems: string;
21
+ collapse: string;
22
+ expand: string;
23
+ yamlParseError: string;
24
+ };
25
+ export default _default;
@@ -0,0 +1,28 @@
1
+ export declare function useT(): import('vue').ComputedRef<{
2
+ clearFilters: string;
3
+ deleteItem: string;
4
+ apiError: string;
5
+ loadFailed: string;
6
+ heading: string;
7
+ completedRatio: string;
8
+ filter: string;
9
+ noItems: string;
10
+ noMatchingFilter: string;
11
+ update: string;
12
+ clearCompleted: string;
13
+ clearButton: string;
14
+ deleteSymbol: string;
15
+ cancel: string;
16
+ previewHeaderIcon: string;
17
+ previewDoneIcon: string;
18
+ previewPendingIcon: string;
19
+ previewMoreLabels: string;
20
+ previewMoreItems: string;
21
+ collapse: string;
22
+ expand: string;
23
+ yamlParseError: string;
24
+ }>;
25
+ /** Tiny `{name}` placeholder substitution — the plugin's locale
26
+ * strings use the same syntax as vue-i18n templates so the keys
27
+ * carry over from the host's i18n verbatim. */
28
+ export declare function format(template: string, params: Record<string, string | number>): string;
@@ -0,0 +1,25 @@
1
+ declare const _default: {
2
+ clearFilters: string;
3
+ deleteItem: string;
4
+ apiError: string;
5
+ loadFailed: string;
6
+ heading: string;
7
+ completedRatio: string;
8
+ filter: string;
9
+ noItems: string;
10
+ noMatchingFilter: string;
11
+ update: string;
12
+ clearCompleted: string;
13
+ clearButton: string;
14
+ deleteSymbol: string;
15
+ cancel: string;
16
+ previewHeaderIcon: string;
17
+ previewDoneIcon: string;
18
+ previewPendingIcon: string;
19
+ previewMoreLabels: string;
20
+ previewMoreItems: string;
21
+ collapse: string;
22
+ expand: string;
23
+ yamlParseError: string;
24
+ };
25
+ export default _default;
@@ -0,0 +1,25 @@
1
+ declare const _default: {
2
+ clearFilters: string;
3
+ deleteItem: string;
4
+ apiError: string;
5
+ loadFailed: string;
6
+ heading: string;
7
+ completedRatio: string;
8
+ filter: string;
9
+ noItems: string;
10
+ noMatchingFilter: string;
11
+ update: string;
12
+ clearCompleted: string;
13
+ clearButton: string;
14
+ deleteSymbol: string;
15
+ cancel: string;
16
+ previewHeaderIcon: string;
17
+ previewDoneIcon: string;
18
+ previewPendingIcon: string;
19
+ previewMoreLabels: string;
20
+ previewMoreItems: string;
21
+ collapse: string;
22
+ expand: string;
23
+ yamlParseError: string;
24
+ };
25
+ export default _default;
@@ -0,0 +1,25 @@
1
+ declare const _default: {
2
+ clearFilters: string;
3
+ deleteItem: string;
4
+ apiError: string;
5
+ loadFailed: string;
6
+ heading: string;
7
+ completedRatio: string;
8
+ filter: string;
9
+ noItems: string;
10
+ noMatchingFilter: string;
11
+ update: string;
12
+ clearCompleted: string;
13
+ clearButton: string;
14
+ deleteSymbol: string;
15
+ cancel: string;
16
+ previewHeaderIcon: string;
17
+ previewDoneIcon: string;
18
+ previewPendingIcon: string;
19
+ previewMoreLabels: string;
20
+ previewMoreItems: string;
21
+ collapse: string;
22
+ expand: string;
23
+ yamlParseError: string;
24
+ };
25
+ export default _default;
@@ -0,0 +1,25 @@
1
+ declare const _default: {
2
+ clearFilters: string;
3
+ deleteItem: string;
4
+ apiError: string;
5
+ loadFailed: string;
6
+ heading: string;
7
+ completedRatio: string;
8
+ filter: string;
9
+ noItems: string;
10
+ noMatchingFilter: string;
11
+ update: string;
12
+ clearCompleted: string;
13
+ clearButton: string;
14
+ deleteSymbol: string;
15
+ cancel: string;
16
+ previewHeaderIcon: string;
17
+ previewDoneIcon: string;
18
+ previewPendingIcon: string;
19
+ previewMoreLabels: string;
20
+ previewMoreItems: string;
21
+ collapse: string;
22
+ expand: string;
23
+ yamlParseError: string;
24
+ };
25
+ export default _default;