Skip to content

Latest commit

 

History

History
684 lines (516 loc) · 23.4 KB

File metadata and controls

684 lines (516 loc) · 23.4 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

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).

Common Commands

Development

# 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

Building

# 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

Testing & Linting

# Run tests (all use Vitest)
pnpm test

# Lint all packages
pnpm lint

# Type check
pnpm typecheck

Running Single Tests

# 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

Publishing Packages

# Build before publishing
pnpm build:packages

# Publish individual packages
pnpm publish:sdk
pnpm publish:wallets
pnpm publish:render-helper
pnpm publish:ui

Monorepo Architecture

Workspace Structure

  • 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).

Package Build Strategy

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.

Package Architecture & Boundaries

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

@ecency/sdk - Core Hive SDK

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 QueryKeys for 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.

@ecency/wallets - Multi-chain Wallet Management

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.

@ecency/render-helper - Markdown Rendering Utilities

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.

@ecency/ui - Shared UI Component Library

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.

@ecency/self-hosted - Self-Hosted Blog/Community App

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 loading
  • apps/self-hosted/src/providers/sdk/broadcast-adapter.ts — Auth bridge to SDK
  • apps/self-hosted/src/routes/__root.tsx — Root layout

Deployment: Docker multi-stage build with Nginx serving static files

@ecency/web - Main Application

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

Important Package Guidelines

❌ DO NOT:

  • Move wallet/asset queries from @ecency/wallets to @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 QueryKeys from @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 QueryKeys from @ecency/sdk for 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 });
}

When to Rebuild Packages

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 build

The web app will not see package changes until they are rebuilt.

Main Application Architecture

Technology Stack

  • 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

Directory Structure

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

State Management Pattern

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/sdk as 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/
  • QueryKeys from @ecency/sdk for cache key management
  • Separate QueryClient instances for client/server

Path Aliases

  • @/*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)

Routing Architecture

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.

Feature Flag System

The EcencyConfigManager (src/config/) provides:

  • Feature flag configuration
  • Conditional rendering components
  • Environment-based configuration
  • Template-based config structure

API Layer Organization

API Files:

  • bridge.ts - Main API bridge with complex routing logic
  • hive.ts - Hive blockchain API
  • hive-engine.ts - Hive Engine token API
  • private-api.ts - Ecency private API
  • format-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:

  1. Gets the active user via useActiveAccount()
  2. Creates a web broadcast adapter via createWebBroadcastAdapter()
  3. 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 QueryKeys from @ecency/sdk as the single source of truth
  • Legacy QueryIdentifiers enum still exists for web-app-only queries (polls, threespeak, market)
  • New queries should define keys in SDK's QueryKeys when possible

Authentication & Broadcasting

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):

  1. Calls adapter.getLoginType() to determine user's auth method
  2. For posting ops with granted posting authority: tries HiveSigner token first (faster)
  3. Falls back to user's actual auth method if token fails
  4. 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:

  1. SDK detects auth failure → calls adapter.showAuthUpgradeUI()
  2. Web adapter dispatches ecency-auth-upgrade CustomEvent
  3. Auth upgrade dialog appears, user selects method (enter active key, use Keychain, etc.)
  4. Dialog resolves the promise with chosen method + optional temp key
  5. SDK retries broadcast with the chosen method
  6. Temp active key auto-clears after 60s or on next auth flow

Testing

Test Structure

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 feature
  • utils/ - Utility function tests

Running 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 --ui

Test Utilities

Use 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 }) }
    );
  });
});

Mocking Strategy

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

Component Testing Patterns

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();
});

Best Practices

  • ✅ Test user-visible behavior, not implementation details
  • ✅ Use screen.getByRole over getByTestId when 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)

Coverage Requirements

  • 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

Main App (Vitest)

  • Test environment: jsdom
  • Test location: src/specs/
  • Test pattern: *.spec.tsx co-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

Packages (Vitest)

  • Each package has its own Vitest configuration
  • Test files co-located with source code

Important Implementation Notes

Component Development

  • 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

State Management

  • 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

Styling

  • TailwindCSS is primary styling method
  • Dark mode via class strategy
  • SCSS modules for component-specific styles
  • Custom theme in tailwind.config.ts

Code Quality

  • TypeScript strict mode enabled
  • All new code should include proper types
  • ESLint and type checking ignored during builds (check before commit)
  • Run pnpm lint and pnpm test before creating PRs

Internationalization

  • All new strings must be added to en-US.json only
  • Use i18next for translations
  • Translation system in src/features/i18n/

Pull Requests

  • Branch from develop (not master)
  • 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

Environment Configuration

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 description

See README.md for detailed environment variable documentation.

Package Manager

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.

Production Build

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

Key Files for Understanding

  1. package.json - Workspace scripts and configuration
  2. apps/web/next.config.js - Next.js configuration, rewrites, Sentry
  3. apps/web/src/app/layout.tsx - Root layout and providers
  4. apps/web/src/core/global-store/index.ts - Global state setup
  5. apps/web/src/core/react-query/index.ts - React Query setup
  6. apps/web/src/api/sdk-mutations/index.ts - All SDK mutation wrapper exports
  7. packages/sdk/src/modules/core/query-keys.ts - Centralized cache key definitions
  8. apps/web/src/providers/sdk/web-broadcast-adapter.ts - Web-specific auth/broadcast adapter
  9. apps/web/src/routes.ts - Route constants
  10. apps/web/tailwind.config.ts - Design system