Air-gapped warning crashes admin UI: Trans <1> targets remainingDays instead of Box (8.9.0 / develop)
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 35/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- react, react-native, typescript
- Domain
- frontend, internationalization
Research direction
The bug lives in apps/meteor/client/sidebar/sections/AirGappedRestrictionBanner/AirGappedRestrictionWarning.tsx, where the Trans children order makes the <1> tag in packages/i18n/src/locales/en.i18n.json point at remainingDays rather than the Box. Read the two Trans branches and the Airgapped_workspace_warning and restriction keys first, then reproduce with the isolated script in the issue. Done means both sentences render for days 7 through 1 and at zero, with a regression test covering both branches; a pull request (#43451) is already open, so check it before starting.
Written by the indexing model from the issue text.
Description
Description
The air-gapped workspace warning can crash the administrator's web UI with React error #130 (Element type is invalid ... got: undefined). The Airgapped_workspace_warning translation uses <1>...</1>, but child index 1 of its <Trans> component is the numeric remainingDays value, not the intended <Box> element.
This is reproducible for every remaining-day value from 7 through 1 in both:
- Rocket.Chat 8.9.0, commit
057a927d903c83b5d61cf236d650c4500ebb4b66. develop, commit9971f492da4960edce747fa9feb07c7fc332dbac(checked November 2, 2026).
There is a related rendering defect at remainingDays = 0: the restricted-mode branch does not crash in the isolated reproduction, but the second sentence disappears because its translation also references the wrong child index.
Relevant source
Permalinks to the checked develop commit:
- AirGappedRestrictionWarning.tsx, both Trans branches.
- English translation strings.
- Warning/restriction phase selection.
- Admin-only sidebar banner rendering.
- Same component in 8.9.0.
Steps to reproduce
- In a test environment, render the sidebar as an administrator with the air-gap warning phase active (
Cloud_Workspace_AirGapped_Restrictions_Remaining_Daysbetween 1 and 7). - Use the English locale.
- Render the sidebar warning, for example with
remainingDays = 6. - Observe the React render exception. Repeat with 7, 5, 4, 3, 2, and 1: the same error occurs.
The component-level reproduction below does not require a running server or any license/configuration changes:
npm install [email protected] [email protected] [email protected] [email protected]
node repro.mjs
// repro.mjs
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { createInstance } from 'i18next';
import { Trans, I18nextProvider } from 'react-i18next';
const i18n = createInstance();
await i18n.init({
lng: 'en',
defaultNS: 'core',
resources: {
en: { core: {
Airgapped_workspace_warning:
'This air-gapped workspace will enter read-only mode in {{remainingDays}} days. <1>Connect it to the internet or upgrade to a premium plan to prevent this.</1>',
} },
},
interpolation: { escapeValue: false },
});
for (const remainingDays of [7, 6, 5, 4, 3, 2, 1]) {
// The same child ordering as the JSX in AirGappedRestrictionWarning.
// A span replaces the presentation-only Box wrapper.
const warning = React.createElement(Trans, {
i18nKey: 'Airgapped_workspace_warning',
values: { remainingDays },
children: [
'This air-gapped workspace will enter read-only mode in ',
remainingDays,
' days.',
' ',
React.createElement('span', { key: 'detail' },
'Connect it to internet or upgrade to a premium plan to prevent this.'),
],
});
try {
console.log(remainingDays, renderToStaticMarkup(
React.createElement(I18nextProvider, { i18n }, warning)));
} catch (error) {
console.log(remainingDays, error.message);
}
}
Repeat with [email protected] for the checked develop dependency version: the same failure occurs.
Expected behavior
The administrator can continue using the UI while the complete warning is displayed, including the remaining days and the explanatory second sentence. At zero days, the complete read-only notification should render.
Actual behavior / root cause
The warning's children are:
| Index | Value |
|---|---|
| 0 | Initial text |
| 1 | remainingDays (a number) |
| 2 | " days." |
| 3 | " " |
| 4 | The intended Box / span |
The translation's <1> targets the number. Trans produces a React element with an undefined type, and React throws. The issue is the child's position/type, not any particular numeric value.
For the restricted-mode branch, the intended Box is at index 2, while <1> refers to the explicit whitespace child. The second sentence is consequently omitted in the reproduction.
Validation
An isolated server-side render test transpiled the actual TSX component from each checked Git ref and loaded that ref's English translations. Only the presentation-only Fuselage Box wrapper was replaced with a span; the Trans JSX was unchanged. A full Rocket.Chat server deployment was not started for this version comparison.
| Version | React / React DOM | react-i18next | i18next | Days 7–1 | Days 0 |
|---|---|---|---|---|---|
| 8.9.0 | 19.2.8 | 17.0.14 | 26.4.2 | Render exception for every value | Second sentence missing |
develop 9971f492 |
19.2.8 | 17.0.16 | 26.4.2 | Render exception for every value | Second sentence missing |
As a diagnostic control, changing the warning's translation tag from <1> to <4> makes every tested positive-day case render successfully. Changing the restricted branch's tag from <1> to <2> restores its second sentence. These controls were applied only in the isolated test resources.
Proposed solution
Prefer an explicit named component instead of relying on JSX child positions, and keep interpolation exclusively in values:
<Trans
i18nKey={isRestricted ? 'Airgapped_workspace_restriction' : 'Airgapped_workspace_warning'}
values={{ remainingDays }}
components={{ detail: <Box fontScale='p2' is='span' /> }}
/>
Update both translation keys to use <detail>...</detail> consistently across locales. For example:
{
"Airgapped_workspace_warning": "This air-gapped workspace will enter read-only mode in {{remainingDays}} days. <detail>Connect it to the internet or upgrade to a premium plan to prevent this.</detail>",
"Airgapped_workspace_restriction": "This air-gapped workspace is in read-only mode. <detail>Connect it to the internet or upgrade to a premium plan to restore full functionality.</detail>"
}
Alternatively, an explicit indexed components mapping could preserve existing <1> translations without relying on implicit children. Merely changing English indices would leave other locales vulnerable and retain fragile coupling to JSX layout.
Suggested regression coverage: render both branches with real locale resources; parameterize days 1–7; assert both sentences and the interpolated day count; verify the zero-day restricted branch; cover a non-English locale and English fallback. This fix should affect presentation only, leaving air-gap detection and restriction behavior unchanged.
Relevant logs
Minified React error #130 ... args[]=undefined
Element type is invalid: expected a string (for built-in components)
or a class/function (for composite components) but got: undefined.
Server topology, database version, and installation-specific information are not needed for the isolated reproduction above.
- Dominant language
- TypeScript
- Stars
- 46.5k
- Forks
- 14.1k
- Avg merge
- 1d 21h
- Merged PRs (30d)
- 381
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from RocketChat/Rocket.Chat
-
Mark the empty state busy while a new search loads, like the table */}Possibly taken @aditya25bcy10173-a11y claimed this 2 days ago. Opentodo type: feature
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
RocketChat/Rocket.Chat#43699 · 1 comment · 1 reaction ·
Maintainers usually reply within 1 day
-
Timestamp with a time but no timezone shows as 1 January 1970Possibly taken @Sarthak-agrawal-tech claimed this 4 days ago. Opentype: bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
RocketChat/Rocket.Chat#43637 · 6 comments ·
Maintainers usually reply within 1 day
-
Move this mapping from useQuery's old `select` into a usePaginatedQuery `combine`Possibly taken @aditya25bcy10173-a11y claimed this 3 days ago. Opentodo type: feature
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
RocketChat/Rocket.Chat#43609 · 4 comments · 1 reaction ·
Maintainers usually reply within 1 day
-
evaluate switching to the other tab when the selected tab's permission is revoked mid-sessionPossibly taken @schizophrenicmaniac claimed this 4 days ago. Opentodo type: feature
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
RocketChat/Rocket.Chat#43579 · 3 comments · 1 reaction ·
Maintainers usually reply within 1 day
-
Docs say packages can be installed with `npm` but use unsupported `patch:` URLPossibly taken A pull request linked to this issue is open or already merged. Opentype: bug
Difficulty 1/5 1-3 hours Newbie friendliness 72/100
RocketChat/Rocket.Chat#43554 · 4 comments ·
Maintainers usually reply within 1 day
All issues in RocketChat/Rocket.Chat
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
open-webui/tether#52 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
voidzero-dev/void#835 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
AykutSarac/jsoncrack.com#616 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
melgarafael/DeskcommCRM#4679 ·
Maintainers usually reply within 1 day