Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .agents/shared/metrics/hits.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@
{"ts":"2026-07-07T08:25:58.831Z","tool":"Edit","file":"src/shared/components/transactionDialog/transactionDialog.tsx","rule":"error-and-monitoring","bytes":2525,"elapsed_ms":2,"adapter":"claude"}
{"ts":"2026-07-07T08:26:08.144Z","tool":"Edit","file":"src/shared/components/transactionDialog/transactionDialog.tsx","rule":"error-and-monitoring","bytes":2525,"elapsed_ms":1,"adapter":"claude"}
{"ts":"2026-07-07T08:27:04.958Z","tool":"Edit","file":"src/shared/components/transactionDialog/transactionDialog.test.tsx","rule":"error-and-monitoring","bytes":2525,"elapsed_ms":1,"adapter":"claude"}
{"ts":"2026-07-08T11:05:20.251Z","tool":"Edit","file":"src/modules/application/components/errorBoundary/errorBoundaryClass.tsx","rule":"error-and-monitoring","bytes":2525,"elapsed_ms":29,"adapter":"claude"}
{"ts":"2026-07-08T11:05:45.029Z","tool":"Edit","file":"src/modules/application/components/errorBoundary/errorBoundaryClass.tsx","rule":"error-and-monitoring","bytes":2525,"elapsed_ms":6,"adapter":"claude"}
{"ts":"2026-07-08T11:06:24.791Z","tool":"Edit","file":"src/shared/utils/monitoringUtils/monitoringUtils.ts","rule":"error-and-monitoring","bytes":2525,"elapsed_ms":3,"adapter":"claude"}
{"ts":"2026-07-08T11:06:45.503Z","tool":"Edit","file":"src/shared/utils/monitoringUtils/monitoringUtils.test.ts","rule":"error-and-monitoring","bytes":2525,"elapsed_ms":4,"adapter":"claude"}
{"ts":"2026-07-07T13:45:43.994Z","tool":"Edit","file":"src/modules/governance/dialogs/selectPluginDialog/selectPluginDialog.tsx","rule":"plugin-visibility","bytes":2379,"elapsed_ms":2,"adapter":"claude"}
{"ts":"2026-07-07T15:31:34.162Z","tool":"Write","file":"src/modules/governance/dialogs/selectPluginDialog/selectPluginDialogItem.tsx","rule":"plugin-visibility","bytes":2379,"elapsed_ms":2,"adapter":"claude"}
{"ts":"2026-07-07T15:31:49.364Z","tool":"Edit","file":"src/modules/governance/dialogs/selectPluginDialog/selectPluginDialog.tsx","rule":"plugin-visibility","bytes":2379,"elapsed_ms":2,"adapter":"claude"}
Expand All @@ -119,6 +123,8 @@
{"ts":"2026-07-08T16:29:42.858Z","tool":"Edit","file":"apps/app/src/modules/governance/dialogs/selectPluginDialog/selectPluginDialog.tsx","rule":"plugin-visibility","bytes":2379,"elapsed_ms":3,"adapter":"claude"}
{"ts":"2026-07-08T16:29:55.658Z","tool":"Edit","file":"apps/app/src/modules/governance/dialogs/selectPluginDialog/selectPluginDialog.tsx","rule":"plugin-visibility","bytes":2379,"elapsed_ms":11,"adapter":"claude"}
{"ts":"2026-07-08T16:30:49.523Z","tool":"Edit","file":"apps/app/src/modules/governance/dialogs/selectPluginDialog/selectPluginDialogProcessListItem.tsx","rule":"plugin-visibility","bytes":2379,"elapsed_ms":2,"adapter":"claude"}
{"ts":"2026-07-14T13:48:20.570Z","tool":"Edit","file":".agents/shared/skills/rules/dialog-conventions.md","rule":"rule-authoring","bytes":2672,"elapsed_ms":1,"adapter":"claude"}
{"ts":"2026-07-14T13:49:10.198Z","tool":"Edit","file":".agents/shared/skills/rules/dialog-conventions.md","rule":"rule-authoring","bytes":2672,"elapsed_ms":4,"adapter":"claude"}
{"ts":"2026-07-09T11:51:56.300Z","tool":"Edit","file":"apps/app/src/modules/governance/dialogs/selectPluginDialog/selectPluginDialogProcessListItem.tsx","rule":"plugin-visibility","bytes":2379,"elapsed_ms":3,"adapter":"claude"}
{"ts":"2026-07-09T12:00:44.020Z","tool":"Edit","file":"apps/app/src/modules/governance/dialogs/selectPluginDialog/selectPluginDialogProcessListItem.tsx","rule":"plugin-visibility","bytes":2379,"elapsed_ms":3,"adapter":"claude"}
{"ts":"2026-07-13T10:09:35.950Z","tool":"Edit","file":"apps/app/src/modules/governance/dialogs/selectPluginDialog/selectPluginDialogProcessListItem.tsx","rule":"plugin-visibility","bytes":2379,"elapsed_ms":3,"adapter":"claude"}
Expand Down
3 changes: 2 additions & 1 deletion .agents/shared/skills/rules/dialog-conventions.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: dialog-conventions
description: Dialog barrel + definitions wiring — dynamic-import the component, static-export the types, register by id through the dialogProvider.
globs: apps/app/src/**/dialogs/*/index.ts, apps/app/src/**/constants/*DialogsDefinitions.ts
globs: apps/app/src/**/dialogs/*/index.ts, apps/app/src/**/*DialogsDefinitions.ts, apps/app/src/**/*DialogDefinitions.ts
kind: rule
---

Expand Down Expand Up @@ -49,6 +49,7 @@ export const financeDialogsDefinitions: Record<FinanceDialogId, IDialogComponent
- **Import the component from the folder barrel (`../dialogs/assetSelectionDialog`), never the `.tsx` directly** (`.../assetSelectionDialog/assetSelectionDialog`). The direct import bypasses the `dynamic()` wrapper and statically bundles the dialog.
- Keys come from the module's `*DialogId` enum — no inline string ids.
- A new module's definitions must be merged into `providersDialogs.ts`, or the id won't resolve at runtime.
- **A dialog that renders `TransactionDialog`, or otherwise needs the connected address to be meaningful, must set `requiresWallet: true`.** `DialogRoot` unmounts and closes flagged dialogs on wallet disconnect; without the flag, a dialog reading the address re-renders without one and throws — there is no error boundary above the dialog layer, so the whole app unmounts. Dialogs stacked on a flagged dialog need the flag too, or the child is orphaned on the stack when its parent closes.

## Non-obvious

Expand Down
5 changes: 5 additions & 0 deletions .changeset/dialog-wallet-disconnect-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@aragon/app": patch
---

Fix the application crashing when the wallet disconnects while a transaction dialog is open. Wallet-requiring dialogs read a connected address during render, so a disconnect made them throw with no error boundary above the dialog layer, unmounting the whole application. Dialog definitions now carry a `requiresWallet` flag and `DialogRoot` unmounts and closes flagged dialogs on disconnect before they can re-render without an address. Connector switches do not close dialogs: while the wallet is connecting or reconnecting the dialog is only hidden, and it reappears once the address returns. It does remount, so state held inside the dialog (form inputs, transaction step) is reset.
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ Shared build/test config also extends from the root: `tsconfig.base.json` (works
- **Slots system** — `apps/app/docs/slots/`
- **Testing** — `apps/app/docs/projectDocs/testing.md`

## Dialogs

When you add a dialog that uses `TransactionDialog` internally — or that otherwise needs the connected wallet address to be meaningful — pass `requiresWallet: true` in its `*DialogsDefinitions.ts` entry. `DialogRoot` unmounts and closes flagged dialogs when the wallet disconnects, which is what stops a mid-transaction disconnect from crashing the app. Dialogs stacked on top of a flagged dialog should be flagged too, otherwise the child is left orphaned on the stack once its parent closes.

Full wiring conventions (dynamic imports, definitions map, params) live in the `dialog-conventions` rule-skill, which auto-injects when you edit a dialog barrel or definitions map.

## Repo layout for agent infra

Two parallel trees, each split into `shared/` (checked in) and `local/` (gitignored):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,15 @@ export const applicationDialogsDefinitions: Record<
'app.application.aragonProfileSubdomainRegisterTransactionDialog.a11y.title',
hiddenDescription:
'app.application.aragonProfileSubdomainRegisterTransactionDialog.a11y.description',
requiresWallet: true,
},
[ApplicationDialogId.ARAGON_PROFILE_SET_PRIMARY_ENS_TRANSACTION]: {
Component: AragonProfileSetPrimaryEnsTransactionDialog,
hiddenTitle:
'app.application.aragonProfileSetPrimaryEnsTransactionDialog.a11y.title',
hiddenDescription:
'app.application.aragonProfileSetPrimaryEnsTransactionDialog.a11y.description',
requiresWallet: true,
},
[ApplicationDialogId.ARAGON_PROFILE]: {
Component: AragonProfileDialog,
Expand All @@ -67,6 +69,7 @@ export const applicationDialogsDefinitions: Record<
Component: AragonProfileUpdateTransactionDialog,
hiddenDescription:
'app.application.aragonProfileUpdateTransactionDialog.a11y.description',
requiresWallet: true,
},
[ApplicationDialogId.ARAGON_PROFILE_RELEASE_ALERT]: {
Component: AragonProfileReleaseAlertDialog,
Expand All @@ -82,6 +85,7 @@ export const applicationDialogsDefinitions: Record<
'app.application.aragonProfileReleaseTransactionDialog.a11y.title',
hiddenDescription:
'app.application.aragonProfileReleaseTransactionDialog.a11y.description',
requiresWallet: true,
},
[ApplicationDialogId.ARAGON_PROFILE_RENAME]: {
Component: AragonProfileRenameDialog,
Expand All @@ -96,5 +100,6 @@ export const applicationDialogsDefinitions: Record<
'app.application.aragonProfileRenameTransactionDialog.a11y.title',
hiddenDescription:
'app.application.aragonProfileRenameTransactionDialog.a11y.description',
requiresWallet: true,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ describe('<AragonProfileDialog />', () => {
useWalletAccountSpy.mockReturnValue({
address: WALLET,
chainId: 1,
isConnecting: false,
isReconnecting: false,
});
useEnsAvatarSpy.mockReturnValue({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ describe('<AragonProfileRenameDialog />', () => {
useWalletAccountSpy.mockReturnValue({
address: '0x1111111111111111111111111111111111111111',
chainId: 1,
isConnecting: false,
isReconnecting: false,
});
useEnsSubdomainFieldSpy.mockReturnValue({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ describe('<AragonProfileRenameTransactionDialog />', () => {
useWalletAccountSpy.mockReturnValue({
address: WALLET,
chainId: 1,
isConnecting: false,
isReconnecting: false,
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { useConnection } from 'wagmi';

/**
* Returns the wagmi-sourced account fields the app actually needs:
* `address`, `chainId`, `isReconnecting`. Intentionally omits `isConnected` —
* use `useWalletConnected` (AppKit-sourced) for any UI gate that must agree
* with the connect modal. `DesyncWatcher` is the only sanctioned consumer of
* wagmi's raw `useConnection`.
* `address`, `chainId`, `isConnecting`, `isReconnecting`. Intentionally omits
* `isConnected` — use `useWalletConnected` (AppKit-sourced) for any UI gate that
* must agree with the connect modal. `DesyncWatcher` is the only sanctioned
* consumer of wagmi's raw `useConnection`.
*/
export const useWalletAccount = () => {
const { address, chainId, isReconnecting } = useConnection();
const { address, chainId, isConnecting, isReconnecting } = useConnection();

return { address, chainId, isReconnecting };
return { address, chainId, isConnecting, isReconnecting };
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ export const capitalFlowDialogsDefinitions: Record<
hiddenDescription:
'app.capitalFlow.setupStrategyDialog.a11y.description',
},
[CapitalFlowDialogId.PREPARE_POLICY]: { Component: PreparePolicyDialog },
[CapitalFlowDialogId.PREPARE_POLICY]: {
Component: PreparePolicyDialog,
requiresWallet: true,
},
[CapitalFlowDialogId.ROUTER_SELECTOR]: {
Component: RouterSelectorDialog,
size: 'lg',
Expand All @@ -36,6 +39,7 @@ export const capitalFlowDialogsDefinitions: Record<
},
[CapitalFlowDialogId.DISPATCH_TRANSACTION]: {
Component: DispatchTransactionDialog,
requiresWallet: true,
size: 'lg',
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ export const createDaoDialogsDefinitions: Record<
CreateDaoDialogId,
IDialogComponentDefinitions
> = {
[CreateDaoDialogId.PUBLISH_DAO]: { Component: PublishDaoDialog },
[CreateDaoDialogId.PUBLISH_DAO]: {
Component: PublishDaoDialog,
requiresWallet: true,
},
[CreateDaoDialogId.CREATE_DAO_DETAILS]: {
Component: CreateDaoDetailsDialog,
size: 'lg',
Expand All @@ -20,7 +23,10 @@ export const createDaoDialogsDefinitions: Record<
Component: CreateProcessDetailsDialog,
size: 'lg',
},
[CreateDaoDialogId.PREPARE_PROCESS]: { Component: PrepareProcessDialog },
[CreateDaoDialogId.PREPARE_PROCESS]: {
Component: PrepareProcessDialog,
requiresWallet: true,
},
[CreateDaoDialogId.SETUP_BODY]: {
Component: SetupBodyDialog,
size: 'lg',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,29 @@ export const governanceDialogsDefinitions: Record<
GovernanceDialogId,
IDialogComponentDefinitions
> = {
[GovernanceDialogId.PUBLISH_PROPOSAL]: { Component: PublishProposalDialog },
[GovernanceDialogId.PUBLISH_PROPOSAL]: {
Component: PublishProposalDialog,
requiresWallet: true,
},
[GovernanceDialogId.DUPLICATE_PROPOSAL_WARNING]: {
Component: DuplicateProposalAlertDialog,
variant: 'warning',
size: 'lg',
hiddenDescription:
'app.governance.duplicateProposalAlertDialog.a11y.description',
},
[GovernanceDialogId.EXECUTE_ACTIONS]: { Component: ExecuteActionsDialog },
[GovernanceDialogId.EXECUTE]: { Component: ExecuteDialog },
[GovernanceDialogId.VOTE]: { Component: VoteDialog },
[GovernanceDialogId.EXECUTE_ACTIONS]: {
Component: ExecuteActionsDialog,
requiresWallet: true,
},
[GovernanceDialogId.EXECUTE]: {
Component: ExecuteDialog,
requiresWallet: true,
},
[GovernanceDialogId.VOTE]: {
Component: VoteDialog,
requiresWallet: true,
},
[GovernanceDialogId.SELECT_PLUGIN]: { Component: SelectPluginDialog },
[GovernanceDialogId.VERIFY_SMART_CONTRACT]: {
Component: VerifySmartContractDialog,
Expand Down Expand Up @@ -56,5 +68,6 @@ export const governanceDialogsDefinitions: Record<
},
[GovernanceDialogId.DELEGATE_STATEMENT_TRANSACTION]: {
Component: DelegateStatementTransactionDialog,
requiresWallet: true,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ describe('useExecutePermissionCheckGuard hook', () => {
useWalletAccountSpy.mockReturnValue({
address: '0xabc',
chainId: 1,
isConnecting: false,
isReconnecting: false,
});
useConnectedWalletGuardSpy.mockImplementation((params) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ describe('useSimulateProposalCreation hook', () => {
useWalletAccountSpy.mockReturnValue({
address: walletAddress,
chainId: 1,
isConnecting: false,
isReconnecting: false,
});
buildTransactionSpy.mockReturnValue({
Expand Down Expand Up @@ -114,6 +115,7 @@ describe('useSimulateProposalCreation hook', () => {
useWalletAccountSpy.mockReturnValue({
address: undefined,
chainId: undefined,
isConnecting: false,
isReconnecting: false,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ describe('<CreateExecuteActionsPageClientSteps /> component', () => {
useWalletAccountSpy.mockReturnValue({
address: '0xwallet',
chainId: 1,
isConnecting: false,
isReconnecting: false,
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const settingsDialogDefinitions: Record<
},
[SettingsDialogId.PREPARE_DAO_CONTRACTS_UPDATE]: {
Component: PrepareDaoContractsUpdateDialog,
requiresWallet: true,
},
[SettingsDialogId.GOVERNANCE_PROCESS_REQUIRED]: {
Component: GovernanceProcessRequiredDialog,
Expand All @@ -32,5 +33,6 @@ export const settingsDialogDefinitions: Record<
},
[SettingsDialogId.PREPARE_PLUGIN_UNINSTALLATION]: {
Component: PreparePluginUninstallationDialog,
requiresWallet: true,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export const capitalDistributorPluginDialogsDefinitions: Record<
},
[CapitalDistributorPluginDialogId.CLAIM_TRANSACTION]: {
Component: CapitalDistributorClaimTransactionDialog,
requiresWallet: true,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ export const gaugeVoterPluginDialogsDefinitions: Record<
[GaugeVoterPluginDialogId.VOTE_GAUGES_TRANSACTION]: {
Component: GaugeVoterVoteTransactionDialog,
size: 'md',
requiresWallet: true,
},
[GaugeVoterPluginDialogId.LOCK_UNLOCK]: {
Component: GaugeVoterLockUnlockDialog,
requiresWallet: true,
},
[GaugeVoterPluginDialogId.VIEW_LOCKS]: {
Component: GaugeVoterLocksDialog,
Expand All @@ -42,6 +44,7 @@ export const gaugeVoterPluginDialogsDefinitions: Record<
},
[GaugeVoterPluginDialogId.EXIT_QUEUE_WITHDRAW_TRANSACTION]: {
Component: GaugeVoterExitQueueWithdrawTransactionDialog,
requiresWallet: true,
},
[GaugeVoterPluginDialogId.LOCK_ONBOARDING_INTRO]: {
Component: GaugeVoterLockOnboardingIntroDialog,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const lockToVotePluginDialogsDefinitions: Record<
},
[LockToVotePluginDialogId.LOCK_UNLOCK_L2V]: {
Component: LockToVoteLockUnlockDialog,
requiresWallet: true,
},
[LockToVotePluginDialogId.UNLOCK_BLOCKED_INFO]: {
Component: UnlockBlockedInfoDialog,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ export const sppPluginDialogsDefinitions: Record<
SppPluginDialogId,
IDialogComponentDefinitions
> = {
[SppPluginDialogId.ADVANCE_STAGE]: { Component: SppAdvanceStageDialog },
[SppPluginDialogId.ADVANCE_STAGE]: {
Component: SppAdvanceStageDialog,
requiresWallet: true,
},
[SppPluginDialogId.REPORT_PROPOSAL_RESULT]: {
Component: SppReportProposalResultDialog,
requiresWallet: true,
},
[SppPluginDialogId.INVALID_ADDRESS_CONNECTED]: {
Component: SppInvalidAddressConnectedDialog,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,22 @@ export const tokenPluginDialogsDefinitions: Record<
TokenPluginDialogId,
IDialogComponentDefinitions
> = {
[TokenPluginDialogId.DELEGATE]: { Component: TokenDelegationDialog },
[TokenPluginDialogId.DELEGATE]: {
Component: TokenDelegationDialog,
requiresWallet: true,
},
[TokenPluginDialogId.APPROVE_TOKENS]: {
Component: TokenApproveTokensDialog,
requiresWallet: true,
},
[TokenPluginDialogId.APPROVE_NFT]: {
Component: TokenApproveNftDialog,
requiresWallet: true,
},
[TokenPluginDialogId.WRAP_UNWRAP]: {
Component: TokenWrapUnwrapDialog,
requiresWallet: true,
},
[TokenPluginDialogId.APPROVE_NFT]: { Component: TokenApproveNftDialog },
[TokenPluginDialogId.WRAP_UNWRAP]: { Component: TokenWrapUnwrapDialog },
[TokenPluginDialogId.DELEGATION_ONBOARDING_INTRO]: {
Component: TokenDelegationOnboardingIntroDialog,
hiddenTitle:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,11 @@ export interface IDialogComponentDefinitions<
* Component to be rendered.
*/
Component: ComponentType<IDialogComponentProps<TParams>>;
/**
* Marks a dialog that requires a connected wallet to be meaningful: DialogRoot unmounts it
* and closes it on wallet disconnect, before a dialog reading the address can throw.
* Dialogs stacked on top of a flagged dialog should be flagged too, otherwise the child is
* left orphaned on the stack once its parent closes.
*/
requiresWallet?: boolean;
Comment thread
thekidnamedkd marked this conversation as resolved.
}
Loading
Loading