update means something now: it depends on tags
"update" was `git pull` and re-install, unconditionally, for every plugin.
nothing could say whether there was anything to pull, so the button was always
there and usually did nothing — and "update all" was twenty-one no-ops with a
spinner on it. the button's own comment admitted it: gating on `outdated` would
hide it for ever, because nothing bumped a version.
versions are dated and tagged now, so the rule the owner asked for is possible:
· a checkout NOT at a tag is always updateable — it is between releases, no
claim is being made about it, pulling is always reasonable
· a checkout AT a tag is updateable only when a NEWER tag exists
the part worth defending is how "newer" is decided. the obvious version-string
comparison is wrong the first time a plugin is tagged `v2` or `1.0.0-rc1`, and
wrong SILENTLY — a plugin that never offers an update looks like one nobody has
released. so newness is asked of git: the newest tag reachable from the REMOTE
branch, compared against the tag at HEAD. that holds for any names anyone
chooses, and there is a test with a tag called `beta` that sorts before `v1` to
keep it honest.
it never fetches. every answer comes from refs already on disk, because this
runs once per plugin on a request that renders a list and twenty-one network
round trips would make the screen feel broken. tags arrive at clone and at the
fetch `pullPlugin` already does.
the VERB refuses too, not just the UI. a plugins list is fetched once and a tag
can be pushed a second later, so the copy someone clicks is routinely older than
the truth — and `updateAll` iterates a list rather than asking per plugin.
"up to date · <tag>" is said out loud rather than shown as an absent button. no
button and up-to-date look identical, and only one of them is information.
verified on the real installs: example pulled to its tag and flipped to
up-to-date, caldav on an older commit stayed updateable, and updatePlugin on
example answered "already at 2026.08.27.01, the newest release" without pulling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
@@ -129,7 +129,7 @@ pluginsRouter.get('/', async (ctx) => {
|
||||
};
|
||||
})(),
|
||||
})),
|
||||
plugins: states.map(({ plugin, install, outdated }, i) => ({
|
||||
plugins: states.map(({ plugin, install, outdated, release }, i) => ({
|
||||
appName: plugin.appName,
|
||||
prefix: mountPrefix(plugin),
|
||||
label: plugin.manifest.label,
|
||||
@@ -165,6 +165,11 @@ pluginsRouter.get('/', async (ctx) => {
|
||||
enabled: install?.enabled ?? false,
|
||||
installedVersion: install?.version ?? null,
|
||||
outdated,
|
||||
// The tag rule — whether the REMOTE has something this checkout does not. See plugins/release.ts.
|
||||
updateable: release.updateable,
|
||||
releaseReason: release.reason,
|
||||
tagAtHead: release.tagAtHead,
|
||||
newestTag: release.newestTag,
|
||||
processStatus: statuses[i] ?? null,
|
||||
// Probed live, against this machine's PATH, on every read.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user