Skip to content

fix(app-detail): coalesce the 10x duplicate Application record fetch - #50

Merged
rubenvdlinde merged 2 commits into
developmentfrom
fix/app-detail-render
Jul 28, 2026
Merged

fix(app-detail): coalesce the 10x duplicate Application record fetch#50
rubenvdlinde merged 2 commits into
developmentfrom
fix/app-detail-render

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

The defect

One load of /applications/hydra-console issued ten identical requests for the same record, all 200:

148, 169, 172, 173, 174, 175, 176, 178, 179, 180
=> GET /apps/openregister/api/objects/openbuild/application/hydra-console

ApplicationDetailHeader and ApplicationDetailDashboard each resolve the record themselves — CnDetailPage's #header / #before-body slots forward only presentational props, not the resolved record — and each is driven by three independent triggers (mounted(), the objectId watcher, the object watcher). None knew about the others.

The fix

A module-scoped in-flight map keyed by uuid, so concurrent callers share one request and one promise.

Deliberately not a cache: the entry is dropped as soon as the request settles, so every fresh call still reaches the server and no component can observe a stale record. Only the concurrent stampede is collapsed. Both components also gained a staleness token so a response for a superseded uuid can't overwrite a newer record on a route change.

One subtlety worth flagging: cleanup uses .then(cleanup, cleanup) rather than .finally(cleanup). .finally() returns a new promise that re-throws the original rejection, and nothing handled it — so every failed fetch logged an unhandled rejection in the console. The new spec caught that in my own first draft.

Correction to #49

I originally filed #49 claiming the header rendered "Untitled application", the register showed openbuild-- with 0 schemas, and four KPI tiles spun forever on a 404. Three of those four were wrong. I observed the page on a host that was thrashing (swap fully exhausted, load ~200 from concurrent e2e suites) and reported a half-loaded page as broken rendering. Re-tested on an idle host:

I claimed Actually
Header "Untitled application" Renders "Hydra Console"
Register openbuild--, 0 schemas Renders openbuild-hydra-console-production, SCHEMAS 1
KPI tiles spin forever on 404 404 handled correctly — banner + zeroed KPIs

fetchInsights() already had correct 404 handling and refreshApplication() already assigned the record properly. I've corrected and retitled that issue. The redundant fetching is the part that survived verification — and it is most of the reason the page takes so long to settle, which is what made it look broken in the first place.

Also fixed: a pre-existing failing test

tests/views/SchemaDesigner.spec.js asserted the raw un-namespaced slug ("new") for addSchema, but #41 deliberately namespaces it ("hello-world-new") so the schema is visible in the list it was created from and attached to the app's register. The assertion encoded pre-#41 behaviour and had been failing ever since that fix landed. It now asserts the namespacing on both the payload and the follow-on navigation.

Tests

New spec locks the measurable contract — N concurrent callers → exactly one request:

✓ collapses ten concurrent callers into one request
✓ does not coalesce across different applications
✓ is not a cache — a later call re-fetches once the first settled
✓ does not pin a rejected promise for later callers
✓ returns null for an empty payload and never calls out for an empty uuid

Full suite: 1347/1347 passing (was 1346/1347 — the SchemaDesigner failure above).

🤖 Generated with Claude Code

One load of /applications/hydra-console issued TEN identical
GET /apps/openregister/api/objects/openbuild/application/hydra-console
requests, all 200.

Two components resolve the same record independently — CnDetailPage's #header
and #before-body slots forward only presentational props, not the resolved
record — and each is driven by three triggers (mounted, the objectId watcher,
the object watcher). None knew about the others.

Adds a module-scoped in-flight map keyed by uuid so concurrent callers share one
request and one promise. Deliberately NOT a cache: the entry is dropped as soon
as the request settles, so every fresh call still reaches the server and no
component can observe a stale record. Only the stampede is collapsed. Both
components now also carry a staleness token, so a response for a superseded uuid
cannot overwrite a newer record on a route change.

Cleanup uses .then(cleanup, cleanup) rather than .finally(cleanup): .finally()
returns a new promise that re-throws, and nothing handled it, so every failed
fetch logged an unhandled rejection. Caught by the new spec.

Ten round-trips for one record is most of why this page takes so long to settle,
and a slow-settling page is why it *looks* broken under load — the placeholder
header and empty register render until the real record lands. I mis-filed those
placeholders as render defects in #49 and have corrected that issue; the
redundant fetching is the part that survived verification.

Also fixes a pre-existing failing test. SchemaDesigner.spec.js still asserted the
raw un-namespaced slug ("new") for addSchema, but #41 deliberately namespaces it
("hello-world-new") so the schema is visible in the list it was created from and
attached to the app's register. The assertion encoded the pre-#41 behaviour and
had been failing since that fix landed; it now asserts the namespacing on both
the payload and the follow-on navigation.

Refs #49
…d fetch too

Six components mix in applicationContext — the detail-page actions component
and five sidebar tabs (Diff, Manifest, Export jobs, Icon, Versions) — and each
issued its own GET for the same Application record.

Measured caveat, recorded honestly: on /applications/hydra-console this changes
nothing, because every one of those consumers short-circuits before fetching
(the slot already passes `object`, so obLoadApp returns early). Instrumenting
the shared helper on the live page showed it is called ZERO times there, while
nine identical requests still go out. Those nine originate in nc-vue, not in
openbuild — see the PR description.

So this commit removes real duplicate-fetch paths for the routes where the
record is NOT passed in, but it does not fix the 10x symptom on the app-detail
page. Not claiming otherwise.
@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

Important correction — this PR does NOT fix the 10x symptom

I verified on the live instance instead of assuming, and the result contradicts my own PR description. Recording it here rather than quietly leaving the claim standing.

I instrumented the shared helper and loaded /applications/hydra-console:

window.__obCalls  = 0     ← the coalescing helper is NEVER called on this page
network requests  = 9     ← nine identical GETs still go out

Every openbuild-side consumer — the header, the dashboard, and all six applicationContext mixin users — short-circuits before fetching, because CnDetailPage's slots already pass a resolved object (obLoadApp returns early; the header only fetches if (!this.application)). So openbuild is not issuing those requests at all.

The nine requests originate in nc-vue (@conduction/nextcloud-vue), not in this repo. Its useObjectStore.fetchObject already has in-flight coalescing keyed ${this.$id}::${type}::${id}, so the likely cause is that key fragmenting — multiple store instances, or different type values for the same URL — but I could not confirm that from the minified bundle and I am not going to guess a third time.

What this PR is still worth

Recommendation

Treat this as hygiene, not as the fix for #49. The actual fix belongs in nc-vue's object store. I'd rather flag that than let a merged PR imply the symptom is resolved — anyone checking would find the same nine requests.

Happy to open the nc-vue investigation as a follow-up if you want it chased.

@rubenvdlinde
rubenvdlinde merged commit 929fe91 into development Jul 28, 2026
2 checks passed
@rubenvdlinde
rubenvdlinde deleted the fix/app-detail-render branch July 28, 2026 16:11
@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

Follow-up: my "this PR does not fix the symptom" comment above was wrong, and I've corrected it on #49.

That measurement was taken against the cached bundle — docker cp doesn't change NC's ?v= cache-buster and app assets are served immutable, so the browser was still executing the pre-fix JS. helper invocations = 0 was true and completely misleading.

Re-measured in a browser context that had never loaded the instance:

before after
cold page load 9–10 3
in-app navigation 6 1

Page renders correctly throughout. #49 closed as fixed by this PR (929fe91c).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants