Repository navigation
feat(investment-calculator): responsive layout, loading skeleton and error boundary - #1308
Merged
nanaf6203-bit merged 1 commit intoOct 21, 2026
Conversation
…or boundary The investment calculator is embedded in other people's pages through an iframe, so it has to work at a width nobody chose: a 600px column on a desktop and a 320px phone screen on a mobile. MettaChain#1267 - The layout assumed a width it could not know. The widget had a fixed padding, fixed font sizes and a metric grid of `repeat(2, 1fr)`. A `1fr` column cannot shrink below the width of its content, so on a narrow frame the numbers were pushed out of the widget. The container now fills the frame and respects its box model, the paddings and the font sizes scale with the viewport, the metric columns are `minmax(0, 1fr)` so they can shrink, and the rows that hold a label and a number wrap instead of squeezing them. A long number breaks inside its own box rather than pushing the layout out. MettaChain#1269 - The embed page showed an empty box while it was booting. An empty 400px box read as a broken widget rather than a loading one. The page now renders `InvestmentCalculatorSkeleton`, which has the same padding, the same grid and the same blocks as the widget, so the calculator takes its place without the frame jumping around. It is a `role="status"` element, so it is also announced. MettaChain#1270 - A failure inside the widget emptied the frame. The widget renders inside somebody else's page, so an error that escaped it left an empty box with no explanation. It is now wrapped in the shared `ErrorBoundary` with a fallback that says what happened and offers a retry; the retry remounts the boundary, which clears the error. While in there: - The projection is calculated by `calculateInvestmentProjection` in `src/lib/investmentProjection.ts`, a pure function that is unit tested on its own, and it is memoised in the component. It used to be calculated in an effect, which painted a panel of zeroes for one render before the real figures arrived. - The inputs are free-form number fields, so an empty, zero or negative amount used to be divided by and rendered as `NaN%` and `Infinity mo`. The projection is now only calculated for a positive amount over a positive holding period. - `Intl.NumberFormat` parses the locale data every time it is built, and the widget formatted a dozen values on every render. There is one formatter for the module now. Closes MettaChain#1267 Closes MettaChain#1269 Closes MettaChain#1270
|
@peaceshallom37-rgb Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
nanaf6203-bit
approved these changes
Oct 21, 2026
nanaf6203-bit
left a comment
Contributor
There was a problem hiding this comment.
The responsive layout, skeleton, and error boundary round this out nicely. Good to merge.
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.
What this does
The investment calculator is the investment form of the app. It is rendered on
/widget/investment-calculatorand embedded in other people's pages through that route, which serves it in an iframe. That means it has to work at a width nobody chose — a 600px column on a desktop, a 320px phone screen on a mobile — and that a failure inside it empties the frame the visitor came for.This is the widget:
src/components/widget/InvestmentCalculatorWidget.tsx.#1267 — the layout assumed a width it could not know
The widget had a fixed padding, fixed font sizes and a metric grid of
repeat(2, 1fr). A1frcolumn cannot shrink below the width of its content, so on a narrow frame the numbers were pushed out of the widget.width: 100%,max-width: 600px,box-sizing: border-box).clamp()), so the widget shrinks with the frame.minmax(0, 1fr), so they can shrink below their content.Checked at 320px, 375px and 600px: the page does not scroll sideways at any of them, and the metrics stay in two columns.
#1269 — the embed page showed an empty box while it was booting
/widget/investment-calculatorrendered an empty 400px box until the client had mounted, which read as a broken widget rather than a loading one. It now rendersInvestmentCalculatorSkeleton, which has the same padding, the same grid and the same blocks as the widget, so the calculator takes its place without the frame jumping around. It is arole="status"element, so it is announced as well.#1270 — a failure inside the widget emptied the frame
The widget renders inside somebody else's page, so an error that escaped it left an empty box with no explanation. It is now wrapped in the shared
ErrorBoundary(src/components/ui/SimpleErrorBoundary, the same one the gallery and the filter panel use) with a fallback that says what happened and offers a retry. The retry remounts the boundary, which clears the error it caught.While in there
src/lib/investmentProjection.ts, is unit tested on its own, and is memoised in the component. It used to be calculated in an effect, which painted a panel of zeroes for one render before the real figures arrived.NaN%andInfinity mo. The projection is only calculated for a positive amount over a positive holding period, and anything that is not a number is read as zero.Intl.NumberFormatparses the locale data every time it is built, and the widget formatted a dozen values on every render. There is one formatter for the module now.Tests
They cover the projection (including the empty, zero, negative and non-finite inputs), the rendered figures, the recalculation when an input changes, the responsive layout, the skeleton, and a failure inside the widget being caught and turned into the retryable fallback.
docs/investment-calculator-widget.mddescribes the projection, the layout, the loading state and the error handling.Notes for the reviewer
There is no
InvestmentFormin the repository, so this PR works on the investment form that does exist: the calculator widget on/widget/investment-calculator. Another open PR (#1305) adds a newInvestmentFormcomponent undersrc/components/forms/; this PR does not touch those files, so the two do not conflict.The fallback of the error boundary is written in the theme of the widget rather than with the shared one, because the widget is embedded in pages that are not part of this app and the shared fallback does not follow the theme. The retry is implemented by remounting the boundary through its
key, which resets the error state; the inputs are therefore back at their defaults after a retry.The labels of the four inputs are not associated with their fields. That is an accessibility issue of its own and it is tracked separately, so it is left alone here.
CI
The workflow run of this pull request is waiting for approval (
action_required), because it comes from a fork. It has to be approved before it starts.mainis currently red for reasons that are not related to this branch:npm run lintreports 82 errors andnpm run typecheckreports 472 errors across the repository (missingreact-pdfand@types/leaflettypings, for instance), and a set of wallet test suites fail. The same failures are onmainand on the other open branches. This branch adds no new lint, type or test failures —eslintandtscare clean on the files it touches, and the full jest run has the same 17 failing suites asmain(all of them pre-existing) with 24 more passing tests.Closes #1267
Closes #1269
Closes #1270