diff --git a/.evolve/tangle-cloud-design-audit-2026-06-08.md b/.evolve/tangle-cloud-design-audit-2026-06-08.md new file mode 100644 index 0000000000..6deb79ef58 --- /dev/null +++ b/.evolve/tangle-cloud-design-audit-2026-06-08.md @@ -0,0 +1,57 @@ +# Tangle Cloud Design Audit - 2026-06-08 + +Status: implemented and browser verified on `feat/tangle-cloud-tangle-dapp-system`. + +## Verdict + +The Blueprints page moved from glass-on-glass catalog cards to a list-first infrastructure console that matches the original Tangle dapp's operational hierarchy while keeping Cloud-specific density. The remaining surface is not a marketing hero; it is a service catalog for deployers, operators, and publishers. + +## Problems Addressed + +- Duplicate Blueprints title in top nav and page header. +- Separate nav/title layer doing no unique work. +- Blueprints catalog readability at roughly 6/10: same-background cards, weak hierarchy, small labels, hidden wallet address, italic controls, and decorative button dots. +- Missing default blueprint visual identity for chain-only blueprints. +- Local preview defaulting to Anvil and firing localhost RPC/indexer calls while the UI showed Base Sepolia. +- Blueprint data refetching felt uncached after leaving and returning to the page. +- Add-capacity/register flow could look blank or broken. + +## Product Direction + +- Use the original Tangle dapp design system selectively: wallet/network affordances, compact chrome, crisp borders, action hierarchy, and restrained Tangle color accents. +- Keep Cloud as an infrastructure console: list/table first, cards only for repeated catalog/mobile/modals, no hero marketing treatment. +- Make Blueprints read like inventory: capacity, trust, source, usage, and actions are first-order columns. + +## Changes Shipped + +- Removed the `/blueprints` top-nav breadcrumb title and let the page own the large `Blueprints` heading. +- Kept wallet connection visible in the top-right shell and made the connected-address affordance visible. +- Forced network/action controls to normal font style and removed button pseudo-dot artifacts. +- Added default blueprint visuals for chain-only catalog rows/cards. +- Added list-first catalog layout with metrics, availability filters, category/filter controls, grid/list toggle, pagination, and mobile cards. +- Added React Query stale/cache behavior for blueprint queries. +- Normalized Cloud's local-preview network to Base Sepolia unless `VITE_FORCE_LOCAL_CHAIN=true`. +- Moved network normalization before indexer health checks and aligned audited-status reads with the selected Cloud network. +- Made the local sandbox `Button` wrapper forward refs for Radix `asChild` compatibility. +- Tightened the add-capacity drawer into a true right-side panel. +- Stubbed `window.scrollTo` in shared Vitest setup to remove noisy route-test output. + +## Verification + +- `NX_DAEMON=false NX_SKIP_NX_CACHE=true yarn nx typecheck tangle-cloud` +- `NX_DAEMON=false NX_SKIP_NX_CACHE=true yarn nx test tangle-cloud` +- `NX_DAEMON=false NX_SKIP_NX_CACHE=true yarn nx build tangle-cloud` +- Playwright screenshots and console checks: + - `/tmp/tangle-cloud-dapp-system-audit/blueprints-desktop-dark.png` + - `/tmp/tangle-cloud-dapp-system-audit/blueprints-desktop-light.png` + - `/tmp/tangle-cloud-dapp-system-audit/blueprints-mobile-dark.png` + - `/tmp/tangle-cloud-dapp-system-audit/instances-desktop-dark.png` + - `/tmp/tangle-cloud-dapp-system-audit/blueprints-add-capacity-flow.png` + +Final browser counters: + +- `local8545`: 0 +- `local8080`: 0 +- Radix ref warnings: 0 +- italic controls: 0 +- measured overflow: 0 diff --git a/apps/tangle-cloud/PRODUCT_BRIEF.md b/apps/tangle-cloud/PRODUCT_BRIEF.md index 34d5958046..1716dca998 100644 --- a/apps/tangle-cloud/PRODUCT_BRIEF.md +++ b/apps/tangle-cloud/PRODUCT_BRIEF.md @@ -28,7 +28,7 @@ Primary actions: Connect wallet, switch network, create service, register operat Trust, risk, and compliance: Money, permissions, and protocol identity must be exact, copyable, and auditable. Destructive or irreversible actions need clear state, provenance, and confirmation. Embedded apps must expose origin, permissions, denied actions, and unavailable bridge methods. Empty/error/loading states must be truthful and actionable. -Design posture: Dense infrastructure console with restrained blueprint identity accents. Use compact tables, split workspaces, tabs, ledgers, event timelines, thin borders, and low-chroma status colors. Cards belong mainly to catalog discovery, repeated items, and modals. +Design posture: Dense infrastructure console with restrained blueprint identity accents. Use compact tables, split workspaces, tabs, ledgers, event timelines, thin borders, and low-chroma status colors. Cards belong mainly to catalog discovery, repeated items, and modals. Cloud should inherit the original Tangle dapp's wallet, network, chrome, and action hierarchy where it improves operator trust, but keep Cloud pages data-first rather than marketing-first. Non-goals: Do not reskin this as the AI trading app. Do not make a marketing landing page, decorative hero wall, app-store gallery, or purple/glow-heavy SaaS dashboard. Do not duplicate sidebar, topbar, breadcrumbs, page headers, tabs, and local nav unless each layer has a distinct job. @@ -39,6 +39,7 @@ Evidence: - `src/pages/services/[id]/page.tsx` mixes service console, blueprint presentation, ACL, jobs, billing, and upgrade surfaces in one vertical stack. - `src/pages/blueprints/[id]/deploy/page.tsx` submits a request but sends users back to blueprint detail instead of request/service status. - Parallel audit reports on 2026-06-05 converged on app/workspace-first IA and infrastructure-console visual direction. +- 2026-06-08 design-system pass restored Tangle dapp-style wallet/network affordances, removed the duplicate Blueprints nav title, converted the catalog to a list-first operator-capacity surface, added blueprint default visuals, and browser-verified desktop/mobile/light/dark render states. Open questions: diff --git a/apps/tangle-cloud/src/app/providers.tsx b/apps/tangle-cloud/src/app/providers.tsx index 46ec215c3c..5ae0a0c9b8 100644 --- a/apps/tangle-cloud/src/app/providers.tsx +++ b/apps/tangle-cloud/src/app/providers.tsx @@ -21,21 +21,25 @@ const NetworkSync: FC = ({ children }) => { const selectedNetwork = useNetworkStore((store) => store.network2); const setNetwork = useNetworkStore((store) => store.setNetwork); - useEffect(() => { - if (forceLocalChain) { - return; - } + const needsCloudNetworkReset = + !forceLocalChain && + selectedNetwork?.evmChainId === ANVIL_LOCAL_NETWORK.evmChainId; - if (selectedNetwork?.evmChainId === ANVIL_LOCAL_NETWORK.evmChainId) { + useEffect(() => { + if (needsCloudNetworkReset) { setNetwork(BASE_SEPOLIA_NETWORK); } - }, [forceLocalChain, selectedNetwork?.evmChainId, setNetwork]); + }, [needsCloudNetworkReset, setNetwork]); useNetworkSync(TANGLE_CLOUD_NETWORKS); useLocalChainGuard({ enabled: forceLocalChain && isLocalPreviewHost(), targetChainId: ANVIL_LOCAL_NETWORK.evmChainId ?? 31337, }); + if (needsCloudNetworkReset) { + return null; + } + return children; }; @@ -75,14 +79,14 @@ const Providers: FC = ({ children }) => { reconnectOnMount={reconnectOnMount} > - - - - + + + + {children} - - - + + + ); diff --git a/apps/tangle-cloud/src/components/sandbox/SandboxUi.tsx b/apps/tangle-cloud/src/components/sandbox/SandboxUi.tsx index 2d2ae55601..522c42143a 100644 --- a/apps/tangle-cloud/src/components/sandbox/SandboxUi.tsx +++ b/apps/tangle-cloud/src/components/sandbox/SandboxUi.tsx @@ -37,7 +37,7 @@ import { forwardRef, type ChangeEvent, type ComponentProps, - type ComponentRef, + type ElementRef, type FC, type ReactNode, } from 'react'; @@ -156,11 +156,8 @@ export type ButtonProps = Omit< disabledTooltip?: string; }; -export const Button = forwardRef< - ComponentRef, - ButtonProps ->( - ( +export const Button = forwardRef, ButtonProps>( + function Button( { variant, size, @@ -178,29 +175,31 @@ export const Button = forwardRef< ...props }, ref, - ) => ( - - {leftIcon} - {children} - {rightIcon} - - ), + ) { + return ( + + {leftIcon} + {children} + {rightIcon} + + ); + }, ); Button.displayName = 'Button'; diff --git a/apps/tangle-cloud/src/pages/blueprints/BlueprintListing.tsx b/apps/tangle-cloud/src/pages/blueprints/BlueprintListing.tsx index ae80aab44d..80c6b44797 100644 --- a/apps/tangle-cloud/src/pages/blueprints/BlueprintListing.tsx +++ b/apps/tangle-cloud/src/pages/blueprints/BlueprintListing.tsx @@ -45,6 +45,7 @@ import { fetchAttestations, fetchAuditorOnChain, } from '@tangle-network/tangle-shared-ui/data/blueprints/useBinaryVersions'; +import useNetworkStore from '@tangle-network/tangle-shared-ui/context/useNetworkStore'; import { AttestationKind, type Auditor, @@ -970,7 +971,9 @@ export default BlueprintListing; * the same react-query cache entry — no duplicate chain reads. */ const useAuditedStatusMap = (blueprintIds: bigint[]): Map => { - const chainId = useChainId(); + const wagmiChainId = useChainId(); + const networkChainId = useNetworkStore((store) => store.network2?.evmChainId); + const chainId = networkChainId ?? wagmiChainId; const publicClient = usePublicClient({ chainId }); const fallback = useMemo(() => auditorFallbackRegistry(), []); diff --git a/apps/tangle-cloud/src/pages/blueprints/RegistrationDrawer/RegistrationDrawer.tsx b/apps/tangle-cloud/src/pages/blueprints/RegistrationDrawer/RegistrationDrawer.tsx index 0c536d8455..86b635b53b 100644 --- a/apps/tangle-cloud/src/pages/blueprints/RegistrationDrawer/RegistrationDrawer.tsx +++ b/apps/tangle-cloud/src/pages/blueprints/RegistrationDrawer/RegistrationDrawer.tsx @@ -548,7 +548,7 @@ const RegistrationDrawer: FC = ({ return ( - +
Register as Operator diff --git a/eslint.config.js b/eslint.config.js index 61b93dd8c7..b0a5b651da 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -7,6 +7,22 @@ import reactRefresh from 'eslint-plugin-react-refresh'; import storybook from 'eslint-plugin-storybook'; import tseslint from 'typescript-eslint'; +const reactCompilerReadinessRules = { + 'react-hooks/config': 'warn', + 'react-hooks/error-boundaries': 'warn', + 'react-hooks/gating': 'warn', + 'react-hooks/globals': 'warn', + 'react-hooks/immutability': 'warn', + 'react-hooks/preserve-manual-memoization': 'warn', + 'react-hooks/purity': 'warn', + 'react-hooks/refs': 'warn', + 'react-hooks/set-state-in-effect': 'warn', + 'react-hooks/set-state-in-render': 'warn', + 'react-hooks/static-components': 'warn', + 'react-hooks/unsupported-syntax': 'warn', + 'react-hooks/use-memo': 'warn', +}; + export default tseslint.config( tseslint.configs.recommended, js.configs.recommended, @@ -39,6 +55,12 @@ export default tseslint.config( ignoreRestSiblings: true, }, ], + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + // The v7 hooks plugin adds React Compiler readiness diagnostics to the + // Nx React preset. Keep them visible without making compiler adoption a + // repo-wide CI gate before the codebase is migrated. + ...reactCompilerReadinessRules, '@nx/enforce-module-boundaries': [ 'error', { @@ -55,11 +77,7 @@ export default tseslint.config( }, }, { - ignores: [ - '**/.netlify/', - '**/contracts/**', - '**/scripts/migration/**', - ], + ignores: ['**/.netlify/', '**/contracts/**', '**/scripts/migration/**'], }, { files: ['**/tailwind.config.ts', '**/eslint.config.{mjs,js}'], diff --git a/libs/tangle-shared-ui/src/context/IndexerStatusContext.tsx b/libs/tangle-shared-ui/src/context/IndexerStatusContext.tsx index d0c3b4c5ef..890ec35f8a 100644 --- a/libs/tangle-shared-ui/src/context/IndexerStatusContext.tsx +++ b/libs/tangle-shared-ui/src/context/IndexerStatusContext.tsx @@ -21,6 +21,7 @@ import { getEnvioNetworkFromChainId, type EnvioNetwork, } from '../utils/executeEnvioGraphQL'; +import useNetworkStore from './useNetworkStore'; // Data source type export type DataSource = 'graphql' | 'onchain' | 'unavailable'; @@ -48,7 +49,9 @@ const IndexerStatusContext = createContext(null); * Should be placed high in the component tree, after WagmiProvider. */ export const IndexerStatusProvider: FC = ({ children }) => { - const chainId = useChainId(); + const wagmiChainId = useChainId(); + const networkChainId = useNetworkStore((store) => store.network2?.evmChainId); + const chainId = networkChainId ?? wagmiChainId; const network = getEnvioNetworkFromChainId(chainId); // Use the shared health check hook @@ -147,7 +150,9 @@ export const useIsGraphQLEnabled = (): { * Useful for hooks that may be used outside the provider. */ export const useIndexerStatusStandalone = () => { - const chainId = useChainId(); + const wagmiChainId = useChainId(); + const networkChainId = useNetworkStore((store) => store.network2?.evmChainId); + const chainId = networkChainId ?? wagmiChainId; const { data: isHealthy, isLoading: isCheckingHealth } = useEnvioHealthCheckByChainId(chainId); diff --git a/libs/tangle-shared-ui/src/utils/setupTest.ts b/libs/tangle-shared-ui/src/utils/setupTest.ts index 92621926a6..c79425fd28 100644 --- a/libs/tangle-shared-ui/src/utils/setupTest.ts +++ b/libs/tangle-shared-ui/src/utils/setupTest.ts @@ -37,6 +37,12 @@ Object.defineProperty(window, 'matchMedia', { })), }); +Object.defineProperty(window, 'scrollTo', { + configurable: true, + writable: true, + value: vi.fn(), +}); + process.on('unhandledRejection', (reason) => { console.log('FAILED TO HANDLE PROMISE REJECTION'); console.log('REASON', reason);