This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Ecency Vision is a Next.js 15-based web client for the Hive blockchain, organized as a pnpm monorepo workspace. The codebase consists of a main Next.js application (apps/web), a self-hosted blog SPA (apps/self-hosted), and four publishable packages (@ecency/sdk, @ecency/wallets, @ecency/render-helper, @ecency/ui).
# Install dependencies (run from workspace root)
pnpm install
# Start main app dev server
pnpm dev
# or: pnpm --filter @ecency/web dev
# Start package dev servers (watch mode)
pnpm dev:sdk
pnpm dev:wallets
pnpm dev:ui
# Start self-hosted app dev server
pnpm dev:self# Build main app
pnpm build
# or: pnpm --filter @ecency/web build
# Build all packages
pnpm build:packages
# or: pnpm -F @ecency/sdk,@ecency/wallets,@ecency/render-helper build
# Build production server
pnpm start# Run tests (all use Vitest)
pnpm test
# Lint all packages
pnpm lint
# Type check
pnpm typecheck# For main app (Vitest)
pnpm --filter @ecency/web test -- path/to/test.spec.tsx
# For packages (Vitest)
pnpm --filter @ecency/sdk test -- path/to/test.spec.ts# Build before publishing
pnpm build:packages
# Publish individual packages
pnpm publish:sdk
pnpm publish:wallets
pnpm publish:render-helper
pnpm publish:ui- apps/web - Main Next.js application (
@ecency/web)- apps/web/src/features/post-renderer - Post rendering components (migrated from packages/renderer)
- apps/self-hosted - Self-hosted blog SPA (
@ecency/self-hosted) — Rsbuild + TanStack Router - packages/sdk - Core Hive SDK with React Query integration (
@ecency/sdk) - packages/wallets - Multi-chain wallet management (
@ecency/wallets) - packages/render-helper - Markdown rendering utilities (
@ecency/render-helper) - packages/ui - Shared UI component library (
@ecency/ui)
All packages use workspace:* protocol for local dependencies. The main app transpiles workspace packages during build (configured in next.config.js).
Packages use dual builds (tsup) to optimize for different environments:
- Browser build: ESM with TypeScript declarations
- Node build: ESM + CJS for SSR compatibility
Exports are platform-specific via package.json exports field.
The monorepo follows a layered architecture with clear separation of concerns:
┌─────────────────────────────────────────────┐
│ @ecency/web (Next.js App) │
│ - UI Components & Pages │
│ - SDK mutation wrappers (api/sdk-mutations)│
│ - App-specific mutation logic │
│ - Feature modules │
│ - Post renderer (features/post-renderer) │
└──────────────────┬──────────────────────────┘
│ imports
┌─────────────┼──────────┬──────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌────────────┐ ┌────────┐ ┌──────────┐ ┌────────┐
│ @ecency/ │ │@ecency/│ │ @ecency/ │ │@ecency/│
│ sdk │ │wallets │ │ render- │ │ ui │
│ - Queries │ │- Asset │ │ helper │ │- Shared│
│ - Mutations│ │ queries│ │- Markdown│ │ UI │
│ - Types │ │- Keys │ │- Content │ │ comps │
│ - QueryKeys│ └───┬────┘ └──────────┘ └────────┘
└──────┬─────┘ │
└───────────┘ wallets imports sdk
┌─────────────────────────────────────────────┐
│ @ecency/self-hosted (Rsbuild SPA) │
│ - Blog/Community hosting │
│ - TanStack Router │
│ - Runtime config (Docker/Nginx) │
└──────────────────┬──────────────────────────┘
│ imports sdk, render-helper, wallets, ui
Purpose: Hive blockchain queries and mutations
Scope:
- ✅ Hive blockchain queries (posts, comments, accounts, communities)
- ✅ Hive blockchain mutations (vote, comment, transfer, delegate, etc.)
- ✅ Hive notifications
- ✅ Core Hive types and interfaces
- ✅ React Query integration for Hive APIs
- ✅ Centralized
QueryKeysfor all cache key management - ✅ Broadcast adapter pattern for platform-specific auth (web, mobile)
- ✅ Lightweight utilities (no heavy dependencies)
Dependencies: NONE (except peer deps: @hiveio/dhive, hivesigner, @tanstack/react-query)
Bundle Size: ~182KB
Why separate: This package is published to npm for use by other Hive applications. It must remain focused and lightweight.
Purpose: Asset queries and wallet management across multiple blockchains
Scope:
- ✅ Hive assets (HIVE, HBD, Hive Power) — queries and balance display
- ✅ Hive Engine tokens — queries and balance display
- ✅ SPK Network (SPK, LARYNX) — queries and balance display
- ✅ Points system — queries
- ✅ External blockchains (BTC, ETH, SOL, TON, TRON, APT, BNB)
- ✅ Wallet balance/transaction queries
- ✅ Multi-chain key management
Note: All blockchain mutations (transfers, delegations, staking, etc.) have been migrated to @ecency/sdk. This package now focuses on queries, balance display, and multi-chain key management.
Dependencies:
- @ecency/sdk (for Hive core functionality)
- @okxweb3 packages (for external blockchain support)
- Heavy crypto libraries
Bundle Size: ~131KB
Why separate: Wallet functionality requires heavy external dependencies. Users who only need Hive SDK shouldn't be forced to download BTC/ETH/SOL libraries.
Purpose: Framework-agnostic markdown and content rendering utilities
Scope:
- ✅ Markdown to HTML conversion
- ✅ Content sanitization
- ✅ Post formatting utilities
- ✅ Image proxification
- ✅ Content summarization
Dependencies: Minimal (htmlparser2, remarkable, xss, he, lolight)
Bundle Size: ~10-12KB
Why separate: Framework-agnostic utility library that can be used in any JavaScript environment (browser, Node.js, React Native). Published to npm for reuse by other Hive applications.
Purpose: Shared React UI components used across apps
Scope:
- ✅ Reusable React UI components
- ✅ Shared between web and self-hosted apps
Why separate: Avoids duplicating UI primitives across multiple apps in the monorepo.
Purpose: Lightweight SPA for self-hosted Hive blogs and communities
Stack: Rsbuild (not Next.js) + TanStack Router + Zustand + TailwindCSS 4
Scope:
- ✅ Runtime config system (hot-swappable config.json for Docker/Nginx)
- ✅ Blog post listing, single post view, comments
- ✅ Auth (Keychain, HiveSigner, HiveAuth)
- ✅ Publish/create posts (TipTap editor)
- ✅ Voting, reblog, comment creation
- ✅ Configurable sidebar, themes, branding
Key files:
apps/self-hosted/src/core/configuration-loader.ts— Runtime config loadingapps/self-hosted/src/providers/sdk/broadcast-adapter.ts— Auth bridge to SDKapps/self-hosted/src/routes/__root.tsx— Root layout
Deployment: Docker multi-stage build with Nginx serving static files
Purpose: Full-featured Hive web client
Scope:
- ✅ All UI components and pages
- ✅ App-specific business logic
- ✅ Feature modules
- ✅ App-specific query wrappers (using both SDK and wallets)
- ✅ State management (Zustand + React Query)
Dependencies: All workspace packages + app-specific libraries
❌ DO NOT:
- Move wallet/asset queries from
@ecency/walletsto@ecency/sdk- Reason: Would bloat SDK with multi-chain dependencies
- Add heavy dependencies to
@ecency/sdk- Reason: SDK must remain lightweight for external consumers
- Create circular dependencies between packages
- Reason: Breaks build order and package independence
- Add app-specific logic to packages
- Reason: Packages should be reusable
- Use hardcoded query key arrays — use
QueryKeysfrom@ecency/sdk- Reason: Single source of truth for cache invalidation
✅ DO:
- Put all Hive blockchain mutations in
@ecency/sdk - Put wallet/asset queries in
@ecency/wallets - Create web-specific mutation wrappers in
apps/web/src/api/sdk-mutations/ - Use
QueryKeysfrom@ecency/sdkfor all cache key references - Rebuild packages after changes:
pnpm build:packages
Query Organization:
// In @ecency/sdk - Hive blockchain queries + mutations
export function getPostQueryOptions(author: string, permlink: string) { ... }
export function useVote(username: string, auth?: AuthContextV2) { ... }
// In @ecency/wallets - Asset/wallet queries (no mutations)
export function getHiveEngineTokensBalancesQueryOptions(username: string) { ... }
export function getSpkWalletQueryOptions(username?: string) { ... }
// In @ecency/web - SDK mutation wrappers (apps/web/src/api/sdk-mutations/)
// Each wrapper adds the web broadcast adapter and active user context
import { useVote } from "@ecency/sdk";
import { createWebBroadcastAdapter } from "@/providers/sdk";
export function useVoteMutation() {
const { activeUser } = useActiveAccount();
const adapter = createWebBroadcastAdapter();
return useVote(activeUser?.username, { adapter });
}After modifying files in packages/sdk or packages/wallets:
# Rebuild all packages
pnpm build:packages
# Or rebuild individual packages
pnpm --filter @ecency/sdk build
pnpm --filter @ecency/wallets buildThe web app will not see package changes until they are rebuilt.
- Next.js 15.4 with App Router
- State Management: Zustand (global state) + @tanstack/react-query (server state)
- Styling: TailwindCSS + SCSS modules
- Editor: TipTap
- Forms: react-hook-form + yup
- Blockchain: @hiveio/dhive, hivesigner, hive-auth-client
- Monitoring: Sentry
apps/web/src/
├── app/ # Next.js App Router pages & layouts
│ ├── (dynamicPages)/ # Dynamic routes (profiles, entries)
│ ├── (staticPages)/ # Static pages
│ └── api/ # API routes
├── api/ # API layer
│ ├── queries/ # App-specific React Query queries (most queries now in SDK)
│ ├── mutations/ # App-specific mutation logic (reply, transfer, etc.)
│ ├── sdk-mutations/ # SDK mutation wrappers (60 hooks)
│ ├── format-error.ts # Error formatting utility
│ ├── bridge.ts # Main API bridge
│ ├── hive.ts # Hive blockchain API
│ └── private-api.ts # Ecency private API
├── core/ # Core infrastructure
│ ├── global-store/ # Zustand global state
│ │ ├── modules/ # State modules (auth, UI, users, etc.)
│ │ └── initialization/ # Client/server initialization
│ ├── react-query/ # React Query configuration
│ └── caches/ # Caching utilities
├── features/ # Feature modules (25 features)
│ ├── shared/ # 64 shared components
│ ├── ui/ # UI component library (22 components)
│ └── [feature-name]/ # Domain-specific features
├── entities/ # Domain entities/types
├── enums/ # TypeScript enums
├── utils/ # Utility functions (79+ files)
├── config/ # Feature flag configuration system
├── routes.ts # Route definitions
└── middleware.ts # Next.js middleware
Global State (Zustand):
- Located in
src/core/global-store/modules/ - Separate client/server initialization to support SSR
- Modules: authentication, global, ui, users, notifications, signing-key, config
Server State (React Query):
- Most queries now live in
@ecency/sdkas query option builders;src/api/queries/has app-specific queries only - 60 SDK mutation wrappers in
src/api/sdk-mutations/ - App-specific mutation orchestration in
src/api/mutations/ QueryKeysfrom@ecency/sdkfor cache key management- Separate QueryClient instances for client/server
@/*→src/*@ui/*→src/features/ui/*
These are configured in:
apps/web/tsconfig.json(TypeScript)apps/web/vitest.config.ts(Vitest)apps/web/next.config.js(Webpack)
The app uses Next.js App Router with complex URL rewrites for legacy compatibility:
- Profile pages:
/:author(@.+)/:section - Entry pages:
/:category/:author/:permlink - Community pages:
/:filter/:community(hive-\d+) - Feed pages:
/:filter(hot|created|...)/:tag
Rewrites are defined in next.config.js. Route constants live in src/routes.ts.
The EcencyConfigManager (src/config/) provides:
- Feature flag configuration
- Conditional rendering components
- Environment-based configuration
- Template-based config structure
API Files:
bridge.ts- Main API bridge with complex routing logichive.ts- Hive blockchain APIhive-engine.ts- Hive Engine token APIprivate-api.ts- Ecency private APIformat-error.ts- Error formatting for user-facing error messages
Mutation Architecture:
All blockchain mutations live in @ecency/sdk and are wrapped for web use in apps/web/src/api/sdk-mutations/. Each wrapper hook:
- Gets the active user via
useActiveAccount() - Creates a web broadcast adapter via
createWebBroadcastAdapter() - Passes both to the SDK mutation hook
@ecency/sdk (platform-agnostic mutations)
↓ wrapped by
apps/web/src/api/sdk-mutations/ (60 web-specific hooks)
↓ used by
apps/web/src/api/mutations/ (app-specific orchestration: optimistic updates, error handling)
↓ used by
Feature components
App-specific mutation logic (optimistic cache updates, error handling, multi-step flows) remains in apps/web/src/api/mutations/.
Cache Key Management:
- Use
QueryKeysfrom@ecency/sdkas the single source of truth - Legacy
QueryIdentifiersenum still exists for web-app-only queries (polls, threespeak, market) - New queries should define keys in SDK's
QueryKeyswhen possible
Multi-method authentication support:
- Private Key (
key) - Direct key signing (posting key stored, active via auth upgrade) - Keychain (
keychain) - Browser extension integration - HiveSigner (
hivesigner) - OAuth token-based broadcasting - HiveAuth (
hiveauth) - QR code + mobile app flow
Broadcast Adapter Pattern (apps/web/src/providers/sdk/web-broadcast-adapter.ts):
The SDK uses a platform adapter pattern to decouple mutations from auth. The web app provides createWebBroadcastAdapter() which implements the PlatformAdapter interface:
SDK mutation (platform-agnostic)
↓ calls adapter methods
Web Broadcast Adapter (web-specific)
↓ resolves auth via
localStorage (keys/tokens) → Keychain extension → HiveSigner API → HiveAuth protocol
Smart Auth Strategy (in SDK's useBroadcastMutation):
- Calls
adapter.getLoginType()to determine user's auth method - For posting ops with granted posting authority: tries HiveSigner token first (faster)
- Falls back to user's actual auth method if token fails
- For active ops: shows auth upgrade dialog via
adapter.showAuthUpgradeUI()
Auth Upgrade Flow (apps/web/src/features/shared/auth-upgrade/):
When an operation requires active authority but the user logged in with posting key:
- SDK detects auth failure → calls
adapter.showAuthUpgradeUI() - Web adapter dispatches
ecency-auth-upgradeCustomEvent - Auth upgrade dialog appears, user selects method (enter active key, use Keychain, etc.)
- Dialog resolves the promise with chosen method + optional temp key
- SDK retries broadcast with the chosen method
- Temp active key auto-clears after 60s or on next auth flow
The web app uses Vitest + React Testing Library. Tests are located in src/specs/:
core/- Core functionality tests (React Query helpers, hooks)features/- Component tests organized by featureutils/- Utility function tests
# All tests
pnpm test
# Specific test file
pnpm test path/to/test.spec.tsx
# Watch mode
pnpm test --watch
# Update snapshots
pnpm test -u
# UI mode
pnpm test --uiUse src/specs/test-utils.tsx for common testing patterns:
import { renderWithQueryClient, mockFullAccount, setupModalContainers } from '@/specs/test-utils';
describe('MyComponent', () => {
beforeEach(setupModalContainers);
test('renders correctly', () => {
const { container } = renderWithQueryClient(
<MyComponent />,
{ queryClient: seedQueryClient({ posts: mockData }) }
);
});
});Global Mocks (setup-any-spec.ts):
- External packages (@ecency/sdk, @ecency/wallets, @ecency/render-helper)
- i18next (returns keys as-is)
- Common utilities
Per-Test Mocks:
- API queries/mutations specific to the component
- Component-specific dependencies
1. Utility Functions (pure functions):
import { myUtilFunction } from '@/utils';
describe('myUtilFunction', () => {
it('should handle edge case', () => {
expect(myUtilFunction(input)).toBe(expected);
});
});2. React Components:
import { render, screen, fireEvent } from '@testing-library/react';
import { renderWithQueryClient } from '@/specs/test-utils';
import { vi } from 'vitest';
describe('MyComponent', () => {
test('user interaction', () => {
renderWithQueryClient(<MyComponent />);
fireEvent.click(screen.getByRole('button'));
expect(screen.getByText('Updated')).toBeInTheDocument();
});
});3. Components with React Query:
import { QueryClient } from '@tanstack/react-query';
import { vi } from 'vitest';
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } }
});
beforeEach(() => {
queryClient.setQueryData(['key'], mockData);
});
test('displays data', () => {
renderWithQueryClient(<MyComponent />, { queryClient });
expect(screen.getByText(mockData.title)).toBeInTheDocument();
});- ✅ Test user-visible behavior, not implementation details
- ✅ Use
screen.getByRoleovergetByTestIdwhen possible - ✅ Mock external dependencies with
vi.fn(), not internal functions - ✅ Keep tests focused and isolated
- ✅ Use factories for mock data (see test-utils.tsx)
- ✅ Clean up after tests (DOM, timers, mocks)
- ❌ Don't test library code (React Query, Zustand)
- ❌ Don't mock too much (makes tests brittle)
- ❌ Don't use snapshots for everything (hard to maintain)
- All new features require tests
- Bug fixes should include regression tests
- Aim for >80% coverage on business logic
- 100% coverage not required for UI components
- Test environment: jsdom
- Test location:
src/specs/ - Test pattern:
*.spec.tsxco-located with components - Setup file:
src/specs/setup-any-spec.ts - Mocked modules: i18next, @ecency/sdk, @ecency/wallets, @ecency/render-helper
- Configuration:
apps/web/vitest.config.ts
- Each package has its own Vitest configuration
- Test files co-located with source code
- Follow feature-based organization in
src/features/ - Shared components go in
features/shared/ - UI primitives go in
features/ui/ - Avoid unnecessary re-renders (performance-critical app)
- Create tests for new components
- Use Zustand for client-side global state
- Use React Query for server state (API data)
- Check
src/core/global-store/modules/for existing state modules - Query identifiers defined in centralized enum
- TailwindCSS is primary styling method
- Dark mode via
classstrategy - SCSS modules for component-specific styles
- Custom theme in
tailwind.config.ts
- TypeScript strict mode enabled
- All new code should include proper types
- ESLint and type checking ignored during builds (check before commit)
- Run
pnpm lintandpnpm testbefore creating PRs
- All new strings must be added to
en-US.jsononly - Use i18next for translations
- Translation system in
src/features/i18n/
- Branch from
develop(notmaster) - Include tests with changes
- Update tests if modifying existing code
- Mark PR as WIP if not ready
- Include description, screenshots/videos, and link to issues
- Ensure linting and tests pass before requesting review
Copy apps/web/.env.template to apps/web/.env and configure:
NEXT_PUBLIC_HS_CLIENT_ID # HiveSigner client ID
NEXT_PUBLIC_HS_CLIENT_SECRET # HiveSigner secret
NEXT_PUBLIC_APP_BASE # App base URL
NEXT_PUBLIC_APP_NAME # App name
NEXT_PUBLIC_APP_TITLE # App title
NEXT_PUBLIC_APP_DESCRIPTION # App descriptionSee README.md for detailed environment variable documentation.
This project uses pnpm@10.26.1. The packageManager field in package.json ensures the correct version is used. Always run commands from the workspace root unless specifically targeting a package.
The production build:
- Generates UUID-based build IDs
- Enables production browser source maps
- Includes PWA support (8MB cache limit)
- Integrates Sentry for error tracking
- Transpiles workspace packages
- Uses sass-embedded for fast SCSS compilation
package.json- Workspace scripts and configurationapps/web/next.config.js- Next.js configuration, rewrites, Sentryapps/web/src/app/layout.tsx- Root layout and providersapps/web/src/core/global-store/index.ts- Global state setupapps/web/src/core/react-query/index.ts- React Query setupapps/web/src/api/sdk-mutations/index.ts- All SDK mutation wrapper exportspackages/sdk/src/modules/core/query-keys.ts- Centralized cache key definitionsapps/web/src/providers/sdk/web-broadcast-adapter.ts- Web-specific auth/broadcast adapterapps/web/src/routes.ts- Route constantsapps/web/tailwind.config.ts- Design system