mirror of
https://github.com/luxfi/safe-wallet.git
synced 2026-10-30 15:19:21 +00:00
* feat(web): add workspace and trusted-safes data hooks - useWorkspacesView: signed-out/no-workspaces/has-workspaces view state - useSpaceSafesById: lazy per-space safes for on-demand lists - useSafeSpaces: reverse lookup from a Safe address to its workspaces - useLocalAccountsView: connect-wallet/add-trusted/list state machine * feat(web): unify welcome nav around Workspaces and Trusted accounts - Tabs: Workspaces first, Trusted accounts second, always visible - Root landing: added safes -> Trusted accounts, otherwise Workspaces - Workspaces tab lists spaces as expandable rows that lazy-load safes - Signed-in users with one space jump straight to it; no forced create-workspace redirect for new users * feat(web): add trusted safes empty-state card * feat(web): split space safe accounts into Workspace and Trusted tabs The Trusted tab lists the user's local safes with a per-row add-to-workspace action and connect-wallet/empty states. * feat(web): add Workspace/Trusted tabs to the in-safe account dropdown The selector exposes both lists via a segmented control: the workspace safes (or a sign-in CTA outside a space) and the trusted safes with manage/add actions. * feat(web): redesign trusted accounts as a sortable table Reusable SafeAccountsTable with Name/Threshold/Networks/Workspaces/ Pending/Balance columns, sortable by name, threshold, networks and workspaces. Multi-chain accounts expand into per-chain child rows and sort as one group. Rows show the full address; long names truncate with a hover tooltip. Column set is configurable for reuse on other surfaces. Widens the welcome accounts column to fit all columns per the design. * feat(web): remove the require_login gate Users are never forced to sign in: the welcome tabs, trusted accounts and deep links work signed-out, so the gate, its ?next= round-trip rules in the route guard, the auth-gate page blanking and the classic view escape hatch are all removed. The REQUIRE_LOGIN and CLASSIC_VIEW feature flags become no-ops and are dropped from the FEATURES enum. Deploying this build launches the signed-out experience; the config flag no longer has any effect. * fix(web): give the accounts table a white panel background The table is a white rounded panel inside the tinted accounts container, per the design; the container itself keeps its tint. * fix(web): move Manage trusted Safes onto the Trusted Safes header row The button sits right-aligned next to the section label instead of centered under the table, per the design. * feat(web): align safe selector rows with the accounts table Dropdown rows (both Workspace and Trusted tabs) show the safe name and address, a threshold pill, the networks stack, a pending-transactions badge and the balance — the same presentation for both tabs. The trigger shows the threshold as an inline pill instead of an avatar overlay. Threshold/pending pills move to a shared AccountBadges module used by the accounts table and the selector. * feat(web): reuse the accounts table on the workspace Safe accounts tabs Both the Workspace and Trusted tabs render SafeAccountsTable instead of the card lists. The table gains a renderActions override (workspace context menu / add-to-workspace button), similarity flagging for the address-poisoning warning, and a configurable actions column width. The card-only list components are removed. * fix(web): show the Safe logo on signed-out welcome pages, align tab widths The welcome tabs hide the Topbar while signed out, leaving no logo — render it top-left. The Workspaces tab container widens to match the Trusted accounts tab so switching tabs doesn't shift the layout. * feat(web): keep the top bar visible on welcome pages, add account menu The welcome tabs always show the Topbar so the Safe logo and the wallet section stay visible regardless of auth state; the logo replaces the search input as the left content there. A round account icon with the sign-out popover sits next to the wallet section whenever the user is signed in to a space account, so signing out no longer requires entering a space. The signed-out header spacer is removed since the Topbar now always occupies that space. * fix(web): align table names in a fixed identicon column, drop the chevron The identicon sits in a fixed-width slot so every name starts at the same x position, and multi-chain rows lose the expand arrow — the row itself stays clickable (aria-expanded), matching the design. * fix(web): center table badge columns, monospace addresses, slim pending Threshold, networks, workspaces and pending center-align (name stays left, balance right), the pending column narrows with a nowrap badge, and full addresses render in a monospaced font. * fix(web): style the topbar account button like the other icon buttons Drops the 48px green-tinted avatar circle in favour of the plain icon at the same size as the neighbouring topbar buttons. * feat(web): center welcome tabs, move actions into panels, add workspaces table * feat(web): open workspaces directly from the welcome list - Welcome workspace rows no longer expand to list safes; the whole row links into the workspace - Move the Create button above the list as an outline button and drop the table header to match the design - Restyle the invite banner: compact header with Decline/Accept design-system buttons, muted workspace row, centered inviter - Remove the now-unused useSpaceSafesById hook * feat(web): redesign the safe switcher dropdown and topbar per Figma Topbar: the safe selector, nested-safes and network controls now render as muted 40px chips inside one white 56px pill, mirrored by the actions cluster on the right; chip buttons fill their chips so hover recolors the whole element. Trigger: full address with emphasised head/tail (middle ellipsizes first), CSS name truncation, icon-only threshold badge for multi-chain safes, foreground/medium balance. Dropdown: search above the tabs filtering both lists with live counts in the tab labels, fixed table-like row columns (empty cells keep their width), full row addresses, hover rename/copy/explorer actions, name and address tooltips (1s delay, non-interactive), the current safe stays in the list highlighted, pointer highlight no longer sticks to the last focused option, and a "Manage trusted accounts" footer row replaces the two footer buttons. * feat(web): rework workspace accounts, trusted-safes picker and nav Rework the workspace Safe accounts experience per the Workspaces UX review: - SafeAccounts page: drop the Workspace/Trusted tabs, show only workspace accounts with a name/address/network search bar; keep multi-chain expansion. - Add accounts: two-view modal — a trusted-only picker plus an inline "Manage trusted Safes" view (owners endpoint + address-poisoning alerts) that saves and returns to the picker. Extract ManageTrustedSafesContent. - SafeAccountsTable: optional selection (checkbox) mode; in selection mode the whole row toggles and no longer navigates. Manage view shows a copy button and block-explorer link next to each address via AddressWithCopy. - Sidebar: swap the logo for a back-arrow pill when inside a space or safe, linking back to /welcome/accounts. - SpaceSafeBar: the trusted-accounts tab lists pinned safes only. * feat(web): manual sort and drag-and-drop across Safe account lists - Add Manual order option (persisted per scope) alongside Name/Last visited - Drag-to-reorder trusted safes on welcome and spaces; grip sits in the Name gutter so no column is added - Apply the shared sort and the sort toggle in the Manage trusted Safes modal - Always surface trusted accounts; remove the redundant "Safes detected on this browser" sign-in alert * feat(web): redesign Select Safes onboarding with unified table - Replace the card list with the shared SafeAccountsTable, split into Trusted and Owned sections (owned shown only when a wallet is connected) - Drop select-all in favour of a per-cap "N of 40 selected" counter + search - Apply address-poisoning protections to owned safes only: high-similarity flag, security banner and a confirm-on-select dialog (trusted safes are treated as vetted and never flagged) - On Next, add each selected owned safe to the global Trusted list - Fix the onboarding panel width so it stays constant across all steps - Make the skip links bolder on Select Safes and Invite Members - Remove the now-unused SafeCard, SafeCardLayout, SelectAllToggle, useSelectAll and selectAllHelpers * fix(web): keep full safe address visible and fix selector tooltips Safe selector trigger: - Overlay the copy/explorer/env actions on the address on hover instead of reserving inline width, so the full address stays visible and the row no longer jumps when the actions appear. - Reclaim width (8px gaps, drop the balance min-width floor, tighter chevron hit-area); the balance column stays protected. Row/trigger tooltips (copy, explorer, rename): - Non-hoverable + pointer-events-none, so they close when the pointer leaves the button and never block clicking the row beneath. - Open after a shared 400ms delay, matching the name/address tooltips. * fix: empty state for trusted safes for connected wallet * refactor(web): drop dead nav hooks, dedupe safe grouping, surface add-accounts errors - delete unused useLocalAccountsView and useWorkspacesView (+tests, exports) - extract triplicated _groupAndSort into @/hooks/safes - replace swallowed console.log in AddAccounts submit with a user-facing error * refactor(web): dedupe row icon buttons, share badge, tidy conventions - extract RowIconAction; Copy/Explorer/Rename buttons become thin wrappers - migrate onboarding ThresholdBadge to shared AccountBadges - split SafeAccountTableRow cell rendering into RowCell (under the size limit) - unify the 'Manage trusted Safes' label; sentence-case + workspace casing fixes - use success theme tokens for the widget count pill; use safeIds helpers - rename useSelectAll.types -> addAccounts.types - add stories (AccountBadges, AddressWithCopy, GetStartedCard, TrustedAccountsActions) + TrustedAccountsActions test * fix(web): refine safe selector dropdown empty states and multi-chain rows - Show a "No trusted accounts" empty state with a Manage list button when connected without trusted safes, and drop the redundant footer row there - Update the disconnected empty state copy and close the dropdown before opening onboarding so the wallet modal is no longer hidden behind the popup - Align expanded multi-chain rows (name, threshold, chain icon, pending, balance) with the summary row and drop the per-chain identicon - Auto-expand the active multi-chain group and highlight only the active network row instead of the whole summary item - Support a function-form emptyStateOverride that receives a close callback * fix(web): unify the trusted-safes CTA label in the dropdown empty state * feat(web): new empty state for trusted safes * feat(web): show footer on welcome accounts and spaces pages The flexbox sticky-footer scaffolding already lives in PageLayout; the footer was just gated off these routes. Add them to the footerPages allow-list and cover the visibility logic with a colocated test. * feat(web): restore brand-green glow on welcome list pages The glow (previously .authShell) was dropped when the welcome nav was unified. Reinstate it at the layout level so it sits behind both the Trusted accounts and Workspaces tabs, and remove the now-dead .authShell. * fix(web): show Safe name/address tooltips only when truncated Adds a TruncatedText component that reveals the full value in a tooltip only when the text is actually clipped (measured lazily on hover, not a resize observer), and clip-gates the row address tooltip via the FullAddress middle span. Also adds a closeDelay prop to the shadcn tooltip. * refactor(web): share one account-row identity cell across dropdown and tables Moves the dropdown's SafeInfoDisplay and its row primitives (RowIconAction, Copy/Explorer/Rename buttons, FullAddress, TruncatedText) into components/common/AccountRow so both the safe-selector dropdown and SafeAccountsTable render rows from the same component. The table's Name cell now uses SafeInfoDisplay (clip-gated name/address tooltips, copy/explorer revealed on row hover) via leading/hideAddress/badge slots, and the obsolete plainCells/showAddressActions props are removed from the table and its callers. * refactor(web): drop the workspaces column inside a workspace, align badge sizing Inside a workspace every Safe belongs to it, so the Safe accounts screen no longer shows the Workspaces column. Workspace avatars are bumped to 24px to match the network logos (column width 116->96px), and each reveals its full workspace name via a hover tooltip instead of a native title. * feat(web): green highlight for the active safe row, grey on hover The selected safe in the dropdown now uses a subtle green contained pill instead of grey, matching the design's Active state; hover stays grey. Table rows gain the same grey hover so the identity cell's copy/explorer icons read as hover-revealed. * fix(web): grey (not green) hover on the account tables The base MUI theme tints every table row green on hover via a high-specificity '& .MuiTableRow-root:hover' override, which beat the row-level sx. Override it at the Table level with the same grey the safe-selector dropdown uses (--muted); locked rows opt out via data-disabled. * feat(web): round and inset the account-table row hover to match the dropdown The row hover now paints a grey pill on the cells (not the tr), inset from the panel edges via 4px transparent side borders + background-clip on the outer cells and rounded outer corners — the same trick the header bar uses. This gives the account tables the contained-pill hover the safe-selector dropdown has, and aligns body content with the header. * fix(web): remove green corner bleed on row hover, clear the bottom edge The base theme's green row-hover tint on the <tr> was showing through the inset/rounded grey pill at the corners; suppress the <tr> hover so only the grey cell pill paints. Also add bottom padding so the last row no longer sits on the panel's rounded border. * fix(web): render the row separators on the account tables The base theme sets cell borderBottom:none with a specificity the per-cell showDivider sx couldn't beat, so no separator showed. Draw it at the Table level keyed off a data-divider attribute (border.light, absent on the last row of a group/list), matching the design. * fix(web): float the row hover pill clear of the separators Give the row cells transparent top/bottom borders (with background-clip) so the grey hover pill insets vertically and no longer touches the separators, matching the design. The separator moves from the cell border to a 1px gradient line on the <tr> (inset 4px), freeing the cell borders for the pill inset. * fix(web): float the bottom of the hover pill too The pill's bottom still touched the separator because the base theme forces cell borderBottom to none at a specificity the per-cell sx can't beat, so the transparent bottom inset border was dropped (only the top, which has no theme rule, worked). Move the vertical inset borders to the Table level where they win. * feat(web): add the hover rename pencil to the account tables * feat(web): white content card for welcome accounts & workspaces Lift both welcome tabs onto a shared white rounded card so their content reads against the page's gradient backdrop. A new WelcomeContentCard wraps the Accounts search + table and the Workspaces Create + list (whose box also gains a border). Non-admin workspace rows now show a disabled kebab with a "You need admin access to edit." tooltip, and the accounts search input gets a visible border, the placeholder "by name, address or network", and an updated accessible label. * fix(web): show data widget in empty state, tighten card spacing Render the export/import DataWidget when a wallet is connected but has no trusted Safes, and drop the empty-state cards' bottom padding so the widget sits 24px below the main content. * feat(web): restyle sidebar workspace switcher and back arrow - Give the workspace switcher a persistent grey active background (per Figma), replacing the green sidebar-accent hover - Align the top-bar back-arrow pill with the workspace avatar (both at 20px) - Inside a Safe, render the back-to-workspace button with a trailing back chevron instead of a leading one, matching the space-selector layout * feat(web): restyle welcome account tabs and import widget - Match the Workspaces / Trusted accounts tabs to Figma: full-width segmented control, white active pill with concentric radius and soft shadow - Rebuild the "Import your Safe data" widget with shadcn components - Reserve the scrollbar gutter so content doesn't shift horizontally when a page toggles between scrollable and non-scrollable states * fix(web): reposition top-bar popovers/toasts and consolidate sign-out The unified top bar is taller than the legacy --header-height, so its dropdowns and toasts rendered on top of it. - Account, WalletConnect, wallet and notifications popovers open with a 12px gap below their trigger instead of pinning to --header-height - Toast stack cleared below the bar (--header-height + --space-4) - Account menu popover gets a white background; WalletConnect and the account popover adopt the notifications popover's 24px radius - Remove the duplicate workspace sign-out from the sidebar footer; it now lives only in the top-bar account menu (getSidebarProfileInfo relocated to HeaderNavigation) * feat(web): reuse shared accounts table in workspace dashboard widget Rework the workspace dashboard Accounts widget to reuse the shared SafeAccountsTable (the component behind Trusted accounts, welcome accounts, and the space accounts page) instead of bespoke row components, unifying the design. - Add an opt-in 'embedded' variant to SafeAccountsTable: no column header, no bordered container, and a colgroup so the Name column flexes while stat columns stay fixed (fits the card, no scroll) - Widen onLinkClick to carry the clicked AccountLine (backward compatible) so the widget keeps per-safe click analytics - Delete AccountWidgetItem, ExpandableAccountItem, AccountItemContent and drive the widget from items: AllSafeItems - Show up to 5 accounts, widen the widget (grid 7/5), add a +N overflow badge (total - displayed), tighten the View all button padding - Remove the Pending widget's View all action - Update legacy Cypress selectors to the shared table's testids * feat(web): hide add accounts button for space members * fix(web): let multi-chain safes expand and navigate while manually sorting * feat(web): float the manual-sort drag handle in the left gutter on hover * refactor(web): move shared safe helpers out of feature barrels to break import cycles isMultiChainSafeItem, FiatBalance and useSafeSpaces were reachable only through the multichain/spaces feature barrels, forming mutual import cycles (myAccounts <-> spaces, multichain <-> hooks/safes). They depend on shared primitives only, so relocate them: - isMultiChainSafeItem -> hooks/safes/isMultiChainSafeItem.ts (dependency-free leaf) - FiatBalance -> components/common/FiatBalance (generic currency formatter) - useSafeSpaces/SafeSpacesMap -> hooks/useSafeSpaces (store + gateway API only) No behaviour change; same symbols, same call sites. Reduces the latent cycles that crash Storybook's webpack module init. * feat(web): align sidebar accent, new-tx and add-to-workspace buttons with Figma - Fix dark-mode --sidebar-accent (#171717 → #052e16) so active menu items and the back-to-home button show the green highlight; remove the grey .sidebarInteractive overrides that compensated for the broken token - New transaction button: filled secondary → outline (bordered) variant - Add Safe to workspace: 16px radius, --border dashed, rounded-square icon chip, centered content, 12px/semibold label * fix(web): reclaim content space when sidebar collapses on Safe routes The collapsed-sidebar padding was gated on isSpaceRoute, so on Safe routes collapsing the sidebar to icon mode left the main content offset by the full 230px sidebar width — a large empty gap. Generalize the state + .main padding class to every sidebar route (guarded by isSidebarVisible) so the content reclaims the freed space on both Safe and space routes. * feat(web): align trusted-safes modals with Figma copy and button layout Update the "Manage trusted Safes" and "Trusted Safe accounts" modals to match the latest Figma: - SecurityBanner: reword the impersonation warning and shorten the link to "Learn more". - ManageTrustedSafesContent: lay out the footer as two equal-width buttons (secondary + Save), with a back chevron on the "Back" label. - AddAccounts: reword the info banner to "What are trusted Safe accounts?", rename the action to "Manage list", and place "Add manually" and "Add accounts" side by side. - AddManually: render the trigger with the shadcn secondary button so it matches the primary button's height and fill. * fix(web): unify header popover radii and sizing Give the connected-wallet popover, safe/network selector dropdowns, and the Nested Safes popover the 24px radius used by the WalletConnect popover, so all header surfaces read as one system. Also drop the chain-selector dropdown 12px below the taller header pill (was touching its bottom edge) and let the Nested Safes popover size to its content, capped at the viewport, instead of always rendering full-height. * fix(web): remove scrollbar-gutter reservation The stable scrollbar gutter double-reserved space against the modal/popover scroll-lock padding, shifting the page when dialogs opened, and left a mismatched strip in dark mode. Revert to the previous behaviour. * fix(web): move block-explorer links onto per-chain rows A multi-chain safe's summary row has no single chain — its chainId is just the first network's — so the explorer link it carried pointed at an arbitrary chain. Drop it from the summary/parent rows and render a per-chain explorer link on each network row instead: - SafeInfoDisplay: when the address is hidden, place the explorer link next to the name; add a nameVariant prop for the bolder table name. - SafeAccountsTable: child rows keep the per-chain link (address hidden), the multi-chain parent gets none; drop the now-unused child chain icon and the indent field, aligning child names via a blank icon spacer. - SafeSelectorDropdown: extract NetworkRow to own the per-chain link; the summary row no longer receives one. - Hoist the shared HOVER_ACTION_CLASS into AccountRow/utils for reuse. * feat(web): rename Trusted accounts to My accounts - Rename "Trusted accounts" / "Trusted Safes" UI copy to "My accounts" - "Manage trusted Safes" buttons -> "Manage list"; modal title -> "Manage my account list" - Rewrite the connected empty state with a "Learn more" link to the address-poisoning article - Rename the workspace sidebar "Home" -> "Dashboard" - AddAccountsChooser: first row -> "Select from my accounts", drop the row subtitles - Update pin tooltips/toasts, NonPinnedWarning, MigrationPrompt and the Safe Shield warning to match - Update tests, stories and Cypress page objects accordingly Analytics event strings and code identifiers/testids are intentionally left unchanged. * feat(web): show Safe logo and Home label in place of sidebar back arrow * feat: add drag and drop to the safe list dropdown * fix(web): show generic Workspace tab label when safe is in no space The Safe selector dropdown reused the fallback space name (resolved by useCurrentSpaceId from last-used/first space) in the out-of-space branch of the workspace tab label. This leaked a specific space name onto the tab even when the current safe belonged to no workspace. Use the generic "Workspace" label unless the safe is actually part of the space. * fix(web): align space Safe account context menu with Figma Use neutral lucide Pencil/LogOut icons and rename the remove action to "Remove from workspace" to match the updated design. * fix(web): show pointer cursor on workspace selector rows DropdownMenuItem defaults to cursor-default; add cursor-pointer to the workspace selector's shared item class so hovering actionable rows shows a pointer. * feat(web): enlarge safe selector dropdown rows and max height Bump dropdown rows to ~60px to match Figma and raise the list ceiling from 34rem to 44rem. The height stays gated by base-ui's --available-height, so it still shrinks and scrolls on short viewports. * fix(web): expand multi-chain safes in the manual-sort dropdown In Manual sort a multi-chain safe rendered as a flat row that navigated straight to its first chain, so it could not be expanded. Reuse MultiChainSafeItemRow via a new leading slot: the summary now expands/collapses, the grip drags the whole group, and the per-chain rows navigate. * feat(web): add tooltips to safe selector row stats Delayed hover tooltips on the threshold, network logos, and pending badge so the row's numbers are self-explanatory. Matches the app's other row tooltips (TOOLTIP_DELAY_MS). * feat(web): add drag-and-drop reordering to the manage accounts list Reuse SafeAccountsTable's reorder path in the Manage my account list modal when Manual sort is active (suppressed while searching), persisting the shared trusted-accounts order. - Forward selection state through ReorderableBody so rows show both a checkbox and a drag grip; the grip hosts on the leading cell. - Portal the lifted row to <body> while dragging so the dialog's centering transform can't become its containing block and offset it. - Render the grip inline in the checkbox cell's padding on hover, so switching sort modes no longer shifts the table sideways. - Give the search input a visible border (--input is white) matching the sort toggle. * fix: don’t display drag and drop hint * feat(web): move connect-wallet hint to manage account list The workspace picker now lists only trusted Safes, so the 'connect a wallet to discover accounts' hint no longer fits there. Move it into the Manage account list view, where connecting discovers owned Safes. Raise the web3-onboard connect modal z-index above shadcn dialogs so it stacks on top of the manage list instead of behind it, keeping the list open and the user's position intact. * fix(web): keep safe selector open when renaming from the dropdown Renaming a safe from the top-bar selector used to close the dropdown because the rename dialog would otherwise render behind the popup and base-ui closes the popup on outside interaction. Layer the rename dialog above the popup (z-index) and add a keepOpen prop that suppresses the popup's close-on-outside-interaction while the dialog is open, so the user keeps their place in the list. * fix(web): make onboarding search border visible and refine scrollbar The Select Safe accounts search relied on the default border-input token, which is white in light mode and invisible against the card. Use border-border to match the sibling account search. Apply the repo's thin themed scrollbar convention to the shared onboarding scroll container and add right padding so the scrollbar no longer sits flush against the table. * fix: align view all * refactor(web): dedupe safe selection/reorder helpers, drop dead code - Extract shared getSelectedLeafKeys/applySafeSelectionToggle so the onboarding picker and workspace "Add accounts" share one toggle - Extract reorderByKey; remove the duplicate reorderAddresses helpers - Slim useSpaceAccountsData to loading/error/refetch (its accounts output was discarded) and delete the now-unused AccountsWidget types - Remove dead code: MyAccountsV2 AccountItem rows, TrustedSafesModal SimilarityWarning + styles.module.css - Focus dropdown rows on hover to clear a stale base-ui highlight * fix: remove drag handle helper text * fix(web): render Add manually dialog above the accounts modal * feat(web): make the whole Safe account row clickable Extend SafeAccountTableRow so a click anywhere on a row navigates to the safe (leaf rows) or expands it (multi-chain group rows), instead of only the name cell. The name keeps its real <a> for keyboard focus and modifier-click, and copy/explorer/rename/actions keep their own behaviour via a closest() guard. Selection surfaces are unaffected. Applies to all navigable account tables (Spaces accounts, Welcome/My accounts, dashboard widget). * feat(web): allow renaming accounts from the Manage list modal * fix(web): raise private-key connect dialog above the onboard modal The onboard connect modal was bumped to z-index 1450 (above shadcn dialogs), which pushed it above the private-key popup it spawns (z-index 1400), covering the input. Raise the popup to 1451 so it stays on top. Fixes wallet-connect Cypress smoke failures. * chore: remove alert from manage trusted safes * refactor(web): remove standalone welcome screen for tabbed welcome The legacy /welcome "Get started" login screen is replaced by the tabbed welcome pages (Workspaces + Trusted accounts). /welcome becomes a redirect stub, and the login gate, home links, and WalletConnect handoff now resolve to the default tab based on locally trusted safes (getWelcomeRoute), mirroring the root / logic. WalletLogin and useSignInRedirect are kept — they are reused by the spaces sign-in flow. * tests(web): drop obsolete space-notice assertions in TrustedSafesModal The workspace-notice alert was removed in 8b67837c2 but the tests still asserted it. Remove the now-stale/vacuous space-notice cases. * fix(web): mark counterfactual safes as not activated in the accounts table The unified SafeAccountsTable balance cell only rendered a fiat balance or skeleton, so undeployed (counterfactual) safes on /welcome/accounts showed a blank balance instead of the "Not activated" badge every other unified surface shows. Carry undeployed/isActivating on the row model and render NotActivatedBadge in the balance cell, matching the dropdown SafeItem and spaces SafeCardReadOnly. * chore(web): drop dead imports in ManageTrustedSafesContent Remove the Info icon, Alert/AlertDescription and useIsQualifiedSafe/isInSpace left unused after the manage-trusted-safes alert was removed; they were failing the lint step. * feat(web): add "Add accounts" chooser to My accounts tab Add an "Add accounts" chooser (Select existing / Create new) to the personal My accounts list toolbar and the connected-but-empty state, giving users outside a Space a way to watch or create Safes. Extract the shared ChooserRow into components/common and reuse it in both the Space and personal choosers. Update the empty-state card copy, layout, and "Learn more" link to match the Figma design. * fix(web): make read-only safe indicator an icon with tooltip The full "Read-only" text widened the per-network dropdown row and pushed the explorer link and stat columns to the right, breaking the menu layout. Replace it with an icon-only indicator plus a "Read-only safe" hover tooltip. * feat(web): flag pending txs awaiting the connected wallet's signature The safe-selector dropdown and My accounts table surfaced only the total queued count, and single-chain rows dropped even that. Thread the overview's awaitingConfirmation through both, and render an orange dot on the pending badge with a tooltip breaking down how many pending txs need the wallet's signature. * tests(web): align safe_selector e2e with the unified navigation UI Repoint the accounts_modal/safe_navigation page objects at the selector dropdown, Manage-list modal and rename dialog; drive add-safe through the AddAccountsChooser; assert the awaiting-confirmation dot; drop the obsolete pin-toggle test (adding to the trusted list is covered by the dashboard action-required test). * tests: fix multichain group selectors in safe-selector POM The unified-nav dropdown renders a multi-chain group as a Collapsible: the name + rename pencil live in the collapsible trigger (SafeInfoDisplay), not a select-item, and an active group expands by default so each network sub-row repeats the summary's chain logos. - renameSafe / clickSafeOptionsBtn: target select-item OR collapsible-trigger so both single safes and groups resolve the rename pencil. - verifyMultichainSafeChainLogos: scope the logo count to the collapsible trigger so expanded sub-rows don't double it. multichain_safe_selector 5/5, smoke/safe_selector 1/1, regression/safe_selector 30/30. * refactor(web): remove orphaned AccountsModal Superseded by the safe-selector dropdown (Workspace/My accounts tabs) and the Manage list modal; nothing rendered it anymore. * chore(web-tanstack): drop require shim orphaned by AccountsModal removal The scoped require() polyfill and compat/require-shim.ts existed solely to shim require('blo') in SpaceSafeBar/AccountsModal/shared.tsx, which was removed with the unified-nav rework. No other bundled apps/web source uses a sync require(), so the vite plugin, the compat module, and its main.tsx import are all dead. web-tanstack type-check passes. * tests: adapt spaces basic flow to nav rework; make it idempotent - sign in via the reworked workspace card (signInWithWallet) and target the SpaceRow (space-row) instead of the removed SpaceCard - navigate onboarding skip + space sections client-side instead of full cy.visit reloads, which rebooted the app and raced the render (the invite-members skip button rendered after the command timed out) - add-manually into a fresh, empty workspace so an already-added Safe can't leave the confirm button disabled; self-delete afterwards - clean up the invite test by re-signing as the admin (the invitee is a member and cannot delete the workspace) - drop dead helpers (getSpaceId, openFirstSpaceFromSpacesView) * fix(e2e): view all acount * fix(web): resolve correctness issues from PR review - useSafeSpaces: key workspace membership by chainId:address so a Safe sharing an address across chains no longer inherits another chain's workspace; reset isResolving on the empty-spaces path so isLoading can't stick true (Codex, Clovis) - Add accounts picker: don't cascade a group toggle onto locked workspace children, and wait for the space-safes query before seeding the reset form so Save can't diff existing members as removals (Codex, Clovis) - Multi-chain dropdown rows: render each network row's own threshold/owners from its per-chain overview instead of the parent aggregate (Codex) * refactor(web): make row copy/explorer buttons tracking-agnostic; drop dead AddressWithCopy - CopyAddressButton/ExplorerLinkButton hard-coded a SIDEBAR_ELEMENT analytics label but are now rendered in the table and dropdown, so every copy/open emitted a bogus 'sidebar' event. Remove the baked-in tracking and expose optional onCopy/onOpen callbacks so each call site labels its own event (Clovis) - Remove AddressWithCopy: no production consumers (only its own story/test), superseded by SafeInfoDisplay + FullAddress (Clovis) * tests: adapt Safe creation e2e to redesigned welcome flow Two UI changes on this branch broke every create-safe spec: - Entry point: /welcome/accounts now renders MyAccountsV2, where creation lives behind the 'Add accounts' chooser instead of a standalone create-safe-btn. Update the shared clickOnCreateNewSafeBtn to open the chooser and pick 'Create new' — this fixes the entry for all 8 flows that go through startCreateSafeFlow/connectWalletAndCreateSafe. - Pay-later now requires a workspace SIWE sign-in before a counterfactual Safe can be created; without it the review step falls back to deploy. Replace the stale selectPayNowOption() in create_safe_cf with clickOnSignInToWorkspaceBtn(), matching the multichain create specs. * chore(web): drop unused OVERVIEW_LABELS enum members welcome_page and owned_safes_modal lost their last consumers when the nav rework removed the welcome page / owned-safes modal UI. knip flags unused enum members as errors (default severity), which was the sole cause of the failing knip:exports CI job. * tests: align spaces dashboard e2e with shared account components The branch replaced bespoke spaces UI with shared components, changing two testids the dashboard regression relied on: - Safe accounts page now renders SafeAccountsTable; count top-level account-table-row rows instead of the removed safe-list-item cards. - Safe header threshold is the shared ThresholdBadge (account-threshold), not the removed safe-selector-threshold. * fix: multichain regression tests * fix: close Update workspace modal after saving a new name Renaming a workspace from the space list's three-dot menu left the modal open on success. The onClose callback was passed to UpdateSpaceDialog but never threaded to the form or the save handler, so it only fired on manual dismiss. Thread onClose through UpdateSpaceDialog -> UpdateSpaceForm -> useUpdateSpace and invoke it after the mutation succeeds (matching the EditContactDialog/EditMemberDialog pattern). Errors keep the modal open with the message shown. * fix(web): improve account list sorting for name, networks, threshold The account-list dialogs (Add to workspace, Manage my account list, and every shared SafeAccountsTable surface) sorted poorly: - Name: unnamed safes kept raw input order instead of falling back to address; the column toggle also never reversed an all-unnamed list. - Networks: sorted by the number of chains, so single-chain lists never reordered. Now sorts by chain identity (Ethereum before Polygon). - Threshold: sorted on M only, so 2/3 and 2/15 tied. Now breaks ties by owner count N (2/3 before 2/15). Fixes the shared comparators (nameComparator + compareGroups) so all Safe lists stay consistent. * fix(web): gate account-table column sorting to Name sort mode The account lists carried two competing sort controls: the Name/Last visited/Manual dropdown and the clickable column headers. A header click silently overrode Last visited with no way back and left the dropdown showing a stale value. Column sorting is now offered only when the active mode is Name — mirroring how Manual already swaps in drag handles. Last visited and Manual (which have no column equivalent) own the order; switching away from Name clears any active column sort. Surfaces without a dropdown (the Add-accounts picker, onboarding) keep columns sortable, as columns are their only sort control there.
248 lines
12 KiB
TypeScript
248 lines
12 KiB
TypeScript
import { defineConfig, loadEnv } from 'vite'
|
|
import { VitePWA } from 'vite-plugin-pwa'
|
|
import react from '@vitejs/plugin-react'
|
|
import tailwindcss from '@tailwindcss/vite'
|
|
import svgr from 'vite-plugin-svgr'
|
|
import mdx from '@mdx-js/rollup'
|
|
import remarkFrontmatter from 'remark-frontmatter'
|
|
import remarkMdxFrontmatter from 'remark-mdx-frontmatter'
|
|
import remarkHeadingId from 'remark-heading-id'
|
|
import remarkGfm from 'remark-gfm'
|
|
import path from 'node:path'
|
|
import { execSync } from 'node:child_process'
|
|
import { readFileSync } from 'node:fs'
|
|
import { swAssets } from './plugins/vite-plugin-sw-assets'
|
|
import { importMapIntegrity } from './plugins/vite-plugin-import-map-integrity'
|
|
|
|
const __dirname = path.dirname(new URL(import.meta.url).pathname)
|
|
const webRoot = path.resolve(__dirname, '../web')
|
|
const pkg = JSON.parse(readFileSync(path.resolve(webRoot, 'package.json'), 'utf-8'))
|
|
|
|
let commitHash = process.env.VITE_COMMIT_HASH || process.env.NEXT_PUBLIC_COMMIT_HASH || ''
|
|
if (!commitHash) {
|
|
try {
|
|
commitHash = execSync('git rev-parse --short HEAD').toString().trim()
|
|
} catch {
|
|
commitHash = ''
|
|
}
|
|
}
|
|
|
|
const appVersion = process.env.VISUAL_REGRESSION_BUILD === 'true' ? 'vistest' : pkg.version
|
|
if (process.env.VISUAL_REGRESSION_BUILD === 'true') commitHash = 'vistest'
|
|
|
|
// See decisions.md: env vars are renamed to VITE_* but a transitional shim
|
|
// re-exports them under their original NEXT_PUBLIC_* names so 99 existing
|
|
// call-sites can migrate incrementally. Build-time constants are injected here.
|
|
export default defineConfig(({ mode }) => {
|
|
// Vite normally exposes prefixed env vars on `import.meta.env`, but in
|
|
// production builds it leaves unknown `import.meta.env.X` references as
|
|
// runtime accesses against an object that doesn't exist — accessing
|
|
// `.NEXT_PUBLIC_SAFE_VERSION` then throws. To make the prod build behave
|
|
// like dev, gather every NEXT_PUBLIC_* / VITE_* value at build time and
|
|
// inline them as a literal `process.env` object via `define`.
|
|
const loadedEnv = loadEnv(mode, process.cwd(), ['VITE_', 'NEXT_PUBLIC_', 'EXPO_PUBLIC_'])
|
|
// `NODE_ENV` is typed as a narrow union, but the host shell can set it to
|
|
// 'cypress' (see comment below). Read it as a plain string so that check holds.
|
|
const hostNodeEnv = process.env.NODE_ENV as string
|
|
const processEnv = {
|
|
...loadedEnv,
|
|
// Build-time constants that override / supplement what loadEnv saw.
|
|
NEXT_PUBLIC_COMMIT_HASH: commitHash,
|
|
NEXT_PUBLIC_APP_VERSION: appVersion,
|
|
NEXT_PUBLIC_APP_HOMEPAGE: pkg.homepage ?? '',
|
|
// Honour an explicit cypress/test NODE_ENV from the host shell so the
|
|
// existing `cross-env NODE_ENV=cypress cypress run` script can flip
|
|
// `IS_TEST_E2E` in apps/web/src/config/constants.ts (and the require-login
|
|
// gate it gates) without needing a separate NEXT_PUBLIC_IS_TEST_E2E flag.
|
|
NODE_ENV:
|
|
hostNodeEnv === 'cypress' || hostNodeEnv === 'test'
|
|
? hostNodeEnv
|
|
: mode === 'production'
|
|
? 'production'
|
|
: 'development',
|
|
}
|
|
|
|
return {
|
|
publicDir: path.resolve(webRoot, 'public'),
|
|
envPrefix: ['VITE_', 'NEXT_PUBLIC_'],
|
|
server: {
|
|
port: 3000,
|
|
strictPort: true,
|
|
},
|
|
preview: {
|
|
port: 3000,
|
|
strictPort: true,
|
|
},
|
|
// Eagerly pre-bundle the heavy MUI / web3 / Redux subgraph. By default Vite
|
|
// discovers deps lazily on first import which produces hundreds of tiny
|
|
// chunks served individually in dev. Forcing inclusion collapses each top
|
|
// dep into a single optimized chunk.
|
|
optimizeDeps: {
|
|
include: [
|
|
'@mui/material',
|
|
'@mui/material/styles',
|
|
'@mui/icons-material',
|
|
'@mui/system',
|
|
'@emotion/react',
|
|
'@emotion/styled',
|
|
'@emotion/cache',
|
|
'react',
|
|
'react/jsx-runtime',
|
|
'react/jsx-dev-runtime',
|
|
'react-dom',
|
|
'react-dom/client',
|
|
'react-redux',
|
|
'react-helmet-async',
|
|
'@reduxjs/toolkit',
|
|
'@reduxjs/toolkit/query/react',
|
|
'@tanstack/react-router',
|
|
'lodash',
|
|
'date-fns',
|
|
'ethers',
|
|
'classnames',
|
|
],
|
|
},
|
|
define: {
|
|
__COMMIT_HASH__: JSON.stringify(commitHash),
|
|
__APP_VERSION__: JSON.stringify(appVersion),
|
|
__APP_HOMEPAGE__: JSON.stringify(pkg.homepage ?? ''),
|
|
// Build-time NEXT_PUBLIC_* vars that apps/web's next.config.mjs injects.
|
|
// Mirror the same set so reused source modules (e.g. config/version.ts)
|
|
// see real values at module-eval time.
|
|
'import.meta.env.NEXT_PUBLIC_COMMIT_HASH': JSON.stringify(commitHash),
|
|
'import.meta.env.NEXT_PUBLIC_APP_VERSION': JSON.stringify(appVersion),
|
|
'import.meta.env.NEXT_PUBLIC_APP_HOMEPAGE': JSON.stringify(pkg.homepage ?? ''),
|
|
// Reused code in apps/web/src and packages/* reads `process.env.NEXT_PUBLIC_*`
|
|
// at module top level. Vite has no `process` global in the browser, so the
|
|
// first such read throws synchronously and React never mounts. Inline an
|
|
// object literal containing every prefixed env var + the build-time
|
|
// constants — works in both dev and prod (unlike `import.meta.env`,
|
|
// which is undefined at runtime in prod builds).
|
|
'process.env': JSON.stringify(processEnv),
|
|
// Some web3 deps reference `global` directly (not just `globalThis`).
|
|
global: 'globalThis',
|
|
},
|
|
resolve: {
|
|
alias: [
|
|
// Next.js compatibility shims (decisions.md: cross-workspace next/* shimming)
|
|
{
|
|
find: 'next/dist/client/resolve-href',
|
|
replacement: path.resolve(__dirname, 'src/compat/next-resolve-href.ts'),
|
|
},
|
|
{ find: 'next/router', replacement: path.resolve(__dirname, 'src/compat/next-router.tsx') },
|
|
{ find: 'next/link', replacement: path.resolve(__dirname, 'src/compat/next-link.tsx') },
|
|
{ find: 'next/head', replacement: path.resolve(__dirname, 'src/compat/next-head.tsx') },
|
|
{ find: 'next/dynamic', replacement: path.resolve(__dirname, 'src/compat/next-dynamic.tsx') },
|
|
{ find: 'next/image', replacement: path.resolve(__dirname, 'src/compat/next-image.tsx') },
|
|
{ find: 'next/app', replacement: path.resolve(__dirname, 'src/compat/next-app.ts') },
|
|
{ find: 'next/navigation', replacement: path.resolve(__dirname, 'src/compat/next-navigation.tsx') },
|
|
|
|
// Node builtins referenced by reused source. `querystring-es3` is the
|
|
// standard browser polyfill webpack/Next uses internally, already in the lockfile.
|
|
{ find: /^querystring$/, replacement: 'querystring-es3' },
|
|
|
|
// Vite-native fork of the features/__core__ barrel. The original
|
|
// uses template-literal dynamic imports that Vite cannot analyze;
|
|
// this fork swaps in a Vite-native createFeatureHandle backed by
|
|
// import.meta.glob. Must appear BEFORE the broader `@/...` alias
|
|
// below so it takes precedence. Match the barrel exactly (no
|
|
// trailing segment) and also the direct module path for callers
|
|
// that bypass the barrel.
|
|
{
|
|
find: /^@\/features\/__core__$/,
|
|
replacement: path.resolve(__dirname, 'src/compat/features-core.ts'),
|
|
},
|
|
{
|
|
find: /^@\/features\/__core__\/createFeatureHandle$/,
|
|
replacement: path.resolve(__dirname, 'src/compat/createFeatureHandle.ts'),
|
|
},
|
|
|
|
// Cross-workspace source aliases — reuse apps/web/src verbatim during cutover.
|
|
{ find: /^@\/public\/(.*)$/, replacement: path.resolve(webRoot, 'public/$1') },
|
|
{ find: /^@\/(.*)$/, replacement: path.resolve(webRoot, 'src/$1') },
|
|
|
|
// Mirror apps/web/tsconfig.json `paths` so reused source resolves shared
|
|
// packages by subpath (e.g. `@safe-global/utils/utils/chains`).
|
|
{ find: /^@safe-global\/theme\/(.*)$/, replacement: path.resolve(__dirname, '../../packages/theme/src/$1') },
|
|
{ find: /^@safe-global\/store\/(.*)$/, replacement: path.resolve(__dirname, '../../packages/store/src/$1') },
|
|
{ find: /^@safe-global\/utils\/(.*)$/, replacement: path.resolve(__dirname, '../../packages/utils/src/$1') },
|
|
{ find: /^@safe-global\/test\/(.*)$/, replacement: path.resolve(__dirname, '../../config/test/$1') },
|
|
|
|
// apps/web/src/pages/_app.tsx uses bare `src/...` imports (Next.js baseUrl convention).
|
|
{ find: /^src\/(.*)$/, replacement: path.resolve(webRoot, 'src/$1') },
|
|
|
|
// Webpack aliases ported verbatim from apps/web/next.config.mjs.
|
|
{ find: /^@mui\/material$/, replacement: path.resolve(webRoot, 'src/components/common/Mui') },
|
|
{ find: 'bn.js', replacement: path.resolve(__dirname, '../../node_modules/bn.js/lib/bn.js') },
|
|
],
|
|
},
|
|
plugins: [
|
|
// Tailwind v4 — required for `@apply`, `@reference`, and all utility
|
|
// classes used both directly in JSX (`flex flex-wrap items-start ...`)
|
|
// and inside CSS modules (`@apply absolute top-0 right-0`). Without
|
|
// this, the entire shadcn/Tailwind layer is silently dropped and the
|
|
// PageLayout positioning collapses.
|
|
tailwindcss(),
|
|
// MDX support for .md/.mdx imports (terms, cookie, privacy pages).
|
|
// Mirrors the remark plugin list documented in
|
|
// docs/migration/state/plan.md and matches what apps/web's next.config.mjs
|
|
// uses through @mdx-js/loader. Must run BEFORE react() so React's
|
|
// JSX transform sees the compiled MDX output.
|
|
mdx({
|
|
remarkPlugins: [remarkFrontmatter, remarkMdxFrontmatter, remarkHeadingId, remarkGfm],
|
|
}),
|
|
react(),
|
|
svgr({
|
|
// Reused apps/web code does `import Icon from './x.svg'` and renders
|
|
// <Icon />. Webpack's @svgr/webpack returns a React component by
|
|
// default; vite-plugin-svgr returns the URL unless we opt in. Include
|
|
// every .svg and emit the component as the default export so the
|
|
// existing import sites work unchanged.
|
|
include: '**/*.svg',
|
|
svgrOptions: {
|
|
prettier: false,
|
|
svgo: false,
|
|
titleProp: true,
|
|
},
|
|
// Under Rolldown-powered Vite (v8) the plugin transpiles its SVG-as-JSX
|
|
// output via Vite's `transformWithOxc`. Without an explicit `jsx`
|
|
// option oxc parses but does not lower the JSX, so raw `<svg>` reaches
|
|
// the bundler and every .svg import fails with a parse error. Force the
|
|
// React 19 automatic runtime (same as @vitejs/plugin-react) so the JSX
|
|
// is lowered to `react/jsx-runtime` calls.
|
|
oxcOptions: {
|
|
jsx: { runtime: 'automatic' },
|
|
},
|
|
}),
|
|
// The worker is bundled by Vite, so the `define` block above (process.env,
|
|
// __APP_VERSION__) and the `@/` resolve aliases apply to it.
|
|
VitePWA({
|
|
strategies: 'injectManifest',
|
|
srcDir: 'src/service-worker',
|
|
filename: 'sw.ts',
|
|
registerType: 'prompt',
|
|
// PwaReloadPrompt's useRegisterSW performs the single registration; no
|
|
// competing auto-injected script.
|
|
injectRegister: false,
|
|
// Reuse the existing safe.webmanifest (linked via MetaTags); don't let
|
|
// the plugin generate/inject its own manifest.
|
|
manifest: false,
|
|
injectManifest: {
|
|
globPatterns: ['**/*.{js,css,html,svg,png,ico,webp,woff2}'],
|
|
// Exclude the stale publicDir service workers, the generated worker, and sourcemaps.
|
|
globIgnores: ['firebase-messaging-sw.js', 'workbox-*.js', 'sw.js', '**/*.map'],
|
|
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
|
|
},
|
|
devOptions: {
|
|
enabled: true,
|
|
type: 'module',
|
|
navigateFallback: 'index.html',
|
|
},
|
|
}),
|
|
// Ordered after VitePWA so its cleanup runs once sw.js is generated.
|
|
swAssets(path.resolve(__dirname, 'src/service-worker/offline.html')),
|
|
importMapIntegrity(),
|
|
],
|
|
}
|
|
})
|