Skip to content

fix(list): scope install status to the editor it is actually in - #5

Merged
Alishahzad1903 merged 5 commits into
mainfrom
zaid/ux-fixes
Aug 3, 2026
Merged

fix(list): scope install status to the editor it is actually in#5
Alishahzad1903 merged 5 commits into
mainfrom
zaid/ux-fixes

Conversation

@Darker98

@Darker98 Darker98 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Fixes apimatic/contextmatic-crawler#51.

Problem

list marked a plugin "installed" the moment it appeared anywhere in the
manifest, ignoring the targets array each manifest entry already records.
A plugin installed only in Cursor showed the same checkmark in Claude Code
and VS Code, which never had a copy — a false impression that it's
installed and functional everywhere when it's actually just in Cursor.

Fix

listPlugins (src/install.js) now builds installed status from each
entry's actual targets, not just its presence in the manifest, and returns
a targets: string[] per plugin. It also accepts an optional target to
scope installed to one specific editor.

cli.js exposes this as a new list --target <name> flag (validated
against claude/cursor/vscode), and --long output now prints an
Installed into: ... line naming the actual editors instead of leaving the
reader to assume "installed" means "installed here".

The default compact grid still shows a single mark meaning "installed in at
least one editor" — that's now an honest signal ("installed somewhere"),
with the misleading per-editor claim removed and the precise per-editor
answer available via --long, --target, or --json.

No manifest schema change or migration: targets was already written on
every install.

Test plan

  • npm test — 157/157 passing
  • New test: a plugin installed only in Cursor reports installed: true
    unscoped, true for --target cursor, false for --target vscode
  • New test: an unknown --target value throws UserError
  • Manual smoke test of listPlugins end-to-end with a stubbed catalog
    confirming the scoped/unscoped behavior above

alifsayalee and others added 5 commits July 31, 2026 18:03
Re-installing a plugin and declining an editor it was already installed into
narrowed the recorded targets without removing the copy. `installed` then
reported that editor clean while the editor went on loading the plugin, and
because `update` replays the recorded targets, that copy was never refreshed
again - a stale plugin with no visibility into it.

Install only ever adds, so the record has to be the union of what is on disk:
what this run installed, plus the editors an earlier run installed into that
this one skipped. `manifest.upsert` stays a plain "make this row say exactly
this" primitive - uninstall relies on that to narrow a row deliberately, so
merging inside upsert would have traded this bug for its mirror image.

The closing summary names the skipped editor ("Already installed: VS Code"),
since that is where the user looks to see where the plugin now lives.

Not covered here: making a decline actually remove the copy. That needs the
prompt reworded (a decline should not read as a silent deletion) and a guard so
a --targets subset in a script cannot delete unlisted editors.

Refs apimatic/contextmatic-crawler#39

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… lines

The `?` sat in a six-column gutter, four spaces from its own question, so there
was nothing for the eye to anchor to; the `[Y/n]` hint stayed on screen after the
decision was made; and nothing tied the three questions to each other or to the
result line they produce.

Redrawn on the pattern Clack established: the glyph sits against its text with a
single gap, a connector runs from each answer down to the next question and into
the closing line, and the hint is replaced by the decision itself once there is
one - a resolved step reads "Yes", not "(Y/n) y".

Rewriting the asked row needs the cursor, so it is attempted only on a TTY and
only when the row cannot have wrapped; past the terminal width the arithmetic
would clear the wrong line and eat real output. Off a TTY nothing echoes the
user's Enter, so the newline has to be written instead of assumed.

Answers still take y/n as well as yes/no, in any case, and a bare Enter still
takes the default. Only the interactive branch changes: --targets, -y, a
non-interactive shell and an injected `confirm` all print exactly what they did
before, which is why the existing suite needed no edits.

Glyphs come from code points and carry ASCII stand-ins, so a cp437/cp1252
console gets `*` and `|` rather than mojibake - the rule log.js already applies
to its check mark.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…yped it

Rewriting the asked row to drop its `(Y/n)` once answered read as a flicker: the
line the user was just looking at changes under them. Keep it exactly as typed.

Dropping the rewrite also removes the cursor arithmetic behind it. That was the
one part of this flow that could destroy output rather than just look wrong - a
row cleared one line off target takes real text with it - and it needed a TTY to
verify, which no test could give it. The flow now only ever grows downwards, so
there is nothing left to get wrong.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ion and hint

Reading back `(Y/n) y` on the question and `Yes` under it is the same answer
twice, and the hint is what tells you which way a bare Enter would have gone -
so the keystroke is the part that goes, not the hint.

The row is redrawn as it was asked. That needs the cursor, so it is attempted
only on a TTY and only when the row cannot have wrapped: past the terminal width
the arithmetic would clear the wrong line and take real output with it. Off a
TTY, or on a row that might have wrapped, nothing is touched.

Two tests cover the contract headless, against a sink that reports itself as a
terminal - that the cleared row comes back with its hint and without the
keystroke, and that neither a narrow terminal nor a pipe emits cursor codes at
all. What they cannot cover is how a real terminal interleaves this with
readline's own echo; that still wants an eye on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`list` marked a plugin installed everywhere the moment it was installed
anywhere, reading `manifest.list()` for a matching plugin id and ignoring the
`targets` array each entry already records. A plugin installed only in
Cursor showed the same checkmark in Claude Code and VS Code, which never had
a copy.

`listPlugins` now carries each plugin's actual `targets` through to the
result, and takes an optional `target` to scope `installed` to one editor.
The CLI exposes this as `list --target <name>`, and `--long` output now
names the editors a plugin is actually installed into rather than leaving
the reader to assume "installed" means "installed here".

Fixes apimatic/contextmatic-crawler#51.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Alishahzad1903
Alishahzad1903 merged commit ec133ca into main Aug 3, 2026
11 checks passed
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 0.5.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants