Skip to content

Context overview shows '3/3 synced' green badge when missing_target > 0 #692

@memtomem

Description

@memtomem

What

The Context Gateway overview card renders a green N/N synced badge for an
artifact type whenever in_sync == total, even when other status counts
(missing_target, missing_canonical, error, parse_error) are non-zero
on the same artifact type. As a result, partial sync states across multiple
runtimes are hidden behind a success-coloured badge.

Live repro

$ curl -s http://127.0.0.1:8080/api/context/overview | jq .commands
{
  "total": 3,
  "in_sync": 3,
  "missing_target": 3
}

The overview card for Commands displays 3/3 synced with the
badge-success (green) class, despite three commands being unsynced for at
least one runtime.

Why this happens

/api/context/overview aggregates (runtime, name, status) triples via
_count_statuses
(packages/memtomem/src/memtomem/web/routes/context_gateway.py:17):

def _count_statuses(triples):
    names = set()
    counts = {}
    for _runtime, name, status in triples:
        names.add(name)
        key = status.replace(" ", "_")
        counts[key] = counts.get(key, 0) + 1
    return {"total": len(names), **counts}

total is the number of distinct names, but each status count is
incremented per (runtime, name) pair. With Claude + Gemini + Codex runtimes
each contributing one triple per name, the per-status counts can sum well
above total. In the live case above:

  • 3 commands total
  • claude_commands: 3 × in sync
  • gemini_commands: 3 × missing target

total=3, in_sync=3, missing_target=3. Per-runtime breakdown is
already visible at /api/context/commands, so the data is correct — it's the
overview's summary that loses information.

The frontend then computes
(packages/memtomem/src/memtomem/web/static/context-gateway.js:102):

const hasIssue = d.error || (total > 0 && inSync < total)
                 || d.status === 'out_of_sync' || d.status === 'error';

This only checks whether in_sync covers total. It does not consider
missing_target, missing_canonical, parse_error, or any other non-in_sync
count. With total=3, in_sync=3, hasIssue is false, badge is green,
badgeText is 3/3 synced.

Suggested fix (frontend-only)

The _count_statuses payload already carries every status as its own key.
Compute hasIssue as "any non-in_sync count is non-zero", e.g.:

const total = d.total || 0;
const inSync = d.in_sync || 0;
const issueKeys = ['missing_target', 'missing_canonical', 'out_of_sync', 'parse_error'];
const issueCount = issueKeys.reduce((sum, k) => sum + (d[k] || 0), 0);
const hasIssue = d.error || issueCount > 0
                 || d.status === 'out_of_sync' || d.status === 'error';

For the badge text, surface the dominant issue when present, e.g.
3 missing target instead of 3/3 synced. The exact wording is up for
discussion — what matters is that the warning state is visible.

Severity

Medium. Not data loss; the underlying /api/context/commands endpoint
reports the truth, so power users notice. But the overview card is the
default landing for sync state, so most users will trust the green badge
and miss the missing_target count entirely.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions