Measured, not inferred
Captured every app_versions URL the browser requested while loading the admin settings page (tests/e2e/pinning.spec.ts, PR #159):
200 /index.php/settings/admin/app_versions
200 /apps/app_versions/css/app_versions-main.css
200 /apps/app_versions/css/main-*.chunk.css
200 /apps/app_versions/js/app_versions-main.mjs
200 /ocs/v2.php/apps/app_versions/api/pats?format=json
200 /ocs/v2.php/apps/app_versions/api/sources?format=json
200 /ocs/v2.php/apps/app_versions/api/cache?format=json
200 /ocs/v2.php/apps/app_versions/api/update-channel?format=json
200 /apps/app_versions/img/app.svg
200 /ocs/v2.php/apps/app_versions/api/apps?format=json
Absent: /api/pins, /api/advisories, /api/policies — which are exactly the last three statements of onMounted:
try {
await checkUpdateChannel()
await loadApps()
} finally {
isLoading.value = false
}
// Kick off advisory correlation, pin state, and auto-update policies after
// the list renders (non-blocking).
void loadAdvisories()
void loadPins()
void loadPolicies()
Everything before that point fires (update-channel, then apps). Everything after it does not.
Visible consequence
pinning.spec.ts:70 has failed on every run: pins stays {}, so the app-card badge's v-if="pinFor(app.id)" never matches and no pin badge renders — even though the pin exists and GET /api/pins returns it when called directly. Advisory badges and auto-update policy state are presumably affected the same way.
What this is NOT
Each ruled out by reading the source, so nobody re-checks:
- not the API — a direct
GET /api/pins returns the pin (asserted in the test before the UI is touched)
- not
Pin::toArray() — it carries no appId, so the + ['appId' => …] union is safe (PHP's + keeps the LEFT operand)
- not an id mismatch —
/api/apps returns 'id' => $appId and /api/pins returns 'appId' => $appId, both the same app id
- not core-app filtering —
coreAppsVisibility defaults to 'show'
- not a throwing loader —
checkUpdateChannel, loadApps and loadAdvisories each wrap their whole body in try/catch
- not the test harness — the capture is proven by a positive control asserting
/api/apps was seen, and the wait uses expect.poll, not a bare assertion
Why static reading did not finish the job
The try has a finally but no catch, so anything thrown by the awaited calls would run finally and then propagate, skipping the three void statements. That is the obvious mechanism — but all three loaders catch internally, so what throws is not visible from the source. It needs a browser console/stack trace from a running instance.
Filed rather than guessed at: changing onMounted blind risks breaking the 92 specs that pass.
Measured, not inferred
Captured every
app_versionsURL the browser requested while loading the admin settings page (tests/e2e/pinning.spec.ts, PR #159):Absent:
/api/pins,/api/advisories,/api/policies— which are exactly the last three statements ofonMounted:Everything before that point fires (
update-channel, thenapps). Everything after it does not.Visible consequence
pinning.spec.ts:70has failed on every run:pinsstays{}, so the app-card badge'sv-if="pinFor(app.id)"never matches and no pin badge renders — even though the pin exists andGET /api/pinsreturns it when called directly. Advisory badges and auto-update policy state are presumably affected the same way.What this is NOT
Each ruled out by reading the source, so nobody re-checks:
GET /api/pinsreturns the pin (asserted in the test before the UI is touched)Pin::toArray()— it carries noappId, so the+ ['appId' => …]union is safe (PHP's+keeps the LEFT operand)/api/appsreturns'id' => $appIdand/api/pinsreturns'appId' => $appId, both the same app idcoreAppsVisibilitydefaults to'show'checkUpdateChannel,loadAppsandloadAdvisorieseach wrap their whole body intry/catch/api/appswas seen, and the wait usesexpect.poll, not a bare assertionWhy static reading did not finish the job
The
tryhas afinallybut nocatch, so anything thrown by the awaited calls would runfinallyand then propagate, skipping the threevoidstatements. That is the obvious mechanism — but all three loaders catch internally, so what throws is not visible from the source. It needs a browser console/stack trace from a running instance.Filed rather than guessed at: changing
onMountedblind risks breaking the 92 specs that pass.