fix: regression sweep — edit-stepper caches, refresh-spin, sort, list counters#5365
Merged
norman-abramovitz merged 9 commits intoMay 23, 2026
Conversation
After CnsiOrgsSource.update / CnsiSpacesSource.update, the steppers auto-navigated to /summary while OrgDataService._org and SpaceDataService._space still held the pre-edit values. Those signals are private caches with warm-cache short-circuits — CnsiXSource patches the EndpointDataService _orgs/_spaces list cache but doesn't reach the detail signal the summary view reads from, so the new name/SSH/quota only appeared after a hard reload. Add patch(p) to both data services that merges into the cached entity in place, and call it from each stepper after the canonical update completes. Mirrors the existing eds.updateOrg(guid, patch) pattern. Verified on dev.98 with browser playwright that the regression reproduces; tests cover the new patch + the no-op-before-load case.
CloudFoundryOrganizationService kicks off orgDataService.load() in its constructor so any consumer reading the org signal sees a populated value, including the /edit-org direct URL. CloudFoundrySpaceService had no equivalent call, so direct navigation to /edit-space landed with spaceDataService.space()===null and the form prefilled empty (Space Name blank, SSH "Disabled" regardless of backend state). The form only populated when the user reached /edit-space via the summary page, which had already warmed the cache through cloud-foundry-space-base. Add the matching load() on construction. Warm-cache short-circuit makes it a no-op on the common summary→edit path.
The "Total X" L5 sub-nav title (e.g. "Total Organizations") was bound to view.totalFilteredResults so a non-matching filter drove the headline to 0 while the underlying dataset still held N items. Users saw "Total Organizations: 0" on an endpoint with 56 orgs — jarring and inconsistent with the label semantics. Add view.totalItems (raw items().length) alongside totalFilteredResults on ViewPipeline (and the endpoints-page local copy of the same class) so the headline can stay anchored to the dataset size while the filter/sort/paginator chain continues to feed off the filtered view. Repoint every L5 sub-nav binding to view.totalItems: orgs, spaces, space-quotas, org-quotas, users (cf/org/space), apps wall, routes-tab, variables-tab, endpoints. The remaining totalFilteredResults reads in the same component files are pass-through to SignalListConfig where the paginator + empty-state branches genuinely want the filtered count — those are unchanged.
Services + Marketplace tabs lead their filter row with a Cloud Foundry dropdown locked to the URL-pinned CNSI so the user can always see which endpoint they're scoped to. Routes was missing the same indicator — the toolbar started with Organization, leaving the CF scope implicit. Adds a single-option Cloud Foundry dropdown (label = endpoint name) at the head of the filter row, disabled, mirroring the existing services + marketplace pattern. Endpoint name is sourced from the existing cfEndpointService.endpoint signal; falls back to the cfGuid string if the EndpointModel hasn't hydrated yet.
The backend clears endpoint.user on disconnect, so the reconnect dialog opened with the credentials form empty even when the same user was about to reconnect to the same endpoint. Friction every time and a likely source of typos (long Cloud Foundry usernames). Cache the username in localStorage on a successful credentials connect (keyed by endpoint guid), and prefill it into the dialog's authValues group when the form has a username control. Token / SSO forms have no username field, so the prefill is a no-op there. Storage is wrapped in try/catch so private-browsing quota failures degrade silently — the user just types the username again, no error surfaces.
The signal-list template gated the inline refresh spinner on config.isAnyLoading(), but most config services wire that to !hasLoadedOnce() — true only during the initial load. Every refresh click after that ran a real HTTP fetch (verified XHR fires) but showed no visual feedback, so the button looked dead. The quota config services were fixed in 2b26a07 by adding a dedicated loading signal; the other 15+ list pages were never updated. Self-contained fix at the signal-list layer: an internal isRefreshing signal flips during invokeRefresh(), and the template gates the spinner on (config.isAnyLoading() || isRefreshing()). Components keep their existing onRefresh wiring; no per-config churn. The page-level loading indicator (empty + loading branch) inherits the same gate for consistency. Verified live on adepttech orgs page: refresh button click fires GET /pp/v1/cf/orgs (7.7s on this dataset), signal repopulates the list — only the animation was missing, this fix restores it.
Orgs list rendered as:
OrgNoSelectedQuota, e2e, opensource, org_1, org_10, org_11, ...,
org_19, org_2, org_20, ...
— two visible problems:
1. Capital-O org jumped above lowercase orgs (raw `<` on strings uses
ASCII codepoint order)
2. org_10 lands between org_1 and org_2 (lexicographic, not natural)
Other places in the codebase already use
`localeCompare(b, undefined, { numeric: true })` for the same need
(routes config, services-wall, marketplace). Pull that into the shared
ViewPipeline default comparator with sensitivity:'base' for the
case-insensitive piece. Mirrors the same change into the endpoints-
page local ViewPipeline copy.
Numbers and non-string comparisons keep their existing paths
unchanged. Date-as-ISO-string columns still sort correctly under
localeCompare since ISO 8601 is lexicographically ordered.
The Total X counter fix (57c051e) repointed L5 sub-nav bindings from view.totalFilteredResults to view.totalItems on 10 list components. Six component specs stub the routesConfig.view (etc.) with { pagedItems, totalFilteredResults, totalPages } and now need a totalItems entry too — without it the compiled template threw "ctx.count is not a function" when the new view.totalItems signal was read by the page's count binding. Adds totalItems to each mock with a value matching the rest of the stub's scale (computed for routes/variables specs that already model a filtered pipeline, signal(0) for the simpler stubs).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Eight-commit regression sweep from a
dev.98adepttech verify session. Each commit is a focused single-file-family fix; all stack cleanly on develop.3a96e524CnsiOrgsSource.update/CnsiSpacesSource.update, the auto-redirect to/summaryrendered stale entity (old name/SSH/quota) until hard reload — the EndpointData list cache was patched butOrgDataService._org/SpaceDataService._spaceweren't25d782ec/edit-spacerendered empty form (SpaceName blank, SSH "Disabled" regardless of backend state) —CloudFoundrySpaceServicehad noload()call analogous toCloudFoundryOrganizationService57c051e2view.totalFilteredResultsinstead of unfiltered total6a406c88f1096293endpoint.user, so reconnect dialog opened with empty username — even when reconnecting as the same user. localStorage prefill restores the previous usernamef9ca59eeisAnyLoadingwas wired to!hasLoadedOnce()on 17 list pages. Self-contained fix in signal-list component (internalisRefreshingsignal), no per-config churnd5d3b5767c194a2e</>so capital-O orgs jumped above lowercase, andorg_10landed betweenorg_1andorg_2. Switch tolocaleCompare { numeric: true, sensitivity: 'base' }for case-insensitive natural sortLive-verified on
dev.99adepttech: refresh-spin engages 31ms after click, persists through the XHR, clears on completion; all five mutation fixes verified browser-side.A separate feature PR is planned to consolidate sort into the single
naturalComparefrom@stratosui/coreand migrate sort dropdowns to support an inlinematch_casetoggle per string column.Test plan
bun run test --runfor each touched spec — all green (added 7 new tests across remembered-username, view-pipeline, signal-list, org-data, space-data)dev.99adepttech (https://console.run.adepttech.ca):