Repository navigation
Conversation
🦋 Changeset detectedLatest commit: 2312d98 The changes in this PR will be included in the next version bump. This PR includes changesets to release 5 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Looks like this PR is not ready to merge, because of the following issues:
Please fix the issues and try again If you have any trouble, please check the PR guidelines |
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #42942 +/- ##
===========================================
+ Coverage 72.16% 72.49% +0.32%
===========================================
Files 5323 3352 -1971
Lines 228566 189594 -38972
Branches 42372 31207 -11165
===========================================
- Hits 164944 137437 -27507
+ Misses 58413 50734 -7679
+ Partials 5209 1423 -3786
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
🖥️ UI previewBuilt from In Rocket.Chat Desktop with Developer Mode on, open one of these links to load this PR's UI into the workspace in focus:
To pick the workspace, add Or run it in a browser against any server (default npx @rocket.chat/ui-preview 42942 --server https://open.rocket.chat |
f6e25f2 to
92ef7df
Compare
ce83ecc to
0035ee2
Compare
c505bd7 to
75c2ce4
Compare
2189996 to
39b444c
Compare
Tamagui ships ESM only, and packages that test against Fuselage sources import it directly.
Tamagui registers global pointer listeners at import whenever `window` exists.
Pilot for moving Fuselage components to Tamagui (`@tamagui/web` 3.x). Decisions, measurements and open questions are in `docs/proposals/fuselage-tamagui.md`. - `Divider` keeps its props, `rcx-divider*` class names and look; Box styling props still apply through css-in-js. - Tamagui rules live in the `fuselage` cascade layer, so unlayered CSS (app, admin custom CSS, css-in-js) keeps overriding them. - `@tamagui/web` is bundled into `dist` with its runtime settings pinned. - `FuselageProvider` is optional; Rocket.Chat mounts one at the app root.
…up clauses A Tamagui group clause subscribes its element to the group's state, and the first subscriber makes the parent render again on mount and attach hover and press handlers. Every message rendered three subscribers (timestamp, toolbar wrapper, toolbar or reaction action) and every item row with hover-revealed actions two. Those clauses are now plain rules in the fuselage layer (.rcx-message:hover .rcx-message-header__time and the like, with hover inside @media (hover: hover) as Tamagui does), and Message, Item and ItemGroupHeader declare no group. Measured on 150 message rows and 200 item rows with actions: mount is 26% and 43% faster. The first update after a message mounts is 17% slower, since it is now the second render that warms Tamagui's per-instance caches; later updates match. Computed styles match at rest, hovered and focused. The collapsed Sidebar keeps its rcx-sidebar group: it has a handful of subscribers per page, not one per row.
…default attributes @tamagui/vite-plugin 3.0.18 flattens a use-site of a styled() frame into a plain element, but on web it dropped the frame's non-style defaults, such as role="group" on ButtonGroup: only the attributes written at the use-site reached the element, while its native path already carried the defaults over. The patch does the same on web, letting a prop written at the use-site still win. Nothing runs the compiler yet.
…values to a class table
The compiler flattened a frame only when every variant prop was a literal or a ternary of literals, so a row state
written as `selected={isSelected}` kept the whole element on the runtime. A variant can only take one of its keys or
none of them, so the patch treats it as a finite domain, as the compiler already does for a frontend's className: each
value resolves at build time and the element's className picks the classes by the runtime value
(`{"true": "...", "false": "", "undefined": ""}[isSelected]`). Class names a variant adds come along.
…piler The Fuselage build now runs the Tamagui compiler, so every frame use-site inside the library that passes no spread renders a plain element with static classes, such as a Button's inner content or a Tag's label: 70 elements, against 20 without `prevalStyled`. - `prevalStyled` evaluates each component module at build time, with `@tamagui/web` stubbed, and rewrites the options of the frames such a use-site renders as JSON literals, since the compiler only reads literals in a module's own definitions. - The extracted CSS goes in the `fuselage` layer, beside the runtime's rules. Jest and Storybook still run `src`, so the compiled output was checked through `dist`: computed styles of the bench rows match the uncompiled build at rest, hovered and focused, and the ui-client Storybook is pixel-identical. Mounting gets 17% faster for Buttons, 25% for Tags and 6-7% for item and message rows; the bundle grows by 4.2 kB of JavaScript and 3.1 kB of CSS (gzip).
…ad of finding them out after mount An Item that is given multiline and interactive skips the registration that re-renders it once its content and link have mounted. A 200-row sidebar list mounts in 27 ms instead of 35 ms when warm and 48 ms instead of 63 ms when cold, with the same computed styles.
…their Tamagui frames Forty-two wrappers that only set rcx-* classes and constant props on a single frame, plus ButtonGroup, ThreadMessageOrigin and MessageGenericPreviewDescription, which also map props to modifier classes, are now the frames themselves. The Tamagui compiler can flatten their use-sites in an app, and a consumer's className now adds to the rcx-* classes instead of replacing them (or being dropped, as States did). The rcx-* classes come from always-on variants, since a className default would be replaced by the consumer's. The Fuselage Storybook is pixel-identical (525 of 531 stories; the other six differ from themselves between captures). With the app compiled, 150 messages mount in 10.1 ms instead of 16.1 and 300 button groups in 1.5 ms instead of 11.8, with the same computed styles.
PureItem and its parts (media, content, row, title, meta, description) are the Item frames with their rcx-* classes as variants. A row declares what Item works out at runtime (surface, pointer, gap, highlight, ARIA ids), so every use-site compiles to a plain element, per-row state props included. 200 sidebar rows compiled in the app mount in 1.7 ms instead of 38.5 ms with Item and update in 1 ms instead of 22, with the same computed styles at rest, hovered and focused.
The helpers took any value and returned undefined for anything outside their list, so a misspelled token compiled and the style quietly disappeared. Frames call them with literals, so they now accept only the names in fuselage-tokens and always return a value; Box styling props, which receive values at runtime, use lenient resolve* versions that stay internal.
Fuselage frames are all View or Text, so the .is_DOM rules never match an element, yet every style recalc still tries them: about 13% of selector matching on a room page.
…eal item rows A descendant with :focus-visible is focused, so :focus-within rules out every unfocused row and group header before :has() walks its subtree. The open-menu rule looks only at the row's own actions, which Item and ItemGroupHeader render as direct children.
Port of #43021 to the Tamagui frames: spacing now resolves to px from the token scale, so Box inset props and the message toolbar's offset go through inset(), which keeps its own rem steps.
#42974 Ports #42974 onto the frames, which won the rebase over develop's Box and SCSS versions: Button compact and link types, IconButton badge position, ButtonGroup stretch='equal', Chip small and label, Divider light, flush, small and stretch, Tag gap, Icon colors, Table columns and row headers, Tabs stretch, Tile flush, Skeleton inverted, Modal hidden, ModalBackdrop transparent, ModalTagline subtle, InputBox end-addon-flush, FieldRow justify, and the internal compositions that stopped passing styling props. CardGrid's and Tile's class rules, which develop styles in SCSS, move to plain CSS. Button's block padding follows the size scale, as #43021 did.
…them its variations Ports #43100 onto the frames, which won the rebase over its Box versions: every component's public props type is now ElementProps plus its declared variations, with className as a string, and the frames gain TableCell truncate, color and strong, Table columns in px and percentages, ModalFooter justify, Modal maxHeight and Tile flush. Styles the PR keeps in SCSS move to plain CSS (NavBar divider, thread follow icons) or useStyle (Callout, StatesIcon, ToastBar, CardGrid's row height).
Since #43100 no caller can pass a styling prop, a free is, an rcx-* prop or a css function as className to a component, so useTamaguiBoxProps had nothing left to translate at render time and is gone. Each component builds its own className and render target, and the styling it used to receive from inside Fuselage (LabelInfo and RequiredMark margins, TableSelection's layout, ToastBar's elevation, Throbber and Icon sizes) lives in its frame. Only Tile, Button, IconButton and Icon, which Margins, Flex and Scrollable still lay out, keep taking BoxTransforms.
Without the Box prop translation, ModalTitle, ModalBackdrop, ModalTagline, ModalFooter, ModalFooterControllers, TableBody, TableFoot, StatesLink, SidebarFooterContent, SidebarButtonGroup, NavBarGroup, MessageBlock and AvatarContainer only style one element, so they are now that frame, and an app that runs the Tamagui compiler can flatten their use-sites. ContextualbarActions, SidebarActions, StatesActions and TableSelectionButtonGroup only renamed ButtonGroup and are now ButtonGroup itself. A consumer's className now adds to their rcx-* classes (NavBarGroup replaced it, MessageBlock dropped it), and ModalFooter and ModalFooterControllers now forward the props they used to drop.
… names is-hovered, is-focused, focus-visible and active classes made a frame render every style of that state inline through forceStyle. Only the Storybook state showcases used them, and forceStyle kept those frames from compiling to static classes, so they no longer pin a state. The disabled class still maps to the disabled look.
The proposal's decisions, measurements, migration and SCSS-removal recipes, the cascade layer and provider-free rules, how far the Tamagui compiler takes Fuselage, the plan for removing its runtime from the web build, and the phase-0 backlog of fixes that land on develop first.
… Tamagui components Ports the base's 6.1 to 6.4 onto the frames and the plain CSS. The style sheets no longer match the class words `hover`, `active`, `focus`, `disabled`, `invalid`, their `is-*` forms, `focus.focus-visible` or `is-visited`; only pseudo-classes, attributes and the components' own modifiers drive state. The frames stop reading a `disabled` class name to pick their disabled look or cursor, so `isForcedDisabled` and `hasDisabledClassName` go away. The `InputBox` wrapper matches `:has(> .rcx-input-box:invalid)` and `:has(> .rcx-input-box:disabled)` instead of `invalid` and `disabled` classes it set from React state, which drops the extra render that state cost. The selects style invalid and disabled through `rcx-select--invalid` and `rcx-select--disabled`, and an open one keeps the focus look through `aria-expanded`.
Text came from develop with an SCSS stylesheet, which the pilot no longer compiles; its scales, colors and truncation become plain CSS imported with the other component stylesheets.
…gui components The Select family batch moved the default look of the selects, the options list and the mobile dropdown from styling props to SCSS; the pilot had already moved those stylesheets to plain CSS, so the new rules move there: the select value, item, field and chip rows and the trigger layout in Select.styles.css, the list height and surface in Options.styles.css, and the bottom sheet in a new Dropdown.styles.css. The component changes (className and style on the trigger, controlled SelectLegacy, aria-multiselectable, PaginatedMultiSelect ref, derived selection, Dropdown surface attributes, chips removed on click) live in files the pilot leaves alone and need no port.
… components The contextual bar, ModalContent and ModalIcon stay Box elements, so the stylesheets the batch wrote for them become plain CSS. The Card, CardGrid, CardGroup and TableSelection frames get the batch's semantics: a horizontal Card wraps below the sm breakpoint through a media style instead of useBreakpoints, CardGrid takes element props and sets its row height through a custom property instead of runtime css, CardGroup drops the small and large props, and the selection bar and the empty cell placeholder are styled from the table stylesheet. The pilot already merges className into the card parts, forwards the modal footer props, pads Tile from its frame, and types TableRow without hasAction.
…the Tamagui components AudioPlayer and Slider stay Box elements, so the stylesheets the batch wrote for them become plain CSS. The frames get the batch's semantics: Bubble and BubbleItem merge a consumer className, the status bullet icons stop emitting an undefined class token, a disabled TabsItem renders a disabled button, FramedIcon honors size, Chip keeps onMouseDown and onClick on their own events, ToastBar renders its id and animates on static keyframes with a --toast-time custom property, the Throbber circles take their timing from custom properties, and AccordionItem collapses through the shared useCollapse hook, so expanded controls it and onToggle reports toggles. Runtime css remains only where the pilot uses it for the ToastBar and Callout icon colors, which the batch did not touch.
…xes to the Tamagui components The Message, NavBar, Pagination, Sidebar and States frames keep their own rcx class next to a consumer className instead of letting it replace them, and the parts that dropped HTML attributes take them again: the generic preview description, footer and icon, the status indicator text, the thread origin and body, the system block, container and timestamp, the option description block and the message divider. InputBox puts a className only on its wrapper when it has an addon, StatesIcon lets the consumer set its size, SidebarCollapseGroup drops the dead actions prop, SidebarLink is typed as an anchor and SidebarItemAction's onClick as a React mouse event, Divider documents which props apply with children, and Item adds the multiline and interactive modifier classes the pilot's declared lines and interactivity already honor; the Item render-count spec declares interactive, which is how the pilot skips the link's registration. The pilot already keeps Button's rel, logs instead of throwing for Field parts outside Field, and re-exports ButtonGroup and Divider under the removed component names.
…props Ports #43157 onto the frames: IconButton takes variant and size, which win over the deprecated boolean props, and Banner, SidebarBanner and MessageMetricsFollowing pass size, the latter before the consumer's props so they can override it.
39b444c to
58734fd
Compare
The frames keep the 2px and numeric spacing their SCSS used, so they look the same; the spacing lint only checked TypeScript, so those values were never in its baseline. Recording them keeps the ratchet blocking any new off-scale value.
Proposed changes (including videos or screenshots)
40 Fuselage components now render with Tamagui (
@tamagui/web3.x). The package no longer contains or builds any SCSS, and Sass is gone from its build, Storybook and lint. Components look and behave as before.Decisions, measurements, the migration recipe and the lessons learned are in
docs/proposals/fuselage-tamagui.md.How it fits Rocket.Chat
Colors and themes: no Tamagui tokens or themes. Values stay on the
--rcx-*custom properties, so palettes, dark mode, high contrast and custom themes keep working.Cascade layer: Tamagui styles sit in
@layer fuselage. Unlayered CSS keeps overriding them: Box styling props (css-in-js), app CSS and admin Custom CSS.Global element rules: the app's Meyer reset and its
button/input/code/pre/blockquote/:focusrules move into@layer reset, so element selectors no longer win over the layered component styles.Public API: props,
rcx-*class names andrefforwarding are kept. State class names (is-hovered,focus focus-visible, …) still pin a component's state, throughforceStyle.DOM: kept as before, except decorative pseudo-elements, which became
aria-hiddenelements.Boxprops reach Tamagui frames throughuseTamaguiBoxProps.What stays plain CSS (
fuselage.css, flat CSS, no Sass): what Tamagui can't express — native:checked/:invalid, rich text,@keyframes,@font-face— plus thercx-*class names other code puts on its own elements (styles/consumer-classes.css).No provider required: Tamagui needs a theme context, so every component used to mount a fallback
FuselageProvider. A patch now lets a frame with noThemeProviderabove act as its own root.fuselage.css.yarn tamagui-cssgenerates it, andyarn lintfails when it is stale.FuselageProvideris an optionalThemeProviderthat only lets components share one root; the app mounts one at its root.Layer order:
@layer reset, fuselage;comes first infuselage.css, and is also prepended to<head>before Tamagui inserts its runtime sheet. Pages that inject CSS from JavaScript (Storybook) would otherwise let element resets beat every frame.Group clauses off the hot rows: a Tamagui
group-*clause subscribes its element to the group, and the first subscriber makes the parent render again on mount. Message and Item reveal their parts (timestamp color, toolbar, hover-revealed actions) with plain rules in thefuselagelayer instead. Mounting gets 26% faster for message rows and 43% faster for item rows with actions. The first update after a message mounts gets 17% slower, since it now warms Tamagui's caches; later updates are unchanged.Compiled with the Tamagui compiler: the Fuselage build runs
@tamagui/vite-plugin, so a frame rendered with literal props becomes a plain element with static classes, with no Tamagui component at runtime.prevalStyled.tsevaluates each module'sstyled()options at build time, since the compiler only reads literals.fuselagelayer.ButtonGroup,NavBar, …), so an app that runs the compiler flattens their use-sites too. Theirrcx-*classes come from always-on variants, so a consumer'sclassNamenow adds to them instead of replacing them, as ondevelop.PureItemand its parts: theItemframes with nothing worked out at runtime; the row declares its state, gap and highlight.Itemalso takesmultilineandinteractive, which skip its re-render after mount (200 sidebar rows mount 24% faster).Bundling:
@tamagui/webis bundled into the Fuselagedistwith its runtime settings pinned, so consumers need no bundler config.Patches: yarn patches cover Tamagui 3.0.18:
forceStyleandonLayoutleaking to the DOM;stopPropagation()after every frameonClick;@tamagui/static): a frame's default attributes (role,aria-*) dropped when it flattens, and variants given a runtime value bailing out instead of compiling to a lookup over their classes.They stay until upstream handles them.
Commits
The first ten commits work on
developas is (no Tamagui) and can each be extracted into their own PR:fix(fuselage): give the private message indicator its modifier class: a stray backtick in its class string;fix(fuselage): stop adding an undefined class to the reaction counter;perf(fuselage): register Item titles only for menu items: EveryItemTitleregistered its id with state, so each row rendered twice on mount; only menu items read those ids.refactor(fuselage): extract rcx-* prop class names from useBoxOnlyProps;refactor: move the global CSS reset into a reset cascade layerandrefactor: move the remaining global element rules into the reset layer: these change the app's cascade, so they need an app A/B againstdevelopbefore they are extracted;chore(fuselage): add a script comparing bundle size and render cost with develop;refactor: render the asset upload control with Button;fix(fuselage): don't read window when OwnerDocument loads;fix: name the license drop zone's stroke colors as strokeColor expects.With only these ten on
develop, Fuselage typechecks and all 494 tests pass.The next nine are phase 0 of the migration, still without Tamagui: visual state is no longer forced or carried by class names (items 6.1–6.5 of
docs/proposals/fuselage-phase-0-inventory.md).Selectopen state comes fromaria-expanded.InputBoxwrapper's invalid and disabled looks come from:has().rcx-select--invalidandrcx-select--disabledmodifiers.Optionsfinds the active option by position.storybook-addon-pseudo-states.Selecttrigger that stayed enabled, aSelectLegacythat needed two clicks to reopen, and the close chat modal's subject field, which never showed a focus ring.On
developalone they typecheck, all 495 tests pass, and 553 of 561 stories are pixel-identical. TheSelectstates story is the one real change: it now shows hover and focus.The next 53 commits are the second phase-0 round, still without Tamagui, in four batches. They are recorded in
docs/proposals/fuselage-phase-0-inventory.md.classNameis merged instead of replaced or dropped;cssbecame static CSS with custom properties;FramedIconignoringsize,ToastBardropping itsid,SelectLegacyignoring a controlledvalue,aria-multiselectablealwaystrue, a lostref,Buttondroppingrel, and anAccordionItemthat would not collapse.On
developalone the whole base typechecks and passes its tests, Meteor'stscpasses, and the Fuselage Storybook is pixel-identical to the base it started from, apart from known timing flakes. The pilot ports these batches onto the frames in its last four commits. The integrated pilot is pixel-identical to the previous pilot in 551 of 562 stories; the other 11 are timing flakes. Six forms that render custom fields are pixel-identical todevelop.Enabling refactors and fixes come next.
Then one commit per component.
Then shared CSS, toolchain and docs.
Some components depend on each other, so the PR is the unit; not every intermediate commit passes on its own.
Measurements
fuselage.production.js: 34.4 → 96.5 kB gzip.fuselage.css: 26.7 → 8.7 kB gzip.PureItem1.7 ms against 38.5 ms withItem. Computed styles are identical at rest, hovered and focused.After rebasing on
developwith #42974 (components take no styling props), #43021 (spacing scale), #43047, #43017 and #42962:compact, ButtonGroupstretch='equal', Dividerflush/light, Chipsmall/label, Tablecolumnsand row headers, Tabsstretch, Modalhidden, InputBoxendAddonFlushand the rest. TheCardGridandrcx-tilerules that develop styles in SCSS are plain CSS here.develop: 554 of 561 stories are pixel-identical. The other 7 (Tooltiparrow positioning,Dropdown,PositionAnimated,OptionsPaginated) differ in when they finish drawing, not in their styles: settled, they match, and they also differ between captures of the same build.tscand oxlint: 0 errors.tsc: 0 errors.scripts/scan-style-props.mjs --max 1: passes.After rebasing on #43100 (components no longer accept Box styling props) and #43087 (positioned popups):
ElementPropsplus its declared variations, withclassNameas a string. The frames also gain refactor(fuselage): components no longer accept Box styling props #43100's variations: TableCelltruncate/color/strong, Tablecolumnsin px and percentages, ModalFooterjustify, ModalmaxHeight, Tileflush.useStyle(Callout, StatesIcon, ToastBar, CardGrid's row height).develop: 551 of 561 stories are pixel-identical. The other 10 differ only in capture timing (Tooltip arrows, OptionsPaginated, PositionAnimated).tsc: 0 errors.tsc: 0 errors.scripts/scan-style-props.mjs --max 0: passes.useTamaguiBoxPropsandwithFuselageProvider({ takesBoxTransforms })no longer have a styling prop to translate, so they can go. Most of the remaining components can then become exported frames the compiler flattens.Issue(s)
https://rocketchat.atlassian.net/browse/ARCH-2565
Steps to test or reproduce
yarn dev:nextwas already running, restart it after removingapps/meteor/node_modules/.vite; otherwise it serves the old Fuselage JS with the new CSS.RC_SERVER_URL=https://open.rocket.chat yarn dev:next.What was compared against
develop:developbuilds.develop-based build. The only differences are avatars still loading and the port number shown in the Site URL warning.rcx-box rcx-box--full, class order, shifted_r_N_ids and the newaria-hiddendecorative elements.tscand the fuselage-dependent packages' typecheck pass, and Fuselage lint has no errors.After the provider-free change, compared against the commit before it:
PositionAnimated,Dropdown) also differ between two runs of the same build.abac, which doesn't use Fuselage and needs a database._r_N_ids, since each component callsuseIdonce less.After the group change:
Tooltippositioning,PositionAnimated) also differ from run to run.t_group_*class names.customOAuth.spec.ts(server) crashes the Jest worker in this environment, as it did before the change.After the compiler changes:
PositionAnimated,Dropdown) also differ between two captures of the same build.dist: the computed styles of the bench pages and the ui-client Storybook built against it match the uncompiled build (the ui-client failure,UserAutoComplete, fails in both).BannedUsersalso lost a stalet_group_rcx-itemleft from the group change.developremoved the Jest snapshot tests (#42977). The branch is rebased on it and carries no snapshots; the snapshot comparisons above were made before that.Style recalc on a room-like page (about 1,010 elements: sidebar rows, messages, toolbar, form; Chromium, median of 15 runs):
develop.is_View/.is_Textbase), and the cascade processes each..is_DOMbase rules, which never match a Fuselage frame, are no longer emitted.:focus-withinbefore:has(). Together these cut the full recalc from 3.87 to 3.51 ms.Further comments
TAMAGUI_RUNTIME=islandcould drop most of it once enough use-sites compile. Seedocs/proposals/fuselage-tamagui.md.MessageHeaderand other components with logic still put theirrcx-*class before the consumer's props, so a consumerclassNamereplaces it.Boxitself still runs on css-in-js.RaisedHandsButtonin ui-conference spreadspressedinto aBoxand gets a React warning; judging from the code, develop does the same.