Skip to content

fix(frontend): an init() failure strands the app on the Home markup with no listeners and no visible error #206

Description

@cristim

Where: frontend/src/app.ts, init().

What happens: init() wraps everything from getCurrentUser() through switchTab() in a single try. Any failure before switchTab() runs is caught, logged to the console, and then nothing else happens. The app is left on the raw markup from index.html, which is not a neutral blank state: it is the Home tab, pre-rendered and pre-highlighted.

The user sees:

  • the Home dashboard, with empty charts, because loadDashboard() never ran
  • the Home item highlighted in the nav, regardless of which URL they requested
  • the generic tab title CUDly - Cloud Commitment Optimizer
  • the address bar still showing the route they asked for
  • no event listeners bound at all, because setupEventListeners() is the line after switchTab()

The only signal is a console.error("Init error:", …) that no user will see.

Expected: an initialization failure surfaces as an error state or a retry, not as a silently wrong page that claims to be Home while the URL says otherwise.

Reproduction. Until LeanerCloud/cloud-commitments-cli#1854 this was reachable deterministically from a URL, which is how it was found. applyTabFromPath used segment in TABS, and in walks the prototype chain, so /constructor resolved as a known tab and TABS['constructor'] returned the Object constructor. Pushing that value threw:

Init error: DataCloneError: Failed to execute 'replaceState' on 'History':
function Object() { [native code] } could not be cloned.

Observed state after that throw, in Chromium against the production bundle:

title = "CUDly - Cloud Commitment Optimizer"   nav = home   panel = home-tab

LeanerCloud/cloud-commitments-cli#1854 fixes the prototype-chain lookup, so that specific trigger is gone. The fragility it exposed is not fixed: any transient failure on the same stretch still produces the identical end state. getCurrentUser() returning a 5xx, a network blip on /api/auth/me, or anything else thrown between the try and switchTab() all land here. Only a 401 is handled specially, and it re-shows the login modal.

Why it matters. Two reasons beyond the bad render.

First, the failure is self-concealing. With no listeners bound, every sidebar item falls back to being a plain <a href="…">, so the next click is a full browser navigation rather than an SPA transition. That reload usually succeeds. The user experiences "it was weird once, then it fixed itself", which is close to unreportable and impossible to distinguish from a routing bug.

Second, that is almost certainly what happened in LeanerCloud/cloud-commitments-cli#1775. That issue reported /plans rendering the Home dashboard. The audit found /plans deep-links correctly and could not reproduce the symptom from any input, but this failure mode reproduces every detail of the report including the URL staying on /plans and including the reporter's observation that clicking the Plans nav item afterwards loads the real page. See the correction comment on LeanerCloud/cloud-commitments-cli#1775 for the full reasoning.

Fix direction, not prescriptive. The decision this needs is how an init failure should surface. Options roughly in increasing cost: render an explicit error state into the target panel instead of leaving Home visible; add a bounded retry for the transient-looking cases; or narrow the try so a failure in the user/permissions fetch cannot prevent routing from running at all, since switchTab() does not depend on that fetch succeeding. The last one is appealing because it makes the page correct even when the data is not, but it needs a check that no downstream loader assumes state.getCurrentUser() is populated.

Not urgent, but should not be lost. Nothing is deterministically broken today. This is filed because it is the most probable explanation for a P1 bug report that was otherwise unexplainable, and that reasoning currently lives only in prose on LeanerCloud/cloud-commitments-cli#1775 and in a PR description.

Surfaced by the routing audit in LeanerCloud/cloud-commitments-cli#1775.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions