Skip to content

fix: render single-resource responses without misclassifying labels as a collection - #54

Open
nico-fioretti wants to merge 1 commit into
democratize-technology:mainfrom
nico-fioretti:fix/single-resource-response-format
Open

fix: render single-resource responses without misclassifying labels as a collection#54
nico-fioretti wants to merge 1 commit into
democratize-technology:mainfrom
nico-fioretti:fix/single-resource-response-format

Conversation

@nico-fioretti

Copy link
Copy Markdown

Problem

vikunja_tasks get and vikunja_tasks create return responses that drop description, project_id, priority, due_date, done, bucket_id, percent_done and every other field. Only the title and labels survive.

Reproducer (against any task with a description and a label):

// vikunja_tasks { subcommand: "get", id: 98 }
// before this PR:
{
  "content": "## ✅ Success\n\nRetrieved task \"\"\n\n**Operation:** get-task\n\n**Results:** 2 item(s)\n\n1. **P2** (ID: 3)\n2. **area:mcp** (ID: 5)\n"
}

The task object the wrapper fetched from Vikunja contains description, project_id, priority, due_date, etc. None of them reach the user.

Root cause

formatSuccessMessage (src/utils/simple-response.ts) classifies response data by probing five collection keys:

const collection = data.tasks || data.projects || data.labels || data.users || data.items;

When data is a single Task object (the case for getTask / createTask, which pass the resource directly via createTaskResponse), the task's own labels: Label[] array matches that probe. The formatter then renders only the labels as **Results:** N item(s) and silently drops the rest of the resource.

The same bug fires on create — the freshly-assigned labels become the visible payload while the rest of the new resource disappears.

Fix

Gate the collection extraction with a looksLikeSingleResource check (has id, has title or name, not an array). When that matches, the existing formatDataItems path handles rendering — it already uses the rich formatTaskItem renderer for Task-shaped objects (title, status, priority, due date, project, labels, assignees, description) and falls back to a compact id/title line for everything else. When the check doesn't match, behaviour is unchanged.

Tests

Four new cases in tests/utils/simple-response.test.ts:

  • A bare Task preserves description, project, priority, due date, labels — and does not render Results: 2 item(s).
  • A bare Task with an empty labels: [] doesn't get misclassified as a 0-item collection either.
  • A bare resource that uses name (Project) rather than title is recognised.
  • A defensive case: a wrapper that carries both an id and a tasks array still renders the collection (the gate requires title|name, not just id).

All 20 tests in tests/utils/simple-response.test.ts pass; npm run lint and npm run typecheck are clean.

Pre-existing test failures

npm test reports 24 failing suites (227 tests). All of them fail on upstream/main before this change — none touch simple-response.ts. Verified locally.

Context

Found while running tasks through the wrapper: a get came back without the description, the user (me) missed a step in the task body, and only noticed because the same task fetched via update still returned previousState with the full object. The Marea fork (0.2.2-marea.11) ships this same upstream simple-response.js, so it's the same bug there.

…s a collection

formatSuccessMessage classifies response data by probing

data.tasks || data.projects || data.labels || data.users || data.items

for a collection. When data is a single Task object (the case for

vikunja_tasks get and create, which pass the resource directly), the

task's own .labels array trips that check. The formatter then renders

only the labels as 'Results: N item(s)' and silently drops description,

project_id, priority, due_date, done, bucket_id, percent_done, etc.

Same effect on create: the freshly-assigned labels become the visible

payload while the rest of the new resource disappears.

Fix: gate the collection extraction with a 'looks like a single

resource' check (has id, has title or name, not an array). When that

matches, the existing formatDataItems path handles rendering, which

already uses the rich Task formatter for Task-shaped objects (showing

title, status, priority, due date, project, labels, assignees and

description) and falls back to a compact id/title line for everything

else. When it doesn't match, behaviour is unchanged.

Tests cover the regression (single Task preserves description and

metadata, empty .labels doesn't trigger the false collection match,

single Project with .name is recognised) plus a defensive case

confirming that a wrapper carrying both id and a tasks array still

renders the collection.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants