Documentation

This directory contains comprehensive documentation for the Requests and Offers application.

Overview

Technical Documentation

UI Architecture

Backend Architecture

Development Guides

Project Overview

Introduction

The Requests and Offers project is a Holochain application (hApp) designed for the hAppenings.community. It facilitates connections within the Holochain ecosystem by providing a simplified bulletin board where users can:

  • Post Requests for services, skills, or resources they need
  • Create Offers to provide services, skills, or resources they can share
  • Discover opportunities through search and tagging systems
  • Connect directly through clearly displayed contact information

Key Features (Simplified MVP)

Service Types & Tag-Based Discovery System

  • Dynamic Service Type Management: DHT-based service type entries with admin validation workflow
  • Tag-Based Discovery: Comprehensive tagging system enabling efficient search across requests, offers, and service types
  • Cross-Entity Discovery: Find related content through tag navigation (service types → requests/offers)
  • Admin Moderation: Full approval/rejection workflow for user-suggested service types
  • Autocomplete & Statistics: Real-time tag suggestions and usage analytics

Request Management

  • Create detailed requests specifying what you need with service type integration
  • Set preferences for communication, timing, and exchange type
  • Link requests to service types for better categorization and discovery
  • Tag-based filtering and search capabilities

Offer Management

  • Create comprehensive offers detailing what you can provide with service type integration
  • Specify availability, skills, and interaction preferences
  • Connect offers to service types for improved discoverability
  • Tag-based filtering and search capabilities

User & Organization Profiles

  • Individual user profiles with skills and preferences
  • Organization/project management with team coordination
  • Multi-device access and profile synchronization

Listing Management

  • Archive your own requests/offers when fulfilled or no longer relevant
  • Delete your own requests/offers permanently
  • View all your listings in a personal dashboard

Administrative Tools

  • Admin interface for approving/rejecting service type suggestions
  • User verification and role management
  • Content moderation capabilities
  • Tag analytics and management dashboards

Technology Stack

Backend (Holochain)

  • Distributed Hash Table (DHT): Peer-to-peer data storage and retrieval
  • Zome Architecture: Modular coordinator/integrity pattern
    • requests_coordinator: Request management and lifecycle
    • offers_coordinator: Offer management and lifecycle
    • service_types_coordinator: Service type management with tag indexing
    • users_organizations: Profile and organization management
    • administration: Admin roles and verification
  • Cross-Zome Integration: Seamless data flow between different functional areas
  • Tag-Based Indexing: Efficient path anchor system for discovery and search

Frontend (SvelteKit + Effect TS)

  • SvelteKit: Modern web framework with server-side rendering
  • Svelte 5 Runes: Reactive state management ($state, $derived, $effect)
  • Effect TS: Functional programming patterns for robust async operations and error handling
  • TailwindCSS + SkeletonUI: Modern, responsive design system

Development Environment

  • Nix: Reproducible development environment (for DNA/zome development)
  • Bun: Fast JavaScript runtime and package manager (for frontend)
  • Sweettest: Holochain testing framework (backend tests, Rust in-process harness)
  • Vitest: Modern testing framework (frontend tests)

Architecture Patterns

Effect Service Pattern

All zome interactions use Effect TS for:

  • Type-safe error handling: Domain-specific error types with proper context
  • Composable async operations: Clean composition of complex workflows
  • Dependency injection: Clean separation of concerns through Context Tags
  • Robust state management: Integration with Svelte stores for reactive UX

Reactive State Management

  • Svelte Stores: Feature-specific stores using Svelte 5 Runes
  • Entity Caching: In-memory caching with expiration and invalidation
  • Event Bus: Cross-store communication for coordinated state updates
  • Tag-Based Reactivity: Dynamic updates based on tag selections and filtering

Component Organization

  • Feature-based structure: Components organized by domain (requests, offers, service-types, tags)
  • Reusable components: Shared UI elements with consistent patterns
  • Accessibility-first: WCAG-compliant interfaces with keyboard navigation
  • Mobile-responsive: Adaptive design for all device sizes

User Experience (Simplified MVP)

For Regular Users

  1. Browse and Discover: Use search and tag-based navigation to find relevant requests and offers
  2. Create Content: Post requests or offers with service type categorization
  3. Connect Directly: View contact information and communicate outside the platform
  4. Manage Listings: Archive or delete your own requests/offers

For Administrators

  1. Moderate Content: Review and approve/reject user-suggested service types
  2. Manage Users: Verify accounts and manage roles
  3. Analytics: View tag usage statistics and platform activity
  4. System Health: Monitor performance and resolve issues

Current Implementation Status

Completed Features

  • Service Types System: Complete implementation with validation workflow
  • Tag-Based Discovery: Full cross-entity discovery and navigation
  • Request/Offer Management: Complete CRUD operations with service type integration
  • User/Organization Profiles: Basic profile management
  • Admin Interface: Complete moderation and management tools
  • Testing Infrastructure: Comprehensive test coverage (backend and frontend)

Simplified MVP Features (In Development)

  • Archive/delete functionality for user listings
  • Contact information display components
  • Simplified navigation and user dashboard
  • Documentation updates for simplified approach

Post-MVP Features (Planned)

  • Exchange coordination system (proposals, agreements, reviews)
  • In-app messaging system
  • Advanced analytics and reporting
  • Enhanced user dashboard features
  • hREA economic resource integration
  • Mobile application
  • Advanced recommendation algorithms
  • Reputation and rating systems

Getting Started

Prerequisites

  • Nix (for Holochain development)
  • Bun (for frontend development)
  • Git

Quick Start

# Clone the repository
git clone https://github.com/hAppening-Community/requests-and-offers.git
cd requests-and-offers

# Enter Nix development environment (for backend)
nix develop

# Install frontend dependencies
cd ui && bun install

# Run development servers
bun run dev

Testing

# Run backend tests
bun run test:backend

# Run frontend tests
bun run test:unit
bun run test:integration

Contributing

We welcome contributions! Please see our contributing guidelines and feel free to:

  • Report bugs and suggest features
  • Contribute code improvements
  • Help with documentation
  • Test new features and provide feedback

Community

Architecture Overview

This document provides a high-level overview of the system architecture, including design patterns, key technical decisions, and component relationships.

Key Sections

🏆 Unified Effect TS Architecture

Domain-by-Domain Standardization Approach

The application follows a 7-Layer Standardization Pattern achieved through iterative, domain-driven refactoring. This architectural evolution represents a major milestone in creating a consistent, maintainable, and type-safe codebase.

Implementation Status:

  1. ✅ Service Types Domain: FULLY COMPLETED (100%) - Complete pattern template established
  2. ✅ Requests Domain: FULLY COMPLETED (100%) - Patterns successfully replicated
  3. ✅ Offers Domain: FULLY COMPLETED (100%) - All 7 layers standardized
  4. ✅ Users Domain: FULLY COMPLETED (100%) - Effect-TS standardization complete
  5. ✅ Organizations Domain: FULLY COMPLETED (100%) - Effect-TS standardization complete
  6. ✅ Administration Domain: FULLY COMPLETED (100%) - Effect-TS standardization complete
  7. ✅ Exchanges Domain: FULLY COMPLETED (100%) - Complete Effect-TS implementation with all layers
  8. ✅ Mediums of Exchange Domain: FULLY COMPLETED (100%) - Effect-TS standardized with store helpers

The 7-Layer Architecture Pattern

Each domain follows this standardized structure, ensuring consistency and maintainability across the entire application:

1. Service Layer (Effect-Native)

  • Pattern: Pure Effect services with Context.Tag dependency injection
  • Error Handling: Domain-specific tagged errors (DomainError)
  • Schema Strategy: callZomeRawEffect for Holochain data, callZomeEffect for business logic
  • Dependency Management: Clean separation through Effect Layer pattern
  • Example: serviceTypes.service.ts (fully standardized template)

2. Store Layer (Svelte + Effect Integration)

  • Pattern: Factory functions returning Effects with Svelte 5 Runes
  • Structure: 9 standardized helper functions for massive code reduction:
    • createUIEntity() - Entity creation from Holochain records
    • mapRecordsToUIEntities() - Consistent record mapping
    • createCacheSyncHelper() - Cache-to-state synchronization
    • createEventEmitters() - Standardized event emission
    • createEntitiesFetcher() - Data fetching with state updates
    • withLoadingState() - Loading state management
    • createRecordCreationHelper() - Record creation patterns
    • createStatusTransitionHelper() - Status transitions
    • processMultipleRecordCollections() - Complex data processing
  • State Management: $state, $derived, $effect with EntityCache integration
  • Event Integration: Standardized EventBus patterns for cross-store communication

3. Schema Validation (Effect Schema)

  • Strategy: Strategic validation boundaries (input validation, business logic, UI transformations)
  • Types: Branded types for domain safety (ActionHash, ServiceTypeName)
  • Classes: Schema.Class for complex entities (UIServiceType)
  • Validation Points: Input forms, API boundaries, cross-service communication

4. Error Handling (Centralized Tagged Errors)

  • Pattern: Domain-specific error hierarchies (Service → Store → Composable)
  • Context: Meaningful error contexts and recovery patterns
  • Export: Centralized through ui/src/lib/errors/index.ts
  • User Experience: Consistent error messaging and fallback handling

5. Composables Layer (Component Logic Abstraction)

  • Pattern: Extract complex component logic into reusable Effect-based functions
  • Integration: Bridge Svelte components with Effect stores/services
  • Interface: Standard state/actions separation with typed interfaces
  • Benefits: Prevent infinite reactive loops, enhance testability

6. Components Layer (Svelte 5 + Accessibility)

  • Integration: Use composables for business logic, focus on presentation
  • Reactivity: Svelte 5 Runes with proper reactive patterns
  • Accessibility: WCAG-compliant with keyboard navigation
  • Performance: Optimized with $derived.by and proper effect management

7. Testing Layer (Comprehensive Effect TS Coverage)

  • Backend: Sweettest multi-agent testing
  • Unit: Effect TS testing utilities with service isolation
  • Integration: End-to-end workflow validation
  • Pattern: Domain-specific testing strategies for all layers

Core Data Flow

The application follows a refined data and control flow leveraging Effect TS patterns for maximum type safety and maintainability:

  1. Rust Zomes (Holochain Backend): Execute core business logic and manage data persistence on the DHT.

  2. Effect Services (ui/src/lib/services):

    • Pure Effect-native services with Context.Tag dependency injection
    • Strategic schema validation at business boundaries
    • Domain-specific error handling with tagged errors
    • Composable async operations with robust error propagation
  3. Svelte Stores (ui/src/lib/stores):

    • Factory function pattern creating Effect-based stores
    • Standardized helper functions (9 core patterns) for code reduction
    • Reactive state management using Svelte 5 Runes ($state, $derived, $effect)
    • Event Bus integration for cross-store communication
    • EntityCache patterns for performance optimization
  4. Composables (ui/src/lib/composables):

    • Component Logic Abstraction Layer extracting complex logic
    • Effect integration for all async operations
    • Standard interfaces with state/actions separation
  5. Svelte UI Components (ui/src/lib/components):

    • Use composables for business logic and state management
    • Focus on presentation and user interaction
    • Svelte 5 patterns with proper reactive design

Architectural Benefits

  • Type Safety: Comprehensive Effect dependency resolution and error handling
  • Code Quality: Massive reduction in duplication through standardized patterns
  • Maintainability: Consistent structure across all domains
  • Performance: Optimized patterns with caching and lazy initialization
  • Testing: Robust testing strategies for all layers
  • Developer Experience: Clear patterns and comprehensive documentation
  • Scalability: Template-based approach for new domain addition

Pattern Documentation

The architecture is supported by comprehensive pattern documentation:

Implementation Timeline

Completed ✅ - MAJOR MILESTONE ACHIEVED

  • All 8 Domains Fully Standardized: Complete 7-layer Effect-TS architecture implementation
    • Service Types Domain: Template and foundation (100%)
    • Requests Domain: Complete standardization (100%)
    • Offers Domain: Complete standardization (100%)
    • Users Domain: Complete Effect-TS conversion (100%)
    • Organizations Domain: Complete Effect-TS conversion (100%)
    • Administration Domain: Complete Effect-TS conversion (100%)
    • Exchanges Domain: Complete implementation with all layers (100%)
    • Mediums of Exchange Domain: Complete Effect-TS standardization (100%)
  • Pattern Documentation: Comprehensive rule files for consistent development
  • Foundation Architecture: Complete Effect TS infrastructure and utilities
  • Testing Infrastructure: All 343 unit tests passing across 20 test files with standardized mocks

Current Focus 🎯

  • Documentation Enhancement: Updating all documentation to reflect completed architecture
  • Pattern Refinement: Continuous improvement of established patterns
  • Architecture Maintenance: Ensuring consistency across all domains

Future Enhancements 📋

  • Performance Optimization: Leverage standardized patterns for enhanced performance
  • Advanced Features: Exchange completion workflows and advanced hREA integration
  • UI/UX Improvements: Standardize UI components and composables for consistency
  • Holo deployment: Deploy to holo legacy network

This layered approach ensures separation of concerns, leverages Effect TS for robust service logic, and maintains Svelte Runes for efficient UI reactivity while providing a consistent, maintainable codebase across all domains.

Architecture Diagrams

1. 7-Layer Effect-TS Architecture Pattern

graph TD
    subgraph "HOLOCHAIN BACKEND"
        HC[Holochain Zomes<br/>Rust Backend<br/>DHT Storage]
    end

    subgraph "EFFECT TS ARCHITECTURE - 7 LAYER PATTERN"
        subgraph "LAYER 1: SERVICE LAYER"
            SL[Effect-Native Services<br/>Context.Tag Dependency Injection<br/>Domain-Specific Tagged Errors<br/>Strategic Schema Validation]
        end

        subgraph "LAYER 2: STORE LAYER"
            ST[Factory Function Pattern<br/>9 Standardized Helper Functions<br/>Svelte 5 Runes Integration<br/>EntityCache + EventBus]
        end

        subgraph "LAYER 3: SCHEMA VALIDATION"
            SC[Effect Schema<br/>Strategic Validation Boundaries<br/>Branded Types<br/>Schema.Class for Complex Entities]
        end

        subgraph "LAYER 4: ERROR HANDLING"
            EH[Centralized Tagged Errors<br/>Domain-Specific Hierarchies<br/>Meaningful Error Contexts<br/>Recovery Patterns]
        end

        subgraph "LAYER 5: COMPOSABLES"
            CO[Component Logic Abstraction<br/>Effect-Based Functions<br/>Bridge Components with Stores<br/>State/Actions Separation]
        end

        subgraph "LAYER 6: COMPONENTS"
            CM[Svelte 5 + Accessibility<br/>Use Composables for Logic<br/>Focus on Presentation<br/>WCAG Compliant]
        end

        subgraph "LAYER 7: TESTING"
            TE[Comprehensive Effect TS Coverage<br/>Backend Sweettest Testing<br/>Unit Testing with Isolation<br/>Integration Workflow Validation]
        end
    end

    %% Layer Flow Connections
    HC --> SL
    SL --> ST
    ST --> SC
    SC --> EH
    EH --> CO
    CO --> CM
    CM --> TE

2. Data Flow Architecture

graph TD
    subgraph "CORE DATA FLOW"
        DF1[Holochain Backend<br/>Business Logic + DHT Persistence]
        DF2[Effect Services<br/>Pure Effect-Native + Dependency Injection]
        DF3[Svelte Stores<br/>Factory Pattern + 9 Helper Functions]
        DF4[Composables<br/>Component Logic Abstraction]
        DF5[Svelte Components<br/>Presentation + User Interaction]
    end

    subgraph "PATTERN DOCUMENTATION SUPPORT"
        DOC1[Service Effect Patterns]
        DOC2[Store Effect Patterns]
        DOC3[Error Management Patterns]
        DOC4[Schema Patterns]
        DOC5[Testing Strategy]
    end

    %% Data Flow Chain
    DF1 --> DF2
    DF2 --> DF3
    DF3 --> DF4
    DF4 --> DF5

    %% Documentation Support
    DOC1 -.-> DF2
    DOC2 -.-> DF3
    DOC3 -.-> DF2
    DOC4 -.-> DF2
    DOC5 -.-> DF5

Project Status

This document reflects the current implementation state as of v0.5.2 (2026-04-30). For full change history, see CHANGELOG.md.

Current Phase: Alpha Testing — Core MVP features are live; active bug triage underway from alpha user feedback.


What's Working

Architecture — Unified Effect-TS (All 8 Domains)

All domains are fully standardized with the 7-layer Effect-TS architecture: Service Types, Requests, Offers, Users, Organizations, Administration, Exchanges, Mediums of Exchange

Pattern established: Context.Tag services → Effect-native stores → Schema.Class validation → Data.TaggedError error handling → composables → Svelte 5 components.

Core Infrastructure

Holochain Backend (Rust):

  • All 7 coordinator + integrity zome pairs in place
  • Progenitor pattern — secure designated network creator for trusted bootstrapping
  • Action hash type safety — compile-time distinct types preventing hash kind confusion
  • Active/Archived/Deleted listing status management across requests and offers
  • Sweettest integration tests (37 tests, all passing — replaced Tryorama in v0.5.0)

Frontend (SvelteKit + Effect-TS):

  • Svelte 5 Runes ($state, $derived, $effect) throughout
  • All 8 domain stores fully Effect-TS based
  • EntityCache pattern for in-memory caching
  • Event Bus system (storeEvents.ts) for cross-store communication
  • Weave/Moss integration — runs as a Weave Tool with hybrid profile display
  • Markdown rendering for descriptions and bios
  • Search and filtering across organizations, requests, and offers
  • Active/Archived listing tabs with status management UI
  • Contact information display components
  • Organization contact person designation (coordinator with role/title)
  • Navigation: Profile section with edit access
  • Alt+A keybinding for contextual admin/public page navigation

Documentation:

  • mdBook-based documentation site with GitHub Pages deployment
  • Developer guide system: getting-started, development-workflow, effect-ts-primer, architectural-patterns, domain-implementation

Testing Infrastructure

LayerCountStatus
Backend (Sweettest / Rust)37 testsAll passing
Frontend unit tests (Vitest)~367 tests across 22 filesAll passing
E2E / PlaywrightPlanned (#10)Not yet implemented

Active Bugs

Bugs from alpha testing, ordered by priority. See the project board for live status.

P1 — Critical

IssueTitleStatus
#56Status Table: only shows last 2 entries, requires page refreshIn progress
#158Profile creation fails with 60s call_zome timeoutReady
#133Request form: Links and Organization optional fields don't persist on saveReady
#134Request update→navigate race triggers Wasm deserialize errorReady
#115Organisation not shown in Request or Offer listingsBacklog
#120Organization Page — needs Edit buttonBacklog
#127Profile creation fails with CellDisabled conductor errorBacklog
#147Alpha Test Step 2.5 — FailBacklog

P2 — High

IssueTitleStatus
#96Profile — variable ability to see others' Request/Offer detailsReady
#136Archive functionality inconsistent across listing viewsReady
#138Connection status shows 'Connected' in airplane modeReady
#157Timezone dropdown in profile form does not commit selectionReady
#86Request — Time Estimate field not includedReady

In Progress

IssueTitlePriority
#1hREA Entity Mapping: Proposals, Intents & Resource SpecificationsP1
#56Status Table bug fixP1
#64Epic: hREA IntegrationP1

Ready for Development

Items prioritized and ready to implement:

IssueTitleSizePriority
#95Network Creation & Joining UX (Kangaroo splash + QR invite)MP2
#143In-app version drift detection and non-breaking auto-updateLP2
#27Role Management Utility Functions for Anchor-Based SystemP3
#155Markdown toolbar scrolls to top after wrap actionXSP3

Tests and Documentation Needed

Implemented but missing tests or documentation:

IssueTitle
#139Profile — First Name and Last Name required fields
#55Navigation: Alt+A keybinding for contextual admin/public page navigation

Remaining MVP Features

Large features deferred to upcoming milestones.

IssueTitleSizePriority
#1hREA Entity Mapping completionXLP1
#12Real-time Signals integrationP2
#91Chat System: Conversation-First MessagingXLP1
#51Global Notification SystemP2
#90hREA Exchange Process: Agreements & Economic EventsXLP1
#52Admin Inbox & Task ManagementP2
#53Admin Audit TrailP2
Review & Reputation System

Post-MVP (Deferred)

  • Internationalization: Multi-language support
  • Mobile App: Native mobile wrapper
  • Advanced recommendation/matching algorithms
  • Geographic Features (#54): Offline experience and local connections
  • Holo Hosting Deployment (#14)
  • Skeleton UI v3 / Tailwind CSS v4 upgrade (#35)
  • Breaking-version migration system (#144)

Quick Reference Guide

Essential commands, patterns, and workflows for efficient development in the Requests and Offers project.

🚀 Getting Started

Initial Setup

# Clone with submodules and enter project
git clone --recurse-submodules https://github.com/happening-community/requests-and-offers.git
cd requests-and-offers

# Setup environment
nix develop                    # Enter Nix shell (required for zomes)
bun install                    # Install dependencies
git submodule update --init --recursive  # Initialize submodules
bun start                      # Start development (2 agents)

Development Commands

# Development servers
bun start                      # 2 agents (default)
AGENTS=3 bun start            # Custom number of agents
bun start:test                # Test mode (dev features, no mock buttons)
bun start:prod                # Production mode (all dev features disabled)

# Building
bun build:zomes               # Build Rust zomes
bun build:happ                # Build complete hApp
bun package                   # Package for distribution

# Testing
bun test                      # All tests
bun test:ui                   # Frontend tests only
bun test:unit                 # Unit tests (requires Nix)
nix develop --command bun test:unit  # Autonomous unit test execution
bun test:integration          # Integration tests
nix develop --command cargo test --manifest-path tests/sweettest/Cargo.toml  # Backend Sweettest tests

# Code quality
cd ui && bun run lint         # Lint frontend code
cd ui && bun run format       # Format frontend code
cd ui && bun run check        # TypeScript check

# Submodule management
git submodule update --remote kangaroo-electron    # Update desktop app
git submodule update --remote homebrew               # Update homebrew formula
cd deployment/kangaroo-electron && npm run tauri dev   # Desktop app development
cd deployment/kangaroo-electron && npm run tauri build  # Build desktop app

# Deployment automation
./deployment/scripts/deploy.sh deploy 0.1.X          # Full deployment
./deployment/scripts/deploy.sh status               # Check deployment status

🏗️ Architecture Quick Reference

7-Layer Effect-TS Architecture

1. Service Layer      → Effect-native services with Context.Tag DI
2. Store Layer        → Svelte 5 Runes + Effect integration
3. Schema Validation  → Effect Schema at business boundaries
4. Error Handling     → Domain-specific tagged errors
5. Composables        → Component logic abstraction
6. Components         → Svelte 5 + accessibility focus
7. Testing            → Comprehensive Effect-TS coverage

Project Structure

requests-and-offers/
├── dnas/requests_and_offers/     # Holochain DNA
│   └── zomes/                    # Coordinator & integrity zomes
├── ui/                           # SvelteKit frontend
│   ├── src/lib/
│   │   ├── components/           # Feature-organized components
│   │   ├── services/             # Effect-TS services
│   │   ├── stores/               # Svelte stores with Effect
│   │   ├── composables/          # Business logic abstraction
│   │   ├── schemas/              # Effect Schema validation
│   │   └── errors/               # Tagged error definitions
│   └── src/routes/               # SvelteKit pages
├── deployment/                   # Deployment repositories as submodules
│   ├── kangaroo-electron/        # Desktop app (Tauri) submodule
│   ├── homebrew/                 # Homebrew formula submodule
│   └── scripts/                  # Deployment automation scripts
├── tests/sweettest/              # Sweettest integration tests (Rust)
└── documentation/                # Comprehensive docs

💻 Development Patterns

Effect-TS Service Pattern

// Service definition with dependency injection
export const MyService = Context.GenericTag<MyService>("MyService");

export const makeMyService = Effect.gen(function* () {
  const client = yield* HolochainClientService;

  const createEntity = (input: CreateInput) =>
    Effect.gen(function* () {
      const validated = yield* Schema.decodeUnknown(InputSchema)(input);
      const result = yield* client.callZome({
        zome_name: "my_zome",
        fn_name: "create_entity",
        payload: validated,
      });
      return yield* Schema.decodeUnknown(EntitySchema)(result);
    }).pipe(Effect.mapError((error) => new MyDomainError({ cause: error })));

  return { createEntity };
});

Svelte Store Pattern

// Store factory with Svelte 5 Runes + Effect
export const createEntitiesStore = () => {
  let entities = $state<UIEntity[]>([]);
  let loading = $state(false);
  let error = $state<string | null>(null);

  const service = yield * MyService;

  const fetchAll = Effect.gen(function* () {
    loading = true;
    error = null;
    try {
      const records = yield* service.getAllEntities();
      entities = mapRecordsToUIEntities(records);
    } catch (err) {
      error = err.message;
    } finally {
      loading = false;
    }
  });

  return {
    entities: () => entities,
    loading: () => loading,
    fetchAll,
  };
};

Component Pattern

<script lang="ts">
  // Props with defaults
  const {
    data = [],
    loading = false,
    onAction = () => {}
  }: ComponentProps = $props();

  // Reactive state
  let localState = $state({ filter: '' });

  // Derived values
  const filteredData = $derived(
    data.filter(item => item.name.includes(localState.filter))
  );

  // Composable for business logic
  const entityManager = useEntityManager();
</script>

<div role="main" aria-label="Entity list">
  <!-- Accessible markup -->
</div>

🧪 Testing Patterns

Backend Testing (Sweettest)

#![allow(unused)]
fn main() {
#[tokio::test(flavor = "multi_thread")]
async fn test_entity_creation() {
    let (conductors, alice, bob) = setup_two_agents_with_alice_as_progenitor().await;

    conductors[0]
        .call::<_, Record>(&alice.zome("users_organizations"), "create_user", sample_user("Alice"))
        .await;

    await_consistency(15, [&alice, &bob]).await.unwrap();

    let record: Record = conductors[0]
        .call(&alice.zome("coordinator"), "create_entity", sample_entity("Test Entity"))
        .await;

    assert!(!record.signed_action.hashed.hash.get_raw_39().is_empty());
}
}

Frontend Testing (Vitest)

describe("EntityService", () => {
  it("should create entity successfully", async () => {
    const program = Effect.gen(function* () {
      const service = yield* EntityService;
      return yield* service.createEntity(testInput);
    });

    const result = await Effect.runPromise(
      program.pipe(Effect.provide(TestServiceLayer)),
    );

    expect(result.name).toBe("Test Entity");
  });
});

🔧 Common Workflows

Adding a New Domain

  1. Backend: Create coordinator & integrity zomes
  2. Service: Implement Effect-TS service with Context.Tag
  3. Store: Create store with 9 standardized helper functions
  4. Composable: Extract business logic
  5. Components: Build UI with accessibility focus
  6. Errors: Define domain-specific tagged errors
  7. Tests: Add backend (Sweettest) + frontend (Vitest) tests

Domain Implementation Checklist

  • Zome implemented (coordinator/integrity pattern)
  • Service layer with Effect-TS and dependency injection
  • Store layer with all 9 helper functions
  • Composable layer for business logic abstraction
  • Component layer using composables
  • Error handling with domain-specific errors
  • Tests covering all layers
  • Documentation updated

The 9 Standardized Store Helper Functions

  1. Entity Creation Helper - Converts records to UI entities
  2. Record Mapping Helper - Maps arrays with error recovery
  3. Cache Sync Helper - Synchronizes cache with state arrays
  4. Event Emission Helpers - Standardized event broadcasting
  5. Data Fetching Helper - Higher-order fetching with loading states
  6. Loading State Helper - Wraps operations with loading patterns
  7. Record Creation Helper - Processes new records and updates cache
  8. Status Transition Helper - Manages status changes atomically
  9. Collection Processor - Handles complex multi-collection responses

🖥️ Desktop Application Development

Kangaroo Desktop App Workflow

# Desktop app development (in submodule)
cd deployment/kangaroo-electron
npm run tauri dev                    # Start desktop app in dev mode

# Build desktop applications
npm run tauri build                  # Build for all platforms
npm run tauri build --target x64   # Build specific platform

# Update kangaroo submodule
git submodule update --remote kangaroo-electron
cd deployment/kangaroo-electron
npm install  # Install any new dependencies

Deployment Automation

# Full automated deployment (webapp + desktop + homebrew)
./deployment/scripts/deploy.sh deploy 0.1.0

# Dry run to preview actions
./deployment/scripts/deploy.sh deploy 0.1.0 --dry-run

# Check deployment status
./deployment/scripts/deploy.sh status

# Validate completed deployment
./deployment/scripts/deploy.sh validate 0.1.0

Platform-Specific Builds

The kangaroo desktop app supports:

  • Windows: .exe installer with code signing
  • macOS: DMG packages (Intel + Apple Silicon)
  • Linux: AppImage and .deb packages

🔍 Troubleshooting

Common Issues

Unit tests failing with hREA errors:

nix develop --command bun test:unit  # Use autonomous execution

Port conflicts:

lsof -ti:8888 | xargs kill -9     # Kill process on port 8888
lsof -ti:4444 | xargs kill -9     # Kill process on port 4444

Nix environment issues:

nix develop --command which holochain  # Verify Nix tools
direnv allow                            # If using direnv

Zome build failures:

nix develop                    # Ensure in Nix shell
bun build:zomes               # Rebuild zomes

Development Features System

The project includes a comprehensive system for managing development-only features through atomic environment variable control:

Available Commands:

# Development Mode - Full Holochain app with atomic feature control
bun start              # Starts complete app (from project root)
AGENTS=3 bun start     # Custom number of agents

# UI Build Mode
cd ui && bun run build         # Production build

Atomic Feature Control:

// Service-based feature checking
import { DevFeaturesServiceTag } from '$lib/services/devFeatures.service';

const devFeatures = yield* DevFeaturesServiceTag;
if (devFeatures.mockButtonsEnabled) {
  // Show mock data button
}

// Convenience functions for components
import { shouldShowMockButtons } from '$lib/services/devFeatures.service';
{#if shouldShowMockButtons()}
  <button onclick={createMockData}>Create Mock Data</button>
{/if}

Environment Variables:

VITE_MOCK_BUTTONS_ENABLED=true|false          # Form mock buttons
VITE_PEERS_DISPLAY_ENABLED=true|false         # Network peers display

Benefits:

  • Atomic Control: Each feature independently enabled/disabled
  • Tree-Shaking: Development code completely removed from production builds
  • Zero Overhead: Production builds contain no development features
  • Runtime Configuration: Features controlled via .env file at runtime
  • Developer Experience: Mock data buttons accelerate development workflow

See Development Features System for complete documentation.

📚 Key Documentation

Essential Reading

API References

Development Guidelines

🤝 Community


💡 Pro Tip: Use Service Types domain as the implementation template - it's 100% complete and follows all established patterns.

⚠️ Important: Unit tests require Nix environment due to hREA integration. Always use nix develop --command bun test:unit for autonomous execution.

Comprehensive Codebase Documentation

Holochain Requests and Offers Project


📋 Executive Summary

The Holochain Requests and Offers project is a decentralized peer-to-peer platform built on Holochain technology, implementing a sophisticated 7-layer Effect-TS architecture with 100% standardized domain patterns. The application enables communities to manage service requests, offers, and economic exchanges using the hREA (Holochain Resource-Event-Agent) framework.

Key Architectural Achievements

  • Service Types Domain: 100% standardized with 9 helper functions
  • Requests Domain: 100% standardized Effect-TS implementation
  • Offers Domain: 100% standardized with full helper function suite
  • 🔄 Users/Organizations: In progress standardization
  • 📊 343 Unit Tests: All passing across 20 test files with Effect-TS integration

🏗️ System Architecture Overview

Technology Stack

graph TB
    subgraph "Frontend Layer"
        A[SvelteKit 5 + Runes]
        B[TailwindCSS + SkeletonUI]
        C[Effect-TS Integration]
    end

    subgraph "Service Layer"
        D[7-Layer Effect Architecture]
        E[Context Dependency Injection]
        F[Schema Validation]
    end

    subgraph "Backend Layer"
        G[Holochain DNA]
        H[Rust Zomes]
        I[hREA Integration]
    end

    A --> D
    D --> G
    G --> I

Core Technologies

  • Backend: Holochain v0.3+ with Rust zomes (coordinator/integrity pattern)
  • Frontend: SvelteKit + Svelte 5 Runes + Effect-TS
  • UI Framework: TailwindCSS + SkeletonUI
  • State Management: Effect-TS + Svelte 5 Runes
  • Runtime: Bun for TypeScript/JavaScript execution
  • Economic Framework: hREA (Holochain Resource-Event-Agent)
  • Desktop Apps: Tauri-based Kangaroo applications (Windows, macOS, Linux)
  • Repository Management: Git submodules for unified development
  • Development Environment: Nix shell (DNA/zome development only)

🎯 7-Layer Effect-TS Architecture

The codebase implements a revolutionary 7-layer architecture pattern using Effect-TS, providing unprecedented type safety, error handling, and maintainability.

Layer 1: Service Layer

// Effect-native services with Context.Tag dependency injection
export const ServiceTypeService =
  Context.GenericTag<ServiceTypeService>("ServiceTypeService");

export const makeServiceTypeService = Effect.gen(function* () {
  const client = yield* HolochainClientService;

  const createServiceType = (input: CreateServiceTypeInput) =>
    Effect.gen(function* () {
      // Business logic with comprehensive error handling
    });

  return { createServiceType };
});

Key Features:

  • Context.Tag dependency injection
  • Comprehensive error boundaries
  • Automatic resource management
  • Type-safe async operations

Layer 2: Store Layer

// Factory functions with Svelte 5 Runes + 9 standardized helper functions
export const createServiceTypesStore = () => {
  let entities = $state<UIServiceType[]>([]);

  const fetchEntities = Effect.gen(function* () {
    const records = yield* serviceTypeService.getAllServiceTypes();
    entities = mapRecordsToUIEntities(records); // Helper function #2
  });

  return { entities: () => entities, fetchEntities };
};

The 9 Standardized Helper Functions:

  1. createUIEntity: Converts Holochain records to UI entities with error recovery
  2. mapRecordsToUIEntities: Maps record arrays to UI entities with null safety
  3. createCacheSyncHelper: Synchronizes cache with state arrays for CRUD operations
  4. createEventEmitters: Standardized event broadcasting for domain operations
  5. createEntityFetcher: Higher-order fetching with loading/error state management
  6. withLoadingState: Wraps operations with consistent loading/error patterns
  7. createRecordCreationHelper: Processes new records and updates cache atomically
  8. createStatusTransitionHelper: Manages status changes with atomic updates
  9. processMultipleRecordCollections: Handles complex responses with multiple collections

Layer 3: Schema Validation

// Effect Schema with strategic validation boundaries
export class ServiceTypeInDHT extends Schema.Class<ServiceTypeInDHT>(
  "ServiceTypeInDHT",
)({
  name: Schema.String.pipe(
    Schema.minLength(2),
    Schema.maxLength(100),
    Schema.annotations({
      title: "Service Type Name",
      description: "The name of the service type",
    }),
  ),
  description: Schema.String.pipe(Schema.minLength(10), Schema.maxLength(500)),
  tags: Schema.Array(
    Schema.String.pipe(Schema.minLength(1), Schema.maxLength(50)),
  ),
}) {}

Layer 4: Error Handling

// Domain-specific tagged errors with centralized management
export class ServiceTypeError extends Data.TaggedError("ServiceTypeError")<{
  readonly context: string;
  readonly message: string;
  readonly cause?: unknown;
}> {}

// Centralized error contexts
export const SERVICE_TYPE_CONTEXTS = {
  CREATE_SERVICE_TYPE: "Failed to create service type",
  GET_SERVICE_TYPE: "Failed to get service type",
  // ... comprehensive error scenarios
} as const;

Layer 5: Composables

// Component logic abstraction using Effect-based functions
export const useServiceTypeFormManagement = () => {
  const store = createServiceTypesStore();

  const handleSubmit = Effect.gen(function* () {
    // Abstract business logic from components
  });

  return { handleSubmit, isLoading, errors };
};

Layer 6: Components

<!-- Svelte 5 + accessibility focus, using composables for business logic -->
<script lang="ts">
  import { useServiceTypeFormManagement } from '$lib/composables';

  const { handleSubmit, isLoading } = useServiceTypeFormManagement();
</script>

Layer 7: Testing

// Comprehensive Effect-TS coverage across all layers
describe("ServiceTypesStore", () => {
  const mockService = createMockService();
  const layer = Layer.succeed(ServiceTypesServiceTag, mockService);

  it("should handle operations with Effect", async () => {
    const result = await runEffect(operation, layer);
    expect(result).toBeDefined();
  });
});

🔧 Backend Implementation: Holochain Zomes

DNA Structure

dnas/requests_and_offers/
├── zomes/
│   ├── coordinator/          # Business logic zomes
│   │   ├── administration/   # Admin roles and system management
│   │   ├── service_types/    # Service type management
│   │   ├── requests/         # Request management
│   │   ├── offers/           # Offer management
│   │   ├── users_organizations/ # User and organization management
│   │   └── mediums_of_exchange/ # Payment methods
│   └── integrity/            # Data validation zomes
│       ├── administration/   # Status validation
│       ├── service_types/    # Service type validation
│       ├── requests/         # Request validation
│       ├── offers/           # Offer validation
│       ├── users_organizations/ # User/org validation
│       └── mediums_of_exchange/ # Payment validation

Coordinator/Integrity Pattern

The backend follows Holochain's coordinator/integrity pattern:

Integrity Zomes (/integrity/):

#![allow(unused)]
fn main() {
// Data validation and entry definitions
#[hdk_entry_helper]
#[derive(Clone, PartialEq)]
pub struct ServiceType {
    pub name: String,
    pub description: String,
    pub tags: Vec<String>,
}

pub fn validate_create_service_type(
    _action: &SignedActionHashed,
    service_type: &ServiceType,
) -> ExternResult<ValidateCallbackResult> {
    if service_type.name.is_empty() {
        return Ok(ValidateCallbackResult::Invalid(
            "ServiceType name cannot be empty".to_string(),
        ));
    }
    Ok(ValidateCallbackResult::Valid)
}
}

Coordinator Zomes (/coordinator/):

#![allow(unused)]
fn main() {
// Business logic and external API
#[hdk_extern]
pub fn create_service_type(input: ServiceTypeInput) -> ExternResult<Record> {
    let is_admin = check_if_agent_is_administrator(agent_info()?.agent_initial_pubkey)?;
    if !is_admin {
        return Err(AdministrationError::Unauthorized.into());
    }

    let service_type_hash = create_entry(EntryTypes::ServiceType(input.service_type.clone()))?;
    // ... additional business logic
}
}

Key Zome Capabilities

Service Types Zome

  • Admin Creation: Only administrators can create service types
  • User Suggestions: Users can suggest new service types for approval
  • Tag-Based Discovery: Advanced tag-based search and categorization
  • Status Management: Pending → Approved/Rejected workflow
  • Tag Statistics: Usage analytics and trending tags

Requests Zome

  • CRUD Operations: Create, read, update, delete requests
  • Status Transitions: Active → Fulfilled/Cancelled workflows
  • Service Type Linking: Link requests to service types for discovery
  • User Association: Track request creators and assignees

Offers Zome

  • Offer Management: Create and manage service offers
  • Request Matching: Link offers to specific requests
  • Status Tracking: Active → Accepted/Completed workflows
  • Multi-Entity Support: Support for individual and organization offers

Administration Zome

  • Role Management: Administrator assignment and validation
  • Status Workflows: Centralized status management across domains
  • Access Control: Permission validation for sensitive operations
  • System Monitoring: Track system-wide administrative actions

💻 Frontend Implementation: SvelteKit + Effect-TS

Project Structure

ui/src/
├── lib/
│   ├── components/          # UI components (organized by feature)
│   │   ├── service-types/   # Service type management UI
│   │   ├── requests/        # Request *management* UI
│   │   ├── offers/          # Offer management UI
│   │   ├── users/           # User management UI
│   │   ├── organizations/   # Organization management UI
│   │   └── shared/          # Reusable components (MarkdownRenderer, MarkdownToolbar, stripMarkdown)
│   ├── services/            # Service layer (Holochain, hREA)
│   │   ├── zomes/           # Zome-specific services
│   │   ├── holochainClient.service.ts
│   │   └── hrea.service.ts
│   ├── stores/              # Svelte stores (state management)
│   │   ├── serviceTypes.store.svelte.ts
│   │   ├── requests.store.svelte.ts
│   │   ├── offers.store.svelte.ts
│   │   └── users.store.svelte.ts
│   ├── composables/         # Component logic abstraction
│   │   ├── domain/          # Domain-specific composables
│   │   ├── search/          # Search functionality
│   │   └── ui/              # UI interaction composables
│   ├── schemas/             # Effect Schema validation
│   │   ├── service-types.schemas.ts
│   │   ├── requests.schemas.ts
│   │   └── common.schemas.ts
│   ├── errors/              # Centralized error handling
│   │   ├── service-types.errors.ts
│   │   ├── requests.errors.ts
│   │   └── error-contexts.ts
│   └── utils/               # Utility functions
│       ├── cache.svelte.ts
│       ├── effect.ts
│       └── validation.ts
└── routes/                  # SvelteKit routes/pages
    ├── (public)/            # Public application routes
    │   ├── service-types/   # Service type listing, suggestions, details
    │   │   ├── suggest/     # User service type suggestions
    │   │   └── [id]/        # Service type details
    │   ├── requests/        # Request management
    │   │   ├── create/      # Create new request
    │   │   ├── [id]/        # Request details
    │   │   └── [id]/edit/   # Edit request
    │   ├── offers/          # Offer management
    │   │   ├── create/      # Create new offer
    │   │   ├── [id]/        # Offer details
    │   │   └── [id]/edit/   # Edit offer
    │   ├── organizations/   # Organization management
    │   │   ├── create/      # Create organization
    │   │   ├── [id]/        # Organization details
    │   │   └── [id]/edit/   # Edit organization
    │   ├── users/           # User directory
    │   │   └── [id]/        # User profile details
    │   ├── user/            # Current user management
    │   │   ├── create/      # User registration
    │   │   └── edit/        # Edit user profile
    │   ├── projects/        # Project listings
    │   ├── mediums-of-exchange/ # Payment methods listing
    │   ├── tags/            # Tag-based discovery
    │   │   └── [tag]/       # Tag-filtered content
    │   └── test-status-history/ # Status history testing
    └── admin/               # Administrative interface
        ├── service-types/   # Admin service type management
        │   ├── create/      # Create service type
        │   ├── moderate/    # Moderate suggestions
        │   ├── [id]/        # Service type admin details
        │   └── [id]/edit/   # Edit service type
        ├── requests/        # Admin request oversight
        ├── offers/          # Admin offer oversight
        ├── organizations/   # Admin organization management
        │   └── status-history/ # Organization status history
        ├── users/           # Admin user management
        │   └── status-history/ # User status history
        ├── administrators/  # Administrator management
        ├── projects/        # Admin project management
        ├── mediums-of-exchange/ # Admin payment method management
        │   ├── create/      # Create payment method
        │   └── [id]/edit/   # Edit payment method
        └── hrea-test/       # hREA integration testing

Route Architecture & Navigation

The application implements a dual-interface routing system with clear separation between public and administrative functionality:

Public Routes ((public)/)

Purpose: User-facing features accessible to all community members

Key Route Groups:

  • Service Types (/service-types/): Browse service categories, view details, submit suggestions
  • Requests (/requests/): View and manage service requests with full CRUD operations
  • Offers (/offers/): Browse available offers and create new ones
  • Organizations (/organizations/): Organization directory and management
  • Users (/users/, /user/): User directory and personal profile management
  • Discovery (/tags/[tag]/): Tag-based content discovery and filtering
  • Projects (/projects/): Project listings and collaboration
  • Mediums of Exchange (/mediums-of-exchange/): Payment method options

Route Patterns:

// Standard CRUD pattern for entities
/entity/              # List view
/entity/create/       # Creation form
/entity/[id]/         # Detail view
/entity/[id]/edit/    # Edit form

// Discovery patterns
/tags/[tag]/          # Tag-filtered content
/users/[id]/          # User profile

Admin Routes (/admin/)

Purpose: Administrative oversight with enhanced permissions and system management

Key Features:

  • Content Moderation: Service type suggestions, user content oversight
  • System Administration: User management, administrator assignment
  • Status Monitoring: Comprehensive status history tracking across domains
  • Testing Tools: hREA integration testing and system diagnostics

Admin-Specific Routes:

  • Moderation (/admin/service-types/moderate/): Review and approve user suggestions
  • Status History (/admin/{domain}/status-history/): Track entity status changes
  • Administrator Management (/admin/administrators/): Role assignment and permissions
  • Testing Interface (/admin/hrea-test/): hREA integration validation

State Management Pattern

The frontend uses Svelte 5 Runes combined with Effect-TS for reactive state management:

// Store factory with Effect integration
export const createServiceTypesStore = () => {
  // Svelte 5 reactive state
  let entities = $state<UIServiceType[]>([]);
  let isLoading = $state(false);
  let error = $state<string | null>(null);

  // Effect-based operations
  const fetchEntities = Effect.gen(function* () {
    const serviceTypesService = yield* ServiceTypesServiceTag;
    const records = yield* serviceTypesService.getAllServiceTypes();
    entities = mapRecordsToUIEntities(records);
  });

  return {
    // Reactive getters
    entities: () => entities,
    isLoading: () => isLoading,
    error: () => error,

    // Effect operations
    fetchEntities,
    createEntity,
    updateEntity,
    deleteEntity,
  };
};

Component Architecture

Components follow clean separation of concerns:

<!-- ServiceTypeForm.svelte -->
<script lang="ts">
  import { useServiceTypeFormManagement } from '$lib/composables';
  import { ServiceTypeFormSchema } from '$lib/schemas';

  // Business logic in composable
  const { handleSubmit, isLoading, errors, form } = useServiceTypeFormManagement();

  // Component focuses on presentation
</script>

<form on:submit={handleSubmit} class="space-y-4">
  <input
    bind:value={form.name}
    class:error={errors.name}
    placeholder="Service Type Name"
  />

  <button
    type="submit"
    disabled={isLoading}
    class="btn variant-filled-primary"
  >
    {isLoading ? 'Creating...' : 'Create Service Type'}
  </button>
</form>

🖥️ Desktop Applications (Kangaroo)

Submodule Structure

The project includes desktop applications as git submodules for unified development:

requests-and-offers/
├── deployment/                   # Deployment repositories as git submodules
│   ├── kangaroo-electron/        # Desktop app repository (submodule)
│   │   ├── src/                 # Tauri application source code
│   │   ├── pouch/               # WebHapp packaging directory
│   │   ├── kangaroo.config.ts   # Desktop app configuration
│   │   └── dist/                # Built applications
│   ├── homebrew/                 # Homebrew formula repository (submodule)
│   └── scripts/                  # Deployment automation scripts
│       ├── deploy.sh            # Main deployment orchestrator
│       ├── config/              # Configuration files
│       └── lib/                 # Deployment utilities

Desktop App Architecture

The Kangaroo desktop applications are built using Tauri with the following architecture:

graph TB
    subgraph "Desktop Application Layer"
        A[Tauri Frontend]
        B[Rust Backend]
        C[WebHapp Integration]
    end

    subgraph "WebHapp Layer"
        D[SvelteKit Application]
        E[Holochain Client]
        F[hREA Framework]
    end

    subgraph "Platform Build System"
        G[Windows Build]
        H[macOS Build]
        I[Linux Build]
    end

    A --> D
    B --> E
    D --> E
    E --> F

    A --> G
    A --> H
    A --> I

Key Desktop Features

Cross-Platform Support

  • Windows: Installer (.exe) with code signing
  • macOS: DMG packages (Intel and Apple Silicon)
  • Linux: AppImage and .deb packages

WebHapp Integration

  • Automatic Updates: Seamless webhapp version synchronization
  • Local Packaging: WebHapp embedded in desktop application
  • Network Configuration: Production network settings for Holochain connectivity

Development Workflow

# Clone with submodules
git clone --recurse-submodules https://github.com/happenings-community/requests-and-offers.git

# Update submodules to latest
git submodule update --remote

# Desktop app development
cd deployment/kangaroo-electron
npm run tauri dev

# Build for production
npm run tauri build

Deployment Automation

The project includes comprehensive deployment automation:

# Full deployment (webapp + desktop + homebrew)
./deployment/scripts/deploy.sh deploy 0.1.X

# Desktop-specific deployment
./deployment/scripts/lib/kangaroo-deployer.sh deploy 0.1.X

# Homebrew formula updates
./deployment/scripts/lib/homebrew-updater.sh update 0.1.X

Automated Features:

  • Environment Validation: Checks submodules, tools, and permissions
  • WebApp Building: Automated hApp compilation and packaging
  • Desktop Builds: Parallel builds across all platforms
  • Asset Validation: Comprehensive asset verification
  • Homebrew Updates: Automatic formula updates with checksums
  • Rollback Capabilities: One-command rollback if issues occur

🔗 hREA Integration

The project integrates with hREA (Holochain Resource-Event-Agent) framework for economic coordination:

Domain Mappings

// Requests → hREA Intents
export const mapRequestToIntent = (request: UIRequest): Intent => ({
  action: IntentAction.WORK,
  resourceClassifiedAs: request.serviceTypeHashes,
  name: request.title,
  note: request.description,
});

// Offers → hREA Proposals
export const mapOfferToProposal = (offer: UIOffer): Proposal => ({
  name: offer.title,
  note: offer.description,
  unitBased: true,
});

// Service Types → ResourceSpecifications
export const mapServiceTypeToResourceSpec = (
  serviceType: UIServiceType,
): ResourceSpecification => ({
  name: serviceType.name,
  note: serviceType.description,
  resourceClassifiedAs: serviceType.tags,
});

Economic Workflows

sequenceDiagram
    participant User as User
    participant App as App
    participant hREA as hREA DNA

    User->>App: Create Request
    App->>hREA: Create Intent
    hREA-->>App: Intent Created

    User->>App: Create Offer
    App->>hREA: Create Proposal
    hREA-->>App: Proposal Created

    User->>App: Accept Offer
    App->>hREA: Create Agreement
    hREA-->>App: Agreement Created

🧪 Testing Strategy

Test Coverage Architecture

The project maintains comprehensive testing across all layers:

tests/
├── unit/                    # Unit tests (343 tests passing)
│   ├── services/            # Service layer tests
│   ├── stores/              # Store layer tests
│   ├── components/          # Component tests
│   └── fixtures/            # Test data fixtures
├── integration/             # Integration tests
│   ├── offers-requests-interaction.test.ts
│   ├── tag-discovery.test.ts
│   └── serviceTypes.test.ts
├── e2e/                     # End-to-end tests
│   ├── specs/
│   │   ├── user-journeys/   # Complete user workflows
│   │   ├── admin/           # Administrative workflows
│   │   └── advanced/        # Complex scenarios
│   └── fixtures/            # E2E test data
└── mocks/                   # Shared test mocks

Effect-TS Testing Pattern

describe("ServiceTypesService", () => {
  const mockClient = createMockHolochainClient();
  const testLayer = Layer.succeed(HolochainClientServiceTag, mockClient);

  it("should create service type successfully", async () => {
    const serviceType = createTestServiceType();
    const effect = serviceTypesService.createServiceType(serviceType);

    const result = await runEffect(effect, testLayer);
    expect(result).toBeDefined();
  });
});

Test Categories

Backend Tests (tests/sweettest/)

  • Sweettest Integration: Multi-agent Holochain scenarios
  • Zome Functionality: Individual zome operation testing
  • Cross-Zome Integration: Inter-zome communication testing
  • Status Workflows: State transition validation

Frontend Unit Tests (ui/tests/unit/)

  • Service Layer: Effect-TS service testing with mocks
  • Store Layer: Reactive state management testing
  • Component Testing: UI component behavior validation
  • Schema Validation: Effect Schema validation testing

Frontend Integration Tests (ui/tests/integration/)

  • Store-Service Integration: End-to-end data flow testing
  • Cross-Domain Workflows: Multi-domain operation testing
  • Cache Management: Cache behavior and invalidation testing

E2E Tests (ui/tests/e2e/)

  • User Journeys: Complete user workflow validation
  • Admin Workflows: Administrative interface testing
  • Cross-Browser: Multi-browser compatibility testing
  • Performance: Load time and interaction performance

🚀 Development Workflow

Environment Setup

# Clone with submodules
git clone --recurse-submodules https://github.com/happenings-community/requests-and-offers.git

# Enter Nix development environment (required for zome development)
nix develop

# Install dependencies
bun install

# Initialize/update submodules if needed
git submodule update --init --recursive

# Download hREA DNA
bun run download-hrea

Development Commands

# Start application with 2 agents
bun start

# Start with custom agent count
AGENTS=3 bun start

# Build and test cycle
bun run build:zomes
bun run build:happ
bun test

# Frontend-only development
cd ui && bun run dev
cd ui && bun run test:unit

# Desktop app development
cd deployment/kangaroo-electron && npm run tauri dev

# Submodule management
git submodule update --remote kangaroo-electron
git submodule update --remote homebrew

Code Quality Pipeline

# TypeScript checking
cd ui && bun run check

# Linting and formatting
cd ui && bun run lint
cd ui && bun run format

# Comprehensive testing
bun test                    # Full test suite
bun test:ui                 # Frontend tests only
bun test:unit              # Unit tests (requires Nix)
bun test:integration       # Integration tests

📊 Current Implementation Status

✅ Completed Domains (100% Standardized)

Service Types Domain 🏆

  • Service Layer: Complete Effect-TS implementation with dependency injection
  • Store Layer: All 9 standardized helper functions implemented
  • Component Layer: Tag-based discovery, suggestion workflows, admin moderation
  • Testing: 100% unit test coverage with Effect-TS patterns
  • Features: Creation, suggestion, approval/rejection, tag-based search, statistics
  • 🎯 Template Status: Serves as architectural template for all new implementations

Requests Domain

  • Service Layer: Full CRUD operations with status management
  • Store Layer: Standardized helper functions with cache synchronization
  • Component Layer: Request forms, tables, detail modals, status transitions
  • Testing: Comprehensive test coverage across all layers
  • Features: Create, edit, fulfill, cancel requests with service type linking

Offers Domain

  • Service Layer: Complete offer management with request matching
  • Store Layer: All standardized helper functions with event emission
  • Component Layer: Offer creation, matching, acceptance workflows
  • Testing: Full test suite with Effect-TS integration
  • Features: Offer creation, request matching, status tracking, completion workflows

📈 Completed Standardization (All Major Domains)

Users Domain

  • Service Layer: Complete Effect-TS conversion with Context.Tag patterns
  • Store Layer: All 9 standardized helper functions implemented
  • Component Layer: User profiles, administration interface complete
  • Testing: Full Effect-TS test coverage

Organizations Domain

  • Service Layer: Complete Effect-TS implementation with dependency injection
  • Store Layer: All standardized helper functions with event emission
  • Component Layer: Organization management UI complete (including contact person designation)
  • Testing: Comprehensive test coverage

Administration Domain

  • Service Layer: Status management and role validation complete
  • Store Layer: Complete standardization with all helper functions
  • Component Layer: Admin interfaces and status workflows complete
  • Testing: Full test coverage across all layers

📈 Metrics & Achievements

  • 343 Unit Tests: All passing across 20 test files with Effect-TS integration
  • 90%+ Code Coverage: Across service and store layers
  • 8 Domains: Fully standardized with proven patterns (100% completion)
  • 9 Helper Functions: Massive code reduction through standardization
  • Zero Breaking Changes: During domain standardization process
  • 100% Type Safety: Complete Effect-TS integration
  • Documentation Overhaul: Consolidated from 25 AI rules to 6 focused guidelines
  • Developer Experience: Quick Reference and Troubleshooting guides added

🔮 Future Roadmap

✅ Phase 1: Complete Standardization (COMPLETED)

  • All Domains: Complete Effect-TS conversion and helper function implementation
  • Architecture Maturity: 7-layer architecture fully implemented across all domains
  • Documentation Enhancement: Consolidated and improved developer experience

Phase 2: Advanced Features (Current Focus)

  • 🎯 Exchange Completion: hREA agreement and commitment workflows
  • 🎯 Advanced Analytics: Usage statistics, trend analysis, reporting dashboards
  • 🎯 Notification System: Real-time updates and user notifications
  • 🎯 Advanced Search: Full-text search, filtering, recommendation engine
  • 🎯 Visual Documentation: Architecture diagrams and system workflow visuals

Phase 3: Platform Enhancement (Upcoming)

  • 🎯 Mobile Application: React Native or Flutter mobile app
  • 🎯 Federation: Multi-community and cross-network coordination
  • 🎯 AI Integration: Intelligent matching, recommendation systems
  • 🎯 Performance Optimization: Advanced caching, lazy loading, bundle optimization

📚 Recent Documentation Revolution

The project underwent a major documentation overhaul to improve developer experience and reduce cognitive overhead:

Documentation Consolidation

  • Before: 25 scattered AI development rule files creating cognitive overload
  • After: 6 focused, coherent guideline files covering all development aspects
  • Reduction: 76% decrease in documentation fragmentation

New Developer Resources

  • Quick Reference Guide: Essential commands, patterns, and workflows for immediate productivity
  • Troubleshooting Guide: Comprehensive solutions for common development issues
  • Consolidated AI Rules: Streamlined development guidelines covering:
    • Development Guidelines (Effect-TS, Svelte 5, schemas, components)
    • Architecture Patterns (7-layer architecture, services, stores, event-driven communication)
    • Testing Framework (Backend Sweettest + Frontend Vitest strategies)
    • Domain Implementation (Administration, error management, guards, utilities)
    • Development Workflow (Continuation, cleanup, planning, changelog maintenance)
    • Environment Setup (Nix configuration, development environment, documentation standards)

Enhanced Navigation

  • Streamlined README: Focused quick start with essential links
  • Improved Documentation Index: Better categorization with visual indicators
  • Cross-Reference Updates: All navigation reflects new structure

Impact Metrics

  • Onboarding Time: Reduced from hours to minutes for new developers
  • Cognitive Load: 76% reduction in scattered documentation files
  • Developer Satisfaction: Improved discoverability and self-service support
  • Maintenance Overhead: Significantly reduced through consolidation

🎯 Key Success Factors

Architectural Excellence

  • 100% Type Safety: Complete Effect-TS integration eliminates runtime errors
  • Standardized Patterns: 9 helper functions reduce code duplication by 60%
  • Error Boundaries: Comprehensive error handling with domain-specific contexts
  • Dependency Injection: Clean separation of concerns with testable architecture

Development Experience

  • Hot Reloading: Instant feedback during development
  • Comprehensive Testing: 343 tests across 20 files ensure reliability and prevent regressions
  • Type-Driven Development: Schemas and validation catch errors at compile time
  • Clear Patterns: Standardized approaches reduce cognitive load

User Experience

  • Responsive Design: Mobile-first approach with TailwindCSS
  • Accessibility: WCAG compliance and keyboard navigation support
  • Markdown Support: Rich text descriptions and bios with MarkdownRenderer + MarkdownToolbar (using marked + DOMPurify)
  • Progressive Enhancement: Works without JavaScript, enhanced with interactivity
  • Performance: Optimized bundles and lazy loading for fast load times

Maintainability

  • Clean Architecture: Clear separation between layers and domains
  • Documentation: Comprehensive documentation and code comments
  • Consistent Patterns: Standardized approaches across all domains
  • Automated Testing: Prevents regressions and ensures quality

📝 Implementation Guidelines

For New Developers

  1. Start with Documentation: Read project overview and technical specifications
  2. Study Completed Domains: Service Types domain as reference implementation
  3. Follow Patterns: Use established helper functions and architectural patterns
  4. Test-Driven Development: Write tests first, then implement functionality
  5. Effect-TS First: All new code must use Effect-TS patterns

For Domain Implementation

  1. Service Layer: Implement Effect-TS service with Context.Tag injection
  2. Store Layer: Use factory function with all 9 standardized helper functions
  3. Schema Layer: Define Effect Schema classes with proper validation
  4. Error Layer: Create domain-specific tagged errors with contexts
  5. Component Layer: Use composables for business logic, components for presentation
  6. Testing Layer: Comprehensive unit, integration, and E2E test coverage

For Maintenance

  1. Follow Standards: Maintain consistency with established patterns
  2. Update Tests: Ensure all changes include corresponding test updates
  3. Documentation: Update documentation for any architectural changes
  4. Performance: Monitor and optimize bundle sizes and load times
  5. Security: Follow security best practices and validate all inputs

This comprehensive codebase documentation provides a complete overview of the Holochain Requests and Offers project's architecture, implementation patterns, and development practices. The 7-layer Effect-TS architecture represents a significant advancement in TypeScript application design, providing unprecedented type safety, error handling, and maintainability.

Moss/Weave Integration

This document describes how Requests & Offers integrates with the Weave/Moss ecosystem as a Moss Tool. It covers architecture, design decisions, and development setup.

Overview

R&O runs in two modes:

  1. Standalone -- Direct connection via AppWebsocket
  2. Moss Tool -- Integrated via WeaveClient within a Moss group

The app detects its context once at startup and adapts connection, profile handling, and admin detection accordingly. Weave-specific concerns are isolated in a dedicated WeaveService + weaveStore layer, keeping HolochainClientService focused on core Holochain connectivity.


1. Context Detection

Context detection uses a two-stage approach coordinated between +layout.svelte and the Weave layer.

Stage 1 -- Hot-reload setup and detection (+layout.svelte onMount):

// +layout.svelte — onMount
onMount(async () => {
  // Sets up window.__WEAVE_API__ which isWeaveContext() checks.
  // MUST run before Weave context detection.
  try {
    await initializeHotReload();
  } catch {
    // Expected to fail in non-Weave environments and production webhapps
  }

  // Detect Weave context AFTER hot-reload has set up the environment
  weaveStore.detectWeaveContext();
  // ...
});

Stage 2 -- Connection delegation (HolochainClientService.connectClient()):

// HolochainClientService.svelte.ts — connectClient()
if (weaveStore.isWeaveContext) {
  console.log('🧶 Detected Weave context, connecting via WeaveClient...');
  const result = await weaveStore.connect();
  client = result.appClient;
} else {
  console.log('📡 Standalone mode, connecting via AppWebsocket...');
  client = await AppWebsocket.connect();
}

The WeaveService lazily detects the Weave context by calling isWeaveContext() from @theweave/api. This lazy detection is necessary because in weave dev mode, initializeHotReload() must set up window.__WEAVE_API__ before isWeaveContext() can detect the environment.

Cross-group views (renderInfo.type !== 'applet-view') are not yet supported and result in a WeaveError.

Key files:

  • ui/src/lib/stores/weave.store.svelte.ts
  • ui/src/lib/services/weave.service.ts
  • ui/src/lib/services/HolochainClientService.svelte.ts

2. Connection Management

Both modes share the same retry logic in HolochainClientService:

  • Max retries: 3 attempts
  • Backoff: Exponential -- 2s, 4s, 8s delays (Math.pow(2, retryCount) * 1000)
  • State tracking: Reactive isConnected / isConnecting flags
  • Concurrent guard: If a connection attempt is already in progress, callers wait via polling

In Weave context, HolochainClientService.connectClient() delegates to weaveStore.connect(), which internally runs WeaveService.connect() via Effect. This returns a WeaveConnectionResult containing the appClient, weaveClient, and profilesClient. In standalone mode, AppWebsocket.connect() is called directly. The retry/backoff/state-tracking logic remains in HolochainClientService for both paths.

Components can call waitForConnection() to block until the client is ready, or use verifyConnection() to test connectivity (attempts client.appInfo() as a health check).

On WebSocket or connection errors during callZome(), the service marks itself as disconnected and nulls the client, forcing reconnection on the next call.


3. Hybrid Profile System

In Moss, users already have a profile (nickname + avatar) managed by the group's ProfilesClient. R&O stores additional professional data (bio, email, skills, location). The weaveStore manages profile fetching, avatar conversion, and enrichment of R&O users with Moss identity data.

WeaveStore Profile Management

The weaveStore provides methods and reactive state for Moss profile handling:

Methods:

MethodPurpose
initialize(agentPubKey)Fetch Moss profile via profilesClient.getAgentProfile(), populate reactive state
refreshMossProfile(agentPubKey)Re-fetch Moss profile (e.g., after profile update)
enrichWithMossProfile(raoUser, agentPubKey)Merge Moss identity onto an R&O UIUser (Effect-based)

Reactive state:

PropertyTypeDescription
mossProfileMossProfile | nullCurrent Moss profile (nickname + avatar)
mossAvatarBlobBlob | nullDecoded avatar as a Blob for direct display
hasMossNicknamebooleanDerived: in Weave context and profile has nickname
hasMossAvatarbooleanDerived: in Weave context and avatar blob exists
mossNicknamestring | nullDerived: the Moss nickname or null

After connection, the layout initializes the Weave store profile state:

// +layout.svelte — initialization orchestrator
if (weaveStore.isWeaveContext) {
  const appInfo = await hc.getAppInfo();
  if (appInfo?.agent_pub_key) {
    await weaveStore.initialize(appInfo.agent_pub_key as AgentPubKey);
  }
}

Enrichment behavior:

  • Moss context: enrichWithMossProfile returns a UIUser with nickname and picture from Moss, all other fields from R&O, and identitySource: 'moss'
  • Standalone: Returns the R&O user as-is with identitySource: 'standalone'
  • If a Moss profile exists but no R&O user record yet (new user in Moss), a minimal UIUser is returned with Moss identity fields and empty R&O fields

Component Integration

Components import weaveStore directly to access Moss profile data. For example, UserForm.svelte uses the store to pre-fill the nickname and avatar when creating a new user in Moss context:

  • weaveStore.mossNickname -- Pre-fills the nickname field
  • weaveStore.hasMossNickname -- Controls whether nickname is editable
  • weaveStore.hasMossAvatar -- Controls avatar display/editing
  • weaveStore.mossAvatarBlob -- Provides the avatar for preview

Error Handling

WeaveError is a tagged error (Data.TaggedError('WeaveError')) with fields for message, cause, context, agentPubKey, and operation. Error contexts are defined in WEAVE_CONTEXTS:

  • DETECT_CONTEXT -- Detecting Weave environment
  • CONNECT -- Connecting to Weave
  • GET_MOSS_PROFILE -- Fetching from Moss profilesClient
  • ENRICH_WITH_MOSS_PROFILE -- Merging Moss data onto R&O profile
  • CHECK_PROGENITOR -- Checking tool installer status
  • AVATAR_CONVERSION -- Converting base64 avatar to binary

Key files:

  • ui/src/lib/services/weave.service.ts
  • ui/src/lib/stores/weave.store.svelte.ts
  • ui/src/lib/errors/weave.errors.ts
  • ui/src/lib/errors/error-contexts.ts
  • ui/src/lib/types/ui.ts (identitySource field on UIUser)

4. Progenitor Detection (Admin)

In Moss, the group "progenitor" (the agent who installed the tool) gets admin rights automatically.

Working approach: toolInstaller() + pubkey comparison

The weaveStore.checkProgenitor(myPubKey) method handles progenitor detection:

// weave.store.svelte.ts — checkProgenitor()
async function checkProgenitor(myPubKey: AgentPubKey): Promise<boolean> {
  if (!isWeaveContext || !weaveClient) {
    isProgenitor = false;
    return false;
  }

  const renderInfo = weaveClient.renderInfo;
  if (renderInfo.type !== 'applet-view') {
    isProgenitor = false;
    return false;
  }

  const appletHash = renderInfo.appletHash;
  const installerPubKey = await weaveClient.toolInstaller(appletHash);
  if (!installerPubKey) {
    isProgenitor = false;
    return false;
  }

  const installerB64 = encodeHashToBase64(installerPubKey);
  const myB64 = encodeHashToBase64(myPubKey);
  isProgenitor = installerB64 === myB64;
  return isProgenitor;
}

toolInstaller(appletHash) returns the AgentPubKey of whoever installed the R&O tool in this Moss group. Comparing it with the current agent's pubkey gives cryptographic proof of installer identity. The result is stored as reactive isProgenitor state.

Failed approach: myAccountabilitiesPerGroup()

An earlier attempt used weaveClient.myAccountabilitiesPerGroup() to check for a "Progenitor" role. This doesn't work because the API returns group role definitions to all agents, not filtered by who holds them. Both the progenitor and regular members receive identical data showing "Progenitor role exists."

Standalone fallback

When not in Moss context, checkProgenitor() sets isProgenitor = false and returns immediately. The existing R&O admin system (explicit registerNetworkAdministrator / addNetworkAdministrator) handles admin rights.

Key file: ui/src/lib/stores/weave.store.svelte.ts


5. Auto-Admin Registration

The +layout.svelte initialization orchestrator runs autoRegisterProgenitorAdminStep after connection, hREA init, network verification, Weave store initialization, and user data loading:

  1. Check weaveStore.isWeaveContext -- skip if standalone
  2. Get agent pubkey from hc.getAppInfo()
  3. Call weaveStore.checkProgenitor(agentPubKey) -- skip if not progenitor
  4. Call administrationStore.hasAnyAdministrators() -- skip if admins already exist
  5. Check usersStore.currentUser exists with an original_action_hash
  6. Call administrationStore.registerNetworkAdministrator(userHash, [pubKey])

This is wrapped in E.catchAll so failures are non-critical -- the app continues normally.

In standalone mode, first-admin registration is triggered via Ctrl+Shift+A keyboard shortcut, which shows a confirmation modal. This shortcut is blocked in Weave context since progenitor auto-admin handles it.

Key files:

  • ui/src/routes/+layout.svelte
  • ui/src/lib/stores/weave.store.svelte.ts

6. Network Peer Discovery

getNetworkPeers() queries the conductor for connected peers:

  1. Checks client instanceof AppWebsocket (method not available on the generic AppClient interface used in Moss)
  2. Calls client.agentInfo({ dna_hashes: null })
  3. Handles multiple response formats (object with agent_pub_key, JSON strings, nested agentInfo.agent)
  4. Returns a deduplicated array of agent public key strings

Similarly, getPeerMetaInfo() collects metadata from peers by calling client.peerMetaInfo() for each discovered agent URL.

Limitation: In Weave context, the client is an AppClient (not AppWebsocket), so agentInfo / peerMetaInfo are not available. These methods return empty results. A future upstream PR to @holochain/client could add these to the AppClient interface.

Key file: ui/src/lib/services/HolochainClientService.svelte.ts


7. Data Flow

User opens R&O
       │
       ▼
 +layout.svelte (onMount)
       │
       ├──► initializeHotReload()
       │
       ▼
 weaveStore.detectWeaveContext()
       │
       ▼
 connectToHolochain()
       │
       ├── Weave? → weaveStore.connect() → WeaveService.connect() → WeaveClient
       │                                          │
       └── Standalone? → AppWebsocket.connect()   │
                │                                  │
                ▼                                  ▼
       Store client reference ◄────────────────────┘
       │
       ▼
 Initialize hREA, verify network, load user data
       │
       ▼
 weaveStore.initialize(agentPubKey)  (Weave only — fetch Moss profile)
       │
       ▼
 autoRegisterProgenitorAdminStep
       │    └── weaveStore.checkProgenitor(pubKey)
       │
       ▼
 App renders
       │
       ├──► Profile display → weaveStore → Moss or R&O profiles
       │
       ├──► Admin checks → weaveStore.isProgenitor → Moss group data or R&O admin system
       │
       └──► Zome calls → HolochainClientService → Holochain conductor

8. Development Setup

Directory structure

weave/
├── applet-dev.sh           # Multi-agent launch script
├── curations-0.15.json     # Tool curation metadata for Moss registry
├── tool-list-0.15.json     # Tool registry (versions, hashes, download URLs)
└── weave.dev.config.json   # Dev sandbox config (groups, agents, applets)

Running in Weave dev mode

bun run applet-dev          # Launches with 2 agents by default
AGENTS=3 bun run applet-dev # Custom number of agents (1-10)

The applet-dev.sh script:

  1. Starts the UI dev server on port 8888
  2. Launches agent 1 with weave --agent-idx 1 --dev-config ./weave/weave.dev.config.json
  3. Launches additional agents with staggered delays (5s between each) and --sync-time 20000
  4. Uses concurrently to run all processes

Dev sandbox config (weave.dev.config.json)

Defines a test group ("R&O Dev Group") with:

  • networkSeed: "ro-dev-test-2026" for isolated test networks
  • Agent 1 as the creating agent (becomes progenitor)
  • The requests_and_offers applet sourced from localhost (hApp from ./workdir/requests_and_offers.happ, UI from port 8888)

Tool registry (tool-list-0.15.json)

Contains the developer collective metadata and tool versions for the Moss tool directory:

  • Developer: hAppenings Community CIC
  • Tool ID: requests-and-offers
  • Version branch: 0.3.x
  • Includes hApp, WebHapp, and UI SHA256 hashes for integrity verification

Curations (curations-0.15.json)

Points Moss to the tool list URL for discovery in the Moss tool directory.


9. Weave API Reference

Key methods used from @theweave/api:

MethodPurposeReturns
isWeaveContext()Detect if running inside Mossboolean
WeaveClient.connect()Establish connection in Moss contextWeaveClient
initializeHotReload()Enable dev hot-reload in Mossvoid
weaveClient.renderInfoGet applet view info, client, profiles clientRenderInfo
weaveClient.toolInstaller(appletHash)Get pubkey of tool installerAgentPubKey | undefined
weaveClient.renderInfo.appletClientHolochain AppClient for zome callsAppClient
weaveClient.renderInfo.profilesClientProfiles service for Moss identitiesProfilesClient
weaveClient.renderInfo.appletHashHash identifying this tool installationAppletHash

10. Version Compatibility

PackageVersion
@theweave/api0.6.3
@theweave/cli0.15.10
Moss0.15.x
Holochain0.6.x
R&O0.3.0

11. Files Reference

CategoryFilePurpose
Weave Serviceui/src/lib/services/weave.service.tsEffect-TS service for context detection + WeaveClient connection
Weave Storeui/src/lib/stores/weave.store.svelte.tsReactive store: profile state, avatar, progenitor check, enrichment
Connectionui/src/lib/services/HolochainClientService.svelte.tsDelegates to weaveStore for Weave connection, peer discovery
Connection Utilsui/src/lib/utils/holochain-client.utils.tsSimple connection/status helpers used by layout
Errorsui/src/lib/errors/weave.errors.tsWeaveError tagged error
Error Contextsui/src/lib/errors/error-contexts.tsWEAVE_CONTEXTS
Typesui/src/lib/types/ui.tsidentitySource field on UIUser
Admin Storeui/src/lib/stores/administration.store.svelte.tsAdmin detection, progenitor auto-registration
Layoutui/src/routes/+layout.svelteInitialization orchestrator, auto-admin step
User Formui/src/lib/components/users/UserForm.svelteMoss profile pre-fill via weaveStore
Weave Configweave/weave.dev.config.jsonDev sandbox configuration
Tool Registryweave/tool-list-0.15.jsonMoss tool directory metadata
Curationsweave/curations-0.15.jsonMoss tool curation entry
Dev Scriptweave/applet-dev.shMulti-agent Weave dev launcher

12. Testing Checklist

  • App loads in Moss group without errors
  • Profile displays correctly (nickname + avatar from Moss)
  • User creation form pre-fills with Moss profile data
  • Group progenitor is auto-registered as admin
  • Non-progenitor agents do not get auto-admin
  • Admin features available to progenitor
  • Network peers visible in standalone mode
  • Standalone mode still works (regression test)
  • DHT propagation between multiple agents in Weave dev mode
  • Ctrl+Shift+A blocked in Weave context

13. Future Considerations

  1. Deeper Moss permissions -- Currently only progenitor status is checked. Moss may offer richer permission models that could map to R&O roles.

  2. Profile sync -- Profiles are read-only from Moss. Bidirectional sync could be explored if R&O has fields that should flow back to Moss.

  3. Cross-group views -- Currently unsupported (renderInfo.type !== 'applet-view' returns a WeaveError). Could enable multi-group visibility in future.

  4. AppClient interface -- Upstream PR to @holochain/client to add agentInfo / peerMetaInfo to AppClient would allow peer discovery in Moss context.

Requirements Overview

This document outlines the project's requirements, covering user needs, features, and goals.

Key Sections

  • Features: Detailed description of system features.
  • MVP: Minimum Viable Product scope and requirements.
  • Roles: Definition of user roles and their capabilities.
  • Use Cases: Description of user workflows and interactions.

Roles and Permissions Specifications

1. User Roles

1.1 Advocate

  • Onboarded/Approved: Advocates are individuals passionate about the Holochain technology, looking to support projects within the ecosystem. They are onboarded and approved to participate in the network by the network's administrators.
  • User Profile: Advocates must file out their user profiles, including essential information and their areas of expertise.
  • Offers: Advocates can offer their skills, talents, or resources to the network. This includes mentorship, brainstorming sessions, or any other form of support they wish to provide.
  • Reporting: Advocates have access to reporting features, allowing them to view their exchanges and a general report of total exchanges for the month.

1.2 Creator

  • Onboarded/Approved: Creators are individuals or groups actively involved in creating or developing projects within the Holochain ecosystem. They are onboarded and approved to participate in the network by the network's administrators.
  • User Profile: Creators can file out their user profiles, including their skills, location, type, etc.
  • Offers: Creators can make offers, including the skills and talents they wish to offer.
  • Requests: Creators can make requests for themselves, such as mentoring, brainstorming time, or testing in the early days of their projects.
  • Reporting: Creators can access reports on their exchanges and a general report of total exchanges for the month.

1.3 Projects and Organizations Coordinators

  • Onboarded/Approved: Projects and organizations coordinators are individuals or groups designated to represent a project or organization within the network. They are onboarded and approved to participate in the network.
  • Project Profile: Projects and organizations coordinators can file out the project profile, including the skills involved, location, type, etc.
  • Offers: Projects and organizations coordinators can make offers for themselves and on behalf of their projects or organizations, such as:
    • Development resources (e.g., reusable Holochain zomes, UI components)
    • Testing environments or infrastructure
    • Documentation support or translation services
    • Community building and engagement resources
    • Training or workshop sessions about their project
    • Collaborative development opportunities
    • Beta testing participation opportunities
  • Contact Person: Coordinators can be designated as the organization's public-facing contact person with a role/title (e.g. Director, President)
  • Requests: Projects and organizations coordinators can make requests for themselves and on behalf of their projects or organizations, such as:
    • Technical expertise (Rust/Holochain development, UI/UX)
    • Code review and architecture feedback
    • Testing and quality assurance
    • Documentation assistance
    • Translation services
    • Community outreach support

1.4 Matchmaker (Future Role)

  • Description: A role that could be added in future versions to facilitate the matching of requests and offers more efficiently.

2. Administrative Roles

2.1 Administrator

  • Access: Full access to Administration Zome and UI
  • Responsibilities:
    • User and Organization verification
    • Project verification
    • Administrator management
    • Suspension management
    • System configuration
    • Full reporting access

2.2 Moderator

  • Access: Limited access to Administration Zome and UI
  • Responsibilities:
    • Content moderation
    • User support
    • Report handling
    • Cannot manage administrators

3. Role Management

3.1 Role Assignment

  • Progenitor Pattern:
    • The progenitor is the agent whose public key is embedded in DNA properties (progenitor_pubkey) at network creation time — not simply the first agent to register
    • The progenitor's pubkey is set by the Kangaroo (Electron) app, which reads the creator's agent pubkey from the conductor admin API before installing the hApp
    • Upon user profile creation, the progenitor is automatically registered as the first network administrator (see #5)
    • The progenitor is a regular revocable administrator — no permanent super-admin power
    • Establishes initial administrative control
    • Other agents join the network by installing the same DNA instance (identified by its hash); they receive no admin role automatically
    • See also: #95 (Network Creation & Joining UX)
  • Administrator Assignment: By existing administrators
  • Moderator Assignment: By administrators
  • Role Verification: Through anchor system

3.2 Access Control

Administrators

  • AdministratorsUser: Link from administrators anchor to user
  • Full System Access: Complete control over all system aspects

Moderators

  • ModeratorsUser: Link from moderators anchor to user
  • Limited Access: All admin functions except administrator management

3.3 Suspension System

  • Temporary Suspension: Time-limited account restrictions
  • Indefinite Suspension: Permanent account restrictions
  • Suspension Management:
    • Reason documentation
    • History tracking
    • Notification system:
      • Initial suspension notification with reason
      • Duration notification for temporary suspensions
      • End of suspension notification
      • Appeal process updates
    • Unsuspension process

3.4 Communication Channels

  • Administrative Inbox: Direct communication channel with administrators
  • Moderation Queue: For handling reported content and issues
  • Support System: For handling user inquiries and appeals
  • Notification System: For administrative updates and actions

3.5 Flagging System

  • User Flagging Rights: Users can flag organizations, projects, requests, and offers
  • Review Process: Flagged content undergoes administrative review
  • Action Types:
    • Content review
    • User warnings
    • Content removal
    • Account suspension
  • Tracking: Maintain history of flags and resolutions

Use Cases Specifications

1. User Management

1.1 User Registration and Profile

  • Use Case: Users can register and create user profile
  • Benefit: Facilitates the creation of a user-centric community, enabling personalized experiences and targeted interactions

1.2 User Recovery

  • Use Case: Users can recover their user profile, including their skills, projects, organizations, requests and offers
  • Benefit: Enables multi-device access and profile recovery, ensuring continuity of user experience and data preservation

2. Project Management

2.1 Project Creation

  • Use Case: Users can create projects, specifying the skills needed and the scope of the project
  • Benefit: Allows for the organization and management of projects, ensuring that the right skills are matched with the right projects

2.2 Project Coordination

  • Use Case: Project coordinators can manage team members, track progress, and update project requirements
  • Benefit: Enables effective project management and team coordination

3. Request and Offer Management

3.1 Request Creation

  • Use Case: Users can create requests for specific skills or resources needed for their projects
  • Details: Requests can be linked to:
    • Specific projects
    • Organizations
    • Skills
    • Team members
  • Benefit: Provides a structured way for users to express their needs, ensuring that the right offers are matched with their requests

3.2 Offer Creation

  • Use Case: Users with relevant skills can create offers to contribute to projects
  • Details: Users can specify:
    • Skills they are offering
    • Intended contribution method
    • Availability
    • Terms of assistance
  • Benefit: Enables direct matching of skills with project needs, facilitating efficient collaboration

3.3 Exchange Management

  • Use Case: Users can manage their requests and offers, including accepting offers from other users
  • Features:
    • Offer acceptance/rejection
    • Request fulfillment tracking
    • Exchange completion verification
    • Feedback system
  • Benefit: Provides a structured way for users to engage with each other, ensuring that interactions are organized and manageable

4. Search and Discovery

4.1 Basic Search Functionality

  • Use Case: Users can search across multiple categories
  • Search Categories:
    • Requests
    • Offers
    • Projects
    • Organizations
    • Users
  • Search Criteria:
    • Name
    • Skills
    • Categories
    • Organizations
    • Projects
  • Benefit: Enhances the discoverability of resources within the community, promoting collaboration and innovation

4.2 Advanced Filtering

  • Use Case: Users can apply multiple filters to refine search results
  • Filter Options:
    • Status
    • Location
    • Time frame
    • Skill level
    • Project type
  • Benefit: Enables precise matching of resources and needs

5. Communication

5.1 Direct Messaging

  • Use Case: Users can communicate directly about requests and offers
  • Features:
    • Real-time messaging
    • Message history
    • File sharing
  • Benefit: Facilitates detailed discussions and negotiations

5.2 Administrative Communication

  • Use Case: Users can communicate with administrators for support
  • Features:
    • Support tickets
    • Issue reporting
    • Feedback submission
  • Benefit: Ensures effective support and issue resolution

6. Reporting

6.1 User Reports

  • Use Case: Users can view their activity and exchange history
  • Report Types:
    • Personal exchanges
    • Monthly summaries
    • Project contributions
  • Benefit: Provides transparency and accountability

6.2 Administrative Reports

  • Use Case: Administrators can access system-wide reports
  • Report Types:
    • User activity
    • Exchange metrics
    • System usage
    • Moderation actions
  • Benefit: Enables effective system monitoring and management

Detailed Feature Specifications

1. User Management

1.1 User Profile

  • Creation and authentication of user profiles
  • Profile categorization as "advocate" or "creator"
  • Multi-device profile access using credentials
  • Integration with hREA agents
  • Custom Holochain zome for profile management

Users can be linked to:

  • Agents
  • Organizations
  • Requests
  • Offers
  • Projects
  • Service Types

1.3 User Entry Structure

The User entry represents profiles within hAppenings.community, supporting:

  • Single user profiles
  • Multi-device profile sharing
  • Personalized experiences
  • Community interactions

User Entry Structure (Current Implementation)

#![allow(unused)]
fn main() {
pub struct User {
  /// User's display name
  pub name: String,
  /// User's chosen nickname/handle
  pub nickname: String,
  /// Brief biographical description
  pub bio: String,
  /// Optional profile picture (serialized bytes)
  pub picture: Option<SerializedBytes>,
  /// User classification: 'advocate' or 'creator'
  pub user_type: String,
  /// Contact email address
  pub email: String,
  /// Optional phone number
  pub phone: Option<String>,
  /// User's time zone
  pub time_zone: String,
  /// Geographic location
  pub location: String,
}
}

User Fields Description

  • name: User's full display name for the platform
  • nickname: Chosen handle or shorter identifier
  • bio: (String, max 1000 chars) Personal description, skills, interests, and background (supports markdown)
  • picture: Optional profile image stored as serialized bytes
  • user_type: Either "advocate" (community supporter) or "creator" (service provider)
  • email: Primary contact email for notifications and communications
  • phone: Optional phone number for alternative contact
  • time_zone: User's time zone for scheduling and coordination
  • location: Geographic location for local coordination and meetups
  • UserUpdates: User create header → update headers
  • UserAgents: User → agent (index of associated agents)
  • AllUsers: Link to users anchor (global user index)
  • MyUser: Current agent → user
  • UserRequests: User → requests (index)
  • UserOffers: User → offers (index)
  • UserProjects: User → projects (index)
  • UserOrganizations: User → organizations (index)
  • UserServiceTypes: User → service types (index)

2. Projects and Organizations

2.1 Projects

Projects are organizations classifiedAs Project in hREA.

Project Features

  • Creation by organizations or users
  • Specific requirements and status tracking
  • Team member management
  • Category classification

Project Status Flow

Project Status Flow

Project Types

Types of Projects

  • AllProjects: Link to projects anchor
  • ProjectCoordinators: Project → coordinators
  • ProjectContributors: Project → contributors
  • ProjectCategories: Project → categories
  • ProjectRequests: Project → requests
  • ProjectOffers: Project → offers

2.2 Organizations

Organizations are agents classifiedAs Organization in hREA.

Organization Entry Structure (Current Implementation)

#![allow(unused)]
fn main() {
pub struct Organization {
  /// Organization's official name
  pub name: String,
  /// Detailed description of the organization
  pub description: String,
  /// Optional organization logo/image
  pub picture: Option<SerializedBytes>,
  /// Organization's contact email
  pub email: String,
  /// Optional phone number
  pub phone: Option<String>,
  /// Website URL
  pub website: String,
  /// Geographic location
  pub location: String,
}
}

Organization Fields Description

  • name: Official organization name
  • description: (String, max 1000 chars) Detailed description of the organization's mission, activities, and focus (supports markdown)
  • picture: Optional organization logo or representative image
  • email: Primary contact email for the organization
  • phone: Optional phone number for contact
  • website: Organization's website URL
  • location: Geographic location or area of operation

Organization Features

  • User-created entities
  • Project management capabilities
  • Member management
  • Category classification
  • AllOrganizations: Link to organizations anchor
  • OrganizationCoordinators: Organization → coordinators
  • OrganizationMembers: Organization → members
  • OrganizationContacts: Organization → contact person (tag = role)
  • OrganizationProjects: Organization → projects
  • OrganizationCategories: Organization → categories
  • OrganizationRequests: Organization → requests
  • OrganizationOffers: Organization → offers

2.4 Contact Person Designation

Organizations can designate a single coordinator as their public-facing contact person.

Features

  • Single contact per organization: Setting a new contact replaces the previous one
  • Coordinator-only: Only coordinators can be designated as the contact person
  • Role/title: Free-text field for the contact's role (e.g. Director, President)
  • Auto-cleanup: Contact links removed automatically when the contact leaves, is removed, or the organization is deleted
  • Optional during creation: Creator can set themselves as contact during organization creation

Contact Functions

  • set_organization_contact: Set or replace the contact person with a role
  • remove_organization_contact: Remove the current contact person
  • get_organization_contact: Retrieve the contact person and their role
  • is_organization_contact: Check if a user is the contact person

2.3 Coordinator Management

Responsibilities

  • Project/Organization representation
  • Organization/Project profile management
  • Member invitation and approval
  • Request/Offer management on behalf of the organization/project

Features

  • Network administrator approval required
  • Organization/Project profile customization (description, type, skills needed, etc.)
  • Request/Offer creation capabilities for the organization/project
  • Member invitation and approval management

3. Requests and Offers

3.1 Core Functionality

  • hREA integration for economic activities
  • Request creation linked to projects/organizations/service types
  • Offer creation linked to requests/projects
  • Implementation as hREA intents and proposals

3.2 Request System

Request Entry Structure (Current Implementation)

#![allow(unused)]
fn main() {
pub struct Request {
  /// The title of the request
  pub title: String,
  /// A detailed description of the request
  pub description: String,
  /// The contact preference for the request
  pub contact_preference: ContactPreference,
  /// The date range for the request
  pub date_range: Option<DateRange>,
  /// The estimated time in hours to complete the request
  pub time_estimate_hours: Option<f32>,
  /// The preferred time of day for the request
  pub time_preference: TimePreference,
  /// The time zone for the request
  pub time_zone: Option<TimeZone>,
  /// The exchange preference for the request
  pub exchange_preference: ExchangePreference,
  /// The interaction type for the request
  pub interaction_type: InteractionType,
  /// Generic links related to the request
  pub links: Vec<String>,
}
}

Supporting Enums and Types

#![allow(unused)]
fn main() {
pub enum ContactPreference {
  Email,
  Phone,
  Other(String),
}

pub enum ExchangePreference {
  Exchange,
  Arranged,
  PayItForward,
  Open(),
}

pub enum InteractionType {
  Virtual,
  InPerson,
}

pub enum TimePreference {
  Morning,
  Afternoon,
  Evening,
  NoPreference,
  Other(String),
}

pub struct DateRange {
  pub start: Option<Timestamp>,
  pub end: Option<Timestamp>,
}

pub type TimeZone = String;
}

Note: Service Types integration is handled through separate linking mechanisms rather than direct embedding in the Request structure. The Service Types system provides tag-based discovery and categorization.

Request Features

  • Creation: By individual users or organizations
  • Validation: Title and description are required; description limited to 1000 characters
  • Lifecycle: Full status tracking, update, and deletion capabilities
  • Service Type Integration: Linked through separate mechanisms for flexible categorization

Current Field Descriptions:

  • title: (String) The title of the request - required field
  • description: (String, max 1000 chars) A detailed description of the request (supports markdown) - required field
  • contact_preference: (ContactPreference enum) How the requester prefers to be contacted (Email, Phone, Other)
  • date_range: (Optional DateRange) Timeframe when the service is needed with start/end timestamps
  • time_estimate_hours: (Optional f32) Estimated time in hours to complete the request
  • time_preference: (TimePreference enum) Preferred time of day (Morning, Afternoon, Evening, NoPreference, Other)
  • time_zone: (Optional String) Requester's time zone
  • exchange_preference: (ExchangePreference enum) How the requester prefers to reciprocate (Exchange, Arranged, PayItForward, Open)
  • interaction_type: (InteractionType enum) Whether virtual or in-person interaction
  • links: (Vec) Generic links for extensibility and integration

Service Types Integration: Service types are linked to requests through separate linking mechanisms rather than direct embedding, enabling flexible categorization and tag-based discovery.

  • AllRequests: Links to requests anchor (global request index)
  • UserRequests: User → requests created by that user
  • OrganizationRequests: Organization → requests associated with it
  • RequestCreator: Request → its creator user profile
  • RequestOrganization: Request → its associated organization (if any)
  • RequestUpdates: Original request action → update actions
  • RequestServiceTypes: Request → linked approved service types

3.3 Offer System

Offer Entry Structure (Current Implementation)

#![allow(unused)]
fn main() {
pub struct Offer {
  /// The title of the offer
  pub title: String,
  /// A detailed description of the offer
  pub description: String,
  /// The preferred time of day for the offer
  pub time_preference: TimePreference,
  /// The time zone for the offer
  pub time_zone: Option<TimeZone>,
  /// The exchange preference for the offer
  pub exchange_preference: ExchangePreference,
  /// The interaction type for the offer
  pub interaction_type: InteractionType,
  /// Generic links related to the offer
  pub links: Vec<String>,
}
}

Note: Service Types integration is handled through separate linking mechanisms rather than direct embedding in the Offer structure. The Service Types system provides tag-based discovery and categorization. Offers use the same supporting enums and types as Requests (see above).

Offer Features

  • Creation: By individual users or organizations
  • Validation: Title and description are required; description limited to 1000 characters
  • Lifecycle: Full status tracking, update, and deletion capabilities
  • Service Type Integration: Linked through separate mechanisms for flexible categorization

Current Field Descriptions

  • title: (String) The title of the offer - required field
  • description: (String, max 1000 chars) A detailed description of the offer (supports markdown) - required field
  • time_preference: (TimePreference enum) Preferred time of day (Morning, Afternoon, Evening, NoPreference, Other)
  • time_zone: (Optional String) Offerer's time zone
  • exchange_preference: (ExchangePreference enum) How the offerer prefers to reciprocate (Exchange, Arranged, PayItForward, Open)
  • interaction_type: (InteractionType enum) Whether virtual or in-person interaction
  • links: (Vec) Generic links for extensibility and integration

Service Types Integration: Service types are linked to offers through separate linking mechanisms rather than direct embedding, enabling flexible categorization and tag-based discovery.

  • AllOffers: Links to offers anchor (global offer index)
  • UserOffers: User → offers created by that user
  • OrganizationOffers: Organization → offers associated with it
  • OfferCreator: Offer → its creator user profile
  • OfferOrganization: Offer → its associated organization (if any)
  • OfferUpdates: Original offer action → update actions
  • OfferServiceTypes: Offer → linked approved service types
  • OfferRequests: Offer → requests it responds to (future enhancement)

3.4 Request-Offer Matching

Matching Process

  • Manual matching in initial implementation
  • Service Type-based discovery and matching:
    • Direct service type alignment between requests and offers
    • Tag-based discovery for related content
    • Cross-entity navigation through shared service types
  • Future algorithmic matching based on:
    • Service type compatibility scoring
    • Tag similarity analysis
    • Availability/urgency compatibility
    • User/organization preferences

Proposal Formation

  • When a request and offer are matched, a proposal is formed
  • Proposal represents the potential agreement between parties
  • Both parties must confirm to create an agreement
  • Agreement leads to commitments and economic events
  • RequestOffers: Request → offers submitted to it
  • OfferRequests: Offer → requests it's submitted to
  • ProposalRequest: Proposal → originating request
  • ProposalOffer: Proposal → originating offer

3.5 Exchange Completion & Feedback-Driven Fulfillment

POST-MVP FEATURE: The exchange completion and feedback system described below has been moved to post-MVP development. This functionality will be implemented after the simplified MVP has been validated with users.

Based on the hREA mapping and exchange process clarifications, the completion process implements a feedback-driven economic flow:

Feedback Process Workflow

  1. Work Completion: Service provider completes the committed work
  2. Feedback Request: Provider can request feedback from the recipient
  3. Feedback Provision: The agent who initiated the request (or accepted the offer) provides feedback
  4. Conditional Fulfillment: Economic events are created only if feedback is positive
  5. Resolution Process: Negative feedback triggers mediation before fulfillment

Feedback Rights and Responsibilities

  • Feedback Providers:
    • Agent who initiates a request (provides feedback on received service)
    • Agent who accepts an offer (provides feedback on delivered outcome)
  • Feedback Requesters:
    • Agent performing work on a request
    • Agent providing service from an offer
  • Quality Assurance: Feedback acts as a quality gate for commitment fulfillment

Feedback Data Structure

interface FeedbackProcess {
  id: string;
  commitmentId: string;
  requesterId: AgentId; // Who can request feedback
  providerId: AgentId; // Who provides feedback
  status: "pending" | "requested" | "completed";
  feedback?: FeedbackEntry;
}

interface FeedbackEntry {
  rating: "positive" | "negative";
  comments: string;
  timestamp: Date;
  providedBy: AgentId;
}

Economic Event Creation

  • Conditional Events: Economic events fulfill commitments only after positive feedback
  • Quality Assurance: Negative feedback triggers resolution processes
  • hREA Integration: Economic events properly fulfill hREA commitments and affect resources
  • Dispute Resolution: Mediation mechanisms for negative feedback resolution

3.6 Types of Support Requested

Types of Support Requested

4. Service Types and Tag-Based Discovery

4.1 Service Types System

Service Types are implemented as Resource Specifications in hREA, providing a comprehensive moderation workflow and tag-based discovery system for categorizing and organizing services, skills, and resources across the platform.

Core Features

  • User Suggestions: Any authenticated user can suggest new service types via suggest_service_type()
  • Admin Moderation: Complete workflow with pending, approved, and rejected states managed through dedicated admin functions
  • Tag-Based Discovery: Comprehensive tagging system with path anchor indexing for efficient search and cross-entity discovery
  • Cross-Entity Integration: Full integration with requests, offers, and discovery workflows through cross-zome linking
  • Quality Assurance: Only approved service types can be used in new requests and offers, enforced at the zome level

Service Type Structure (Current Implementation)

#![allow(unused)]
fn main() {
pub struct ServiceType {
    pub name: String,        // E.g., "Web Development", "Graphic Design", "Childcare"
    pub description: String, // Detailed explanation of the service type (max 500 chars)
    pub tags: Vec<String>,   // Keywords for searchability, e.g., ["svelte", "rust", "holochain"]
}
}

Validation Rules

  • name: Required, non-empty, maximum 100 characters
  • description: Required, non-empty, maximum 500 characters (supports markdown)
  • tags: Optional vector, individual tags normalized (lowercase, trimmed), no empty strings or duplicates

Status Management Workflow

Service Types follow a three-state validation workflow managed through path anchors:

  • Pending (service_types.status.pending): User-suggested service types awaiting admin review
  • Approved (service_types.status.approved): Admin-approved service types available for use in requests/offers
  • Rejected (service_types.status.rejected): Admin-rejected service types with optional reasoning

Admin Functions

  • create_service_type(): Direct creation and immediate approval by administrators
  • approve_service_type(): Approve pending service types, creates tag anchor links
  • reject_service_type(): Reject service types, triggers cross-zome cleanup
  • update_service_type(): Update existing service types with tag link management
  • delete_service_type(): Delete service types with comprehensive cleanup

4.2 Tag-Based Discovery System

Path Anchor Architecture

  • Tag Anchors: Dynamic paths (service_types.tags.{url_encoded_tag_string}) linking approved service types to specific tags
  • All Tags Anchor: Static path (service_types.all_tags) maintaining unique tag registry
  • Cross-Zome Integration: Tag-based discovery extends to requests and offers through linking mechanisms

Discovery Functions

  • get_service_types_by_tag(): Retrieve approved service types for a specific tag
  • get_service_types_by_tags(): AND logic search across multiple tags
  • get_all_unique_tags(): List all tags currently associated with approved service types
  • search_tags_by_prefix(): Real-time tag suggestions with prefix matching for autocomplete

Tag Management Features

  • Dynamic Tag Creation: Tags are created when service types are approved, automatically indexed
  • Automatic Cleanup: Tag links removed when service types are rejected or deleted
  • Orphan Detection: Automatic removal of tags no longer associated with any approved service types
  • Cross-Entity Discovery: Find requests and offers through service type tag relationships
  • Tag Statistics: Usage analytics and popularity metrics for tag cloud weighting

4.3 User Workflow

For General Users

  1. Suggest Service Types: Submit suggestions with name, description, and tags
  2. Browse Approved Types: View and search approved service types
  3. Tag-Based Discovery: Explore content through tag navigation
  4. Use in Requests/Offers: Select approved service types when creating content

For Administrators

  1. Review Suggestions: Access dedicated moderation interface for pending items
  2. Approve/Reject: Make decisions with optional reasoning
  3. Direct Creation: Create and immediately approve service types
  4. Bulk Operations: Manage multiple service types efficiently
  5. Analytics Dashboard: View usage statistics and system health metrics

4.4 UI Components and Pages

Public Interface Pages

  • Service Types Listing (/service-types): Browse and search approved service types with tag-based filtering
  • Service Type Details (/service-types/[id]): Individual service type information with related requests/offers
  • Suggestion Form (/service-types/suggest): User-facing suggestion interface with validation
  • Tag Discovery (/tags/[tag]): Tag-based content discovery showing related requests, offers, and service types

Admin Interface Pages

  • Admin Dashboard (/admin/service-types): Complete management interface with statistics and bulk operations
  • Moderation Queue (/admin/service-types/moderate): Dedicated pending suggestions review interface
  • Direct Creation (/admin/service-types/create): Admin service type creation with immediate approval
  • Detail Management (/admin/service-types/[id]): Individual item management with moderation actions

Core UI Components

  • ServiceTypeCard.svelte: Display component with status indicators, tags, and navigation actions
  • ServiceTypeSelector.svelte: Multi-select component for forms with search and filtering capabilities
  • ServiceTypeSuggestionForm.svelte: Complete form for user suggestions with validation (500 char limits)
  • ServiceTypeList.svelte: Filterable lists with status-based organization

Tag-Based Discovery Components

  • TagAutocomplete.svelte: Real-time tag suggestion with debounced search and prefix matching
  • TagCloud.svelte: Visual tag cloud weighted by popularity statistics
  • TagDetailsView.svelte: Comprehensive view showing all content associated with a specific tag
  • TagNavigationBar.svelte: Navigation component for tag-based browsing with breadcrumbs

Admin Interface Components

  • ServiceTypeAdminDashboard.svelte: Complete dashboard for moderators with statistics and bulk operations
  • ServiceTypeModerationCard.svelte: Specialized card for admin review workflow with approve/reject actions

4.5 Integration with Requests and Offers

Request Integration

  • Service Selection: Choose from approved service types when creating requests
  • Tag Discovery: Find related requests through service type tags
  • Requirement Specification: Link requests to specific service type requirements

Offer Integration

  • Capability Declaration: Specify offered services using approved service types
  • Skill Matching: Match offers to requests based on service type alignment
  • Tag-Based Promotion: Discover offers through service type tag navigation

4.6 Technical Implementation

Backend (Holochain Zomes)

  • service_types_integrity: Entry definitions, validation rules, and link types
  • service_types_coordinator: Business logic, moderation workflow, and external functions
  • Cross-Zome Integration: Validation and cleanup coordination with requests/offers zomes

Frontend (SvelteKit + Effect TS)

  • Service Layer: Effect TS-based service with dependency injection
  • Store Management: Reactive Svelte stores with caching and event bus integration
  • Component Library: Comprehensive UI components for all user interactions
  • Type Safety: Complete TypeScript integration with Effect TS error handling

Performance Optimizations

  • Path Anchor Indexing: Efficient tag-based queries and discovery
  • Caching Strategy: Service and store-level caching with intelligent invalidation
  • Lazy Loading: On-demand loading of large datasets with pagination support
  • Event-Driven Updates: Real-time state synchronization across components

4.5 Reporting Features

User Reports

  • Personal exchange history
  • Monthly activity summaries
  • Project contribution tracking
  • Service Types utilization metrics

Administrative Reports

  • Network activity metrics
  • User verification status
  • Project status tracking
  • Exchange completion rates
  • Moderation action logs

5. Search Functionality

5.1 Search Capabilities

Each major section includes search functionality:

  • Name
  • Service types (through associated requests/offers)
  • Tags (from service type associations)
  • Location
  • Organizations
  • Projects
  • Name
  • Organization
  • Status
  • Service types (through associated requests/offers)
  • Tags (from service type associations)
  • Name
  • Members
  • Projects
  • Service types (through associated requests/offers)
  • Tags (from service type associations)
  • Service types (approved only)
  • Tags (from associated service types)
  • Status
  • Associated project/organization
  • Tag-based discovery and navigation
  • Cross-entity search through service type relationships

6. Network Administration

6.0 Network Creation & Joining (Kangaroo)

The following UX is implemented in the Kangaroo (Electron) host app, not in the Svelte UI. It runs before the hApp is installed.

References: #5 (progenitor backend), #45 (integrity validation), #95 (full UX spec)

Splash Screen (first launch)

On first launch (no network installed), Kangaroo shows three options:

  • Create Network — for community founders who want to start a new requests-and-offers network
  • Join Network — for members who received an invite (DNA hash / QR code) from a founder
  • Join Holochain Pilot — joins the pre-bundled community pilot network; no invite needed

Create Network Flow

  1. Kangaroo reads the agent pubkey from the conductor admin WebSocket API
  2. Installs the hApp with DNA properties: { progenitor_pubkey: <agent_pubkey> }
  3. Reads the resulting DNA hash from the conductor
  4. Displays the DNA hash as:
    • A copyable string
    • A QR code
  5. The founder shares this hash/QR with their community

The progenitor's pubkey is now cryptographically bound to the network. No one else can become progenitor (see integrity validation in #45).

Join Network Flow

  1. Member pastes the DNA hash or scans the QR code
  2. Kangaroo installs the hApp targeting that exact DNA hash
  3. Member is on the same DHT network as the founder
  4. Member creates a profile → standard pending user (no admin role)

Membership Model

  • Open network: anyone with the correct DNA hash can join — no membrane proof required
  • Admin role is controlled by explicit administrator delegation, not by joining order

Multi-Network Support

  • A user may join multiple networks (e.g., their own community + the pilot)
  • Kangaroo provides a network switcher (sidebar or header dropdown) listing all installed networks
  • Switching networks changes the active DNA context without restarting the conductor

Pre-loaded Networks

NetworkHow loadedProgenitor
Holochain pilotPre-bundled DNA hash in Kangaroo buildDesignated pilot operator
Test networkTryorama DNA modifiers (dynamic pubkey)Alice (test keypair)

6.1 Core Administrative Features

  • User and Organization verification
  • Project verification
  • Service Types Moderation: Complete workflow for reviewing, approving, and rejecting user-suggested service types
  • Moderator role management
  • Suspension system
  • Flagging system
  • Administrative inbox

6.2 User Interface

  • Administration Dashboard
  • User management interface
  • Project/Organization management
  • Service Types Moderation Interface: Dedicated tools for reviewing pending suggestions, bulk operations, and analytics
  • Request/Offer moderation
  • Tag-Based Discovery Tools: Administrative insights into tag usage, statistics, and content organization
  • Search and reporting tools

7. Communication and Negotiation

7.1 Messaging System

POST-MVP FEATURE: The messaging system described below has been moved to post-MVP development. This functionality will be implemented after the simplified MVP has been validated with users.

  • User-to-User Messaging: Allows users to communicate directly with one another, facilitating negotiations and discussions related to requests and offers.
  • Administrative Communication: Provides a channel for users to send messages to network administrators through an inbox within the administration panel.
  • Features:
    • Direct messaging between users
    • Negotiation support for requests/offers
    • Administrative communication channel
    • Message history tracking
    • Agreement finalization support

7.2 Notification System

  • Suspension Notifications: Users receive notifications about account suspension status and reasons
  • Exchange Updates: Notifications for request/offer matches and updates
  • Administrative Alerts: Important system and moderation notifications
  • Recovery Notifications: Updates about account recovery process

Simplified MVP Specifications

1. Introduction

The Requests & Offers - Simplified MVP project aims to develop a Holochain application designed to facilitate a simple bulletin board for requests and offers within the hAppenings.community. This document outlines the core requirements and objectives for the minimum viable product.

2. Objective

The primary objective is to create a simple, open-source Holochain application that enables Creators, Projects, and Developers to post requests for support and Advocates to post offers of support. The application will function as a bulletin board with direct contact facilitation.

3. Target Audience

  • Holochain Creators/Projects/Developers: Individuals or groups actively developing projects within the Holochain ecosystem
  • Holochain Advocates: Individuals passionate about supporting Holochain projects
  • HoloHosts: Organizations or individuals hosting Holochain nodes

4. Core MVP Requirements

4.1 User Management

  • Basic user registration and authentication
  • User profile creation and management
  • Role-based access control (Advocate, Creator, Administrator)
  • Multi-device profile access

4.2 Request/Offer System

  • Request creation and management
  • Offer creation and management
  • Simple browsing and search functionality
  • Contact information display for direct communication

4.3 User Listing Management

  • Archive own requests/offers
  • Delete own requests/offers
  • View personal listings dashboard

4.4 Administration

  • User verification
  • Basic moderation tools
  • System configuration
  • Essential reporting

4.5 Search and Discovery

  • Basic search functionality
  • Simple filtering options
  • Category-based browsing

5. MVP Scope

5.1 Included Features

  • Essential user management
  • Core request/offer posting functionality
  • Simple browsing and search capabilities
  • Contact information display
  • Archive/delete functionality for users
  • Fundamental administrative tools

5.2 Excluded Features (Post-MVP)

  • Exchange coordination system
  • Proposal and agreement workflows
  • In-app messaging
  • Review and reputation systems
  • Advanced matching algorithms
  • Mutual Credit Currency components

6. Success Criteria

6.1 Functional Requirements

  • Successful user registration and authentication
  • Working request/offer creation and browsing
  • Contact information clearly displayed
  • Archive/delete functionality for users
  • Essential administrative controls
  • Simple search and discovery features

6.2 Performance Requirements

  • Response time under 2 seconds for basic operations
  • Support for multiple concurrent users
  • Basic error handling and recovery
  • Multi-device compatibility

6.3 User Experience Requirements

  • Intuitive navigation
  • Clear user feedback
  • Basic responsive design
  • Essential accessibility features

7. Conclusion

This simplified MVP specification focuses on the core value proposition: a bulletin board where users can post requests and offers with clear contact information for direct communication. By removing the complex exchange process, we can deliver value to users faster while gathering feedback for future enhancements.

The MVP prioritizes:

  • Essential user management and authentication
  • Core request and offer posting functionality
  • Simple browsing and search capabilities
  • Contact information display for direct communication
  • Basic listing management (archive/delete)

Post-MVP versions will add the exchange coordination system, proposal workflows, in-app messaging, and other advanced features based on user feedback and community needs.

MVP Features

This document outlines the features included in the MVP version of the Requests and Offers application.

Core Features (Delivered)

1. Request Management

  • Create new requests with title, description, tags
  • View request details
  • Archive own requests
  • Delete own requests

2. Offer Management

  • Create new offers with title, description, tags
  • View offer details
  • Archive own offers
  • Delete own offers

3. Public Listing Views

  • Browse all active requests
  • Browse all active offers
  • Search listings by keyword
  • Filter listings by tags

4. Contact Information Display

  • View contact information for request creators
  • View contact information for offer providers
  • Contact methods include email and phone (if provided)
  • Organization contact information (if applicable)
  • View designated organization contact person with role/title

5. User Dashboard

  • View all own requests and offers
  • Quick actions to archive/delete listings
  • Simple status indicators (active/archived)

Upcoming MVP Features

6. hREA Completion & Real-time Signals

Milestone: MVP: hREA Completion & Real-time Signals

  • Complete hREA entity mapping end-to-end (#1)
  • Real-time signal integration throughout the application (#12)
  • Action hash type safety improvements (#25)

7. Chat System

Milestone: MVP: Chat System

  • 1-to-1 conversations initiated from request/offer listings (#91)
  • Real-time messaging via Holochain signals
  • Global notification system (#51)
  • "Propose a Deal" action within conversation threads
  • System messages for proposal lifecycle events

8. Exchange Process & Reputation

Milestone: MVP: Exchange Process & Reputation

  • hREA Agreement/Commitment/EconomicEvent lifecycle (#90)
  • Conversation-embedded proposals and negotiation
  • Review and reputation system (star ratings, on-time/as-agreed flags)
  • Administrator inbox and task management (#52)
  • Administrator audit trail (#53)
  • Feedback-conditional fulfillment gate

Future Features (Post-MVP)

These features will be implemented after the MVP:

  • Advanced matching algorithms

MVP User Stories

This document describes the primary user journeys for the simplified MVP version.

Primary User Roles

Requester

A user who needs help or services from others in the community.

Provider

A user who offers help or services to others in the community.

User Stories

As a Requester

  1. Create a Request

    • I want to post a request describing what I need
    • So that potential providers can see what I'm looking for
  2. View My Requests

    • I want to see a list of my own requests
    • So that I can manage them
  3. Archive a Request

    • I want to archive a request that's been fulfilled
    • So that it's no longer visible to the public but I can still reference it
  4. Delete a Request

    • I want to permanently delete a request I no longer need
    • So that it's completely removed from the system
  5. Browse Offers

    • I want to browse all active offers from providers
    • So that I can find someone who can help me
  6. Contact a Provider

    • I want to see contact information for providers
    • So that I can reach out to them directly

As a Provider

  1. Create an Offer

    • I want to post an offer describing what services I can provide
    • So that potential requesters can see what I'm offering
  2. View My Offers

    • I want to see a list of my own offers
    • So that I can manage them
  3. Archive an Offer

    • I want to archive an offer that's no longer available
    • So that it's no longer visible to the public but I can still reference it
  4. Delete an Offer

    • I want to permanently delete an offer I no longer want to provide
    • So that it's completely removed from the system
  5. Browse Requests

    • I want to browse all active requests from requesters
    • So that I can find someone I can help
  6. Contact a Requester

    • I want to see contact information for requesters
    • So that I can reach out to them directly

Secondary User Stories

As Any User

  1. Search Listings

    • I want to search requests and offers by keywords
    • So that I can quickly find relevant listings
  2. Filter by Tags

    • I want to filter listings by tags/categories
    • So that I can narrow down to my areas of interest
  3. View Contact Information

    • I want to see clear contact information for listings
    • So that I know how to get in touch

Contact Information Display

This document describes how contact information is displayed in the MVP version.

Purpose

The MVP focuses on facilitating direct communication between users by clearly displaying contact information. This removes the need for complex in-app messaging systems while still enabling connections.

Contact Information Fields

Required Information

  • Username/display name
  • Profile picture (if available)

Optional Information

  • Email address
  • Phone number
  • Preferred contact method
  • Organization name (if applicable)
  • Organization contact info (if applicable)

Display Locations

Request Detail View

Contact information for the requester is displayed at the bottom of the request details page.

Offer Detail View

Contact information for the provider is displayed at the bottom of the offer details page.

Privacy Considerations

User Control

  • Users can choose which contact methods to display
  • Users can hide contact information entirely (though this reduces utility)
  • All contact information is opt-in

Data Handling

  • Contact information is only visible to logged-in users
  • No public scraping of contact information is allowed
  • Users can update their contact information at any time

UI Implementation

Contact Display Component

A standardized component displays contact information consistently:

  • Clear visual separation from listing content
  • Prominent "Contact" heading
  • Standardized formatting for each contact method
  • Links/buttons for easy contact initiation

Responsive Design

  • Mobile-friendly contact display
  • Appropriate sizing for different screen widths
  • Accessible for users with disabilities

Post-MVP Features Overview

This directory contains documentation for features planned beyond the current MVP scope.

Re-scoped to MVP

The following features were originally post-MVP but have been re-scoped into the MVP based on project priorities:

  1. Exchange Process - Conversation-first exchange coordination system (#90) — Milestone: MVP: Exchange Process & Reputation
  2. Chat System - Conversation-first messaging with exchange integration (#91) — Milestone: MVP: Chat System
  3. Reputation System - Review and rating mechanisms — Milestone: MVP: Exchange Process & Reputation

Note: Documentation remains in this directory for organizational continuity. These features are now actively targeted for MVP delivery.

Remaining Post-MVP Features

  1. Matching System - Algorithmic matching of requests with offers

Implementation Approach

Post-MVP features will be implemented after the MVP has been validated with users:

  • Gather feedback on core functionality
  • Iterate on user experience
  • Gradually add advanced features
  • Maintain backward compatibility

Exchange Process Feature

Status: Re-scoped to MVP. Targeted for MVP: Exchange Process & Reputation milestone. See #90.

Overview

The Exchange Process is the core value exchange mechanism of the Requests and Offers application. This feature transforms static requests and offers into dynamic, managed transactions between community members, providing the economic coordination layer that enables actual value exchange within the peer-to-peer marketplace.

Architecture Strategy: hREA-First

Rather than building custom exchange zomes from scratch, this feature leverages the hREA (Holochain Resource-Event-Agent) DNA that is already integrated into the application. The hREA DNA provides built-in zomes for Agreement, Commitment, EconomicEvent, and Fulfillment -- covering the core economic coordination flow. Only the review/reputation system requires a lightweight custom zome, since subjective quality ratings are outside the ValueFlows ontology.

This approach:

  • Minimizes custom Rust code by reusing battle-tested hREA zomes
  • Aligns with ValueFlows -- the open vocabulary for distributed economic coordination
  • Builds on existing infrastructure -- Proposals and Intents are already mapped via the hREA service and GraphQL layer
  • Reduces maintenance burden -- hREA upstream improvements benefit the project automatically

Vision

The Exchange Process will evolve the platform from a simple bulletin board into a sophisticated economic coordination system enabling:

  • Conversation-Driven Exchanges: Natural chat-first flow that progressively formalizes into hREA entities
  • Structured Negotiations: Formal proposal and agreement workflows via hREA, triggered from within conversations
  • Trust Building: Review and reputation systems (custom zome)
  • Economic Coordination: Complete lifecycle management from conversation to completion

Core Value Proposition

  • For Users: Simple, natural workflow — chat first, formalize when ready
  • For Community: Trust-building through structured feedback and reputation
  • For Platform: Foundation for economic activity and user engagement

Design Philosophy

Conversation-First Model (Simbi-Inspired)

Inspired by Simbi's skills exchange platform, the exchange process follows a conversation-first, proposal-second model. Rather than starting with formal hREA entities, users begin by chatting — discussing details, negotiating terms, and building trust — then optionally formalize their agreement through a "Propose a Deal" action within the conversation.

Core Principles

  1. Chat Before Contract: Every exchange begins as a conversation, not a form submission
  2. Optional Formalization: The hREA lifecycle (Proposal → Agreement → Commitment → EconomicEvent) is triggered from within conversations, not as a standalone flow
  3. Context Preservation: The full conversation history accompanies every formal agreement
  4. Human-Centered Flow: Technology adapts to how people naturally negotiate, not the other way around
  5. Progressive Formalization: Informal chat → optional proposal → formal agreement → tracked fulfillment

User Action → hREA Entity Mapping

User ActionhREA Entity CreatedNotes
Start ConversationNonePure chat — no hREA involvement yet
"Propose a Deal" (in chat)Proposal + IntentsCounter-proposal linked to original listing
Accept ProposalAgreement + CommitmentsFormalizes what both parties committed to
Complete WorkEconomicEventRecords actual fulfillment
Confirm FulfillmentFulfillment linkConnects Event → Commitment
Leave ReviewCustom zome entryOutside ValueFlows ontology

User Journey & Workflow

graph TD
    A[User Sees Request/Offer] --> B[Start Conversation]
    B --> C[Chat Freely - Discuss Details & Terms]
    C --> D{Ready to Formalize?}

    D -->|Yes| E[Click 'Propose a Deal' in Chat]
    D -->|Keep Chatting| C

    E --> F[Fill Proposal Details]
    F --> G[hREA Proposal Created - Status: Pending]

    G --> H[Other Party Reviews Proposal in Chat]
    H --> I{Decision}

    I -->|Accept| J[hREA Agreement Created with Commitments]
    I -->|Counter| K[Counter-Proposal Created in Chat]
    I -->|Decline| L[Continue Chatting or End]

    K --> H

    J --> M[Both Parties Work on Exchange]
    M --> N[Provider Records EconomicEvent]
    N --> O[Receiver Confirms Fulfillment]

    O --> P{Both Commitments Fulfilled?}
    P -->|Yes| Q[Exchange Complete - Review Phase]
    P -->|No| R[Wait for Other Party]
    R --> P

    Q --> S[Both Parties Submit Reviews]
    S --> T[Exchange Fully Closed]

    L --> U[End - No Deal Made]

Key User Actions

  1. Conversing: Users start a conversation from a request/offer listing (#91)
  2. Proposing: Either party creates a Proposal (hREA) from within the conversation via "Propose a Deal"
  3. Negotiating: Accept, counter, or decline proposals — all within the chat thread
  4. Agreeing: Acceptance creates an hREA Agreement bundling Commitments from both parties
  5. Working: Both parties collaborate on the actual service/exchange
  6. Completing: Each party records an EconomicEvent fulfilling their Commitment
  7. Reviewing: Mutual feedback via the custom reviews system

Core Workflow Principles

  • Conversation First: Exchange begins with human interaction, not form filling
  • Creator Control: Request/offer creators choose their collaboration partners
  • Progressive Formalization: Chat → Proposal → Agreement → Fulfillment
  • Mutual Completion: Both parties must record fulfillment independently
  • Quality Feedback: Review system as a conditional gate for economic events
  • Comprehensive Dashboard: Clear overview of all exchange activities

hREA Mapping

How Exchange Concepts Map to ValueFlows

Exchange ConcepthREA EntityNotes
Request/Offer listingProposal + IntentsAlready implemented with mappers
Conversation about a listingNo hREA entityChat system (#91) — prerequisite
"Propose a Deal" in chatProposal (counter-offer)New Proposal linked to original via Intents
Approved exchangeAgreementBundles Commitments from both parties
Party obligationsCommitmentWhat each party commits to deliver
Work completionEconomicEventRecords actual fulfillment of a Commitment
Completion trackingFulfillmentLinks EconomicEvent → Commitment
Intent satisfactionSatisfactionLinks Commitment → Intent
Service categoriesResourceSpecificationAlready mapped from Service Types
Star ratings / reviewsCustom reviews zomeNo hREA equivalent exists
Reputation scoringCustom reviews zomeAggregated from review data

Data Linking Model

The conversation-first model creates bidirectional relationships between the chat layer (conversations zome) and the hREA economic layer. These links enable navigating from a conversation to the resulting agreement and back.

Conversation → Agreement Linking

When a counter-proposal is accepted and an Agreement is created:

  1. The conversationId is stored in the Agreement's note field — giving the Agreement a permanent, human-readable reference to its originating conversation.
  2. A ConversationToAgreement link is created in the requests_and_offers DNA, using the ActionHash of both entities as anchor and target. This link enables efficient bidirectional lookup without scanning all entries.
// conversationId flows from CounterProposalInput into Agreement creation
// Agreement.note stores the conversationId for human-readable reference
// ConversationToAgreement link in requests_and_offers DNA enables fast lookup

Cross-Reference Table

FromToLink MechanismVisibility
ConversationAgreementnote field + ConversationToAgreement linkPrivate (participants only)
AgreementConversationReverse lookup via link anchorPrivate
AgreementCommitmentshREA native bundlingPrivate
ConversationCommitmentsTransitive via AgreementPrivate
Counter-ProposalOriginal ProposalhREA Intent references same ResourceSpecsPrivate
Request/OfferProposal (hREA)hREA Intent → ResourceSpecificationPublic

Lookup Functions

Two zome functions enable bidirectional navigation:

  • get_conversation_for_agreement(agreement_id: ActionHash) → returns the conversationId from the Agreement's note field
  • get_agreement_for_conversation(conversation_id: ActionHash) → traverses the ConversationToAgreement link to return the Agreement ActionHash

Economic Flow (hREA)

graph TD
    A[Alice - Agent] -- creates --> RP[Request Proposal + Intents]
    B[Bob - Agent] -- starts conversation about --> RP

    subgraph Conversation Thread
        CHAT[Chat: Discuss details & terms]
        PROPOSE[Bob clicks 'Propose a Deal']
        CHAT --> PROPOSE
        PROPOSE --> CP[Counter-Proposal + Intents]
    end

    B --> CHAT
    CP -- linked to --> RP

    CP -- approved, creates --> AG[Agreement]
    AG -- bundles --> C1[Commitment: Bob provides service]
    AG -- bundles --> C2[Commitment: Alice provides payment/exchange]

    C1 -- fulfilled by --> EE1[EconomicEvent: Service delivered]
    C2 -- fulfilled by --> EE2[EconomicEvent: Payment/exchange completed]

    EE1 --> F1[Fulfillment link]
    EE2 --> F2[Fulfillment link]

    F1 --> DONE[Agreement Complete]
    F2 --> DONE

    DONE --> REV[Review Phase - Custom Zome]
    REV -- conditional gate --> CLOSED[Exchange Closed]

Feedback-Conditional Fulfillment

As documented in the hREA Integration Specification, the feedback mechanism acts as a quality gate:

  1. Work Completion: Provider completes committed work and records an EconomicEvent
  2. Feedback Request: Provider can request feedback from recipient
  3. Feedback Evaluation: Recipient provides positive/negative feedback via the custom reviews zome
  4. Conditional Fulfillment: Full economic fulfillment is confirmed only with positive feedback
  5. Resolution Process: Negative feedback triggers a resolution pathway before final fulfillment

Core Feature Set

1. Conversation-Based Proposal System (hREA Proposals)

Responses to listings are modeled as hREA Proposals with linked Intents, created from within conversation threads (#91). This uses the same GraphQL API already in place for request/offer creation.

// A counter-proposal is a new Proposal whose Intents reference
// the same ResourceSpecifications as the original listing.
// Created via "Propose a Deal" action within a conversation.

interface CounterProposalInput {
  originalProposalId: string;       // hREA ID of the request/offer
  conversationId: string;           // Conversation where the proposal was made
  description: string;
  timeEstimate?: number;
  mediumOfExchange?: string;        // ResourceSpecification ID
  customTerms?: string;
}

Capabilities:

  • "Propose a Deal" action within conversation threads (relies on #91)
  • Intents reference the same ResourceSpecifications as the original
  • Track all proposals in unified dashboard
  • Withdraw proposals before approval (delete hREA Proposal)
  • Creator control over collaboration partner selection
  • Status derived from hREA Proposal state
  • Full conversation context preserved with every proposal

2. Agreement & Commitment System (hREA)

When a counter-proposal is approved, an hREA Agreement is created that bundles Commitments from both parties.

// Uses hREA Agreement GraphQL mutations (to be added)
// Agreement bundles Commitments via Satisfaction links to Intents

interface AgreementCreation {
  counterProposalId: string;        // The approved counter-proposal
  originalProposalId: string;       // The original request/offer
  conversationId: string;           // Stored in Agreement.note + creates ConversationToAgreement link
  // Commitments are auto-created from the Intents of both Proposals
}

Capabilities:

  • Automatic Agreement creation upon counter-proposal approval
  • Commitments derived from both parties' Intents
  • Progress tracked via Fulfillment links (Commitment → EconomicEvent)
  • Timeline tracking through EconomicEvent timestamps
  • Full request/offer context preserved via Proposal references

3. Completion Tracking (hREA EconomicEvents)

Each party records an EconomicEvent when they fulfill their Commitment.

// Uses hREA EconomicEvent GraphQL mutations (to be added)

interface CompletionRecord {
  commitmentId: string;             // Which Commitment is being fulfilled
  note?: string;                    // Optional completion notes
  // Creates an EconomicEvent + Fulfillment link
}

Capabilities:

  • Independent completion confirmation from both parties
  • EconomicEvent records what actually happened
  • Fulfillment links connect Events to Commitments
  • Status derived from fulfillment state of all Commitments in the Agreement

4. Review & Feedback (Custom Zome)

This is the only custom Rust zome needed, since hREA/ValueFlows has no concept of subjective quality ratings. Reviews are triggered after both parties fulfill their Commitments, and act as a conditional gate for final economic fulfillment confirmation.

  • Both parties submit a review after exchange completion
  • Star ratings (1-5), "on time" and "as agreed" flags, optional comments
  • Positive feedback confirms fulfillment; negative feedback triggers resolution

For the full review data model, reputation scoring, moderation, privacy considerations, and technical architecture, see the dedicated Reputation System document.

5. Exchange Dashboard

  • Tabbed Interface: Conversations | Proposals | Active | Completed | Reviews
  • Status Filtering: Real-time status-based filtering and display
  • Search Capabilities: Find specific exchanges quickly
  • User Statistics: Total exchanges, average rating, reputation metrics
┌───────────────────────────────────────────────────────┐
│  My Exchanges                                         │
├───────────────────────────────────────────────────────┤
│ ┌─────────────┬──────────┬────────┬──────────┬──────┐ │
│ │Conversations│Proposals │Active  │Completed │Reviews│ │
│ └─────────────┴──────────┴────────┴──────────┴──────┘ │
│                                                       │
│ [Exchange List/Grid View]                             │
│                                                       │
│ ┌─────────────────────────────────────────────────┐   │
│ │ Reputation Score: 4.8 ⭐                        │   │
│ │ Total Exchanges: 47                             │   │
│ │ Completion Rate: 96%                            │   │
│ └─────────────────────────────────────────────────┘   │
└───────────────────────────────────────────────────────┘

6. Communication System (Prerequisite)

The communication system is now a prerequisite for the exchange process, not a future feature. Conversations are the entry point for all exchanges.

See Chat System and Issue #91 for the full technical specification, including:

  • 1-to-1 conversations initiated from request/offer listings
  • "Propose a Deal" action within conversation threads
  • System messages for proposal actions (accepted, countered, declined)
  • Organization chat channels
  • Real-time message delivery via Holochain signals

Advanced Features (Future)

7. Advanced Matching Algorithm

interface MatchingAlgorithm {
  serviceTypeAlignment: number;     // ResourceSpecification compatibility
  timeCompatibility: number;
  locationScore: number;
  trustCompatibility: number;       // Based on review reputation data
  preferenceMatch: number;
  calculateScore(): number;
}
  • AI-powered suggestions
  • Preference learning
  • Compatibility scoring based on ResourceSpecifications
  • Reputation-weighted matching via review data

8. Mutual Credit System

Could potentially leverage hREA's EconomicEvent and Resource tracking for credit flows.

interface MutualCreditAccount {
  holder: AgentPubKey;
  balance: number;
  creditLimit: number;
  transactions: CreditTransaction[];
  trustNetwork: TrustRelationship[];
}

interface CreditTransaction {
  id: ActionHash;
  from: AgentPubKey;
  to: AgentPubKey;
  amount: number;
  agreementId: string;              // hREA Agreement ID
  timestamp: Timestamp;
  status: TransactionStatus;
}
  • Zero-sum credit creation
  • Trust-based credit limits
  • Transaction history via hREA EconomicEvents
  • Balance management
  • Network visualization

Future: Unyt Smart Agreements

Unyt is a Holochain-based decentralized accounting platform that provides Smart Agreements called RAVEs (Recorded Agreement, Verifiably Executed). RAVEs are similar to blockchain smart contracts but designed for Holochain's agent-centric architecture, using Rhai scripting for programmable business logic.

Potential Integration Points

  • Agreement Enforcement: When an hREA Agreement is created, a RAVE could enforce its conditions programmatically (deadlines, quality gates, automatic fulfillment)
  • Automated Fulfillment: RAVEs could auto-create EconomicEvents when both parties confirm completion
  • Mutual Credit Bridge: Unyt already supports community currencies — potential backend for our medium-of-exchange system
  • Dispute Resolution: Rhai scripts could automate timeout-based dispute triggers

RAVE Three-Layer Architecture

  1. Reusable Code Templates — Pre-built Rhai scripts for common exchange patterns
  2. Configured Agreements — Specific rules per exchange (inputs, logic, outputs)
  3. SAVEDs (execution records) — Cryptographically signed, tamper-proof records of execution

See Issue #92 for the dedicated exploration and research tasks.

Technical Architecture

Backend: Hybrid hREA + Custom Zome

The exchange process uses a hybrid architecture: hREA handles the economic coordination flow, while a single custom zome handles reviews.

┌──────────────────────────────────────────────────┐
│ hREA DNA (existing, bundled)                     │
│                                                  │
│  ┌──────────┐  ┌────────────┐  ┌──────────────┐  │
│  │ Proposal │  │ Agreement  │  │ EconomicEvent│  │
│  │ + Intent │  │+Commitment │  │ +Fulfillment │  │
│  └──────────┘  └────────────┘  └──────────────┘  │
│  ┌─────────────────────┐  ┌───────────────┐      │
│  │ResourceSpecification│  │ Satisfaction  │      │
│  └─────────────────────┘  └───────────────┘      │
└──────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────┐
│ requests_and_offers DNA (existing custom zomes)  │
│                                                  │
│  ┌──────────┐  ┌────────┐  ┌───────────────┐     │
│  │ requests │  │ offers │  │ service_types │     │
│  └──────────┘  └────────┘  └───────────────┘     │
│  ┌────────┐  ┌────────────────┐  ┌───────────┐   │
│  │ users  │  │ organizations  │  │   admin   │   │
│  └────────┘  └────────────────┘  └───────────┘   │
│  ┌─────────────────────┐  ┌─────────────────┐    │
│  │ mediums_of_exchange │  │    reviews      │    │
│  └─────────────────────┘  │  (NEW - custom) │    │
│                           └─────────────────┘    │
│  ┌─────────────────────┐                         │
│  │   conversations     │                         │
│  │  (NEW - #91)        │                         │
│  └─────────────────────┘                         │
└──────────────────────────────────────────────────┘

New custom zome: reviews

dnas/requests_and_offers/zomes/
├── integrity/reviews/
│   └── src/lib.rs          # ExchangeReview entry, validation, link types
└── coordinator/reviews/
    └── src/lib.rs          # CRUD handlers, reputation aggregation

hREA GraphQL operations to add (frontend only):

  • Agreement: create, update, query
  • Commitment: create, query
  • EconomicEvent: create, query
  • Fulfillment: create, query
  • Satisfaction: create, query

Frontend (SvelteKit + Effect-TS 7-Layer Architecture)

Service Layer:

The exchange service orchestrates calls to both the hREA GraphQL API and the custom reviews zome.

export const ExchangeService = Context.GenericTag<ExchangeService>("ExchangeService");

export const makeExchangeService = Effect.gen(function* () {
  const hrea = yield* HREAService;          // Existing hREA GraphQL service
  const client = yield* HolochainClientService; // For custom reviews zome

  // hREA operations (via GraphQL)
  const createCounterProposal = (input: CounterProposalInput) => ...
  const approveProposal = (id: string) => ...     // Creates Agreement + Commitments
  const rejectProposal = (id: string) => ...      // Withdraws/deletes Proposal
  const getAgreement = (id: string) => ...
  const recordCompletion = (commitmentId: string) => ... // Creates EconomicEvent
  const getExchangeStatus = (agreementId: string) => ... // Checks Fulfillment state

  // Custom reviews zome operations (see reputation-system.md for details)
  const submitReview = (input: CreateReviewInput) => ...
  const getReviews = (agreementId: ActionHash) => ...
  const getReputation = (agentPubKey: AgentPubKey) => ...

  return { createCounterProposal, approveProposal, rejectProposal,
           getAgreement, recordCompletion, getExchangeStatus,
           submitReview, getReviews, getReputation };
});

Store Layer:

  • Svelte 5 Runes reactive state management
  • All 9 standardized helper functions implemented
  • Cache management with TTL and sync helpers
  • Status-aware event emission system
  • Combines hREA data (agreements, fulfillments) with custom data (reviews)

Component Library:

components/exchanges/
├── proposals/
│   ├── CounterProposalForm.svelte
│   ├── ProposalCard.svelte
│   └── ProposalManager.svelte
├── agreements/
│   ├── AgreementDashboard.svelte
│   ├── AgreementTimeline.svelte
│   └── CompletionConfirm.svelte
├── reviews/                       (see reputation-system.md)
│   ├── ReviewForm.svelte
│   ├── StarRating.svelte
│   └── ReputationDisplay.svelte
└── credit/                        (future)
    ├── BalanceDisplay.svelte
    ├── TransactionHistory.svelte
    └── CreditNetwork.svelte

Performance Optimizations:

  • Cache strategy with module-level cache (5 minutes default TTL)
  • Comprehensive loading/error state management
  • Sub-2-second dashboard load times
  • Responsive design for mobile and desktop

Integration Points

  • Chat System (#91): Conversations are the entry point — proposals created from within chat threads
  • hREA DNA: Agreement, Commitment, EconomicEvent, Fulfillment via GraphQL
  • Request/Offer Domains: Proposals linked via hREA Intent references
  • User/Organization System: Agents mapped to hREA Agents (already implemented)
  • Service Types / MoE: Mapped to hREA ResourceSpecifications (already implemented)
  • Administration: Platform-level moderation only (user approval, service type management) — administrators do NOT have access to private exchange data (see Privacy Model)
  • Navigation: "My Exchanges" in primary nav, deep linking, breadcrumbs, URL state

Current Status

  • hREA DNA: Bundled and integrated with Proposal, Intent, Agent, ResourceSpecification operations working
  • hREA GraphQL: Agreement, Commitment, EconomicEvent, Fulfillment, Satisfaction operations not yet added
  • Custom reviews zome: Not yet implemented
  • Chat system (#91): Not yet implemented — prerequisite for conversation-first flow
  • Exchange UI: Removed -- ready for fresh rebuild

Next Steps

  1. Implement the chat system (#91) — prerequisite for conversation-first exchange flow
  2. Add GraphQL mutations/queries/fragments for Agreement, Commitment, EconomicEvent, Fulfillment, Satisfaction
  3. Extend the hREA service (hrea.service.ts) with the new operations
  4. Implement the lightweight reviews custom zome (integrity + coordinator)
  5. Build the ExchangeService orchestrating hREA + reviews
  6. Create the exchange store with Svelte 5 Runes
  7. Design and build UI components (integrated with conversation threads)
  8. Implement routing and navigation
  9. Add comprehensive testing

Implementation Roadmap

Phase 0: Chat System (Prerequisite)

Issue #91 must be substantially complete before exchange features can be built. The conversation system provides:

  • 1-to-1 conversations initiated from request/offer listings
  • "Propose a Deal" action within conversation threads
  • System messages for proposal lifecycle events
  • Real-time message delivery

Phase 1: hREA Exchange Foundation

  • Add Agreement, Commitment, EconomicEvent GraphQL operations
  • Extend hREA service and store
  • Build "Propose a Deal" flow within conversation threads (relies on #91)
  • Implement approval → Agreement creation workflow (triggered from chat acceptance)
  • Build completion tracking via EconomicEvent + Fulfillment

Phase 2: Reviews & Reputation (Custom Zome)

  • Implement reviews integrity + coordinator zome
  • Add review service and store (Effect-TS)
  • Build review UI components (StarRating, ReviewForm, ReputationDisplay)
  • Integrate feedback-conditional fulfillment gate
  • Exchange dashboard UI

Phase 3: Advanced Features

  • Matching algorithm (leveraging ResourceSpecification compatibility + reputation data)
  • Advanced search and discovery
  • Analytics dashboard

Phase 4: Future Explorations

  • Unyt Smart Agreements: Programmatic agreement enforcement via RAVEs (see #92)
  • Mutual Credit System: Potentially via hREA EconomicEvent tracking or Unyt community currencies
  • AI-Powered Matching: Intelligent pairing based on conversation history and reputation

Success Metrics

User Engagement

  • Conversation-to-proposal conversion rate
  • Agreement completion rate
  • Review submission rate
  • User retention

Quality Metrics

  • Average review score
  • Dispute rate
  • Time to completion
  • Match success rate

Economic Metrics

  • Total value exchanged
  • Credit velocity
  • Network growth rate
  • Active user ratio

Technical Considerations

Performance

  • Lazy loading for large datasets
  • Pagination for exchange history
  • Caching strategy for reputation scores
  • GraphQL query optimization for hREA operations

Security

  • hREA validation rules enforce economic integrity
  • Custom zome validation prevents review tampering
  • Feedback-conditional fulfillment as quality gate
  • Dispute resolution process

Privacy Model (Critical)

All exchange data is private to the two exchange parties only. This is a fundamental design requirement enforced at the zome level, not a frontend concern.

What is private:

  • Conversations (all messages, proposal actions, negotiation history)
  • Counter-proposals and their terms
  • Agreements and all associated data
  • Commitments (what each party is obligated to deliver)
  • Economic Events (records of actual fulfillment)
  • Fulfillment links and completion records
  • Reviews tied to a specific exchange

Administrator exclusion — by design:

  • The Administration role covers platform-level moderation only: user approval/suspension, service type management, organization oversight
  • Administrators have NO ACCESS to exchange data — this is enforced at the zome level, not the frontend
  • No get_agreement, get_commitment, or get_economic_event zome functions are accessible to admin capability tokens
  • This design is intentional: peer-to-peer exchanges are private contracts between two consenting parties

Enforcement mechanism:

  • Private Holochain entries restricted to exchange participants via capability tokens
  • get_* zome functions for exchange entities validate the calling agent is a named participant
  • The conversations zome restricts message/conversation access to participants only
  • ConversationToAgreement links are only traversable by the conversation participants

Scalability

  • DHT sharding strategy (inherited from hREA)
  • Cross-DNA indexing for hREA ↔ custom zome references
  • Query performance tuning
  • State management efficiency

Dependencies

Technical:

  • hREA DNA with Agreement, Commitment, EconomicEvent zomes
  • @valueflows/vf-graphql-holochain GraphQL layer
  • Effect-TS error handling
  • SvelteKit frontend

Feature:

  • Chat/Conversation system (#91) — prerequisite for conversation-first flow
  • User authentication system (existing)
  • Request/Offer foundation (existing)
  • Service types → ResourceSpecifications mapping (existing)
  • hREA Agent mapping (existing)

References:

Matching System

This document describes the algorithmic matching system planned for post-MVP implementation.

Overview

The matching system will automatically suggest relevant connections between requests and offers based on:

  • Semantic content analysis
  • User preferences and history
  • Geographic proximity
  • Reputation scores

Matching Algorithms

Content-Based Matching

  • Keyword analysis
  • Tag similarity
  • Category matching
  • Semantic understanding

Collaborative Filtering

  • User behavior patterns
  • Similar user preferences
  • Historical matching success
  • Community trends

Contextual Matching

  • Time sensitivity
  • Geographic relevance
  • Availability windows
  • Resource constraints

Implementation Phases

Phase 1: Basic Tag Matching

  • Simple tag-based suggestions
  • Category filtering
  • Manual feedback incorporation

Phase 2: Semantic Analysis

  • Natural language processing
  • Intent recognition
  • Context understanding

Phase 3: Machine Learning

  • Predictive matching
  • Personalization algorithms
  • Continuous improvement

User Interface

Matching Dashboard

  • Suggested connections
  • Matching confidence scores
  • Quick action buttons
  • Feedback mechanisms

Notification System

  • Match alerts
  • Preference updates
  • Performance reports

Privacy Considerations

Data Usage

  • Transparent matching criteria
  • User-controlled preferences
  • Opt-out mechanisms
  • Data minimization
  • Explicit matching consent
  • Preference management
  • Feedback control
  • #90 — hREA Exchange Process (matching feeds into proposal discovery)
  • #91 — Chat System (matched users start conversations)
  • #92 — Unyt Smart Agreements Exploration (future)

Chat System: Conversation-First Messaging

Status: Re-scoped to MVP. Targeted for MVP: Chat System milestone. See #91.

Overview

The chat system provides conversation-first messaging that serves as the entry point for all exchanges. Inspired by Simbi's model where conversations precede proposals, this system enables users to discuss, negotiate, and build trust before optionally formalizing exchanges through hREA.

Core principle: Conversations are the primary unit. Proposals (hREA) are optional actions embedded within chat threads, not standalone entities.

User finds Request/Offer → Starts Conversation → Chat freely →
  Optionally Propose Deal → Accept/Counter/Decline → Complete Exchange

See Issue #91 for the full technical specification, architecture design, and implementation phases.

Design Philosophy

  • Chat Before Contract: Every exchange begins as a conversation, not a form
  • Optional Formalization: hREA proposals are created from within chat, not as standalone flows
  • Context Preservation: Full conversation history accompanies every formal agreement
  • Human-Centered: Technology adapts to natural negotiation patterns

Core Features

Real-time Communication

  • Instant messaging with Holochain signal-based delivery
  • Typing indicators (ephemeral signals, ZipZap pattern)
  • Message status (sent/received/read)
  • Online presence

Rich Content

  • Markdown support in messages
  • File attachments (via holochain-open-dev/file-storage)
  • System messages for proposal actions ("User proposed a deal", "Proposal accepted")
  • Reply-to threading within conversations

Conversation Management

  • Conversations linked to specific requests/offers
  • Organization chat channels (Simbi-inspired public walls)
  • Searchable message history
  • Notification controls

Conversation-Exchange Integration

graph LR
    A[Conversation Created] --> B[Users Chat Freely]
    B --> C{Propose a Deal?}
    C -->|Yes| D[hREA Proposal Created]
    C -->|Not Yet| B
    D --> E[System Message Posted]
    E --> F{Accept / Counter / Decline}
    F -->|Accept| G[Agreement + Commitments Created]
    F -->|Counter| D
    F -->|Decline| B
    G --> H[Exchange Tracked via hREA]

Security and Privacy

End-to-End Encryption

  • Message encryption via Holochain's agent-centric security model
  • Cryptographic signing of all messages
  • Conversation access restricted to participants

Access Controls

  • Only participants can read/write messages
  • Organization channel moderation by coordinators/admins
  • User blocking capabilities
  • Message retention policies

Implementation Reference

The full technical specification lives in Issue #91, including:

  • Backend architecture (integrity + coordinator zomes)
  • Volla Messages patterns (time-bucketed indexing, signal lifecycle)
  • ZipZap patterns (ephemeral signals for typing indicators)
  • Vines patterns (bead-thread model for rich message types)
  • Frontend 7-layer Effect-TS implementation
  • 10-phase implementation roadmap

Key References

  • Volla Messages: https://github.com/holochain-apps/volla-messages (primary Holochain chat reference)
  • Vines: https://github.com/lightningrodlabs/vines (bead-thread conceptual model)
  • ZipZap: https://github.com/lightningrodlabs/zipzap (ephemeral signal pattern)
  • Simbi: https://simbi.com (conversation-first UX inspiration)
  • #90 — hREA Exchange Process (proposals and agreements triggered from conversations)
  • #91 — Chat System implementation (full technical specification)
  • #92 — Unyt Smart Agreements exploration (future agreement enforcement)

Review & Reputation System

Status: Re-scoped to MVP. Part of MVP: Exchange Process & Reputation milestone. See #90.

Overview

The Review & Reputation System provides trust signals for users based on exchange outcomes. It is the only custom Rust zome required for the exchange process, since hREA/ValueFlows covers the economic coordination flow but has no concept of subjective quality ratings.

This system is tightly coupled with the Exchange Process: reviews are triggered after both parties fulfill their Commitments in an hREA Agreement, and positive feedback acts as a conditional gate for final economic fulfillment confirmation.

Scope

  • Exchange Reviews: Post-completion feedback between exchange parties (core)
  • Reputation Scoring: Aggregated trust metrics derived from review data
  • Moderation: Quality control, spam prevention, dispute handling
  • Privacy & Fairness: Bias prevention and data protection

Review System

Data Model

Backend (Custom Zome)

#![allow(unused)]
fn main() {
// Integrity zome: reviews_integrity

#[hdk_entry_helper]
struct ExchangeReview {
    agreement_id: ActionHash,       // References the hREA Agreement
    reviewer: AgentPubKey,
    reviewed: AgentPubKey,
    rating: u8,                     // 1-5 stars
    on_time: bool,
    as_agreed: bool,
    comments: Option<String>,       // Max 200 chars
}

// Link types:
// - AgreementToReview: find reviews for a given exchange
// - AgentToReview: find all reviews for a given user (reputation queries)
}

Frontend Types

interface ExchangeReview {
  id: ActionHash;
  agreementId: string;              // hREA Agreement ID
  reviewer: AgentPubKey;
  reviewed: AgentPubKey;
  rating: number;                   // 1-5 stars
  feedback: {
    onTime: boolean;
    asAgreed: boolean;
    comments?: string;              // Max 200 chars
  };
  createdAt: Timestamp;
}

Rating Structure

  • Star Rating: 1-5 scale for overall service quality
  • On Time: Boolean -- was the exchange completed within the agreed timeframe?
  • As Agreed: Boolean -- did the outcome match what was committed?
  • Comments: Optional free-text feedback (max 200 characters)

Review Criteria

CriterionMeasured ByWeight
Quality of serviceStar rating (1-5)High
TimelinessonTime booleanMedium
ReliabilityasAgreed booleanMedium
Overall feedbackCommentsQualitative

Review Workflow

  1. Both parties fulfill their Commitments (EconomicEvents recorded in hREA)
  2. Exchange enters "Review Phase" -- both parties are prompted to submit reviews
  3. Reviews are submitted independently (mutual review requirement)
  4. Positive feedback confirms final economic fulfillment
  5. Negative feedback triggers the resolution process before fulfillment is confirmed
  6. Ratings are aggregated into the user's reputation score

Feedback-Conditional Fulfillment

As documented in the hREA Integration Specification:

  • Positive feedback confirms that the EconomicEvent truly fulfills the Commitment
  • Negative feedback triggers a resolution pathway -- the exchange is not considered fully closed until resolved
  • This creates a quality assurance layer within the hREA economic flow

Reputation Metrics

Individual Metrics

interface UserReputation {
  userId: AgentPubKey;
  totalExchanges: number;
  averageRating: number;            // Weighted average of all star ratings
  completionRate: number;           // Percentage of agreements fulfilled
  onTimeRate: number;               // Percentage of exchanges completed on time
  asAgreedRate: number;             // Percentage of exchanges completed as agreed
  badges: ReputationBadge[];
  trustScore: number;               // Composite calculated metric
}
MetricSourceCalculation
Average ratingStar ratingsWeighted mean across all reviews received
Completion ratehREA Fulfillment dataFulfilled Commitments / Total Commitments
On-time rateonTime flagsPositive onTime / Total reviews
As-agreed rateasAgreed flagsPositive asAgreed / Total reviews
Trust scoreCompositeWeighted combination of all metrics above

Community Metrics (Future)

  • Peer endorsements
  • Community contributions
  • Leadership roles in organizations
  • Mentorship activities

Achievement Badges

BadgeCriteria
NewcomerFirst completed exchange
Reliable10+ exchanges with 90%+ completion rate
Trusted25+ exchanges with 4.5+ average rating
On Time20+ exchanges with 95%+ on-time rate
Community BuilderActive in 3+ organizations

Technical Architecture

Custom Zome Structure

dnas/requests_and_offers/zomes/
├── integrity/reviews/
│   └── src/lib.rs
│       ├── ExchangeReview entry definition
│       ├── Validation rules:
│       │   ├── Reviewer must be a party to the Agreement
│       │   ├── Rating must be 1-5
│       │   ├── Comments max 200 chars
│       │   ├── One review per reviewer per Agreement
│       │   └── Agreement must have fulfilled Commitments
│       └── Link types: AgreementToReview, AgentToReview
│
└── coordinator/reviews/
    └── src/lib.rs
        ├── create_review(input) -> ExchangeReview
        ├── get_reviews_for_agreement(agreement_id) -> Vec<ExchangeReview>
        ├── get_reviews_for_agent(agent_pub_key) -> Vec<ExchangeReview>
        └── get_reputation(agent_pub_key) -> UserReputation

Frontend Service (Effect-TS)

export const ReviewService = Context.GenericTag<ReviewService>("ReviewService");

export const makeReviewService = Effect.gen(function* () {
  const client = yield* HolochainClientService;

  const submitReview = (input: CreateReviewInput) =>
    client.callZome({
      zome_name: "reviews",
      fn_name: "create_review",
      payload: input,
    }).pipe(
      Effect.mapError((error) => new ReviewError({ cause: error }))
    );

  const getReviewsForAgreement = (agreementId: ActionHash) =>
    client.callZome({
      zome_name: "reviews",
      fn_name: "get_reviews_for_agreement",
      payload: agreementId,
    });

  const getReviewsForAgent = (agentPubKey: AgentPubKey) =>
    client.callZome({
      zome_name: "reviews",
      fn_name: "get_reviews_for_agent",
      payload: agentPubKey,
    });

  const getReputation = (agentPubKey: AgentPubKey) =>
    client.callZome({
      zome_name: "reviews",
      fn_name: "get_reputation",
      payload: agentPubKey,
    });

  return { submitReview, getReviewsForAgreement, getReviewsForAgent, getReputation };
});

Frontend Components

components/exchanges/reviews/
├── ReviewForm.svelte           # Star rating + feedback form
├── StarRating.svelte           # Reusable 1-5 star input/display
├── ReputationDisplay.svelte    # User reputation card with metrics
└── ReviewCard.svelte           # Individual review display

Cross-DNA References

The reviews zome lives in the requests_and_offers DNA but references hREA Agreement IDs (which live in the hrea DNA). This cross-DNA reference is handled by storing the Agreement's ActionHash as a field in the ExchangeReview entry. Queries go through the frontend service layer, which orchestrates calls to both DNAs.

Moderation

Review Quality

  • Validation at zome level: Reviewer must be a party to the Agreement; one review per party per Agreement
  • Rating bounds: Enforced 1-5 range in integrity zome validation
  • Comment length: Enforced 200-char max in integrity zome validation
  • Spam prevention: Cannot review an Agreement you're not part of

Dispute Resolution

  • Negative feedback pathway: Triggers resolution process before economic fulfillment is confirmed
  • Appeal process: Disputed reviews can be flagged for admin moderation (integrates with existing administration zome)
  • Statistical outlier detection: Reviews significantly deviating from a user's average can be flagged

Privacy & Fairness

Bias Prevention

  • Mutual review requirement: Both parties must submit reviews, reducing retaliation bias
  • Statistical outlier detection: Identifies potential review manipulation
  • Recalibration mechanisms: Reputation scores account for review volume (low sample size = lower confidence)

Data Protection

  • Aggregation: Reputation scores are computed aggregates, not exposing individual review details unnecessarily
  • Individual privacy: Reviews are linked to Agreements, not publicly browsable outside exchange context
  • Data portability: Reputation data is stored on the user's source chain, portable by design (Holochain architecture)

Implementation Priority

This system is part of Phase 2 of the exchange process implementation roadmap (see Exchange Process):

  1. Implement reviews integrity + coordinator zome
  2. Add ReviewService (Effect-TS) and review store (Svelte 5 Runes)
  3. Build review UI components (ReviewForm, StarRating, ReputationDisplay)
  4. Integrate feedback-conditional fulfillment gate with hREA flow
  5. Add reputation aggregation and display

Dependencies

  • hREA Agreement system must be operational (Phase 1 of exchange process)
  • Existing administration zome for moderation integration
  • Existing user/organization system for agent identity
  • #90 — hREA Exchange Process (reviews are Phase 2 of exchange roadmap)
  • #91 — Chat System (exchange context for reviews originates from conversations)
  • #92 — Unyt Smart Agreements Exploration (future automated fulfillment gates)

Enhanced Dispute Resolution System

Post-Alpha 6 Feature Proposal

Status: Post-MVP Enhancement
Priority: High for Production Scale
Target Audience: Open Value Networks, Commons-Based Peer Production
Strategic Alignment: Sensorica Governance Principles, Nondominium Architecture


Executive Summary

This proposal outlines a comprehensive dispute resolution system for the Requests and Offers platform, designed to handle conflicts professionally while maintaining alignment with Open Value Network (OVN) principles and agent-centric governance models established in Sensorica and Nondominium projects.

Key Features

  • Agent-Centric Investigation: Evidence collection from individual agent perspectives
  • Progressive Resolution: Multiple escalation levels with community transparency
  • Capture-Resistant Design: Distributed authority preventing admin overreach
  • System Learning: Dispute pattern analysis for continuous improvement
  • OVN Compliance: Full compatibility with Open Value Network governance principles

Strategic Context & Alignment

Sensorica Governance Integration

This system directly supports Sensorica's core principles:

Agent-Centric Design

  • All dispute data flows from individual agent perspectives
  • Evidence collection respects agent autonomy and narrative
  • Resolution outcomes document each stakeholder's experience

Capture-Resistant Governance

  • Transparent documentation prevents administrative capture
  • Multiple resolution pathways avoid binary win/lose scenarios
  • Community learning mechanisms prevent systemic exploitation
  • Appeal processes maintain distributed authority

Commons-Based Coordination

  • Progressive trust model: self-resolution → mediation → formal arbitration
  • Community transparency with privacy protections
  • Collective learning improves governance over time

Nondominium Architecture Synergy

Shared Technical Patterns:

  • Effect-TS integration for robust error handling
  • Agent-centric zome structure for distributed validation
  • Governance logic embedded in business workflows
  • Progressive trust mechanisms for stakeholder relationships

Resource Management Integration:

  • Dispute resolution as governance primitive for Nondominium resources
  • hREA/ValueFlows compatibility for economic coordination conflicts
  • Organization-agnostic design supporting multiple value networks

Technical Architecture

Data Model

Enhanced Dispute Resolution Entity

#![allow(unused)]
fn main() {
pub struct DisputeResolution {
    pub dispute_type: DisputeType,
    pub resolution_type: DisputeResolutionType,
    pub admin_notes: String,
    pub evidence_reviewed: Vec<EvidenceItem>,
    pub stakeholder_interviews: Vec<InterviewSummary>,
    pub compensation_notes: Option<String>,
    pub lessons_learned: Option<String>,
    pub follow_up_required: bool,
    pub resolution_timestamp: Timestamp,
    pub admin_agent: ActionHash,
    pub appeal_window: Duration,
    pub community_feedback: Vec<CommunityInput>,
}

pub enum DisputeType {
    QualityIssues,          // Unsatisfactory completion
    ComplexDispute,         // Multi-faceted conflicts
    BadFaithRefusal,        // Unjustified completion refusal
    ProcessViolation,       // Platform rule violations
    ResourceConflict,       // Nondominium resource disputes
}

pub enum DisputeResolutionType {
    ProviderFavored,        // Quality standards met
    ReceiverFavored,        // Legitimate quality concerns
    SplitDecision,          // Shared responsibility
    MutualAgreementReached, // Admin-mediated resolution
    AppealGranted,          // Resolution overturned
    SystemicIssueIdentified, // Platform improvement required
}

pub struct EvidenceItem {
    pub evidence_type: EvidenceType,
    pub submitted_by: ActionHash,
    pub content_hash: String,
    pub description: String,
    pub timestamp: Timestamp,
    pub verification_status: VerificationStatus,
}

pub enum EvidenceType {
    Communication,          // Messages, emails, chat logs
    Deliverable,           // Files, screenshots, work product
    Timeline,              // Chronological documentation
    RequirementComparison, // Original specs vs delivery
    ThirdPartyAttestation, // External validation
}

pub struct InterviewSummary {
    pub interviewee: ActionHash,
    pub interviewer: ActionHash,
    pub key_points: Vec<String>,
    pub timestamp: Timestamp,
    pub follow_up_actions: Vec<String>,
}
}

Progressive Resolution Workflow States

#![allow(unused)]
fn main() {
pub enum ResolutionStage {
    // Self-Resolution Attempts
    QualityAssessment,      // Provider responds to quality concerns
    RemediationNegotiation, // Parties negotiate fixes

    // Administrative Investigation
    EvidenceCollection,     // Gathering all relevant materials
    StakeholderInterviews,  // Individual agent perspectives
    ContextAnalysis,        // System and relationship factors

    // Resolution Implementation
    DecisionFormulation,    // Admin team consensus building
    OutcomeDocumentation,   // Comprehensive resolution record
    CommunityNotification,  // Privacy-protected transparency

    // Post-Resolution
    AppealPeriod,          // Time for appeal requests
    LessonsLearned,        // System improvement identification
    FollowUpMonitoring,    // Ensure resolution compliance
}
}

Process Flow Diagram

graph TD
    A[Completion Validation Failure] --> B{Dispute Type Classification}

    B -->|Quality Issues| C[Quality Assessment Process]
    B -->|Complex Multi-Party| D[Complex Dispute Escalation]
    B -->|Bad Faith Refusal| E[Bad Faith Investigation]
    B -->|Process Violation| F[Rule Violation Review]

    C --> G[Evidence Collection Phase]
    G --> H[Provider Response Window - 48 Hours]
    H --> I{Provider Response Analysis}

    I -->|Acknowledge + Remediation| J[Remediation Plan Review]
    I -->|Dispute Quality Claims| K[Escalate to Full Investigation]
    I -->|No Response| L[Default Process Initiation]

    J --> M{Receiver Accepts Remediation?}
    M -->|Yes| N[Return to Service Delivery]
    M -->|No| O[Escalate to Administrative Review]

    D --> P[Administrative Investigation Process]
    E --> P
    F --> P
    K --> P
    O --> P
    L --> P

    P --> Q[Comprehensive Evidence Review]
    Q --> R[Multi-Stakeholder Interviews]
    R --> S[Context & Relationship Analysis]
    S --> T[Administrative Team Deliberation]

    T --> U{Resolution Decision Matrix}

    U -->|Provider Favored| V[Quality Standards Validated]
    U -->|Receiver Favored| W[Legitimate Concerns Confirmed]
    U -->|Split Decision| X[Shared Responsibility Determination]
    U -->|Mediated Agreement| Y[Facilitated Mutual Resolution]
    U -->|Systemic Issue| Z[Platform Improvement Required]

    V --> AA[Comprehensive Documentation]
    W --> AA
    X --> AA
    Y --> AA
    Z --> AA

    AA --> BB[Resolution Implementation]
    BB --> CC[Community Transparency Report]
    CC --> DD[Appeal Window Opening]
    DD --> EE[Lessons Learned Analysis]
    EE --> FF[System Improvement Integration]

    FF --> GG[Case Closure - Professional Resolution]
    N --> HH[Return to Main Exchange Flow]

    DD --> II{Appeal Submitted?}
    II -->|Yes| JJ[Appeal Review Process]
    II -->|No| GG
    JJ --> KK{Appeal Validity}
    KK -->|Valid| T
    KK -->|Invalid| GG

Implementation Features

Investigation Tools

Evidence Collection System

  • Automated Timeline Construction: Chronological reconstruction of all exchange communications and events
  • Multi-Format Evidence Support: Text, images, files, video recordings, screen captures
  • Evidence Integrity Verification: Cryptographic hashing and timestamp validation
  • Privacy-Preserving Collection: Redaction tools for sensitive information
  • Cross-Platform Integration: Import from email, chat systems, project management tools

Stakeholder Interview Framework

  • Structured Interview Templates: Standardized questions ensuring comprehensive coverage
  • Individual Perspective Capture: Separate interviews preventing groupthink
  • Cultural Sensitivity Protocols: Adaptation for different communication styles and backgrounds
  • Documentation Standards: Consistent summary formats with key point extraction
  • Follow-Up Action Tracking: Commitments and next steps from each interview

Context Analysis Tools

  • Relationship Mapping: Historical interactions between disputing parties
  • System Pattern Recognition: Identification of recurring conflict themes
  • Resource Dependency Analysis: Understanding of shared resources and constraints
  • Communication Pattern Assessment: Quality and frequency of project communication
  • External Factor Evaluation: Market conditions, technical constraints, force majeure events

Resolution Mechanisms

Multi-Dimensional Decision Framework

  • Quality Standards Assessment: Objective evaluation against defined criteria
  • Process Compliance Review: Adherence to agreed-upon workflows and timelines
  • Communication Effectiveness Analysis: Quality of stakeholder coordination
  • Resource Utilization Evaluation: Efficient use of shared resources and capabilities
  • Community Impact Consideration: Effects on broader network relationships

Compensation Calculation Engine

  • Time Investment Tracking: Documentation of effort and time committed by each party
  • Resource Utilization Assessment: Evaluation of materials, tools, and infrastructure used
  • Opportunity Cost Calculation: Consideration of alternative activities foregone
  • Market Rate Benchmarking: Comparison with industry standard rates for similar work
  • Proportional Responsibility Assignment: Fair allocation based on contributing factors

Mediation Support Tools

  • Real-Time Collaboration Platforms: Secure spaces for guided discussion
  • Proposal Generation Assistance: Templates and prompts for solution development
  • Agreement Documentation: Formal recording of mutually accepted terms
  • Implementation Timeline Creation: Structured plans for resolution execution
  • Progress Monitoring Dashboard: Tracking of agreed-upon actions and deliverables

Documentation System

Comprehensive Case Records

  • Complete Dispute Timeline: Chronological documentation of all events and decisions
  • Evidence Repository: Secure storage with access controls and audit trails
  • Decision Rationale Documentation: Detailed explanation of resolution reasoning
  • Stakeholder Impact Assessment: Analysis of outcomes for all affected parties
  • Precedent Value Identification: Recognition of decisions useful for future cases

Community Learning Integration

  • Anonymized Case Studies: Privacy-protected examples for educational purposes
  • Pattern Recognition Reports: Identification of systemic issues requiring attention
  • Best Practice Extraction: Successful resolution strategies for community adoption
  • Training Material Development: Educational content for dispute prevention
  • Policy Recommendation Generation: Suggestions for platform governance improvements

Privacy Protection Framework

  • Selective Disclosure Controls: Granular privacy settings for different information types
  • Identity Anonymization: Protection of stakeholder identities in public summaries
  • Sensitive Information Redaction: Automatic removal of personal or confidential details
  • Consent Management: Clear opt-in/opt-out mechanisms for information sharing
  • Data Retention Policies: Time-limited storage with secure deletion procedures

Quality Metrics & KPIs

Resolution Performance Indicators

  • Investigation Completion Time: Target 72 hours from escalation to decision
  • Decision Implementation Time: Target 24 hours from decision to action initiation
  • Stakeholder Satisfaction Rates: Post-resolution surveys from all involved parties
  • Resolution Sustainability: Tracking of recurring conflicts between same parties
  • Community Trust Metrics: Periodic surveys on dispute resolution system confidence

System Health Monitoring

  • Case Volume Trends: Tracking dispute frequency and type patterns over time
  • Resolution Type Distribution: Analysis of decision outcomes and their effectiveness
  • Appeal Rate Analysis: Frequency and success rate of appeal requests
  • Administrative Load Assessment: Resource requirements for dispute resolution operations
  • System Improvement Implementation: Rate of policy changes based on lessons learned

Community Impact Assessment

  • Platform Usage Confidence: User willingness to engage in exchanges post-dispute exposure
  • Network Relationship Health: Quality of ongoing relationships after dispute resolution
  • Knowledge Transfer Effectiveness: Community adoption of best practices from case studies
  • Preventive Measure Success: Reduction in similar disputes following system improvements
  • Ecosystem Trust Building: Overall network resilience and collaboration quality

Integration with Existing Systems

hREA/ValueFlows Compatibility

  • Economic Events Modeling: Dispute resolution outcomes as trackable economic events
  • Resource Impact Tracking: Effects of disputes on shared resources and commitments
  • Agent Relationship Management: Integration with existing trust and reputation systems
  • Planning Process Enhancement: Incorporating dispute risk into future planning workflows
  • Value Network Optimization: Using dispute data to improve network coordination

Nondominium Resource Governance

  • Shared Resource Conflict Resolution: Specialized workflows for commons-based resources
  • Access Rights Arbitration: Resolution of conflicting claims to nondominium resources
  • Governance Primitive Integration: Dispute resolution as foundational governance capability
  • Progressive Trust Enhancement: Dispute resolution outcomes informing trust progression
  • Community Stewardship Support: Tools for collective resource management decisions

Platform Administration Integration

  • Role-Based Access Control: Integration with existing admin privilege systems
  • Audit Trail Connectivity: Linking dispute decisions with broader system audit logs
  • Policy Enforcement Coordination: Alignment with existing community guidelines and terms of service
  • Escalation Path Clarity: Clear handoff procedures between different administrative functions
  • System Health Monitoring: Integration with broader platform health and performance metrics

Implementation Phases

Phase 1: Foundation (Post-Alpha 6)

Timeline: 3-4 months
Focus: Core dispute investigation and resolution capabilities

Infrastructure Development

  • Dispute Entity Design: Implementation of comprehensive dispute data model
  • Evidence Collection System: Basic file upload and evidence management
  • Administrative Interface: Dashboard for dispute management and resolution
  • Stakeholder Communication: Secure messaging system for dispute participants
  • Basic Documentation: Resolution outcome recording and participant notification

Process Implementation

  • Quality Assessment Workflow: Structured process for service delivery evaluation
  • Evidence Review Procedures: Standardized methods for information analysis
  • Decision Framework: Clear criteria and processes for resolution determination
  • Basic Transparency: Simple reporting of resolution outcomes to community
  • Appeal Mechanism: Initial version of decision review and appeal process

Phase 2: Enhancement (6 months post-Alpha 6)

Timeline: 4-5 months
Focus: Advanced investigation tools and community integration

Advanced Capabilities

  • Automated Timeline Construction: Smart chronological reconstruction of dispute events
  • Multi-Format Evidence Support: Enhanced media support and integrity verification
  • Stakeholder Interview Tools: Structured interview templates and documentation
  • Context Analysis Engine: Pattern recognition and relationship mapping
  • Compensation Calculation: Sophisticated algorithms for fair outcome determination

Community Integration

  • Privacy-Protected Learning: Anonymized case studies for community education
  • Pattern Recognition Reporting: System-wide dispute trend analysis and reporting
  • Preventive Measure Integration: Proactive conflict identification and early intervention
  • Training Material Development: Educational content for dispute prevention
  • Community Feedback Integration: Stakeholder input on system effectiveness

Phase 3: Ecosystem Integration (12 months post-Alpha 6)

Timeline: 3-4 months
Focus: Full Sensorica/OVN alignment and cross-platform compatibility

Governance Ecosystem Integration

  • hREA/ValueFlows Full Integration: Complete economic event modeling for disputes
  • Nondominium Resource Governance: Specialized workflows for commons-based conflicts
  • OVN License Compliance: Full alignment with Open Value Network principles
  • Progressive Trust Integration: Dispute outcomes informing broader trust systems
  • Cross-Network Compatibility: Integration with other OVN platforms and tools

Advanced Analytics & Learning

  • Machine Learning Integration: Pattern recognition for dispute prediction and prevention
  • Cross-Platform Data Exchange: Interoperability with other governance systems
  • Advanced Metrics Dashboard: Comprehensive system health and effectiveness monitoring
  • Research Integration: Connection with academic research on digital governance
  • Policy Recommendation Engine: Automated suggestions for governance improvements

Business Value & Impact

For Individual Users

  • Fair Conflict Resolution: Professional handling of disputes with transparent processes
  • Reputation Protection: Evidence-based decisions protecting against false claims
  • Learning Opportunities: Access to best practices and conflict prevention education
  • Trust Building: Increased confidence in platform fairness and reliability
  • Skill Development: Enhanced collaboration and communication capabilities

For Communities & Networks

  • Governance Infrastructure: Foundational capability for commons-based coordination
  • Trust Network Strengthening: Improved relationships through fair conflict resolution
  • Knowledge Accumulation: Collective learning from dispute patterns and resolutions
  • Cultural Evolution: Development of mature collaboration norms and practices
  • Ecosystem Resilience: Enhanced ability to handle conflicts without network fragmentation

For Platform & Ecosystem

  • Scalability Enablement: Professional dispute handling supporting platform growth
  • Compliance & Legal Protection: Robust processes reducing platform liability
  • Quality Assurance: System-wide improvement through dispute feedback integration
  • Competitive Advantage: Professional dispute resolution as platform differentiator
  • Research Contribution: Advancing understanding of digital governance and coordination

Risk Assessment & Mitigation

Technical Risks

  • Complexity Management: Risk of over-engineering dispute resolution processes
    • Mitigation: Phased implementation with user feedback integration at each stage
  • Privacy Balance: Difficulty balancing transparency with stakeholder privacy
    • Mitigation: Sophisticated privacy controls and community input on disclosure policies
  • Performance Impact: Dispute system potentially affecting platform performance
    • Mitigation: Efficient system design with appropriate resource allocation and monitoring

Social & Governance Risks

  • Administrative Capture: Risk of dispute resolution system becoming centralized control mechanism
    • Mitigation: Built-in transparency, appeal processes, and community oversight mechanisms
  • Cultural Resistance: Community rejection of formal dispute resolution processes
    • Mitigation: Gradual introduction with extensive community input and cultural adaptation
  • Bias Introduction: Risk of systematic bias in dispute resolution decisions
    • Mitigation: Diverse administrative teams, regular bias audits, and transparent decision criteria

Implementation Risks

  • Resource Requirements: High time and skill investment for comprehensive system
    • Mitigation: Phased approach with clear resource planning and stakeholder commitment
  • Adoption Challenges: Low uptake of dispute resolution services by community
    • Mitigation: Education campaigns, success story sharing, and gradual trust building
  • Integration Complexity: Difficulty integrating with existing platform systems
    • Mitigation: Careful architecture planning and extensive testing with existing systems

Success Criteria

Short-Term (6 months post-implementation)

  • System Operational: Dispute resolution system handling cases within target timeframes
  • User Satisfaction: >80% satisfaction rate from dispute participants
  • Case Volume Management: Successfully handling 10-15 disputes per month
  • Documentation Quality: Comprehensive case documentation meeting transparency standards
  • Community Acceptance: >70% community confidence in dispute resolution fairness

Medium-Term (12 months post-implementation)

  • Pattern Recognition: Identification and prevention of recurring dispute types
  • Process Optimization: <72-hour resolution time for standard disputes
  • Learning Integration: Community adoption of best practices from resolved cases
  • System Reliability: 99%+ uptime for dispute resolution infrastructure
  • Ecosystem Integration: Full compatibility with hREA/ValueFlows and Nondominium systems

Long-Term (24 months post-implementation)

  • Dispute Prevention: 30% reduction in dispute frequency through preventive measures
  • Network Effect: Adoption by other OVN platforms and commons-based projects
  • Research Contribution: Published studies on digital governance based on system data
  • Cultural Evolution: Measurable improvement in collaboration quality across platform
  • Scalability Proof: Successful handling of disputes in networks with 1000+ active participants

Conclusion

This Enhanced Dispute Resolution System represents a strategic investment in the long-term sustainability and scalability of commons-based peer production platforms. By aligning with Sensorica's governance principles and the broader Open Value Network ecosystem, it positions the Requests and Offers platform as foundational infrastructure for the next generation of collaborative economic coordination.

The system's agent-centric design, capture-resistant architecture, and commitment to community learning make it a natural evolution of the governance patterns already established in the Nondominium and True Commons projects. Its implementation will not only serve the immediate needs of the Requests and Offers platform but contribute to the broader research and development of decentralized governance systems.

Strategic Recommendation: Begin Phase 1 implementation 6 months post-Alpha 6 launch, allowing for community growth and the identification of actual dispute patterns to inform system design priorities.

Technical Specifications Overview

This document outlines the technical foundation of the project, including technologies used, development setup, dependencies, and specific implementation details.

Key Sections

Implementation Patterns

  • Services (ui/src/lib/services): All services are implemented as Effect Services. They define a service interface, a Context.Tag, and provide the implementation via an Effect Layer. This pattern facilitates dependency injection and composition within Effect workflows across all domains.
  • Stores (ui/src/lib/stores): All stores are implemented using Svelte Factory Functions. These factories use Svelte 5 Runes ($state, $derived, etc.) internally to manage reactive UI state. Store methods orchestrate actions by creating and running Effect pipelines, which consume the Effect Services.

Technical Specifications

1. Infrastructure

1.1 Holo Hosting

  • Holo Hosting Overview: Leverages HoloHosts for robust and scalable infrastructure, contributing to the network's infrastructure and enhancing performance and reliability.
  • Hosting Requirements: Requires a distributed network of HoloHosts for high availability and redundancy. The Holo Network can supply multiple HoloHosts for redundancy.
  • Hosting Benefits: Fosters a decentralized and resilient infrastructure, aligning with Holochain ecosystem principles.

2. Technologies

2.1 Core Technologies

  • Holochain: Core technology for building the application, ensuring local-first and peer-to-peer network capabilities.
  • SvelteKit: Utilized for the guest/front-end, providing a modern and efficient framework for web development.
    • Version: Using Svelte 5.19.2 with SvelteKit 2.16.1
    • Svelte 5 Runes: Advanced reactivity system with $state, $derived, and $effect for fine-grained reactivity
    • Skeleton UI: A component library for building user interfaces with SvelteKit and Tailwind (using @skeletonlabs/skeleton 2.7.0).
  • Effect TS: Functional programming library for TypeScript (effect 3.14.18) providing robust error handling, dependency management, and asynchronous control flow.
    • 7-Layer Architecture: Standardized implementation pattern across all domains
    • Service Layer: Effect-native services with Context.Tag dependency injection
    • Store Layer: Factory functions with Svelte 5 Runes + 9 standardized helper functions
    • Schema Validation: Effect Schema with strategic validation boundaries
    • Error Handling: Domain-specific tagged errors with centralized management
  • TailwindCSS: Utility-first CSS framework (v3.4.17) for rapid UI development.
  • GraphQL: Using Apollo Client (3.13.8) for data fetching and state management.
  • hREA: hREA (Holochain Resource-Event-Agent) is an implementation of the Valueflows specification. It enables a transparent and trusted account of resource and information flows between decentralized and independent agents, across and within ecosystems.
    • Version: @valueflows/vf-graphql 0.9.0-alpha.10 and @valueflows/vf-graphql-holochain 0.0.3-alpha.10
    • For detailed hREA integration specifications, see hREA Integration

2.2 Communication Systems

Messaging System

  • User-to-User Messaging
  • Administrator Communication Channel
  • Notification System for Suspensions

Real-time Features

  • Direct messaging capabilities
  • Negotiation support
  • Agreement finalization
  • Status updates
  • Exchange notifications

2.3 Security Features

  • User Authentication: Agent-centric authentication using Holochain's cryptographic keys.
  • Profile Recovery System: Secure identity recovery mechanisms with multi-agent verification.
  • Administrator Access Controls: Role-based access control system with granular permissions.
  • Suspension Management: Sophisticated system for handling user violations while maintaining decentralized principles.
  • Data Validation: Comprehensive input validation using Effect TS Schema validation.
  • Error Handling: Centralized tagged error system ensuring robust error recovery.
  • Secure Communications: End-to-end encrypted messaging between users.
  • Audit Logging: Comprehensive activity logging for security monitoring.

2.4 Data Management

  • User Links: Profile updates, agent associations, requests/offers
  • Project Links: Coordinators, contributors, categories
  • Organization Links: Members, projects, categories
  • Skill Links: Users, projects, requests/offers
  • Category Links: Projects, offers, organizations

Anchor System

  • Administrators Anchor: Index of network administrators
  • Moderators Anchor: Index of network moderators
  • Users Anchor: Global user index
  • Projects Anchor: Global project index
  • Organizations Anchor: Global organization index

3. User Interface

3.1 Design System

Color palette:

hAppening Community color palette

hAppening Community color palette

3.2 Key Interfaces

  • User Dashboard
  • Project Management Interface
  • Request/Offer Management
  • Administration Dashboard
  • Search Interface
  • Messaging Interface

3.3 Administrative Interface

  • User Management
  • Project Verification
  • Organization Management
  • Request/Offer Moderation
  • Reporting Tools

4. System Architecture

The system architecture is documented using the C4 model, which provides different levels of abstraction to understand the system structure.

4.1 System Context

The following diagram shows the high-level system context and key user roles:

C4Context
    title System Context diagram for Requests and Offers System

    Person(advocate, "Advocate", "Individual supporting Holochain projects")
    Person(creator, "Creator", "Individual or group developing projects")
    Person(admin, "Administrator", "System administrator")

    System(requestsAndOffers, "Requests and Offers hApp", "A decentralized application for creating, managing, and responding to requests and offers")

    System_Ext(holoHost, "Holo Host", "Decentralized hosting infrastructure")
    System_Ext(hrea, "hREA", "Resource-Event-Agent implementation")

    Rel(advocate, requestsAndOffers, "Creates offers, responds to requests")
    Rel(creator, requestsAndOffers, "Creates requests, manages projects")
    Rel(admin, requestsAndOffers, "Manages users and system")

    Rel(requestsAndOffers, holoHost, "Hosted on")
    Rel(requestsAndOffers, hrea, "Uses for resource management")

4.2 Container Structure

The container diagram shows the high-level technical building blocks:

C4Container
    title Container diagram for Requests and Offers System

    Person(advocate, "Advocate", "Individual supporting Holochain projects")
    Person(creator, "Creator", "Individual or group developing projects")
    Person(admin, "Administrator", "System administrator")

    System_Boundary(c1, "Requests and Offers hApp") {
        Container(frontend, "Frontend UI", "SvelteKit + Skeleton UI", "Provides user interface for interacting with the system")
        Container(backend, "DNA", "Rust Holochain Zome", "Core business logic and data validation")
        Container(storage, "DHT", "Holochain Distributed Hash Table", "Decentralized data storage")
        Container(messaging, "Messaging System", "Rust Holochain Zome", "Handles user-to-user communication")
    }

    System_Ext(holoHost, "Holo Host", "Decentralized hosting infrastructure")
    System_Ext(hrea, "hREA", "Resource-Event-Agent implementation")

    Rel(advocate, frontend, "Interacts with")
    Rel(creator, frontend, "Interacts with")
    Rel(admin, frontend, "Manages through")

    Rel(frontend, backend, "Calls zome functions")
    Rel(frontend, messaging, "Sends/receives messages")

    Rel(backend, storage, "Stores and retrieves data")
    Rel(backend, hrea, "Manages resources and events")
    Rel(messaging, storage, "Stores messages")

    Rel(backend, holoHost, "Hosted on")
    Rel(messaging, holoHost, "Hosted on")

4.3 Component Structure

The component diagram details the internal structure of each container:

C4Component
    title Component diagram for Requests and Offers System

    Container_Boundary(frontend, "Frontend") {
        Component(ui, "UI Components", "Skeleton UI", "User interface elements")
        Component(auth, "Auth Manager", "SvelteKit", "Handles authentication")
        Component(state, "State Manager", "SvelteKit Store", "Manages application state")
    }

    Container_Boundary(backend, "Backend DNA") {
        Component(userMgmt, "User Management", "Holochain Zome", "Handles user profiles and roles")
        Component(projectMgmt, "Project Management", "Holochain Zome", "Manages projects and teams")
        Component(requestOffer, "Request/Offer System", "Holochain Zome", "Handles requests and offers")
        Component(admin, "Admin Tools", "Holochain Zome", "System administration features")
        Component(search, "Search Engine", "Holochain Zome", "Search and discovery")
    }

    Container_Boundary(messaging, "Messaging DNA") {
        Component(direct, "Direct Messaging", "Holochain Zome", "User-to-user messaging")
        Component(notifications, "Notifications", "Holochain Zome", "System notifications")
    }

    Rel(ui, auth, "Uses")
    Rel(ui, state, "Updates/reads")

    Rel(auth, userMgmt, "Authenticates")
    Rel(state, userMgmt, "Syncs user data")
    Rel(state, projectMgmt, "Syncs project data")
    Rel(state, requestOffer, "Syncs requests/offers")
    Rel(state, direct, "Syncs messages")
    Rel(state, notifications, "Syncs notifications")

    Rel(userMgmt, projectMgmt, "Associates users")
    Rel(projectMgmt, requestOffer, "Links projects")
    Rel(admin, userMgmt, "Manages users")
    Rel(search, requestOffer, "Indexes")
    Rel(search, projectMgmt, "Indexes")

4.4 Code Structure

The code structure diagram shows the organization of the codebase:

C4Component
    title Requests and Offers Code Structure Diagram

    Container_Boundary(ui, "UI (Svelte)") {
        Component(routes, "+page.svelte", "Route components")
        Component(components, "Components", "Reusable UI components")
        Component(stores, "Stores", "State management")
        Component(services, "Services", "API communication")
    }

    Container_Boundary(dna, "DNA (Rust)") {
        Component(entries, "Entries", "Data structures")
        Component(zomes, "Zomes", "Business logic")
        Component(links, "Links", "Relationships")
        Component(validation, "Validation", "Data validation rules")
    }

    Rel(routes, components, "Uses")
    Rel(routes, stores, "Uses")
    Rel(components, stores, "Uses")
    Rel(services, stores, "Updates")

    Rel(services, zomes, "Calls")
    Rel(zomes, entries, "Creates/Updates")
    Rel(zomes, links, "Manages")
    Rel(entries, validation, "Validates against")

4.5 7-Layer Effect-TS Architecture

The application follows a standardized 7-layer architecture pattern ensuring consistency and maintainability across all domains:

7. Testing Layer     ← Comprehensive coverage across all layers
6. Component Layer   ← Svelte 5 components using composables
5. Composable Layer  ← Business logic abstraction
4. Error Layer       ← Domain-specific error handling
3. Schema Layer      ← Effect Schema validation
2. Store Layer       ← Svelte 5 Runes + Effect-TS integration
1. Service Layer     ← Effect-native services with dependency injection

Implementation Status

All domains have been fully standardized with this pattern:

  • Service Types (Reference Implementation - 100% complete)
  • Requests (100% complete)
  • Offers (100% complete)
  • Users (100% complete)
  • Organizations (100% complete)
  • Administration (100% complete)

Key Features

  • 9 Standardized Helper Functions: Each store implements consistent helper functions for entity creation, mapping, caching, events, fetching, loading states, record creation, status transitions, and collection processing
  • Effect-TS Dependency Injection: Services use Context.Tag for clean dependency management
  • Tagged Error System: Domain-specific errors with comprehensive context
  • Event Bus Communication: Cross-domain communication through standardized events
  • Module-Level Caching: TTL-based caching with cache synchronization

4.6 Architecture Principles

  1. Decentralization: The system leverages Holochain's peer-to-peer architecture to ensure:

    • Data sovereignty
    • Resilient infrastructure
    • No single point of failure
  2. Modularity: The system is built with clear separation of concerns:

    • Frontend (SvelteKit + Skeleton UI + 7-layer architecture)
    • Backend DNA (Holochain Zomes)
    • Messaging DNA
    • Data storage (DHT)
  3. Consistency: Standardized patterns across all domains:

    • Same 7-layer structure for every domain
    • Identical helper function implementations
    • Consistent error handling and state management
    • Unified testing approaches
  4. Security: Built-in security features:

    • User authentication
    • Role-based access control
    • Data validation
    • Secure messaging
  5. Scalability: The system is designed to scale through:

    • Distributed hosting
    • Efficient data structures
    • Modular components
    • Standardized optimization patterns

4.7 Testing Framework

  1. Frontend Testing:

    • Unit Tests: Using Vitest (0.28.4) with @effect/vitest (0.21.1) for Effect-specific testing
    • Store Testing: All 9 helper functions tested for every domain store
    • Service Testing: Effect-TS services tested with mock dependencies
    • Component Tests: Testing UI components in isolation
    • Integration Tests: End-to-end testing with Playwright (1.50.0)
    • Status: All 343 unit tests passing across 20 test files with no unhandled Effect errors
  2. Backend Testing:

    • Zome Unit Tests: Testing individual zome functions with Rust testing framework
    • Multi-Agent Tests: Testing peer-to-peer interactions with Sweettest
    • Domain-Specific Tests: Comprehensive tests for each domain (service-types, requests, offers, users, organizations, administration)
    • Performance Benchmarks: Ensuring system efficiency under load
  3. Testing Architecture Integration:

    • Layer-Specific Testing: Each layer of the 7-layer architecture tested independently
    • Effect-TS Testing Patterns: Standardized testing approach for Effect-based services and stores
    • Mock Implementations: Consistent mocking strategies across all domains
    • Error Boundary Testing: Comprehensive testing of tagged error handling
  4. Testing Principles:

    • Comprehensive Coverage: All critical paths tested across all 7 layers
    • Isolated Components: Pure unit testing with dependency injection
    • Real-World Scenarios: Integration tests reflecting actual usage patterns
    • Effect-TS Integration: Proper testing of Effect operations with dependency injection
    • Automated CI/CD: Tests run on every commit through GitHub Actions

Action Hash Type Safety

Compile-time distinct types for OriginalActionHash and PreviousActionHash to prevent accidental hash swapping between original entity identifiers and update-chain head pointers.

Problem

Holochain's CRUD model uses two different action hashes for updates:

  • Original action hash: The hash of the initial Create action — serves as the immutable entity identity throughout its lifecycle.
  • Previous action hash: The hash of the most recent action in the update chain — needed by update() to append to the chain correctly.

Both are ActionHash at the type level. Swapping them compiles but produces silent data corruption: updates target the wrong chain entry or create orphaned forks.

Rust Newtypes

File: dnas/requests_and_offers/utils/src/types.rs

#![allow(unused)]
fn main() {
/// The ActionHash of the original Create action — immutable entity identifier.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(transparent)]
pub struct OriginalActionHash(pub ActionHash);

/// The ActionHash of the most recent action in the update chain.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(transparent)]
pub struct PreviousActionHash(pub ActionHash);
}

Key properties

  • #[serde(transparent)]: Wire format is identical to a plain ActionHash. No migration needed for existing data.
  • From trait implementations: Both types implement From<T> for ActionHash, From<T> for AnyDhtHash, and From<T> for AnyLinkableHash for seamless HDK 0.6 interop.
  • Direct access: Use .0 to access the inner ActionHash when needed.
  • Wrapping: Use OriginalActionHash(hash) or PreviousActionHash(hash) to wrap a plain ActionHash.
  • Conversion: Use .into() when passing to HDK functions that expect ActionHash, AnyDhtHash, or AnyLinkableHash.

Usage in update inputs

All update input structs use the distinct types:

#![allow(unused)]
fn main() {
pub struct UpdateServiceTypeInput {
    pub original_action_hash: OriginalActionHash,
    pub previous_action_hash: PreviousActionHash,
    pub updated_service_type: ServiceType,
}
}

TypeScript Branded Types

File: ui/src/lib/schemas/holochain.schemas.ts

// Branded action hash types for compile-time type safety.
// serde(transparent) on the Rust side means wire format is identical to ActionHash.
export type OriginalActionHash = ActionHash & { readonly __brand: 'OriginalActionHash' };
export type PreviousActionHash = ActionHash & { readonly __brand: 'PreviousActionHash' };

/** Cast an ActionHash as an OriginalActionHash (zero-cost, compile-time only) */
export const asOriginalActionHash = (hash: ActionHash): OriginalActionHash =>
  hash as OriginalActionHash;

/** Cast an ActionHash as a PreviousActionHash (zero-cost, compile-time only) */
export const asPreviousActionHash = (hash: ActionHash): PreviousActionHash =>
  hash as PreviousActionHash;

Key properties

  • Intersection types: ActionHash & { readonly __brand: ... } — the brand field exists only at compile time, not at runtime.
  • Zero-cost: No runtime overhead. The helper functions are simple type casts (as).
  • Narrowing helpers: asOriginalActionHash() and asPreviousActionHash() provide readable call sites instead of inline as casts.

Entity Identity Model

original_action_hash is the canonical identity for all non-exchange entity types in the UI layer.

UI entity types (ui/src/lib/types/ui.ts)

Every core UI type declares both hash fields as required:

export type UIUser = UserInDHT & {
  original_action_hash: ActionHash;
  previous_action_hash: ActionHash;
  // ... other fields
};

export type UIServiceType = ServiceTypeInDHT & {
  original_action_hash: ActionHash;
  previous_action_hash: ActionHash;
  // ... other fields
};

The same pattern applies to UIStatus, UIOrganization, UIRequest, and UIOffer.

CacheableEntity (ui/src/lib/types/store-helpers.ts)

The cache interface keeps original_action_hash optional for hREA compatibility (hREA entities use a different identity scheme):

export interface CacheableEntity {
  readonly original_action_hash?: ActionHash;
  readonly [key: string]: unknown;
}

Exchange types

Exchange types (UIExchangeResponse, UIExchangeAgreement, UIExchangeReview) use actionHash instead of original_action_hash because exchanges are not updated via the standard CRUD chain — they follow a proposal/agreement lifecycle.

Key Files Reference

FileRole
dnas/requests_and_offers/utils/src/types.rsRust newtype definitions and From implementations
ui/src/lib/schemas/holochain.schemas.tsTypeScript branded type definitions and cast helpers
ui/src/lib/types/ui.tsUI entity types with required hash fields
ui/src/lib/types/store-helpers.tsCacheableEntity interface (optional hash for hREA compat)
ui/src/lib/utils/store-helpers/cache-helpers.tsCache lookup using original_action_hash

Component Library Documentation

This document provides a comprehensive inventory of the reusable UI components in the Requests and Offers application.

Component Organization

Components are organized by domain with a feature-based approach, located in ui/src/lib/components:

/components/
├── service-types/     # Service type related components
├── requests/         # Request management components
├── offers/           # Offer management components
├── users/            # User profile components
├── organizations/    # Organization management components
├── tags/             # Tag related components
├── shared/           # Shared utility components
├── hrea/             # hREA integration components
├── moe/              # Medium of Exchange components
└── mediums-of-exchange/ # Medium of Exchange components

Core Components

Service Types Components

ServiceTypeCard.svelte

Purpose: Displays a service type with its metadata in a card format.

Props:

  • serviceType: ServiceTypeOutput - The service type to display
  • showActions: boolean - Whether to show action buttons (edit, delete)
  • compact: boolean - Whether to show a compact version of the card

Events:

  • edit: When edit button is clicked
  • delete: When delete button is clicked
  • select: When the card is selected

ServiceTypeSelector.svelte

Purpose: Multi-select component for choosing service types with search capability.

Props:

  • selectedServiceTypes: ServiceTypeOutput[] - Currently selected service types
  • multiple: boolean - Whether multiple selection is allowed
  • required: boolean - Whether selection is required
  • disabled: boolean - Whether the component is disabled

Events:

  • selection: When selection changes, emits array of selected service types
  • search: When search text changes

Requests/Offers Components

RequestForm.svelte / OfferForm.svelte

Purpose: Form for creating or editing a request/offer.

Props:

  • request/offer: Optional - Existing request/offer for editing
  • serviceTypes: ServiceTypeOutput[] - Available service types
  • organizations: OrganizationOutput[] - User's organizations

Events:

  • submit: When form is submitted successfully
  • cancel: When form is cancelled
  • error: When form submission fails

Users Components

UserName.svelte

Purpose: Renders a user's display name with the mononym sentinel stripped via formatUserName. Use wherever user.name would otherwise be interpolated directly.

Props:

  • user: UIUser | UserInDHT | { name?: string } | null | undefined - The user, or any object carrying a name
  • fallback: string - Text shown when no name is available (default: "User")
  • class: string - Optional CSS class applied to the wrapping <span>

Shared Components

TagAutocomplete.svelte

Purpose: Tag input with autocomplete suggestions.

Props:

  • selectedTags: string[] - Currently selected tags
  • suggestTags: boolean - Whether to show tag suggestions
  • maxTags: number - Maximum number of tags allowed

Events:

  • update: When selected tags change

LoadingSpinner.svelte

Purpose: Consistent loading indicator for async operations.

Props:

  • size: 'small' | 'medium' | 'large' - Size of the spinner
  • message: string - Optional loading message

ErrorDisplay.svelte

Purpose: Standardized error display component.

Props:

  • error: any - Error object to display
  • retry: () => void - Optional retry function

Styling and Theming

The application uses a combination of TailwindCSS and SkeletonUI for styling, with a custom theme defined in src/happening_theme.ts.

Custom Theme Configuration

The Happening theme extends the base Skeleton theme with:

  1. Custom Colors: Brand-specific color palette with primary, secondary, and accent colors
  2. Typography: Custom font family and size scale
  3. Component Styling: Customized appearance for buttons, cards, and form elements

Using the Theme

Theme values should be accessed through Skeleton's theme system rather than hard-coding colors:

<!-- Recommended -->
<button class="btn variant-filled-primary">Submit</button>

<!-- Avoid -->
<button class="bg-blue-500 hover:bg-blue-600">Submit</button>

Custom Utility Classes

In addition to Tailwind's utility classes, the application defines several custom utilities for consistent styling:

  • .card-hover: Standard hover effects for cards
  • .form-input-wrapper: Consistent styling for form inputs
  • .tag-pill: Styling for tag elements

Best Practices

  1. Component Composition: Prefer composition over complex, monolithic components
  2. Props Typing: Always define proper TypeScript interfaces for component props
  3. Error Handling: Use ErrorDisplay component for consistent error presentation
  4. Loading States: Use LoadingSpinner for all asynchronous operations
  5. Accessibility: Ensure all components meet WCAG AA standards

Component Development Guidelines

  1. New Components: Place in appropriate domain folder
  2. Testing: Create component tests in corresponding test file
  3. Documentation: Update this document when adding new components
  4. Composition: Extract reusable logic to composables when appropriate

Development Features System

A centralized system for managing development-only features and mock data in the requests-and-offers application.

Overview

The Development Features System provides a unified approach to controlling development tools, mock data, and debugging features across different deployment environments. It ensures production builds are clean while enhancing the developer experience during development and testing.

Architecture

7-Layer Effect-TS Integration

The system follows the established 7-layer Effect-TS architecture pattern:

  1. Service Layer: DevFeaturesService with Context.Tag dependency injection
  2. Environment Configuration: Vite environment variables for build-time optimization
  3. Component Integration: Conditional rendering using shouldShowMockButtons()
  4. Build-Time Tree-Shaking: Automatic removal of dev features in production
  5. Mock Data Layer: Enhanced mock utilities for realistic testing data
  6. Testing Strategy: Different modes for development, testing, and production
  7. Documentation: Comprehensive usage guidelines and examples

Core Components

DevFeaturesService

Location: ui/src/lib/services/devFeatures.service.ts

export class DevFeaturesServiceTag extends Context.Tag("DevFeaturesService")<
  DevFeaturesServiceTag,
  DevFeaturesService
>() {}

export const shouldShowMockButtons = (): boolean => {
  return import.meta.env.VITE_MOCK_BUTTONS_ENABLED === "true";
};

Key Features:

  • Effect-TS Context.Tag pattern for dependency injection
  • Build-time evaluation using Vite environment variables
  • Tree-shaking optimization for production builds
  • Type-safe feature flag management

Environment Configuration

Development Environment (.env):

# Atomic feature control - each independently managed
VITE_MOCK_BUTTONS_ENABLED=true      # Show mock data buttons in forms
VITE_PEERS_DISPLAY_ENABLED=true     # Show network peers for testing

Usage

Component Integration

All form components use the service for conditional mock button rendering:

<script lang="ts">
  import { shouldShowMockButtons } from '$lib/services/devFeatures.service';
  // ... other imports
</script>

<!-- Form content -->

{#if mode === 'create' && shouldShowMockButtons()}
  <button
    type="button"
    class="variant-soft-secondary btn"
    onclick={handleMockSubmit}
    disabled={isSubmitting}
  >
    Create Mock Data
  </button>
{/if}

Enhanced Mock Functions

Location: ui/src/lib/utils/mocks.ts

New mock functions for comprehensive testing:

// Medium of Exchange mocks
export const createMockedMediumOfExchange = (): MediumOfExchangeInDHT => ({
  code: faker.finance.currencyCode(),
  name: faker.finance.currencyName(),
  description: faker.lorem.sentence(),
  resource_spec_hrea_id: faker.string.uuid(),
});

export const createSuggestedMockedMediumOfExchange =
  (): MediumOfExchangeInDHT => ({
    code: `${faker.finance.currencyCode()}_SUGGESTED`,
    name: `${faker.finance.currencyName()} (Suggested)`,
    description: faker.lorem.sentence(),
    resource_spec_hrea_id: faker.string.uuid(),
  });

Deployment Modes

Development Mode

  • Purpose: Full development experience with atomic feature control
  • Features: Mock buttons, network peers display, development utilities
  • Command: bun start (from project root)
  • Environment: Features controlled via .env file

Build Mode

  • Production Build: cd ui && bun run build
  • Features: Atomic feature control with production optimizations

Build Scripts

UI Scripts (ui/package.json)

{
  "scripts": {
    "build": "bun run check && vite build"
  }
}

Root Scripts (package.json)

{
  "scripts": {
    "start": "AGENTS=${AGENTS:-2} BOOTSTRAP_PORT=$(get-port) bun run network"
  }
}

Tree-Shaking Verification

The system implements build-time optimization that completely removes development features from production builds:

Development Build:

const shouldShowMockButtons = () => true;

Production Build:

const shouldShowMockButtons = () => false; // or minified as ()=>!1

Verification Command:

bun run build && grep -r "shouldShowMockButtons" .svelte-kit/output/client/

Integrated Forms

The following forms have been integrated with the development features system:

  1. RequestForm.svelte - Request creation with mock data
  2. OfferForm.svelte - Offer creation with mock data
  3. OrganizationForm.svelte - Organization creation with mock data
  4. UserForm.svelte - User creation with mock data
  5. ServiceTypeForm.svelte - Service type creation with mock data
  6. ServiceTypeSuggestionForm.svelte - Service type suggestions
  7. MediumOfExchangeForm.svelte - Currency/payment method creation
  8. MediumOfExchangeSuggestionForm.svelte - Currency suggestions

Benefits

Developer Experience

  • Rapid Prototyping: Instant mock data generation for testing workflows
  • Consistent Testing: Realistic data across all forms and components
  • Flexible Development: Easy switching between development modes
  • Debugging Support: Development-only features for troubleshooting

Production Safety

  • Zero Overhead: Complete removal of dev code in production builds
  • Security: No development utilities exposed in production
  • Performance: Optimized builds without development features
  • Clean Deployment: Professional appearance without debug tools

Testing Efficiency

  • Multi-Environment: Test different deployment scenarios
  • Alpha Testing: Test mode simulates production with dev features
  • Integration Testing: Verify tree-shaking and build optimization
  • Quality Assurance: Consistent testing data across environments

Implementation Guidelines

Adding New Development Features

  1. Environment Variable: Add feature flag to environment files
  2. Service Integration: Extend DevFeaturesService with new feature checks
  3. Component Usage: Use service functions for conditional rendering
  4. Testing: Verify tree-shaking removes feature in production builds

Best Practices

  • Build-Time Evaluation: Use import.meta.env for compile-time optimization
  • Conditional Rendering: Wrap dev features in conditional blocks
  • Descriptive Naming: Use clear, descriptive variable names
  • Documentation: Document all new dev features and their purpose
  • Testing: Always test production builds to verify clean removal

Troubleshooting

Common Issues

Mock Buttons Not Appearing:

  • Check VITE_MOCK_BUTTONS_ENABLED=true in .env file for mock button visibility
  • Ensure running in development or test mode

Production Build Contains Dev Code:

  • Verify environment variables are not set in .env.production
  • Check Vite build mode is set to production
  • Test tree-shaking with build verification commands

Environment Not Loading:

  • Ensure .env file is in project root (same level as package.json)
  • Verify environment variables are properly set in .env
  • Check Vite configuration (envDir: '../' points to project root)

Future Enhancements

Planned Features

  • Feature Toggles: Runtime feature toggling for specific development tools
  • Debug Panels: Development-only debug information displays
  • Performance Monitoring: Development-mode performance metrics
  • Test Data Management: Advanced mock data scenarios and fixtures

Architecture Improvements

  • Service Extensions: Additional dev services for specific domains
  • Configuration Management: Centralized configuration for complex dev features
  • Plugin System: Modular development feature plugins
  • Integration Testing: Automated testing of tree-shaking effectiveness

Event Bus Pattern

This document details the Event Bus pattern implemented using Effect TS in the Requests and Offers application.

Overview

The Event Bus pattern provides a pub/sub mechanism for communication between different parts of the application, particularly between stores. This helps decouple components and enables real-time updates.

In our implementation, we use Effect TS to handle asynchronous operations and error management, providing a robust, type-safe event system.

Architecture

The Event Bus system is implemented using Effect TS's event handling capabilities, with a central store event bus that manages domain-specific events.

Core Implementation

The core of our Event Bus is defined in ui/src/lib/stores/storeEvents.ts:

import { EventTag } from "effect";

// Event tags with typed payloads
export const ServiceTypeCreatedEvent = EventTag<{
  type: "service_type_created";
  payload: {
    serviceType: any; // ServiceTypeOutput in actual implementation
  };
}>();

export const ServiceTypeApprovedEvent = EventTag<{
  type: "service_type_approved";
  payload: {
    serviceType: any; // ServiceTypeOutput in actual implementation
  };
}>();

// Other domain-specific events...

// Generic domain events
export const EntityCreatedEvent = EventTag<{
  type: "entity_created";
  payload: {
    domain: "service_types" | "requests" | "offers" | "users" | "organizations";
    entity: any;
  };
}>();

export const EntityUpdatedEvent = EventTag<{
  type: "entity_updated";
  payload: {
    domain: "service_types" | "requests" | "offers" | "users" | "organizations";
    entity: any;
  };
}>();

// Create an event emitter interface
export interface StoreEvents {
  emit<E>(eventTag: EventTag<E>, event: E): void;
}

// Concrete event bus implementation
export const storeEventBus: StoreEvents = {
  emit: (eventTag, event) => {
    // Actual implementation with Effect EventEmitter
  },
};

Event Types

The system uses two types of events:

  1. Domain-Specific Events: Focused on specific entity operations (e.g., ServiceTypeCreatedEvent)
  2. Generic Domain Events: Cross-cutting events with domain identifiers (e.g., EntityCreatedEvent)

Common Event Categories

CategoryEventsPurpose
Creation*CreatedEventNotify when new entities are created
Update*UpdatedEventNotify when entities are modified
Status Change*ApprovedEvent, *RejectedEventNotify when entity status changes
Deletion*DeletedEventNotify when entities are removed
Selection*SelectedEventNotify when entities are selected in UI

Usage Patterns

Publishing Events

Stores emit events when significant state changes occur:

// Inside a store method
const createServiceType = (
  input: ServiceTypeInput,
): E.Effect<ServiceTypeOutput, StoreError> =>
  pipe(
    serviceTypeService.createServiceType(input),
    E.map((record) => {
      const serviceType = createUIServiceType(record);

      // Emit domain-specific event
      storeEventBus.emit(ServiceTypeCreatedEvent, {
        type: "service_type_created",
        payload: { serviceType },
      });

      // Emit generic domain event
      storeEventBus.emit(EntityCreatedEvent, {
        type: "entity_created",
        payload: {
          domain: "service_types",
          entity: serviceType,
        },
      });

      return serviceType;
    }),
    // Error handling...
  );

Subscribing to Events

Stores and components can subscribe to events using Effect's Stream API:

// In a store or component
$effect(() => {
  const subscription = pipe(
    EventBus.subscribe(ServiceTypeCreatedEvent),
    S.tap((event) => {
      // Handle the event
      console.log("Service type created:", event.payload.serviceType);
      // Update local state as needed
    }),
    S.runDrain,
  );

  // Cleanup subscription when component is destroyed
  return () => subscription.interrupt();
});

Cross-Store Communication

The Event Bus enables communication between stores without direct dependencies:

// In offers.store.svelte.ts
$effect(() => {
  const subscription = pipe(
    EventBus.subscribe(ServiceTypeApprovedEvent),
    S.tap((event) => {
      // Update offers that use this service type
      const serviceTypeId = event.payload.serviceType.id;
      // Refresh related offers...
    }),
    S.runDrain,
  );

  return () => subscription.interrupt();
});

Standardized Event Helpers

The 9-Helper Function Pattern includes createEventEmitters() to standardize event emission:

const createEventEmitters = () => {
  return {
    emitServiceTypeCreated: (serviceType: UIServiceType): void => {
      try {
        storeEventBus.emit(ServiceTypeCreatedEvent, {
          type: "service_type_created",
          payload: { serviceType },
        });

        storeEventBus.emit(EntityCreatedEvent, {
          type: "entity_created",
          payload: {
            domain: "service_types",
            entity: serviceType,
          },
        });
      } catch (error) {
        console.error("Failed to emit service_type:created event:", error);
      }
    },
    // Other event emitters...
  };
};

Benefits

  1. Decoupling: Stores and components can communicate without direct dependencies
  2. Reactivity: UI can respond to backend changes in real-time
  3. Testability: Event handling can be easily tested in isolation
  4. Type Safety: Event payloads are fully typed with Effect's EventTag system
  5. Consistency: Standardized event naming and payload structure

Best Practices

  1. Event Granularity: Create specific events for important state changes
  2. Error Handling: Always wrap event emission in try/catch blocks
  3. Cleanup: Always return interrupt functions from subscriptions
  4. Naming Conventions: Use consistent naming patterns across all events
  5. Documentation: Document all events in this document
  6. Payload Design: Keep payloads minimal but complete for consumers

Implementation Status

DomainEvent ImplementationNotes
ServiceTypes✅ CompleteFull event coverage with proper typing
Requests✅ CompleteFull event coverage with proper typing
Offers🔄 In ProgressAdding proper event emissions
Users/Organizations📋 PlannedNeed full event implementation
Administration📋 PlannedNeed full event implementation

Core Implementation

The event bus is implemented in ui/src/lib/utils/eventBus.effect.ts as a generic, reusable system with the following key features:

  • Type-safe: Uses TypeScript generics to ensure events and payloads are type-checked.
  • Effect-based: Leverages Effect TS for error handling and asynchronous operations.
  • Layered design: Follows the Context/Layer pattern from Effect TS for dependency injection.

Key Components

  1. EventBusService: The core interface that defines the operations available on an event bus:

    • on: Subscribe to events
    • emit: Publish events
    • off: Unsubscribe from events
  2. Context Tag: Created with createEventBusTag<T>() for dependency injection.

  3. Layer Implementation: Created with createEventBusLiveLayer() to provide the actual implementation.

  4. Error Handling: Custom EventBusError class with standardized error handling and utility functions.

Usage Pattern

1. Define Event Map

First, define the events and their payload types:

// storeEvents.ts
export type StoreEvents = {
  "request:created": { request: UIRequest };
  "request:updated": { request: UIRequest };
  "request:deleted": { requestHash: ActionHash };
  "offer:created": { offer: UIOffer };
  "offer:updated": { offer: UIOffer };
  "offer:deleted": { offerHash: ActionHash };
};

2. Create Tag and Layer

// storeEvents.ts
const Tag = createEventBusTag<StoreEvents>("StoreEventBus");
const Live = createEventBusLiveLayer(Tag);

export { Tag as StoreEventBusTag, Live as StoreEventBusLive };

3. Use in Stores or Components

// Example from a store
import { StoreEventBusTag, StoreEventBusLive } from "$lib/stores/storeEvents";

// To emit an event
E.gen(function* () {
  const eventBus = yield* StoreEventBusTag;
  yield* eventBus.emit("request:created", { request: newRequest });
});

// Provide the layer
E.provide(StoreEventBusLive);

Integration with Stores

The event bus is used for communication between stores, particularly for CRUD operations:

  1. Create operations: Emit events when new entities are created
  2. Update operations: Notify other stores when entities are updated
  3. Delete operations: Alert subscribers when entities are removed

Implementation in Store Methods

Each store method that modifies data emits appropriate events:

const createRequest = (
  request: RequestInDHT,
): E.Effect<Record, RequestStoreError, EventBusService<StoreEvents>> =>
  pipe(
    // Create the request
    requestsService.createRequest(request),

    // Map and cache the result
    E.map((record) => {
      const newRequest = mapRecordToUIRequest(record);
      cache.set(newRequest);
      return { record, newRequest };
    }),

    // Emit the event
    E.tap(({ newRequest }) =>
      newRequest
        ? E.gen(function* () {
            const eventBus = yield* StoreEventBusTag;
            yield* eventBus.emit("request:created", { request: newRequest });
          })
        : E.asVoid,
    ),

    // Final transformation and error handling
    E.map(({ record }) => record),
    E.catchAll(handleError),

    // Provide the layer
    E.provide(StoreEventBusLive),
  );

Best Practices

  1. Define clear event types: Create a well-defined event map with descriptive event names and payload types.
  2. Use consistent naming conventions: Follow patterns like entity:action (e.g., request:created).
  3. Proper error handling: Always catch and handle errors from event bus operations.
  4. Event isolation: Keep event payloads focused and minimal, containing only what subscribers need.
  5. Type safety: Leverage TypeScript to ensure type correctness across the event system.

Performance Considerations

  • The event bus implementation uses an efficient data structure (HashSet) to store and manage event handlers.
  • Events are processed concurrently using Effect's parallel processing capabilities.
  • Error handling is isolated per handler to prevent one failing handler from affecting others.

Cross-Store Communication Example

In our application, the event bus facilitates communication between different stores:

  1. requestsStore.createRequest() emits a request:created event
  2. offersStore can subscribe to this event to update its state based on new requests
  3. This decouples the stores while maintaining synchronized state

This pattern ensures that stores remain independent but can react to changes in related data, improving maintainability and robustness.

History/Revision Feature Documentation

Overview

The history/revision feature in the requests-and-offers application tracks and displays status changes for users and organizations over time. This feature provides administrators with a complete audit trail of all status changes, including who was affected, when changes occurred, and the reasons for status modifications.

Architecture

Data Flow

graph TD
    A[Administrator Action] --> B[Status Update]
    B --> C[Holochain DNA]
    C --> D[Status Record Created]
    D --> E[Link to Entity]
    E --> F[Frontend Fetch]
    F --> G[Store Update]
    G --> H[UI Display]

    I[Event Bus] --> J[Real-time Updates]
    J --> F

Components

Backend (Holochain DNA)

Status Management (administration/src/status.rs):

  • Creates new status records with timestamp
  • Links status to entities (users/organizations)
  • Maintains revision chain through links
  • Provides get_all_revisions_for_status function for history retrieval

Key Functions:

  • create_status: Creates initial pending status
  • update_entity_status: Updates status with new revision
  • get_all_revisions_for_status: Retrieves complete history chain

Frontend Components

UI Components:

  1. StatusHistoryModal (lib/components/shared/status/StatusHistoryModal.svelte)

    • Modal dialog for displaying status history
    • Uses StatusTable for consistent rendering
  2. StatusTable (lib/components/shared/status/StatusTable.svelte)

    • Reusable table component for status history display
    • Responsive design with mobile card view
    • Color-coded status indicators
    • Timestamp formatting and duration display

Store Layer (lib/stores/administration.store.svelte.ts):

  • fetchAllUsersStatusHistory: Aggregates status history for all users
  • fetchAllOrganizationsStatusHistory: Aggregates status history for organizations
  • getEntityStatusHistory: Retrieves history for individual entity
  • Real-time updates via event bus integration

Route Pages:

  • /admin/users/status-history: Displays all user status changes
  • /admin/organizations/status-history: Displays all organization status changes
  • /test-status-history: Development tool for testing status history

Data Types

Core Types

// Revision type represents a single status change
export type Revision = {
  status: UIStatus; // The status details
  timestamp: number; // When the change occurred
  entity: UIUser | UIOrganization; // Who was affected
};

// UIStatus contains the actual status information
export type UIStatus = {
  status_type: StatusType;
  reason?: string;
  duration?: number;
  original_action_hash?: ActionHash;
  previous_action_hash?: ActionHash;
  created_at?: number;
  updated_at?: number;
};

// StatusType enum
type StatusType =
  | "pending"
  | "accepted"
  | "rejected"
  | "suspended temporarily"
  | "suspended indefinitely";

Features

Status Tracking

The system tracks five distinct status types:

  1. Pending (yellow) - Initial state for new users/organizations
  2. Accepted (green) - Approved entities
  3. Rejected (red) - Denied access
  4. Suspended Temporarily (orange) - Time-limited suspension
  5. Suspended Indefinitely (red) - Permanent suspension

Real-time Updates

Status changes trigger events through the event bus:

  • user:status:updated - User status change
  • organization:status:updated - Organization status change

These events automatically refresh the status history displays.

History Collection

The system collects history by:

  1. Fetching all entities (users/organizations)
  2. For each entity, retrieving their complete status revision chain
  3. Aggregating all revisions into a chronological list
  4. Sorting by timestamp for proper display order

UI Features

  • Responsive Design: Desktop table view, mobile card view
  • Color Coding: Visual status indicators
  • Timestamp Display: Human-readable date/time formatting
  • Duration Display: Shows suspension duration in days
  • Search/Filter: (Planned) Filter by entity, status type, date range

Implementation Details

Frontend Workarounds

Due to backend limitations in revision ordering, the frontend implements:

  • Client-side timestamp sorting
  • Aggregation of individual entity histories
  • Reactive updates through event subscriptions
  • Svelte 5 state proxy handling with array spreading

Performance Considerations

  • Batch fetching of entity histories
  • Effect-TS for efficient async operations
  • Caching at service layer (5-minute TTL)
  • Loading states during data fetching

Event-Driven Architecture

// Status update triggers cascade
updateUserStatus()
  -> emitUserStatusUpdated()
  -> Event: 'user:status:updated'
  -> Listeners refresh history
  -> UI updates automatically

Usage Examples

Viewing Status History

  1. Navigate to Admin panel
  2. Go to Users or Organizations section
  3. Click "Status History" button
  4. View chronological list of all status changes

Individual Entity History

// Get history for specific user
const history = await getAllRevisionsForStatus(user);

// Display in modal
modalStore.trigger({
  type: "component",
  component: "statusHistoryModal",
  meta: {
    statusHistory: history,
    title: `Status History for ${user.name}`,
  },
});

Testing

Test Page (/test-status-history)

Development tool that:

  • Simulates status history data
  • Validates logging patterns
  • Tests UI rendering
  • Confirms data flow

Integration Tests

  • Backend Sweettest tests verify revision creation
  • Frontend tests validate history display
  • E2E tests check complete workflow

Future Enhancements

Planned Features

  1. Advanced Filtering

    • Filter by date range
    • Filter by status type
    • Search by entity name
  2. Export Functionality

    • CSV export of history
    • PDF report generation
  3. Analytics Dashboard

    • Status change trends
    • Administrator activity tracking
    • Suspension duration analysis
  4. Audit Improvements

    • Record which admin made changes
    • Add change justification field
    • Version control for bulk operations

Technical Improvements

  1. Backend Optimization

    • Native revision ordering in Holochain
    • Pagination for large histories
    • Indexed queries for performance
  2. Frontend Enhancements

    • Virtual scrolling for large datasets
    • Real-time collaborative updates
    • Offline support with sync

Troubleshooting

Common Issues

  1. Empty History Display

    • Ensure entities have status changes
    • Check network connectivity
    • Verify administrator permissions
  2. Incorrect Timestamps

    • Frontend converts microseconds to milliseconds
    • Check timezone settings
    • Validate Holochain timestamp format
  3. Missing Updates

    • Verify event bus subscriptions
    • Check for errors in console
    • Ensure proper Effect-TS error handling

Debug Tools

  • TDD test page for isolated testing
  • Console logging with 🔄 prefix for history operations
  • Effect-TS error contexts for debugging

Medium of Exchanges Feature Documentation

Holochain Requests and Offers Project


📋 Executive Summary

The Medium of Exchanges (MoE) feature enables communities to define and manage various payment methods and value exchange mechanisms within the requests and offers ecosystem. This feature supports both traditional currencies (USD, EUR) and alternative exchange systems (Pay It Forward, Local Exchange Trading Systems, Time Banking, etc.).

Key Capabilities

  • Multi-Currency Support: Traditional and alternative currencies
  • Admin Approval Workflow: Suggestion → Approval/Rejection → Activation
  • hREA Integration: Maps to ResourceSpecification entries for economic coordination
  • Entity Linking: Associate mediums of exchange with requests and offers
  • Status Management: Comprehensive status tracking throughout lifecycle

🏗️ Architecture Overview

System Components

graph TB
    subgraph "Frontend Layer"
        A[MoE Components]
        B[MoE Store]
        C[MoE Service]
    end

    subgraph "Backend Layer"
        D[MoE Coordinator Zome]
        E[MoE Integrity Zome]
        F[Administration Zome]
    end

    subgraph "External Integration"
        G[hREA ResourceSpec]
        H[Request/Offer Linking]
    end

    A --> B
    B --> C
    C --> D
    D --> E
    D --> F
    D --> G
    D --> H

Data Flow Architecture

  1. User Suggestion: Users suggest new mediums of exchange
  2. Admin Review: Administrators review and approve/reject suggestions
  3. hREA Integration: Approved mediums create ResourceSpecification entries
  4. Entity Association: Link mediums to requests and offers
  5. Status Tracking: Monitor lifecycle from suggestion to activation

🔧 Backend Implementation

Data Structure

Core Medium of Exchange Entry

#![allow(unused)]
fn main() {
#[hdk_entry_helper]
#[derive(Clone, PartialEq)]
pub struct MediumOfExchange {
    /// Unique identifier (e.g., 'EUR', 'USD', 'TIME', 'LOCAL')
    pub code: String,
    /// Human-readable name (e.g., 'Euro', 'US Dollar', 'Time Banking', 'Local Currency')
    pub name: String,
    /// Detailed description of the medium of exchange
    pub description: Option<String>,
    /// Exchange type: "base" (foundational categories) or "currency" (specific monetary units)
    pub exchange_type: String,
    /// ID of corresponding hREA ResourceSpecification (only for approved)
    pub resource_spec_hrea_id: Option<String>,
}
}

Input Structures

#![allow(unused)]
fn main() {
// Suggestion/Creation input
pub struct MediumOfExchangeInput {
    pub medium_of_exchange: MediumOfExchange,
}

// Linking input for requests/offers
pub struct MediumOfExchangeLinkInput {
    pub medium_of_exchange_hash: ActionHash,
    pub action_hash: ActionHash,
    pub entity: String, // "request" or "offer"
}

// Update links for entity
pub struct UpdateMediumOfExchangeLinksInput {
    pub action_hash: ActionHash,
    pub entity: String,
    pub new_medium_of_exchange_hashes: Vec<ActionHash>,
}
}

Status Management System

The system uses path-based status tracking with three primary states:

Status Paths

#![allow(unused)]
fn main() {
const PENDING_MEDIUMS_OF_EXCHANGE_PATH: &str = "mediums_of_exchange.status.pending";
const APPROVED_MEDIUMS_OF_EXCHANGE_PATH: &str = "mediums_of_exchange.status.approved";
const REJECTED_MEDIUMS_OF_EXCHANGE_PATH: &str = "mediums_of_exchange.status.rejected";
}

Status Transition Workflow

stateDiagram-v2
    [*] --> Pending: suggest_medium_of_exchange()
    Pending --> Approved: approve_medium_of_exchange()
    Pending --> Rejected: reject_medium_of_exchange()
    Approved --> [*]: Active for linking
    Rejected --> [*]: Cannot be used

Core API Functions

User Operations

#![allow(unused)]
fn main() {
// Suggest new medium of exchange (accepted users + administrators)
#[hdk_extern]
pub fn suggest_medium_of_exchange(input: MediumOfExchangeInput) -> ExternResult<Record>

// Get medium of exchange details
#[hdk_extern]
pub fn get_medium_of_exchange(medium_of_exchange_hash: ActionHash) -> ExternResult<Option<Record>>

// Get latest version of medium
#[hdk_extern]
pub fn get_latest_medium_of_exchange_record(original_action_hash: ActionHash) -> ExternResult<Option<Record>>

// Get all approved mediums (public access)
#[hdk_extern]
pub fn get_approved_mediums_of_exchange(_: ()) -> ExternResult<Vec<Record>>
}

Administrative Operations

#![allow(unused)]
fn main() {
// Approve medium and create hREA ResourceSpec (admin only)
#[hdk_extern]
pub fn approve_medium_of_exchange(medium_of_exchange_hash: ActionHash) -> ExternResult<()>

// Reject medium (admin only)
#[hdk_extern]
pub fn reject_medium_of_exchange(medium_of_exchange_hash: ActionHash) -> ExternResult<()>

// Update medium details (admin only)
#[hdk_extern]
pub fn update_medium_of_exchange(input: UpdateMediumOfExchangeInput) -> ExternResult<Record>

// Delete medium (admin only)
#[hdk_extern]
pub fn delete_medium_of_exchange(medium_of_exchange_hash: ActionHash) -> ExternResult<()>

// Get pending mediums for review (admin only)
#[hdk_extern]
pub fn get_pending_mediums_of_exchange(_: ()) -> ExternResult<Vec<Record>>
}

Entity Linking Operations

#![allow(unused)]
fn main() {
// Create bidirectional links between medium and request/offer
#[hdk_extern]
pub fn link_to_medium_of_exchange(input: MediumOfExchangeLinkInput) -> ExternResult<()>

// Remove bidirectional links
#[hdk_extern]
pub fn unlink_from_medium_of_exchange(input: MediumOfExchangeLinkInput) -> ExternResult<()>

// Update all medium links for entity
#[hdk_extern]
pub fn update_medium_of_exchange_links(input: UpdateMediumOfExchangeLinksInput) -> ExternResult<()>

// Get all mediums linked to entity
#[hdk_extern]
pub fn get_mediums_of_exchange_for_entity(input: GetMediumOfExchangeForEntityInput) -> ExternResult<Vec<ActionHash>>
}

Access Control & Permissions

Permission Matrix

OperationUserAccepted UserAdministrator
Suggest Medium
View Approved
Approve/Reject
Update/Delete
View Pending
Link to Requests/Offers

Validation Rules

#![allow(unused)]
fn main() {
// Entry validation
pub fn validate_create_medium_of_exchange(
    _action: &SignedActionHashed,
    medium_of_exchange: &MediumOfExchange,
) -> ExternResult<ValidateCallbackResult> {
    if medium_of_exchange.code.is_empty() {
        return Ok(ValidateCallbackResult::Invalid(
            "MediumOfExchange code cannot be empty".to_string(),
        ));
    }
    if medium_of_exchange.name.is_empty() {
        return Ok(ValidateCallbackResult::Invalid(
            "MediumOfExchange name cannot be empty".to_string(),
        ));
    }
    Ok(ValidateCallbackResult::Valid)
}

// Author verification for updates/deletes
pub fn validate_update_medium_of_exchange(
    action: &SignedActionHashed,
    // ... parameters
) -> ExternResult<ValidateCallbackResult> {
    // Verify author matches original creator
    if action.action().author() != original_create_action.author() {
        return Ok(ValidateCallbackResult::Invalid(
            "Only the original author can update the MediumOfExchange.".to_string(),
        ));
    }
    Ok(ValidateCallbackResult::Valid)
}
}

💻 Frontend Implementation

Type System & Schemas

Core Types

// DHT representation (backend)
export interface MediumOfExchangeInDHT {
  code: string;
  name: string;
  description?: string | null;
  exchange_type: string; // "base" | "currency"
  resource_spec_hrea_id?: string | null;
}

// UI representation (frontend)
export interface UIMediumOfExchange {
  actionHash: ActionHash;
  original_action_hash?: ActionHash;
  code: string;
  name: string;
  description?: string | null;
  exchangeType: "base" | "currency";
  resourceSpecHreaId?: string | null;
  status: "pending" | "approved" | "rejected";
  createdAt: Date;
  updatedAt?: Date;
}

Effect Schema Validation

export const MediumOfExchangeInDHTSchema = Schema.Struct({
  code: Schema.String,
  name: Schema.String,
  description: Schema.optional(Schema.Union(Schema.String, Schema.Null)),
  exchange_type: Schema.Union(
    Schema.Literal("base"),
    Schema.Literal("currency"),
  ),
  resource_spec_hrea_id: Schema.optional(
    Schema.Union(Schema.String, Schema.Null),
  ),
});

export const UIMediumOfExchangeSchema = Schema.Class<UIMediumOfExchange>(
  "UIMediumOfExchange",
)({
  actionHash: Schema.Unknown,
  code: Schema.String,
  name: Schema.String,
  description: Schema.optional(Schema.Union(Schema.String, Schema.Null)),
  exchangeType: Schema.Union(
    Schema.Literal("base"),
    Schema.Literal("currency"),
  ),
  resourceSpecHreaId: Schema.optional(Schema.Union(Schema.String, Schema.Null)),
  status: Schema.Literal("pending", "approved", "rejected"),
  createdAt: Schema.Date,
  updatedAt: Schema.optional(Schema.Date),
});

Service Layer (Effect-TS)

Service Interface

export interface MediumsOfExchangeService {
  readonly suggestMediumOfExchange: (
    mediumOfExchange: MediumOfExchangeInDHT,
  ) => E.Effect<Record, MediumOfExchangeError>;

  readonly getMediumOfExchange: (
    mediumOfExchangeHash: ActionHash,
  ) => E.Effect<Record | null, MediumOfExchangeError>;

  readonly getAllMediumsOfExchange: () => E.Effect<
    Record[],
    MediumOfExchangeError
  >;

  readonly getPendingMediumsOfExchange: () => E.Effect<
    Record[],
    MediumOfExchangeError
  >;

  readonly getApprovedMediumsOfExchange: () => E.Effect<
    Record[],
    MediumOfExchangeError
  >;

  readonly approveMediumOfExchange: (
    mediumOfExchangeHash: ActionHash,
  ) => E.Effect<void, MediumOfExchangeError>;

  readonly rejectMediumOfExchange: (
    mediumOfExchangeHash: ActionHash,
  ) => E.Effect<void, MediumOfExchangeError>;

  // Entity linking operations
  readonly linkToMediumOfExchange: (input: {
    mediumOfExchangeHash: ActionHash;
    actionHash: ActionHash;
    entity: string;
  }) => E.Effect<void, MediumOfExchangeError>;

  readonly updateMediumOfExchangeLinks: (input: {
    actionHash: ActionHash;
    entity: string;
    newMediumOfExchangeHashes: ActionHash[];
  }) => E.Effect<void, MediumOfExchangeError>;
}

Service Implementation Pattern

export const makeMediumsOfExchangeService = E.gen(function* () {
  const client = yield* HolochainClientServiceTag;

  const suggestMediumOfExchange = (mediumOfExchange: MediumOfExchangeInDHT) =>
    E.gen(function* () {
      const result = yield* client.callZome(
        "mediums_of_exchange",
        "suggest_medium_of_exchange",
        { medium_of_exchange: mediumOfExchange },
      );
      return yield* Schema.decodeUnknown(MediumOfExchangeRecordSchema)(result);
    }).pipe(
      E.catchAll((error) =>
        E.fail(
          MediumOfExchangeError({
            context: MEDIUM_OF_EXCHANGE_CONTEXTS.SUGGEST_MEDIUM_OF_EXCHANGE,
            message: "Failed to suggest medium of exchange",
            cause: error,
          }),
        ),
      ),
    );

  return { suggestMediumOfExchange /* ... other methods */ };
});

Store Layer (Svelte 5 + Effect-TS)

Store Factory Pattern

export const createMediumsOfExchangeStore = () => {
  // Reactive state with Svelte 5 Runes
  let entities = $state<UIMediumOfExchange[]>([]);
  let pendingEntities = $state<UIMediumOfExchange[]>([]);
  let approvedEntities = $state<UIMediumOfExchange[]>([]);
  let isLoading = $state(false);
  let error = $state<string | null>(null);

  // Effect-TS operations
  const fetchAllMediumsOfExchange = E.gen(function* () {
    yield* withLoadingState(() =>
      E.gen(function* () {
        const service = yield* MediumsOfExchangeServiceTag;
        const records = yield* service.getAllMediumsOfExchange();
        entities = mapRecordsToUIEntities(records);
      }),
    );
  });

  const suggestMediumOfExchange = (mediumOfExchange: MediumOfExchangeInDHT) =>
    E.gen(function* () {
      const service = yield* MediumsOfExchangeServiceTag;
      const record = yield* service.suggestMediumOfExchange(mediumOfExchange);
      const uiEntity = createUIEntity(record);

      // Update local state
      entities = [...entities, uiEntity];
      pendingEntities = [...pendingEntities, uiEntity];

      // Emit event
      yield* emitMediumOfExchangeSuggested(uiEntity);
      return uiEntity;
    });

  return {
    // Reactive getters
    entities: () => entities,
    pendingEntities: () => pendingEntities,
    approvedEntities: () => approvedEntities,
    isLoading: () => isLoading,
    error: () => error,

    // Operations
    fetchAllMediumsOfExchange,
    suggestMediumOfExchange,
    approveMediumOfExchange,
    rejectMediumOfExchange,
  };
};

Component Layer

MediumOfExchangeForm Component

<script lang="ts">
  import type { UIMediumOfExchange, MediumOfExchangeInDHT } from '$lib/schemas/mediums-of-exchange.schemas';
  import mediumsOfExchangeStore from '$lib/stores/mediums_of_exchange.store.svelte';
  import { runEffect } from '$lib/utils/effect';

  type Props = {
    mediumOfExchange?: UIMediumOfExchange | null;
    mode?: 'create' | 'suggest' | 'edit';
    onSubmitSuccess?: (result: UIMediumOfExchange) => void;
    onCancel?: () => void;
  };

  let { mediumOfExchange = null, mode = 'create', onSubmitSuccess, onCancel }: Props = $props();

  // Form state
  let code = $state(mediumOfExchange?.code ?? '');
  let name = $state(mediumOfExchange?.name ?? '');
  let description = $state(mediumOfExchange?.description ?? '');
  let isSubmitting = $state(false);
  let errors = $state({} as Record<string, string>);

  const isValid = $derived(
    code.trim() !== '' && name.trim() !== '' && Object.keys(errors).length === 0
  );

  const handleSubmit = async (event: Event) => {
    event.preventDefault();
    if (!isValid || isSubmitting) return;

    isSubmitting = true;

    const mediumOfExchange: MediumOfExchangeInDHT = {
      code: code.trim(),
      name: name.trim(),
      description: description.trim() || null
    };

    const effect = mediumsOfExchangeStore.suggestMediumOfExchange(mediumOfExchange);

    try {
      const result = await runEffect(effect);
      onSubmitSuccess?.(result);
    } catch (error) {
      errors = { submit: 'Failed to suggest medium of exchange' };
    } finally {
      isSubmitting = false;
    }
  };
</script>

<form on:submit={handleSubmit} class="space-y-4">
  <label class="label">
    <span>Code (e.g., USD, EUR, TIME)</span>
    <input
      bind:value={code}
      class="input"
      placeholder="Enter unique code"
      required
    />
  </label>

  <label class="label">
    <span>Name</span>
    <input
      bind:value={name}
      class="input"
      placeholder="Enter display name"
      required
    />
  </label>

  <label class="label">
    <span>Description (optional)</span>
    <textarea
      bind:value={description}
      class="textarea"
      placeholder="Describe this medium of exchange"
    ></textarea>
  </label>

  <div class="flex gap-2">
    <button
      type="submit"
      disabled={!isValid || isSubmitting}
      class="btn variant-filled-primary"
    >
      {isSubmitting ? 'Suggesting...' : 'Suggest Medium'}
    </button>

    <button
      type="button"
      on:click={onCancel}
      class="btn variant-ghost"
    >
      Cancel
    </button>
  </div>
</form>

MediumOfExchangeSelector Component

<script lang="ts">
  import type { UIMediumOfExchange } from '$lib/schemas/mediums-of-exchange.schemas';
  import mediumsOfExchangeStore from '$lib/stores/mediums_of_exchange.store.svelte';
  import MediumOfExchangeSuggestionForm from './MediumOfExchangeSuggestionForm.svelte';
  import { onMount } from 'svelte';
  import { runEffect } from '$lib/utils/effect';

  type Props = {
    selectedMediums?: UIMediumOfExchange[];
    onSelectionChange?: (mediums: UIMediumOfExchange[]) => void;
    multiple?: boolean;
    showSuggestionButton?: boolean;
  };

  let { selectedMediums = [], onSelectionChange, multiple = true, showSuggestionButton = true }: Props = $props();

  const approvedMediums = mediumsOfExchangeStore.approvedEntities;
  const isLoading = mediumsOfExchangeStore.isLoading;

  let showSuggestionForm = $state(false);

  // Categorize mediums for enhanced UX
  const baseCategoryMediums = $derived(
    approvedMediums().filter(m => ['PAY_IT_FORWARD', 'LETS', 'TIME'].includes(m.code))
  );

  const currencyMediums = $derived(
    approvedMediums().filter(m => !['PAY_IT_FORWARD', 'LETS', 'TIME'].includes(m.code))
  );

  onMount(async () => {
    const effect = mediumsOfExchangeStore.fetchApprovedMediumsOfExchange();
    await runEffect(effect);
  });

  const handleSelectionChange = (medium: UIMediumOfExchange, selected: boolean) => {
    let newSelection: UIMediumOfExchange[];

    if (multiple) {
      newSelection = selected
        ? [...selectedMediums, medium]
        : selectedMediums.filter(m => m.actionHash !== medium.actionHash);
    } else {
      newSelection = selected ? [medium] : [];
    }

    onSelectionChange?.(newSelection);
  };

  const handleSuggestionSuccess = () => {
    showSuggestionForm = false;
    // Refresh the list to show new suggestion (if approved)
    runEffect(mediumsOfExchangeStore.fetchApprovedMediumsOfExchange());
  };
</script>

<div class="space-y-4">
  <div class="flex justify-between items-center">
    <h4 class="h4">Select Payment Methods</h4>
    {#if showSuggestionButton}
      <button
        class="btn btn-sm variant-outline-primary"
        on:click={() => showSuggestionForm = true}
      >
        Suggest New Medium
      </button>
    {/if}
  </div>

  {#if isLoading()}
    <div class="placeholder animate-pulse">Loading payment methods...</div>
  {:else}
    <!-- Base Categories Section -->
    <div class="space-y-3">
      <div class="flex items-center gap-2">
        <span class="text-lg">📂</span>
        <h5 class="h5">Base Categories</h5>
      </div>
      <div class="grid grid-cols-1 md:grid-cols-2 gap-2">
        {#each baseCategoryMediums as medium}
          {@const isSelected = selectedMediums.some(m => m.actionHash === medium.actionHash)}

          <label class="flex items-center space-x-2 p-3 border rounded-lg cursor-pointer hover:bg-surface-100-800-token">
            <input
              type={multiple ? 'checkbox' : 'radio'}
              checked={isSelected}
              on:change={(e) => handleSelectionChange(medium, e.currentTarget.checked)}
              class="checkbox"
            />
            <div class="flex-1">
              <div class="font-semibold">{medium.name}</div>
              <div class="text-sm opacity-75">{medium.code}</div>
              {#if medium.description}
                <div class="text-xs opacity-60">{medium.description}</div>
              {/if}
            </div>
          </label>
        {/each}
      </div>
    </div>

    <!-- Currencies Section -->
    {#if currencyMediums.length > 0}
      <div class="space-y-3">
        <div class="flex items-center gap-2">
          <span class="text-lg">💰</span>
          <h5 class="h5">Currencies</h5>
        </div>
        <div class="grid grid-cols-1 md:grid-cols-3 gap-2">
          {#each currencyMediums as medium}
            {@const isSelected = selectedMediums.some(m => m.actionHash === medium.actionHash)}

            <label class="flex items-center space-x-2 p-2 border rounded cursor-pointer hover:bg-surface-100-800-token">
              <input
                type="checkbox"
                checked={isSelected}
                on:change={(e) => handleSelectionChange(medium, e.currentTarget.checked)}
                class="checkbox"
              />
              <div class="flex-1">
                <div class="font-medium text-sm">{medium.name}</div>
                <div class="text-xs opacity-75">{medium.code}</div>
              </div>
            </label>
          {/each}
        </div>
      </div>
    {/if}
  {/if}

  <!-- Suggestion Form Modal -->
  {#if showSuggestionForm}
    <div class="card p-4 bg-surface-200-700-token">
      <div class="flex justify-between items-center mb-4">
        <h5 class="h5">Suggest New Medium of Exchange</h5>
        <button
          class="btn btn-sm variant-ghost"
          on:click={() => showSuggestionForm = false}
        >
          ✕
        </button>
      </div>
      <MediumOfExchangeSuggestionForm
        onSubmitSuccess={handleSuggestionSuccess}
        onCancel={() => showSuggestionForm = false}
      />
    </div>
  {/if}
</div>

🔗 hREA Integration

Economic Framework Mapping

The Medium of Exchanges feature integrates with hREA (Holochain Resource-Event-Agent) framework to enable economic coordination:

ResourceSpecification Mapping

// Medium of Exchange → hREA ResourceSpecification
export const mapMediumToResourceSpec = (
  medium: UIMediumOfExchange,
): ResourceSpecification => ({
  name: medium.name,
  note: medium.description || undefined,
  resourceClassifiedAs: [medium.code],
  defaultUnitOfResource: medium.code === "TIME" ? "hours" : "units",
});

Integration Workflow

sequenceDiagram
    participant User as User
    participant App as MoE System
    participant hREA as hREA DNA

    User->>App: Suggest Medium (e.g., "TIME")
    App->>App: Create Entry (status: pending)

    User->>App: Admin Approves Medium
    App->>hREA: Create ResourceSpecification
    hREA-->>App: ResourceSpec ID
    App->>App: Update Entry (add hREA ID, status: approved)

    User->>App: Link Medium to Request
    App->>App: Verify Medium is Approved
    App->>App: Create Bidirectional Links

Approval Process Enhancement

#![allow(unused)]
fn main() {
// When approving medium, create hREA ResourceSpecification
#[hdk_extern]
pub fn approve_medium_of_exchange(medium_of_exchange_hash: ActionHash) -> ExternResult<()> {
    // ... admin permission check ...

    // TODO: Create hREA ResourceSpecification here
    // For now, placeholder ID - will be implemented with full hREA integration
    let resource_spec_id = format!("hrea_resource_spec_{}", entry.code);

    // Update entry with hREA ResourceSpecification ID
    let updated_entry = MediumOfExchange {
        code: entry.code,
        name: entry.name,
        description: entry.description,
        resource_spec_hrea_id: Some(resource_spec_id), // Link to hREA
    };

    // ... update entry and status links ...
}
}

🧪 Testing Strategy

Backend Testing (Sweettest)

Multi-Agent Test Scenarios

test("basic MediumOfExchange suggestion and approval workflow", async () => {
  await runScenarioWithTwoAgents(
    async (_scenario: Scenario, alice: PlayerApp, bob: PlayerApp) => {
      // Setup: Create users and admin
      const aliceUser = sampleUser({ name: "Alice" });
      const aliceUserRecord = await createUser(alice.cells[0], aliceUser);

      const bobUser = sampleUser({ name: "Bob" });
      const bobUserRecord = await createUser(bob.cells[0], bobUser);

      // Register Alice as network administrator
      await registerNetworkAdministrator(
        alice.cells[0],
        aliceUserRecord.signed_action.hashed.hash,
      );

      // Test: Bob suggests a medium of exchange
      const sampleMedium = sampleMediumOfExchange({
        code: "TIME",
        name: "Time Banking Hours",
      });

      const suggestedRecord = await suggestMediumOfExchange(bob.cells[0], {
        medium_of_exchange: sampleMedium,
      });

      // Verify: Medium is created and pending
      assert.ok(suggestedRecord);

      // Test: Alice (admin) approves the medium
      await approveMediumOfExchange(
        alice.cells[0],
        suggestedRecord.signed_action.hashed.hash,
      );

      // Verify: Medium is now approved and has hREA ID
      const approvedMediums = await getApprovedMediumsOfExchange(
        alice.cells[0],
      );
      expect(approvedMediums).toHaveLength(1);

      const approvedMedium = decode(
        approvedMediums[0].entry,
      ) as MediumOfExchange;
      expect(approvedMedium.resource_spec_hrea_id).toBeTruthy();
    },
  );
});

Permission Testing

test("unauthorized users cannot suggest mediums of exchange", async () => {
  await runScenarioWithTwoAgents(
    async (_scenario: Scenario, alice: PlayerApp, bob: PlayerApp) => {
      // Bob tries to suggest without being an accepted user
      const medium = sampleMediumOfExchange();

      try {
        await suggestMediumOfExchange(bob.cells[0], {
          medium_of_exchange: medium,
        });
        expect.fail("Should have thrown unauthorized error");
      } catch (error) {
        expect(error.message).toContain("Unauthorized");
      }
    },
  );
});

Entity Linking Tests

test("link medium of exchange to request", async () => {
  // ... setup approved medium and request ...

  // Link medium to request
  await linkToMediumOfExchange(alice.cells[0], {
    medium_of_exchange_hash: mediumHash,
    action_hash: requestHash,
    entity: "request",
  });

  // Verify bidirectional links
  const linkedRequests = await getRequestsForMediumOfExchange(
    alice.cells[0],
    mediumHash,
  );
  expect(linkedRequests).toHaveLength(1);

  const linkedMediums = await getMediumsOfExchangeForEntity(alice.cells[0], {
    original_action_hash: requestHash,
    entity: "request",
  });
  expect(linkedMediums).toHaveLength(1);
});

Frontend Testing

Store Testing (Effect-TS)

describe("MediumsOfExchangeStore", () => {
  let store: MediumsOfExchangeStore;
  let mockService: MediumsOfExchangeService;

  beforeEach(() => {
    mockService = createMockMediumsOfExchangeService();
    store = createMediumsOfExchangeStore();
  });

  it("should suggest medium of exchange successfully", async () => {
    const medium: MediumOfExchangeInDHT = {
      code: "TIME",
      name: "Time Banking",
      description: "Hour-based time banking system",
    };

    const effect = store.suggestMediumOfExchange(medium);
    const layer = Layer.succeed(MediumsOfExchangeServiceTag, mockService);

    const result = await runEffect(effect, layer);

    expect(result.code).toBe("TIME");
    expect(store.entities()).toHaveLength(1);
    expect(store.pendingEntities()).toHaveLength(1);
  });

  it("should handle approval workflow", async () => {
    // Setup pending medium
    const pendingMedium = createMockUIMediumOfExchange({ status: "pending" });
    store.entities = [pendingMedium];
    store.pendingEntities = [pendingMedium];

    // Approve medium
    const effect = store.approveMediumOfExchange(pendingMedium.actionHash);
    await runEffect(effect, layer);

    // Verify state changes
    expect(store.pendingEntities()).toHaveLength(0);
    expect(store.approvedEntities()).toHaveLength(1);
    expect(store.approvedEntities()[0].status).toBe("approved");
  });
});

Component Testing

describe("MediumOfExchangeSelector", () => {
  it("should display approved mediums for selection", () => {
    const approvedMediums = [
      createMockUIMediumOfExchange({
        code: "USD",
        name: "US Dollar",
        status: "approved",
      }),
      createMockUIMediumOfExchange({
        code: "TIME",
        name: "Time Banking",
        status: "approved",
      }),
    ];

    const { container } = render(MediumOfExchangeSelector, {
      props: { approvedMediums },
    });

    expect(container.querySelectorAll('input[type="checkbox"]')).toHaveLength(
      2,
    );
    expect(container.textContent).toContain("US Dollar");
    expect(container.textContent).toContain("Time Banking");
  });

  it("should handle selection changes correctly", async () => {
    const onSelectionChange = vi.fn();
    const mediums = [createMockUIMediumOfExchange()];

    const { container } = render(MediumOfExchangeSelector, {
      props: { approvedMediums: mediums, onSelectionChange },
    });

    const checkbox = container.querySelector(
      'input[type="checkbox"]',
    ) as HTMLInputElement;
    await fireEvent.click(checkbox);

    expect(onSelectionChange).toHaveBeenCalledWith([mediums[0]]);
  });
});

🎯 Use Cases & Examples

Traditional Currency Support

// Example: USD medium of exchange
const usdMedium: MediumOfExchangeInDHT = {
  code: "USD",
  name: "US Dollar",
  description: "United States Dollar - traditional fiat currency",
};

// Usage in request
const request: RequestInDHT = {
  title: "Website Development",
  description: "Need a business website built",
  // ... other fields
};

// Link USD as accepted payment method
await linkToMediumOfExchange({
  mediumOfExchangeHash: usdMediumHash,
  actionHash: requestHash,
  entity: "request",
});

Alternative Exchange Systems

Time Banking

const timeBankingMedium: MediumOfExchangeInDHT = {
  code: "TIME",
  name: "Time Banking Hours",
  description:
    "Hour-for-hour time exchange system where all hours are valued equally",
};

Local Exchange Trading System (LETS)

const letsMedium: MediumOfExchangeInDHT = {
  code: "LETS_COMMUNITY",
  name: "Community LETS Points",
  description:
    "Local Exchange Trading System points for community resource sharing",
};

Pay It Forward

const payItForwardMedium: MediumOfExchangeInDHT = {
  code: "PAY_IT_FORWARD",
  name: "Pay It Forward",
  description:
    "No direct payment - recipient commits to helping others in the community",
};

Multi-Medium Requests

// Request accepting multiple payment methods
const flexibleRequest: RequestInDHT = {
  title: "Garden Maintenance",
  description: "Weekly garden care and maintenance",
  // ... other fields
};

// Link multiple mediums
const acceptedMediums = [
  usdMediumHash,
  timeBankingMediumHash,
  payItForwardMediumHash,
];

await updateMediumOfExchangeLinks({
  actionHash: requestHash,
  entity: "request",
  newMediumOfExchangeHashes: acceptedMediums,
});

🚀 Administrative Interface

Admin Dashboard Features

Pending Approvals

<!-- Admin dashboard showing pending mediums -->
<script lang="ts">
  import mediumsOfExchangeStore from '$lib/stores/mediums_of_exchange.store.svelte';

  const pendingMediums = mediumsOfExchangeStore.pendingEntities;

  const handleApprove = async (medium: UIMediumOfExchange) => {
    const effect = mediumsOfExchangeStore.approveMediumOfExchange(medium.actionHash);
    await runEffect(effect);
  };

  const handleReject = async (medium: UIMediumOfExchange) => {
    const effect = mediumsOfExchangeStore.rejectMediumOfExchange(medium.actionHash);
    await runEffect(effect);
  };
</script>

<div class="admin-dashboard">
  <h3>Pending Medium of Exchange Approvals</h3>

  {#each pendingMediums() as medium}
    <div class="card p-4 mb-4">
      <div class="flex justify-between items-start">
        <div>
          <h4 class="h4">{medium.name} ({medium.code})</h4>
          {#if medium.description}
            <p class="text-sm opacity-75">{medium.description}</p>
          {/if}
          <p class="text-xs opacity-60">Suggested: {medium.createdAt.toLocaleDateString()}</p>
        </div>

        <div class="flex gap-2">
          <button
            class="btn btn-sm variant-filled-success"
            on:click={() => handleApprove(medium)}
          >
            Approve
          </button>
          <button
            class="btn btn-sm variant-filled-error"
            on:click={() => handleReject(medium)}
          >
            Reject
          </button>
        </div>
      </div>
    </div>
  {/each}
</div>

System Statistics

// Admin analytics for medium usage
interface MediumUsageStats {
  medium: UIMediumOfExchange;
  requestCount: number;
  offerCount: number;
  popularityScore: number;
}

const calculateMediumUsageStats = async (): Promise<MediumUsageStats[]> => {
  const approvedMediums = await getAllApprovedMediums();
  const stats: MediumUsageStats[] = [];

  for (const medium of approvedMediums) {
    const requests = await getRequestsForMediumOfExchange(medium.actionHash);
    const offers = await getOffersForMediumOfExchange(medium.actionHash);

    stats.push({
      medium,
      requestCount: requests.length,
      offerCount: offers.length,
      popularityScore: requests.length + offers.length,
    });
  }

  return stats.sort((a, b) => b.popularityScore - a.popularityScore);
};

🔮 Future Enhancements

Phase 1: Enhanced hREA Integration

  • Complete ResourceSpecification Creation: Full integration with hREA DNA
  • Economic Event Tracking: Track actual exchanges using approved mediums
  • Agreement Templates: Pre-defined exchange agreements for common mediums

Phase 2: Advanced Features

  • Exchange Rate Management: Support for conversion rates between different mediums
  • Geographic Scope: Regional availability and restrictions for mediums
  • Verification System: Community verification for alternative currency systems

Phase 3: Analytics & Intelligence

  • Usage Analytics: Detailed statistics on medium popularity and usage patterns
  • Recommendation Engine: Suggest appropriate mediums based on request/offer characteristics
  • Trend Analysis: Track adoption of alternative currencies within the community

Phase 4: Integration Expansion

  • External Currency APIs: Real-time exchange rates for traditional currencies
  • Blockchain Integration: Support for cryptocurrency mediums
  • Banking Integration: Direct integration with payment processors for traditional currencies

📊 Current Implementation Status

✅ Completed Features

  • Backend Implementation: Complete Rust zome with all core functions
  • Status Management: Full approval workflow with path-based tracking
  • Entity Linking: Bidirectional linking with requests and offers
  • Access Control: Comprehensive permission system
  • Frontend Service Layer: Effect-TS service with error handling
  • Store Implementation: Svelte 5 + Effect-TS reactive store with all 9 standardized helper functions
  • Enhanced UI Components: Complete form and selector components with categorization
  • Testing Infrastructure: Sweettest tests and frontend unit tests (all 343 tests passing across 20 files)
  • UI/UX Enhancements: Generic vs specific MoE distinction, suggestion functionality
  • Form Integration: Seamless integration across requests and offers forms

🎉 Major Achievements

  • Visual Distinction: Clear separation between "Base Categories" (📂) and "Currencies" (💰)
  • Interactive Checkbox Interface: Enhanced user experience with dynamic currency selection
  • Suggestion System: MediumOfExchangeSuggestionForm.svelte component for user contributions
  • Navigation Cleanup: Removed unnecessary public page and fixed all broken links
  • Complete Architecture: Full 7-layer Effect-TS implementation with domain-specific tagged errors

🔄 In Progress (5% Remaining)

  • Final Verification: Comprehensive testing across all scenarios
  • UI/UX Polish: Minor adjustments based on real-world usage testing
  • Cross-Browser Validation: Ensuring compatibility across different browsers

📋 Future Enhancements

  • Analytics Dashboard: Usage statistics and trend analysis
  • Enhanced hREA Integration: Full ResourceSpecification creation workflow
  • Advanced Features: Exchange rate management, geographic scope, verification systems

This comprehensive documentation provides complete technical coverage of the Medium of Exchanges feature, from backend Rust implementation through frontend TypeScript/Svelte integration, including testing strategies and future enhancement roadmap.

Organization Contact Person

Overview

Each organization can designate a single contact person — a coordinator who serves as the public-facing representative. The contact person is stored as an OrganizationContacts link with the role/title encoded in the link tag.

Key Concepts

  • Single contact per organization: Setting a new contact automatically replaces the previous one
  • Coordinator-only: Only organization coordinators can be designated as contact person
  • Role/title: A free-text field (e.g. "Director", "President", "Contact Person") stored in the link tag
  • Auto-cleanup: Contact links are automatically removed when the contact person leaves, is removed from the organization, or the organization is deleted

Backend API

#![allow(unused)]
fn main() {
// In dnas/requests_and_offers/zomes/integrity/users_organizations/src/lib.rs
pub enum LinkTypes {
    // ... existing link types ...
    OrganizationContacts,  // Organization → User (tag = role string)
}
}

The OrganizationContacts link connects an organization's ActionHash to a user's ActionHash, with the role/title encoded as the link tag bytes.

New Types

#![allow(unused)]
fn main() {
// In dnas/requests_and_offers/utils/src/types.rs

/// Input for setting an organization contact with a role
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct OrganizationContactInput {
    pub organization_original_action_hash: ActionHash,
    pub user_original_action_hash: ActionHash,
    pub role: String,
}
}

The existing OrganizationUser type was renamed to OrganizationUserInput for clarity.

New Errors

#![allow(unused)]
fn main() {
// In dnas/requests_and_offers/utils/src/errors.rs (OrganizationsError enum)
AlreadyContact,  // User is already the contact person
NotContact,      // No contact person exists (when trying to remove)
}

Zome Functions

All functions are in the users_organizations coordinator zome (organization.rs).

#![allow(unused)]
fn main() {
pub fn get_organization_contacts_links(
    organization_original_action_hash: ActionHash
) -> ExternResult<Vec<Link>>
}

Returns all OrganizationContacts links for an organization. In practice, there should be at most one (single contact enforcement).

get_organization_contact

#![allow(unused)]
fn main() {
pub fn get_organization_contact(
    organization_original_action_hash: ActionHash
) -> ExternResult<Option<(User, String)>>
}

Returns the contact person's User entry and their role string, or None if no contact is set.

set_organization_contact

#![allow(unused)]
fn main() {
pub fn set_organization_contact(
    input: OrganizationContactInput
) -> ExternResult<bool>
}

Sets the contact person for an organization.

Preconditions:

  • Caller must be a coordinator of the organization
  • Target user must be a coordinator of the organization

Behavior:

  • Removes any existing contact link (single contact enforcement)
  • Creates a new OrganizationContacts link with the role as the tag

remove_organization_contact

#![allow(unused)]
fn main() {
pub fn remove_organization_contact(
    organization_original_action_hash: ActionHash
) -> ExternResult<bool>
}

Removes the contact person from an organization.

Preconditions:

  • Caller must be a coordinator of the organization
  • A contact must exist (returns NotContact error otherwise)

is_organization_contact

#![allow(unused)]
fn main() {
pub fn is_organization_contact(
    input: OrganizationUserInput
) -> ExternResult<bool>
}

Checks whether a specific user is the contact person for the organization.

Cleanup Behavior

Contact links are automatically cleaned up in these existing functions:

  • leave_organization: If the leaving member is the contact, their contact link is removed
  • remove_organization_member: If the removed member is the contact, their contact link is removed
  • delete_organization: All contact links are deleted as part of organization cleanup

Frontend Service

File: ui/src/lib/services/zomes/organizations.service.ts

Three new methods on the OrganizationsService interface:

readonly getOrganizationContactsLinks: (
    organization_original_action_hash: ActionHash
) => E.Effect<Link[], OrganizationError>;

readonly setOrganizationContact: (
    organization_original_action_hash: ActionHash,
    user_original_action_hash: ActionHash,
    role: string
) => E.Effect<boolean, OrganizationError>;

readonly removeOrganizationContact: (
    organization_original_action_hash: ActionHash
) => E.Effect<boolean, OrganizationError>;

Error Contexts

// In ui/src/lib/errors/error-contexts.ts (ORGANIZATION_CONTEXTS)
SET_ORGANIZATION_CONTACT: 'Failed to set organization contact',
REMOVE_ORGANIZATION_CONTACT: 'Failed to remove organization contact',
GET_ORGANIZATION_CONTACTS: 'Failed to get organization contacts'

Frontend Store

File: ui/src/lib/stores/organizations.store.svelte.ts

New State

readonly currentContact: { user_hash: ActionHash; role: string } | null;

Derived from currentOrganization?.contact || null.

New Methods

setContact: (
    orgHash: ActionHash,
    userHash: ActionHash,
    role: string
) => E.Effect<boolean, OrganizationError>;

removeContact: (
    orgHash: ActionHash
) => E.Effect<boolean, OrganizationError>;

Both methods invalidate the organization cache after successful operation.

Organization Loading

The getLatestOrganization function fetches contact links alongside member and coordinator links, then transforms the first contact link into a { user_hash, role } object stored on the UIOrganization.

UI Type

File: ui/src/lib/types/ui.ts

export type UIOrganization = OrganizationInDHT & {
    members: ActionHash[];
    coordinators: ActionHash[];
    contact?: { user_hash: ActionHash; role: string };
    status?: UIStatus;
    original_action_hash?: ActionHash;
    previous_action_hash?: ActionHash;
};

UI Components

OrganizationDetailsModal

Displays the contact person's name (resolved via usersStore.getUserByActionHash) and role in the "Contact Information" section. The contact name links to the user's profile page.

OrganizationCoordinatorsTable

Adds an isContact() helper to check if a coordinator is the current contact person. Used to display a visual indicator next to the contact coordinator.

OrganizationForm

Unchanged for contact management (contact is managed separately from the organization form).

UserOrganizationsTable

Shows contact information when displaying organizations a user belongs to.

Organization Detail Page (/organizations/[id])

Displays the contact person with name, profile link, and role in the "Contact" section alongside other organization details (legal name, email, location).

Organization Edit Page (/organizations/[id]/edit)

Provides a full contact management UI:

  • Displays current contact person (if any) with a "Remove Contact" button
  • Coordinator dropdown to select a new contact
  • Role/title text input field
  • "Set Contact" / "Change Contact" button

Organization Create Page (/organizations/create)

Allows the creator to optionally designate themselves as contact during organization creation:

  • "I am the contact person for this organization" checkbox
  • Role/title input (shown when checked)
  • Contact is set after organization creation via setCreatorAsContact()

Business Rules

  1. Coordinator-only: Only coordinators can be designated as contact person. The backend enforces this by checking is_organization_coordinator before setting the contact.
  2. Single contact: Each organization has at most one contact person. Setting a new contact replaces the previous one by deleting existing contact links first.
  3. Auto-removal on leave/remove: When a member leaves or is removed from the organization, if they are the contact person, their contact link is automatically cleaned up.
  4. Auto-removal on delete: When an organization is deleted, all contact links are removed as part of the cleanup process.
  5. Optional during creation: The organization creator can optionally set themselves as the contact person during creation. This is handled as a post-creation step (separate from the create_organization call).
  6. Role flexibility: The role/title is a free-text string, allowing organizations to use whatever title is appropriate (Director, President, Secretary, Contact Person, etc.).

Services Layer Documentation

This document provides a comprehensive overview of the services layer in the Requests and Offers application, which handles all communication with the Holochain backend.

Services Architecture

The services layer is built on Effect TS for robust error handling, dependency injection, and asynchronous operations. It follows a standardized 7-layer pattern that ensures consistency across all domain services.

Service Organization

/services/
├── holochainClient.service.ts           # Base Holochain client service
├── HolochainClientService.svelte.ts     # Svelte integration for Holochain client
├── hrea.service.ts                      # hREA integration service
└── zomes/                               # Domain-specific zome services
    ├── serviceTypes.service.ts          # Service types functionality
    ├── requests.service.ts              # Requests functionality
    ├── offers.service.ts                # Offers functionality
    ├── users.service.ts                 # Users functionality
    ├── organizations.service.ts         # Organizations functionality
    ├── administration.service.ts        # Admin functionality
    ├── mediums-of-exchange.service.ts   # Medium of exchange functionality
    └── projects.service.ts              # Projects functionality

The 7-Layer Effect Service Pattern

All services follow a standardized 7-layer pattern for consistency and maintainability:

1. Service Interface Definition

Clearly defines the contract for each service:

export interface ServiceTypeService {
  readonly createServiceType: (
    input: ServiceTypeInput,
  ) => E.Effect<Record, ServiceTypeError>;
  readonly getServiceType: (
    hash: ActionHash,
  ) => E.Effect<Record | null, ServiceTypeError>;
  readonly getAllServiceTypes: () => E.Effect<Record[], ServiceTypeError>;
  readonly searchServiceTypes: (
    query: string,
  ) => E.Effect<Record[], ServiceTypeError>;
  readonly approveServiceType: (
    hash: ActionHash,
  ) => E.Effect<Record, ServiceTypeError>;
  readonly rejectServiceType: (
    hash: ActionHash,
  ) => E.Effect<Record, ServiceTypeError>;
  // Other service-specific methods
}

2. Context Tag for Dependency Injection

Enables clean dependency injection using Effect-TS Context.GenericTag:

export const ServiceTypeService =
  Context.GenericTag<ServiceTypeService>("ServiceTypeService");

3. Error Type Definition

Domain-specific tagged errors using Effect-TS Data.TaggedError:

import { Data } from "effect";

export class ServiceTypeError extends Data.TaggedError("ServiceTypeError")<{
  readonly message: string;
  readonly cause?: unknown;
  readonly context?: string;
  readonly entityId?: string;
  readonly operation?: string;
}> {
  static fromError(
    error: unknown,
    context: string,
    entityId?: string,
    operation?: string,
  ): ServiceTypeError {
    const message = error instanceof Error ? error.message : String(error);
    return new ServiceTypeError({
      message,
      cause: error,
      context,
      entityId,
      operation,
    });
  }

  static create(
    message: string,
    context?: string,
    entityId?: string,
    operation?: string,
  ): ServiceTypeError {
    return new ServiceTypeError({
      message,
      context,
      entityId,
      operation,
    });
  }
}

4. Schema Validation

Rigorous input/output validation:

export const ServiceTypeSchema = S.struct({
  name: S.string,
  description: S.string,
  tags: S.array(S.string),
  status: S.enums(["pending", "approved", "rejected"]),
  createdAt: S.number,
});

export type ServiceType = S.Schema.To<typeof ServiceTypeSchema>;

5. Service Implementation

Concrete implementation using Effect TS with dependency injection:

export const makeServiceTypeService = Effect.gen(function* () {
  const client = yield* HolochainClientService;

  const createServiceType = (input: CreateServiceTypeInput) =>
    Effect.gen(function* () {
      const record = yield* client.callZome({
        zome_name: "service_types",
        fn_name: "create_service_type",
        payload: input,
      });

      const entity = createUIServiceType(record);
      if (!entity) {
        yield* Effect.fail(
          ServiceTypeError.create("Failed to create UI entity from record"),
        );
      }

      return entity;
    }).pipe(
      Effect.mapError((error) =>
        ServiceTypeError.fromError(
          error,
          SERVICE_TYPE_CONTEXTS.CREATE_SERVICE_TYPE,
        ),
      ),
      Effect.withSpan("ServiceTypeService.createServiceType"),
    );

  const getAllServiceTypes = () =>
    Effect.gen(function* () {
      const records = yield* client.callZome({
        zome_name: "service_types",
        fn_name: "get_all_service_types",
        payload: null,
      });

      return mapRecordsToUIServiceTypes(records);
    }).pipe(
      Effect.mapError((error) =>
        ServiceTypeError.fromError(
          error,
          SERVICE_TYPE_CONTEXTS.GET_ALL_SERVICE_TYPES,
        ),
      ),
      Effect.withSpan("ServiceTypeService.getAllServiceTypes"),
    );

  return {
    createServiceType,
    getAllServiceTypes,
    // Other methods...
  };
});

6. Effect Layer Definition

Layer for dependency injection:

export const ServiceTypeServiceLive = Layer.effect(
  ServiceTypeService,
  makeServiceTypeService,
).pipe(Layer.provide(HolochainClientServiceLive));

7. Unit Tests

Comprehensive testing with mock implementations:

describe("ServiceTypeService", () => {
  const mockClient = {
    callZomeEffect: vi.fn(),
  } as unknown as HolochainClientService;

  const service = makeServiceTypeService(mockClient);

  beforeEach(() => {
    vi.resetAllMocks();
  });

  test("createServiceType should call the correct zome function", async () => {
    // Test implementation
  });

  // Other tests...
});

Holochain Client Service

The foundation for all zome services is the HolochainClientService, which provides standardized methods for interacting with the Holochain conductor:

export interface HolochainClientService {
  readonly callZomeRawEffect: <I, O>(
    dnaName: string,
    zomeName: string,
    fnName: string,
    payload: I,
  ) => E.Effect<O, HolochainError>;

  readonly callZomeEffect: <I, O>(
    dnaName: string,
    zomeName: string,
    fnName: string,
    payload: I,
  ) => E.Effect<O, HolochainError>;

  // Other utility methods
}

Key differences between call methods:

  • callZomeRawEffect: Direct pass-through to Holochain without schema validation
  • callZomeEffect: Includes schema validation, error handling, and logging

Integration with hREA

The application integrates with hREA (Holochain Resource Event Agent) through the hrea.service.ts service:

export interface HreaService {
  readonly getResourceSpecifications: () => E.Effect<Record[], HreaError>;
  readonly createResourceSpecification: (
    input: any,
  ) => E.Effect<Record, HreaError>;
  // Other hREA-specific methods
}

This service provides a standardized interface for working with economic resources, events, and agents in the Holochain ecosystem.

Implementation Status

ServiceImplementation StatusNotes
HolochainClientService✅ CompleteFoundation service with schema validation
serviceTypes.service✅ CompleteFully standardized - Reference implementation
requests.service✅ CompleteFully standardized with 7-layer pattern
offers.service✅ CompleteFully standardized with 7-layer pattern
users.service✅ CompleteConverted to Effect architecture
organizations.service✅ CompleteConverted to Effect architecture
administration.service✅ CompleteConverted to Effect architecture

Best Practices

  1. Effect TS for all async operations: Avoid mixing Promises and Effects
  2. Tagged errors with context: Provide meaningful error messages and context
  3. Schema validation: Validate all inputs and outputs
  4. Clean dependency injection: Use Context.Tag and Layer pattern
  5. Comprehensive testing: Test all service methods
  6. Documentation: Document all service interfaces
  7. Error handling: Properly map and transform errors

Working with Services

Service Usage in Stores

Services are consumed by stores using the Effect TS dependency injection system:

export const createServiceTypeStore = (): E.Effect<
  ServiceTypeStore,
  never,
  ServiceTypeServiceTag
> =>
  E.gen(function* () {
    const service = yield* Context.get(ServiceTypeServiceTag);

    // Use service methods to implement store functionality
  });

Service Usage in Components

Components should never directly use services. Instead, they should use stores which internally use services:

// In a Svelte component
import { serviceTypeStore } from "$lib/stores/serviceTypes.store.svelte";

function handleCreateServiceType(input) {
  serviceTypeStore.createServiceType(input);
}

Adding a New Service Method

To add a new method to a service:

  1. Add the method signature to the service interface
  2. Implement the method in the service implementation
  3. Add appropriate error types
  4. Add schema validation if needed
  5. Write tests for the new method
  6. Update the store to use the new method

Service Error Handling

All service errors should be properly typed and mapped:

export const getAllServiceTypes = (): E.Effect<Record[], ServiceTypeError> =>
  pipe(
    client.callZomeEffect(
      "requests_and_offers",
      "service_types",
      "get_all_service_types",
      null,
    ),
    E.mapError((err) =>
      err instanceof HolochainError
        ? err
        : new ServiceTypeGetAllError({ cause: err }),
    ),
  );

State Management Documentation

This document details the state management approach in the Requests and Offers application, which uses Svelte stores integrated with Effect TS for robust error handling and data flow management.

Store Architecture

The application uses a standardized store architecture located in ui/src/lib/stores/, with factory functions creating Effect-integrated Svelte stores.

Store Organization

/stores/
├── serviceTypes.store.svelte.ts      # Service types state
├── requests.store.svelte.ts          # Requests state
├── offers.store.svelte.ts            # Offers state
├── users.store.svelte.ts             # User profiles state
├── organizations.store.svelte.ts     # Organizations state
├── administration.store.svelte.ts    # Admin functionality state
├── mediums_of_exchange.store.svelte.ts # Medium of exchange state
├── hrea.store.svelte.ts              # hREA integration state
└── storeEvents.ts                    # Event bus definitions

The 9-Helper Function Pattern

Stores implement a standardized architecture using nine helper functions for consistent patterns:

  1. createUIEntity(): Transforms Holochain records into UI-friendly entities

    const createUIEntity = (record: Record): UIEntity => ({
      id: encodeHashToBase64(record.actionHash),
      // Transform record entry data to UI model
    });
    
  2. mapRecordsToUIEntities(): Handles bulk record mapping with error handling

    const mapRecordsToUIEntities = (
      records: Record[],
    ): E.Effect<UIEntity[], DomainError> =>
      E.gen(function* () {
        // Map and validate records to UI entities
      });
    
  3. createCacheSyncHelper(): Synchronizes cache with application state

    const createCacheSyncHelper = () => {
      return {
        syncCacheToState: () => {
          /* Implementation */
        },
        invalidateCache: () => {
          /* Implementation */
        },
      };
    };
    
  4. createEventEmitters(): Standardized event emission patterns

    const createEventEmitters = () => {
      return {
        emitEntityCreated: (entity: UIEntity) => {
          /* Implementation */
        },
        emitEntityUpdated: (entity: UIEntity) => {
          /* Implementation */
        },
        // Other event emitters
      };
    };
    
  5. createEntitiesFetcher(): Data fetching with state updates

    const createEntitiesFetcher = (service: DomainService, state: State) => {
      return {
        fetchAllEntities: () => {
          /* Implementation */
        },
        fetchEntityById: (id: string) => {
          /* Implementation */
        },
      };
    };
    
  6. withLoadingState(): Loading state management for consistent UX

    const withLoadingState = async <T>(
      operation: () => Promise<T>,
      state: { loading: boolean },
    ): Promise<T> => {
      state.loading = true;
      try {
        return await operation();
      } finally {
        state.loading = false;
      }
    };
    
  7. createRecordCreationHelper(): Record creation operation patterns

    const createRecordCreationHelper = (
      service: DomainService,
      state: State,
    ) => {
      return {
        createEntity: (input: EntityInput) => {
          /* Implementation */
        },
        // Other creation methods
      };
    };
    
  8. createStatusTransitionHelper(): Status transition management

    const createStatusTransitionHelper = (
      service: DomainService,
      state: State,
    ) => {
      return {
        approveEntity: (id: string) => {
          /* Implementation */
        },
        rejectEntity: (id: string) => {
          /* Implementation */
        },
        // Other status transitions
      };
    };
    
  9. processMultipleRecordCollections(): Complex collection processing

    const processMultipleRecordCollections = <T>(
      collections: Record[][],
      processor: (records: Record[]) => T[],
    ): T[] => {
      // Implementation for handling multiple record collections
    };
    

Store Factory Pattern

Stores are created using a factory pattern that returns store objects directly:

export const createDomainStore = () => {
  // Reactive state with Svelte 5 Runes
  let entities = $state<UIDomainEntity[]>([]);
  let isLoading = $state(false);
  let error = $state<string | null>(null);

  // Cache management
  const cache = createModuleCache<ActionHash, UIDomainEntity>(
    "domain",
    5 * 60 * 1000,
  );

  // Implement all 9 helper functions
  const createUIEntity = (record: Record): UIDomainEntity | null => {
    // Implementation
  };

  const mapRecordsToUIEntities = (records: Record[]): UIDomainEntity[] => {
    // Implementation
  };

  // ... other helper functions

  // Main operations using Effect-TS
  const fetchEntities = Effect.gen(function* () {
    const domainService = yield* DomainService;
    const result = yield* domainService.getAllEntities();
    entities = mapRecordsToUIEntities(result);
    return entities;
  });

  return {
    // Reactive state accessors
    entities: () => entities,
    isLoading: () => isLoading,
    error: () => error,

    // Operations
    fetchEntities,
    // ... other operations

    // Helper functions
    createUIEntity,
    mapRecordsToUIEntities,
    // ... other helpers
  };
};

Lazy Initialization with Proxy Pattern

Stores use a proxy pattern for lazy initialization to prevent circular dependencies:

let _domainStore: DomainStore | undefined;

export const getDomainStore = (): DomainStore => {
  if (!_domainStore) {
    _domainStore = pipe(
      createDomainStore(),
      E.provide(DomainServiceLive),
      E.provide(CacheServiceLive),
      E.runSync,
    );
  }
  return _domainStore;
};

export const domainStore = new Proxy({} as DomainStore, {
  get: (target, prop) => {
    return getDomainStore()[prop as keyof DomainStore];
  },
});

Cross-Store Communication with Event Bus

Stores communicate using an event bus pattern defined in storeEvents.ts:

export const EntityCreatedEvent = EventTag<{
  type: "entity_created";
  payload: {
    domain: "service_types" | "requests" | "offers" | "users" | "organizations";
    entity: any;
  };
}>();

export const EntityUpdatedEvent = EventTag<{
  type: "entity_updated";
  payload: {
    domain: "service_types" | "requests" | "offers" | "users" | "organizations";
    entity: any;
  };
}>();

// Other events...

Stores can subscribe to events from other stores:

$effect(() => {
  const subscription = pipe(
    EventBus.subscribe(EntityCreatedEvent),
    S.filter((event) => event.payload.domain === "service_types"),
    S.tap((event) => {
      // Handle the event in this store
    }),
    S.runDrain,
  );

  return () => subscription.interrupt();
});

Data Flow

The application follows a unidirectional data flow pattern:

  1. User Interaction: Component triggers an action via store method
  2. Store Processing: Store calls appropriate service method using Effect TS
  3. Service Execution: Service communicates with Holochain backend
  4. Data Return: Results flow back through service to store
  5. State Update: Store updates its state using Svelte 5 reactivity
  6. UI Update: Components reactively update based on store state changes
  7. Cross-Store Updates: Event bus notifies other stores of relevant changes

Implementation Status

StoreImplementation StatusNotes
serviceTypes.store✅ CompleteFully standardized - Reference implementation
requests.store✅ CompleteFully standardized with 9-helper pattern
offers.store✅ CompleteFully standardized with 9-helper pattern
users.store✅ CompleteConverted to Effect architecture with 9-helper pattern
organizations.store✅ CompleteConverted to Effect architecture with 9-helper pattern
administration.store✅ CompleteConverted to Effect architecture with 9-helper pattern

Best Practices

  1. Always use Effect for async operations: Avoid mixing Promises and Effects
  2. Implement proper error handling: Use tagged errors with context
  3. Maintain store isolation: Use event bus for cross-store communication
  4. Follow the 9-helper pattern: For consistent implementation
  5. Add proper type definitions: For all store state and methods
  6. Document store interfaces: For developer reference
  7. Add comprehensive tests: For store functionality

UI Structure Documentation

This document provides a comprehensive overview of the UI structure for the Requests and Offers application.

Project Structure

The UI is built with:

  • SvelteKit as the framework
  • TailwindCSS for styling
  • SkeletonUI for UI components
  • Svelte 5 features (runes and native HTML events)
  • Effect TS (effect) for functional error handling and asynchronous operations

Overview of UI Architecture

The UI follows a structured architecture with clear separation of concerns:

  1. Components Layer - Reusable UI elements and views
  2. Composables Layer - Component logic abstraction
  3. Stores Layer - State management with Effect integration
  4. Services Layer - Backend communication with Holochain
  5. Schema/Types Layer - Type definitions and validations
  6. Error Handling Layer - Centralized error management

This multi-layered approach promotes maintainability, testability, and clean code practices throughout the application.

🏆 MAJOR UPDATE: Unified Effect TS Integration has been implemented with complete 7-layer standardization achieved in the Service Types domain. This architecture serves as the template for all other domains, ensuring consistent patterns, robust error handling, and maintainable code across the entire application.

🏆 Effect TS Architecture Implementation Status

Service Types Domain - FULLY STANDARDIZED (100%)

  • Complete 7-Layer Pattern: Service + Store + Schema + Error + Composables + Components + Testing
  • Pattern Template Established: Ready for replication across all domains
  • Code Quality Revolution: 9 standardized helper functions, massive duplication reduction
  • Type Safety Excellence: 100% Effect dependency resolution
  • Documentation Complete: Comprehensive pattern documentation for domain replication

🔄 Current Focus: Offers Domain Standardization

  • Goal: Apply ALL established patterns from Service Types and Requests domains
  • Progress: Service & Store layers refactoring in progress
  • Target: Complete 7-layer standardization following established template

📋 Planned Implementation

  • Offers Domain: Apply refined patterns from Service Types + Requests domains
  • Non-Effect Domain Conversion:
    • Users/Organizations/Administration: Convert from Promise-based to Effect architecture
    • Apply standardized patterns across all 7 layers

Domain-by-Domain Progress Overview

DomainServiceStoreSchemaErrorComposablesComponentsTesting
Service Types✅ 100%✅ 100%✅ 100%✅ 100%✅ 100%✅ 100%✅ 100%
Requests✅ 100%✅ 100%✅ 100%✅ 100%✅ 100%✅ 100%🔄 In Progress
Offers🔄 In Progress🔄 In Progress📋 Planned📋 Planned📋 Planned📋 Planned📋 Planned
Users❌ Promise-based❌ Promise-based❌ Basic❌ Basic❌ Basic❌ Basic❌ Basic
Organizations❌ Promise-based❌ Promise-based❌ Basic❌ Basic❌ Basic❌ Basic❌ Basic
Administration❌ Promise-based❌ Promise-based❌ Basic❌ Basic❌ Basic❌ Basic❌ Basic

Main directories:

  • /src/routes: Application routes and pages
  • /src/lib: Reusable components and utilities
  • /src/services: Effect-native service layer for backend communication
  • /src/stores: Standardized Effect-integrated Svelte stores for state management
  • /src/lib/composables: Component Logic Abstraction Layer with Effect integration
  • /src/types: TypeScript type definitions
  • /src/utils: Utility functions
  • /src/lib/errors: Centralized tagged error management
  • /src/lib/schemas: Strategic Effect Schema validation

Routes

The application uses SvelteKit's file-based routing system with two main sections:

Main Application Routes (/src/routes/(app)/)

Contains the main application routes that are accessible to regular users:

  • /: Home page (+page.svelte)
  • /service-types: Service Types management and discovery
    • /service-types: Service types listing and search
    • /service-types/create: Suggest new service type
    • /service-types/[id]: Single service type view
  • /requests: Request management
    • /requests: Requests listing
    • /requests/create: New request creation
    • /requests/[id]: Single request view
    • /requests/[id]/edit: Edit request
  • /offers: Offer management
    • /offers: Offers listing
    • /offers/create: New offer creation
    • /offers/[id]: Single offer view
    • /offers/[id]/edit: Edit offer
  • /tags: Tag-based discovery system
    • /tags: Browse all tags
    • /tags/[tag]: View content by specific tag (requests, offers, service types)
  • /organizations: Organization management
    • /organizations: Organizations listing
    • /organizations/create: Create new organization
    • /organizations/[id]: Single organization view
    • /organizations/[id]/edit: Edit organization
  • /user: User profile and settings
    • /user: User profile
    • /user/create: Create new user
    • /user/edit: Edit user profile
  • /users: User directory
    • /users: User directory listing
    • /users/[id]: Single user view

Layout:

  • (app)/+layout.svelte: Layout for main application routes

Admin Routes (/src/routes/admin/)

Contains administrative routes and functionalities:

  • /admin: Admin dashboard (+page.svelte)
  • /admin/service-types: Service Types administration
    • Service type approval/rejection workflow
    • Manage suggested service types (pending → approved/rejected)
    • Tag analytics and management
  • /admin/administrators: Administrator management
  • /admin/requests: Request administration
  • /admin/offers: Offer administration
  • /admin/organizations: Organization administration
  • /admin/users: User administration

Layout:

  • admin/+layout.svelte: Layout for admin routes

Root Layout

  • +layout.svelte: The root layout component that wraps all routes
  • +layout.ts: Layout load function for initialization
  • +error.svelte: Global error handling component

Services - EFFECT TS ARCHITECTURE

Located in /src/services, handling all communication with the Holochain backend using **unified Effect TS patterns **.

🏆 Standardized Effect Service Pattern

All services follow the 7-Layer Effect Service Pattern:

1. Service Interface Definition

export interface DomainService {
  readonly createEntity: (entity: EntityInput) => E.Effect<Record, DomainError>;
  readonly getEntity: (
    hash: ActionHash,
  ) => E.Effect<Record | null, DomainError>;
  readonly getAllEntities: () => E.Effect<Record[], DomainError>;
  // ... domain-specific methods
}

2. Context Tag for Dependency Injection

export class DomainServiceTag extends Context.Tag("DomainService")<
  DomainServiceTag,
  DomainService
>() {}

3. Effect Layer Implementation

export const DomainServiceLive: Layer.Layer<
  DomainServiceTag,
  never,
  HolochainClientServiceTag
> = Layer.effect(DomainServiceTag, implementation);

Implementation Status by Domain:

  • HolochainClientService: Complete Effect-native - Foundation service with schema validation
  • serviceTypes.service.ts: FULLY STANDARDIZED - Complete Effect patterns with dependency injection
  • requests.service.ts: FULLY STANDARDIZED – Complete Effect patterns with dependency injection
  • 🔄 offers.service.ts: In Standardization - Applying Service Types patterns
  • 📋 users.service.ts: Needs Effect Conversion - Convert from Promise-based
  • 📋 organizations.service.ts: Needs Effect Conversion - Convert from Promise-based
  • 📋 administration.service.ts: Needs Effect Conversion - Convert from Promise-based

Service Architecture Features:

  • Pure Effect-Native: No Promise mixing, complete Effect ecosystem integration
  • Strategic Schema Usage: callZomeRawEffect for Holochain pass-through, callZomeEffect for business logic
  • Domain-Specific Errors: Tagged error system with meaningful context
  • Dependency Injection: Clean Context.Tag/Layer pattern for composability

Stores - STANDARDIZED EFFECT PATTERNS

Located in /src/lib/stores, implementing factory function pattern with Effect integration.

🏆 Standardized Store Architecture

The 9-Helper Function Pattern:

  1. createUIEntity() - Entity creation from Holochain records
  2. mapRecordsToUIEntities() - Consistent record mapping with error handling
  3. createCacheSyncHelper() - Cache-to-state synchronization
  4. createEventEmitters() - Standardized event emission patterns
  5. createEntitiesFetcher() - Data fetching with state updates
  6. withLoadingState() - Loading state wrapper for consistent UX
  7. createRecordCreationHelper() - Record creation operation patterns
  8. createStatusTransitionHelper() - Status transition management
  9. processMultipleRecordCollections() - Complex collection processing

Store Factory Pattern:

export const createDomainStore = (): E.Effect<
  DomainStore,
  never,
  DomainServiceTag | CacheServiceTag
> =>
  E.gen(function* () {
    // Standardized implementation using 9 helper functions
  });

Implementation Status by Domain:

  • serviceTypes.store.svelte.ts: FULLY STANDARDIZED - Complete helper function architecture
  • requests.store.svelte.ts: FULLY STANDARDIZED – Complete helper function architecture
  • 🔄 offers.store.svelte.ts: In Standardization - Applying established patterns
  • 📋 users.store.svelte.ts: Needs Standardization - Apply 9-helper pattern
  • 📋 organizations.store.svelte.ts: Needs Standardization - Apply 9-helper pattern
  • 📋 administration.store.svelte.ts: Needs Standardization - Apply 9-helper pattern

Store Architecture Features:

  • Lazy Initialization: Proxy pattern for safe module-level creation
  • Svelte 5 Runes: $state, $derived, $effect with proper reactivity
  • EntityCache Integration: Performance optimization with expiration
  • Event Bus Communication: Cross-store coordination
  • Effect Error Handling: Comprehensive error management with context

Composables - COMPONENT LOGIC ABSTRACTION LAYER

Located in /src/lib/composables, providing Effect-integrated component logic.

Standardized Composable Architecture:

File Organization:

/composables/
├── domain/           # Domain-specific business logic
│   ├── useServiceTypesManagement.svelte.ts  ✅ STANDARDIZED
│   ├── useRequestsManagement.svelte.ts      ✅ STANDARDIZED
│   └── useOffersManagement.svelte.ts        📋 PLANNED
├── search/           # Search and filtering functionality
│   ├── useServiceTypeSearch.svelte.ts       ✅ STANDARDIZED
│   └── useEntitySearch.svelte.ts           📋 PLANNED
├── ui/               # UI state management
│   ├── useModal.svelte.ts                  📋 PLANNED
│   └── usePagination.svelte.ts             📋 PLANNED
└── utils/            # Utility composables
    ├── useUrlParams.svelte.ts              📋 PLANNED
    └── useDebounce.svelte.ts               📋 PLANNED

Standard Interface Pattern:

export interface UseComposableReturn<TState, TActions> {
  state: TState; // Derived state for reactivity
  actions: TActions; // Effect-based action functions
  cleanup?: () => void; // Resource cleanup
}

Composable Features:

  • Effect Integration: All async operations use Effect TS patterns
  • Standard Interfaces: Consistent state/actions separation
  • Error Management: Domain-specific error transformation
  • Performance: Prevent infinite reactive loops through proper encapsulation

Components - SVELTE 5 + EFFECT INTEGRATION

Feature-Based Organization:

Components are organized by domain with complete Effect integration support:

Service Types Components - ✅ FULLY STANDARDIZED:

  • ServiceTypeCard.svelte: Complete integration with standardized composables
  • ServiceTypeSelector.svelte: Multi-select with Effect-based search
  • ServiceTypeSuggestionForm.svelte: Form with Effect validation
  • TagAutocomplete.svelte: Real-time suggestions with Effect debouncing
  • TagCloud.svelte: Statistical visualization with Effect data fetching
  • Admin Interface Components: Complete moderation workflow integration

Request/Offer Components - 🔄 IN STANDARDIZATION:

  • Updating to use standardized composable patterns
  • Integration with Effect-based state management
  • Consistent error handling and loading states

Shared Components - 📋 NEEDS UPDATES:

  • Updating to support standardized patterns across domains
  • Enhanced error display with tagged error support
  • Consistent loading state management

Component Architecture Features:

  • Composable Integration: Business logic delegated to Effect-based composables
  • Presentation Focus: Components handle user interaction and display
  • Svelte 5 Patterns: Proper use of $props, $state, $derived, $effect
  • Accessibility: WCAG-compliant with keyboard navigation
  • Error Handling: Graceful error display with user-friendly messages

Error Management - CENTRALIZED TAGGED ERROR SYSTEM

Located in /src/lib/errors, implementing comprehensive error architecture.

Error Architecture:

Domain-Specific Error Hierarchies:

// Service Layer
DomainError;

// Store Layer
DomainStoreError;

// Composable Layer
DomainManagementError;

Implementation Status:

  • ✅ Service Types: Complete error hierarchy implemented
  • 🔄 Requests/Offers: Applying Service Types error patterns
  • 📋 Users/Organizations/Administration: Need error system implementation

Error Features:

  • Tagged Errors: Data.TaggedError for type-safe error handling
  • Meaningful Context: Rich error information with operation context
  • Centralized Export: Single import point through errors/index.ts
  • Recovery Patterns: Standardized error recovery and user messaging

Schema Validation - STRATEGIC EFFECT SCHEMA USAGE

Located in /src/lib/schemas, implementing pragmatic validation strategy.

Schema Strategy:

Validation Boundaries:

  • Input Validation: Form data, user inputs, search parameters
  • Business Logic: Complex transformations, API responses
  • Cross-Service Communication: Service-to-service data exchange

Implementation Status:

  • ✅ Service Types: Complete schema consolidation and validation strategy
  • 🔄 Requests/Offers: Applying schema patterns
  • 📋 Other Domains: Need schema implementation

Schema Features:

  • Branded Types: Domain-specific type safety (ActionHash, ServiceTypeName)
  • Class-Based Schemas: Schema.Class for complex entities
  • Strategic Application: Avoid over-validation, focus on value-adding boundaries
  • Centralized Export: Single import point through schemas/index.ts

Testing - COMPREHENSIVE EFFECT TS COVERAGE

Testing Strategy:

3-Layer Testing Approach:

  1. Backend Tests (tests/sweettest/): Sweettest multi-agent testing
  2. Unit Tests (ui/tests/unit/): Service/store isolation with Effect utilities
  3. Integration Tests (ui/tests/integration/): End-to-end workflow validation

Implementation Status:

  • ✅ Service Types: Complete testing coverage across all layers
  • 🔄 Requests/Offers: Applying testing patterns
  • 📋 Other Domains: Need comprehensive test coverage

Testing Features:

  • Effect Testing Utilities: Specialized helpers for Effect-based code
  • Service Isolation: Clean dependency injection for unit testing
  • Workflow Validation: End-to-end user journey testing
  • Performance Standards: Defined execution time targets

🎯 IMPLEMENTATION ROADMAP: The Service Types domain serves as the complete pattern template for all other domains. The systematic application of these patterns ensures consistent, maintainable, and robust code across the entire application.

UI Types and Schemas Documentation

This document details the type system and schema validation approach used in the Requests and Offers application.

Type Architecture

The application uses a layered type architecture that maps between different representations of data:

  1. DHT Types: Raw data structures stored in Holochain DHT
  2. UI Types: Enhanced types for frontend use with additional metadata
  3. Form Types: Specialized types for user input forms
  4. API Types: Types for external API communication

Type Organization

/lib/types/
├── holochain.ts       # Holochain-specific type definitions
├── ui.ts              # UI-specific type definitions
├── common.ts          # Shared type definitions
└── domain/            # Domain-specific types
    ├── serviceTypes.ts # Service types domain types
    ├── requests.ts     # Requests domain types
    ├── offers.ts       # Offers domain types
    └── ...             # Other domain types

Schema Validation

The application uses Effect Schema for robust validation at key boundaries:

/lib/schemas/
├── serviceTypes.schemas.ts   # Service types validation schemas
├── requests.schemas.ts        # Requests validation schemas
├── offers.schemas.ts          # Offers validation schemas
└── ...                        # Other domain schemas

Schema Strategy

Validation is applied at three key boundaries:

  1. User Input: Validate form data before submission
  2. Service Layer: Validate data before sending to Holochain
  3. Store Processing: Validate data before updating UI state

Core Type Patterns

Entity Base Types

All entities follow a consistent pattern with DHT and UI variants:

// DHT Types (stored in Holochain)
export type ServiceTypeInDHT = {
  name: string;
  description: string;
  tags: string[];
  status: "pending" | "approved" | "rejected";
  createdAt: number;
};

// UI Types (used in frontend)
export type UIServiceType = ServiceTypeInDHT & {
  original_action_hash: ActionHash; // Immutable entity identity
  previous_action_hash: ActionHash; // Update chain head
  creator?: ActionHash;
  created_at?: number;
  updated_at?: number;
  status: 'pending' | 'approved' | 'rejected';
};

Form Input Types

Specialized types for form handling:

export type ServiceTypeFormInput = {
  name: string;
  description: string;
  tags: string[];
};

Tagged Error Types

Domain-specific error hierarchies:

export class ServiceTypeNotFoundError extends TaggedError<{
  readonly _tag: "ServiceTypeNotFoundError";
  readonly hash: string;
}>() {
  get message() {
    return `Service type with hash ${this.hash} not found`;
  }
}

Schema Examples

Input Validation Schema

export const ServiceTypeInputSchema = S.struct({
  name: pipe(S.string, S.minLength(3), S.maxLength(100)),
  description: pipe(S.string, S.minLength(10), S.maxLength(1000)),
  tags: pipe(S.array(S.string), S.maxLength(10)),
});

Response Validation Schema

export const ServiceTypeResponseSchema = S.struct({
  name: S.string,
  description: S.string,
  tags: S.array(S.string),
  status: S.enums(["pending", "approved", "rejected"]),
  createdAt: S.number,
});

Type Transformations

The application includes utility functions for transforming between type representations:

// Convert Holochain record to UI entity
const createUIServiceType = (record: HolochainRecord): UIServiceType => {
  const entry = decode((record.entry as any).Present.entry) as ServiceTypeInDHT;
  const actionHash = record.signed_action.hashed.hash;
  const timestamp = record.signed_action.hashed.content.timestamp;

  return {
    ...entry,
    id: encodeHashToBase64(actionHash),
    actionHash,
    original_action_hash: actionHash,
    createdAt: new Date(timestamp / 1000), // Convert microseconds to milliseconds
  };
};

Branded Hash Types

The application uses branded types to distinguish OriginalActionHash (immutable entity identity) from PreviousActionHash (update chain head) at compile time. These are defined in ui/src/lib/schemas/holochain.schemas.ts:

// Compile-time distinct hash types — zero runtime cost
export type OriginalActionHash = ActionHash & { readonly __brand: 'OriginalActionHash' };
export type PreviousActionHash = ActionHash & { readonly __brand: 'PreviousActionHash' };

// Cast helpers
export const asOriginalActionHash = (hash: ActionHash): OriginalActionHash =>
  hash as OriginalActionHash;
export const asPreviousActionHash = (hash: ActionHash): PreviousActionHash =>
  hash as PreviousActionHash;

All non-exchange UI entity types (UIUser, UIServiceType, UIRequest, UIOffer, UIOrganization, UIStatus) require both original_action_hash and previous_action_hash fields. Exchange types use a separate actionHash field since they follow a proposal/agreement lifecycle rather than standard CRUD updates.

See Action Hash Type Safety for the full technical specification.

Best Practices

  1. Consistent Naming: Follow established naming conventions

    • *InDHT for Holochain data types
    • UI* for frontend-enhanced types
    • *FormInput for form data types
  2. Explicit Transformations: Always use explicit transformation functions

    • createUI* for DHT → UI conversions
    • create*Input for UI → form conversions
  3. Schema Validation: Apply schemas at all key boundaries

    • User input validation
    • Service method parameter validation
    • Response data validation
  4. Error Types: Use tagged errors with informative contexts

    • Define domain-specific error hierarchies
    • Include relevant context in error payloads
  5. Documentation: Document complex types and transformations

    • Explain the purpose of each type
    • Document relationships between types

Type Lifecycle

A typical data flow through the type system:

  1. User Input → Form validation using *FormInput types and schemas
  2. Form Submission → Transform to *InDHT type for Holochain
  3. Service Layer → Validate using schemas before sending to Holochain
  4. Holochain Response → Transform to UI* type for frontend use
  5. Store Update → Store and distribute UI* entity through the application
  6. Component Rendering → Components consume and display UI* entities

Implementation Status

DomainType ImplementationSchema ImplementationNotes
ServiceTypes✅ Complete✅ CompleteFull type safety with schemas
Requests✅ Complete✅ CompleteFull type safety with schemas
Offers✅ Complete✅ CompleteFull Effect-TS implementation
Users✅ Complete✅ CompleteFull type safety with schemas
Organizations✅ Complete✅ CompleteEnhanced with full_legal_name field
Administration✅ Complete✅ CompleteFull type safety with schemas
Exchanges✅ Complete✅ CompleteFull Effect-TS implementation
MediumsOfExchange✅ Complete✅ CompleteFull Effect-TS implementation

API Documentation

Comprehensive API documentation for the Requests & Offers application, covering both frontend and backend interfaces.

Documentation Structure

Frontend APIs

Backend APIs

API Categories

Domain APIs

Each domain provides a complete set of APIs following the 7-layer architecture:

DomainService APIStore APIStatus
Service Types✅ Complete✅ CompleteReference Implementation
Requests✅ Complete✅ CompleteFull Implementation
Offers✅ Complete✅ CompleteFull Implementation
Users✅ Complete✅ CompleteFull Implementation
Organizations✅ Complete✅ CompleteFull Implementation
Administration✅ Complete✅ CompleteFull Implementation

Cross-Domain APIs

  • Event Bus: Cross-domain communication and state synchronization
  • Cache Management: Module-level caching with TTL and synchronization
  • Error Boundaries: Composable error handling with retry logic
  • Schema Validation: Strategic validation boundaries with Effect Schema

Usage Patterns

Service Layer Usage

// Dependency injection pattern
const result = await Effect.runPromise(
  Effect.gen(function* () {
    const service = yield* ServiceTypeService;
    return yield* service.getAllServiceTypes();
  }).pipe(Effect.provide(ServiceTypeServiceLive)),
);

Store Layer Usage

// Factory pattern with reactive state
const store = createServiceTypesStore();

// Access reactive state
const entities = store.entities();
const isLoading = store.isLoading();

// Execute operations
await Effect.runPromise(store.fetchEntities);

Composable Usage

// Business logic abstraction
const { state, operations } = useServiceTypesManagement();

// React to state changes
$effect(() => {
  console.log("Entities updated:", state.entities());
});

// Execute business operations
await operations.createEntity(input);

Architecture Integration

7-Layer Integration

All APIs follow the standardized 7-layer architecture:

  1. Service Layer: Effect-native APIs with dependency injection
  2. Store Layer: Reactive state management with standardized helpers
  3. Schema Layer: Validation boundaries with Effect Schema
  4. Error Layer: Domain-specific tagged errors
  5. Composable Layer: Business logic abstraction
  6. Component Layer: UI integration points
  7. Testing Layer: Comprehensive test coverage

Development Patterns

  • Effect.gen vs .pipe: Clear guidelines for when to use each pattern
  • Error Handling: Standardized error transformation and context
  • Cache Management: Consistent caching strategies across domains
  • Event Communication: Cross-domain event patterns

Getting Started

  1. Explore by Layer: Start with Services for core API patterns
  2. Follow Domain Examples: Use Service Types as the reference implementation
  3. Understand Patterns: Study the 9 standardized helper functions
  4. Practice Integration: Follow the Development Workflow Guide

Reference Implementation

The Service Types domain serves as the complete reference implementation, demonstrating all patterns and APIs in their fully realized form. Use this domain as the template for understanding API usage across all layers.

For implementation guidance, see:

Backend Entry Types API

Complete reference for all Holochain entry types and data structures.

Entry Type Architecture

All entry types follow consistent patterns for serialization, validation, and relationships.

Base Entry Pattern

#![allow(unused)]
fn main() {
#[hdk_entry_helper]
#[derive(Clone, PartialEq)]
pub struct BaseEntity {
    pub name: String,
    pub description: String,
    pub status: EntityStatus,
    pub created_at: Timestamp,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum EntityStatus {
    Pending,
    Approved,
    Rejected,
}
}

Service Types Entry

File: dnas/requests_and_offers/zomes/integrity/service_types/src/lib.rs

ServiceType Entry

#![allow(unused)]
fn main() {
#[hdk_entry_helper]
#[derive(Clone, PartialEq)]
pub struct ServiceType {
    pub name: String,
    pub description: String,
    pub tags: Vec<String>,
    pub status: ServiceTypeStatus,
    pub created_at: Timestamp,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum ServiceTypeStatus {
    Pending,
    Approved,
    Rejected,
}
}

Input Types

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize, Debug)]
pub struct CreateServiceTypeInput {
    pub name: String,
    pub description: String,
    pub tags: Vec<String>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct UpdateServiceTypeInput {
    pub original_action_hash: OriginalActionHash,
    pub previous_action_hash: PreviousActionHash,
    pub updated_service_type: ServiceType,
}
}

Request Entry

File: dnas/requests_and_offers/zomes/integrity/requests/src/lib.rs

Request Entry

#![allow(unused)]
fn main() {
#[hdk_entry_helper]
#[derive(Clone, PartialEq)]
pub struct Request {
    pub title: String,
    pub description: String,
    pub service_type_hash: ActionHash,
    pub urgency: UrgencyLevel,
    pub time_preference: TimePreference,
    pub interaction_type: InteractionType,
    pub status: RequestStatus,
    pub created_at: Timestamp,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum RequestStatus {
    Open,
    InProgress,
    Fulfilled,
    Closed,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum UrgencyLevel {
    Low,
    Medium,
    High,
    Critical,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum TimePreference {
    ASAP,
    Flexible,
    Specific(Timestamp),
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum InteractionType {
    InPerson,
    Remote,
    Hybrid,
}
}

Offer Entry

File: dnas/requests_and_offers/zomes/integrity/offers/src/lib.rs

Offer Entry

#![allow(unused)]
fn main() {
#[hdk_entry_helper]
#[derive(Clone, PartialEq)]
pub struct Offer {
    pub title: String,
    pub description: String,
    pub service_type_hash: ActionHash,
    pub time_preference: TimePreference,
    pub interaction_type: InteractionType,
    pub availability: Availability,
    pub status: OfferStatus,
    pub created_at: Timestamp,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum OfferStatus {
    Available,
    Accepted,
    Completed,
    Closed,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Availability {
    pub start_date: Option<Timestamp>,
    pub end_date: Option<Timestamp>,
    pub hours_per_week: Option<u32>,
}
}

User Profile Entry

File: dnas/requests_and_offers/zomes/integrity/users/src/lib.rs

UserProfile Entry

#![allow(unused)]
fn main() {
#[hdk_entry_helper]
#[derive(Clone, PartialEq)]
pub struct UserProfile {
    pub username: String,
    pub display_name: String,
    pub bio: Option<String>,
    pub avatar_url: Option<String>,
    pub skills: Vec<String>,
    pub interests: Vec<String>,
    pub location: Option<String>,
    pub contact_info: ContactInfo,
    pub created_at: Timestamp,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct ContactInfo {
    pub email: Option<String>,
    pub website: Option<String>,
    pub social_links: Vec<SocialLink>,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct SocialLink {
    pub platform: String,
    pub url: String,
}
}

Organization Entry

File: dnas/requests_and_offers/zomes/integrity/organizations/src/lib.rs

Organization Entry

#![allow(unused)]
fn main() {
#[hdk_entry_helper]
#[derive(Clone, PartialEq)]
pub struct Organization {
    /// Display name of the organization
    pub name: String,

    /// Organization's vision and mission statement (UI: "Vision/Mission")
    pub description: String,

    /// Full legal name for business registration compliance
    pub full_legal_name: String,

    /// Optional organization logo (serialized)
    pub logo: Option<SerializedBytes>,

    /// Contact email for the organization
    pub email: String,

    /// Related URLs (website, social media, etc.)
    pub urls: Vec<String>,

    /// Organization's location
    pub location: String,
}
}

Note: Organization contact person designation is handled through the OrganizationContacts link type (not an entry). See link-types.md for details.

Administration Entries

File: dnas/requests_and_offers/zomes/integrity/administration/src/lib.rs

AdminRole Entry

#![allow(unused)]
fn main() {
#[hdk_entry_helper]
#[derive(Clone, PartialEq)]
pub struct AdminRole {
    pub agent: AgentPubKey,
    pub role_type: RoleType,
    pub granted_by: AgentPubKey,
    pub granted_at: Timestamp,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum RoleType {
    Administrator,
    Moderator,
}
}

UserSuspension Entry

#![allow(unused)]
fn main() {
#[hdk_entry_helper]
#[derive(Clone, PartialEq)]
pub struct UserSuspension {
    pub suspended_agent: AgentPubKey,
    pub reason: String,
    pub suspended_by: AgentPubKey,
    pub suspended_at: Timestamp,
    pub suspension_end: Option<Timestamp>,
    pub is_active: bool,
}
}

Validation Rules

Field Validation

#![allow(unused)]
fn main() {
// Example validation for ServiceType
pub fn validate_service_type(service_type: &ServiceType) -> ExternResult<ValidateCallbackResult> {
    // Name validation
    if service_type.name.trim().is_empty() {
        return Ok(ValidateCallbackResult::Invalid("Name cannot be empty".to_string()));
    }

    if service_type.name.len() > 100 {
        return Ok(ValidateCallbackResult::Invalid("Name too long".to_string()));
    }

    // Description validation
    if service_type.description.trim().is_empty() {
        return Ok(ValidateCallbackResult::Invalid("Description cannot be empty".to_string()));
    }

    // Tags validation
    if service_type.tags.is_empty() {
        return Ok(ValidateCallbackResult::Invalid("At least one tag required".to_string()));
    }

    Ok(ValidateCallbackResult::Valid)
}
}

Status Transition Validation

#![allow(unused)]
fn main() {
pub fn validate_status_transition(
    old_status: &EntityStatus,
    new_status: &EntityStatus
) -> ExternResult<ValidateCallbackResult> {
    match (old_status, new_status) {
        (EntityStatus::Pending, EntityStatus::Approved) => Ok(ValidateCallbackResult::Valid),
        (EntityStatus::Pending, EntityStatus::Rejected) => Ok(ValidateCallbackResult::Valid),
        (EntityStatus::Approved, EntityStatus::Rejected) => Ok(ValidateCallbackResult::Valid),
        (EntityStatus::Rejected, EntityStatus::Approved) => Ok(ValidateCallbackResult::Valid),
        _ => Ok(ValidateCallbackResult::Invalid("Invalid status transition".to_string())),
    }
}
}

Entry Relationships

Service Type → Request/Offer

#![allow(unused)]
fn main() {
// Requests and offers reference service types
pub struct Request {
    pub service_type_hash: ActionHash, // References ServiceType entry
    // ... other fields
}
}

User → Organization

#![allow(unused)]
fn main() {
// Organizations maintain member relationships through links
// Links created between Organization entry and UserProfile entries
}

Cross-Domain References

Entry types maintain relationships through ActionHash references and link structures, enabling rich domain interactions while maintaining data integrity.

This entry type reference provides the complete data structure definitions for the Holochain application.

Backend Link Types API

Complete reference for all Holochain link types and relationship patterns.

Link types define relationships between entries and enable efficient querying and indexing.

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize)]
#[hdk_link_types]
pub enum LinkTypes {
    AllEntities,           // Path -> Entity (for listing all entities)
    EntityToRelated,       // Entity -> Related Entity
    TagToEntity,           // Tag Path -> Entity (for tag-based search)
}
}

File: dnas/requests_and_offers/zomes/integrity/service_types/src/lib.rs

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize)]
#[hdk_link_types]
pub enum LinkTypes {
    AllServiceTypes,              // Path("all_service_types") -> ServiceType
    ServiceTypeToTag,             // ServiceType -> Tag Path
    TagToServiceType,             // Tag Path -> ServiceType
    ApprovedServiceTypes,         // Path("approved_service_types") -> ServiceType
    PendingServiceTypes,          // Path("pending_service_types") -> ServiceType
    RejectedServiceTypes,         // Path("rejected_service_types") -> ServiceType
}
}

All Service Types Index

#![allow(unused)]
fn main() {
// Create link when service type is created
let path = Path::from("all_service_types");
create_link(
    path.path_entry_hash()?,
    service_type_hash.clone(),
    LinkTypes::AllServiceTypes,
    ()
)?;

// Query all service types
let path = Path::from("all_service_types");
let links = get_links(path.path_entry_hash()?, LinkTypes::AllServiceTypes, None)?;
}

Tag-Based Indexing

#![allow(unused)]
fn main() {
// Create tag links for each tag
for tag in &service_type.tags {
    let tag_path = Path::from(format!("tag.{}", tag));
    create_link(
        tag_path.path_entry_hash()?,
        service_type_hash.clone(),
        LinkTypes::TagToServiceType,
        ()
    )?;
}

// Query by tag
let tag_path = Path::from(format!("tag.{}", search_tag));
let links = get_links(tag_path.path_entry_hash()?, LinkTypes::TagToServiceType, None)?;
}

Status-Based Indexing

#![allow(unused)]
fn main() {
// Create status-specific links
match service_type.status {
    ServiceTypeStatus::Approved => {
        let path = Path::from("approved_service_types");
        create_link(path.path_entry_hash()?, service_type_hash, LinkTypes::ApprovedServiceTypes, ())?;
    },
    ServiceTypeStatus::Pending => {
        let path = Path::from("pending_service_types");
        create_link(path.path_entry_hash()?, service_type_hash, LinkTypes::PendingServiceTypes, ())?;
    },
    ServiceTypeStatus::Rejected => {
        let path = Path::from("rejected_service_types");
        create_link(path.path_entry_hash()?, service_type_hash, LinkTypes::RejectedServiceTypes, ())?;
    }
}
}

File: dnas/requests_and_offers/zomes/integrity/requests/src/lib.rs

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize)]
#[hdk_link_types]
pub enum LinkTypes {
    RequestUpdates,               // Original request -> Updated request (tracking chain)
    AllRequests,                  // Legacy (still present for compatibility)
    ActiveRequests,               // Path("requests.active") -> Active requests only
    ArchivedRequests,             // Path("requests.archived") -> Archived requests only
    UserRequests,                 // UserProfile -> Requests created by user
    OrganizationRequests,         // Organization -> Requests associated with organization
    RequestCreator,               // Request -> UserProfile (creator of request)
    RequestOrganization,          // Request -> Organization (if associated)
}
}

Active/Archived Path Pattern

The application uses separate DHT paths for active and archived requests to optimize query performance:

  • ActiveRequests: Path("requests.active") → Request (for visible/active requests)
  • ArchivedRequests: Path("requests.archived") → Request (for archived requests)

Benefits:

  • Queries fetch only relevant items (no client-side filtering needed)
  • Performance remains optimal as archived requests accumulate
  • Clear semantic separation of data states
  • Reduced DHT load for common queries

Archive Flow:

  1. New requests are created in requests.active path with ActiveRequests link type
  2. When archived, the link is deleted from requests.active and created in requests.archived with ArchivedRequests link type
  3. Entry status is also updated to ListingStatus::Archived for backward compatibility

Usage Examples

Service Type Relationships

#![allow(unused)]
fn main() {
// Link request to service type
create_link(
    request_hash.clone(),
    service_type_hash.clone(),
    LinkTypes::RequestToServiceType,
    ()
)?;

// Reverse link for querying requests by service type
create_link(
    service_type_hash.clone(),
    request_hash.clone(),
    LinkTypes::ServiceTypeToRequest,
    ()
)?;
}

User Ownership

#![allow(unused)]
fn main() {
// Link user to their requests
create_link(
    user_profile_hash.clone(),
    request_hash.clone(),
    LinkTypes::UserToRequest,
    ()
)?;
}

Status and Urgency Indexing

#![allow(unused)]
fn main() {
// Status-based indexing
let status_path = Path::from(format!("{:?}_requests", request.status).to_lowercase());
create_link(status_path.path_entry_hash()?, request_hash.clone(), /* appropriate LinkType */, ())?;

// Urgency-based indexing
let urgency_path = Path::from(format!("urgency.{:?}", request.urgency).to_lowercase());
create_link(urgency_path.path_entry_hash()?, request_hash.clone(), LinkTypes::UrgencyToRequest, ())?;
}

File: dnas/requests_and_offers/zomes/integrity/offers/src/lib.rs

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize)]
#[hdk_link_types]
pub enum LinkTypes {
    OfferUpdates,                 // Original offer -> Updated offer (tracking chain)
    AllOffers,                    // Legacy (still present for compatibility)
    ActiveOffers,                 // Path("offers.active") -> Active offers only
    ArchivedOffers,               // Path("offers.archived") -> Archived offers only
    UserOffers,                   // UserProfile -> Offers created by user
    OrganizationOffers,           // Organization -> Offers associated with organization
    OfferCreator,                 // Offer -> UserProfile (creator of offer)
    OfferOrganization,            // Offer -> Organization (if associated)
}
}

Active/Archived Path Pattern

The application uses separate DHT paths for active and archived items to optimize query performance:

  • ActiveOffers: Path("offers.active") → Offer (for visible/active offers)
  • ArchivedOffers: Path("offers.archived") → Offer (for archived offers)

Benefits:

  • Queries fetch only relevant items (no client-side filtering needed)
  • Performance remains optimal as archived items accumulate
  • Clear semantic separation of data states
  • Reduced DHT load for common queries

Archive Flow:

  1. New offers are created in offers.active path with ActiveOffers link type
  2. When archived, the link is deleted from offers.active and created in offers.archived with ArchivedOffers link type
  3. Entry status is also updated to ListingStatus::Archived for backward compatibility

File: dnas/requests_and_offers/zomes/integrity/users/src/lib.rs

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize)]
#[hdk_link_types]
pub enum LinkTypes {
    AllUsers,                     // Path("all_users") -> UserProfile
    AgentToProfile,               // AgentPubKey -> UserProfile
    ProfileToAgent,               // UserProfile -> AgentPubKey
    SkillToUser,                  // Path("skill.{name}") -> UserProfile
    UserToSkill,                  // UserProfile -> Path("skill.{name}")
    LocationToUser,               // Path("location.{name}") -> UserProfile
}
}

User Indexing Patterns

Agent-Profile Mapping

#![allow(unused)]
fn main() {
// Map agent to their profile
create_link(
    agent_hash.clone().into(),
    profile_hash.clone(),
    LinkTypes::AgentToProfile,
    ()
)?;

// Reverse mapping
create_link(
    profile_hash.clone(),
    agent_hash.clone().into(),
    LinkTypes::ProfileToAgent,
    ()
)?;
}

Skill-Based Discovery

#![allow(unused)]
fn main() {
// Create skill links
for skill in &user_profile.skills {
    let skill_path = Path::from(format!("skill.{}", skill.to_lowercase()));
    create_link(
        skill_path.path_entry_hash()?,
        profile_hash.clone(),
        LinkTypes::SkillToUser,
        ()
    )?;
}
}

File: dnas/requests_and_offers/zomes/integrity/organizations/src/lib.rs

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize)]
#[hdk_link_types]
pub enum LinkTypes {
    AllOrganizations,             // Path("all_organizations") -> Organization
    OrganizationToMember,         // Organization -> UserProfile
    MemberToOrganization,         // UserProfile -> Organization
    TypeToOrganization,           // Path("type.{org_type}") -> Organization
    LocationToOrganization,       // Path("location.{location}") -> Organization
    OrganizationContacts,         // Organization -> UserProfile (tag = role string)
}
}

Organization Contact Person

The OrganizationContacts link type designates a single coordinator as the public-facing contact person for an organization. The role/title is stored in the link tag.

#![allow(unused)]
fn main() {
// Set contact person with role encoded in tag
create_link(
    organization_hash.clone(),
    user_hash.clone(),
    LinkTypes::OrganizationContacts,
    role.as_bytes().to_vec(),
)?;
}

Organization Membership

#![allow(unused)]
fn main() {
// Add member to organization
create_link(
    organization_hash.clone(),
    user_profile_hash.clone(),
    LinkTypes::OrganizationToMember,
    ()
)?;

// Reverse link for user's organizations
create_link(
    user_profile_hash.clone(),
    organization_hash.clone(),
    LinkTypes::MemberToOrganization,
    ()
)?;
}

File: dnas/requests_and_offers/zomes/integrity/administration/src/lib.rs

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize)]
#[hdk_link_types]
pub enum LinkTypes {
    AdminsAnchor,                 // Path("admins") -> AdminRole
    ModeratorsAnchor,             // Path("moderators") -> AdminRole
    SuspensionsAnchor,            // Path("suspensions") -> UserSuspension
    AgentToSuspension,            // AgentPubKey -> UserSuspension
}
}

Administrative Indexing

#![allow(unused)]
fn main() {
// Add admin role to index
let admins_path = Path::from("admins");
create_link(
    admins_path.path_entry_hash()?,
    admin_role_hash,
    LinkTypes::AdminsAnchor,
    ()
)?;

// Link agent to suspension
create_link(
    suspended_agent.clone().into(),
    suspension_hash,
    LinkTypes::AgentToSuspension,
    ()
)?;
}
#![allow(unused)]
fn main() {
pub fn validate_create_link(
    action: CreateLink,
    base_address: AnyLinkableHash,
    target_address: AnyLinkableHash,
    tag: LinkTag,
) -> ExternResult<ValidateCallbackResult> {
    match action.link_type {
        LinkTypes::AllServiceTypes => {
            // Validate that base is the correct path
            // Validate that target is a ServiceType entry
            Ok(ValidateCallbackResult::Valid)
        },
        LinkTypes::TagToServiceType => {
            // Validate tag path format
            // Validate service type exists
            Ok(ValidateCallbackResult::Valid)
        },
        _ => Ok(ValidateCallbackResult::Valid),
    }
}
}
#![allow(unused)]
fn main() {
pub fn validate_delete_link(
    action: DeleteLink,
    original_action: CreateLink,
    base: AnyLinkableHash,
    target: AnyLinkableHash,
    tag: LinkTag,
) -> ExternResult<ValidateCallbackResult> {
    // Validate that agent has permission to delete this link
    // Check if link deletion is allowed for this link type
    Ok(ValidateCallbackResult::Valid)
}
}

Query Patterns

Efficient Queries

#![allow(unused)]
fn main() {
// Get all entities of a type
pub fn get_all_service_types() -> ExternResult<Vec<Record>> {
    let path = Path::from("all_service_types");
    let links = get_links(path.path_entry_hash()?, LinkTypes::AllServiceTypes, None)?;

    let get_input: Vec<GetInput> = links
        .into_iter()
        .map(|link| GetInput::new(
            ActionHash::from(link.target).into(),
            GetOptions::default(),
        ))
        .collect();

    let records = HDK.with(|hdk| hdk.borrow().get(get_input))?;
    let records: Vec<Record> = records.into_iter().filter_map(|r| r).collect();

    Ok(records)
}

// Get entities by tag
pub fn get_service_types_by_tag(tag: String) -> ExternResult<Vec<Record>> {
    let tag_path = Path::from(format!("tag.{}", tag));
    let links = get_links(tag_path.path_entry_hash()?, LinkTypes::TagToServiceType, None)?;

    // Convert links to records (same pattern as above)
    // ...
}
}

Performance Considerations

Indexing Strategy

  • Create multiple indexes for different query patterns
  • Use hierarchical paths for efficient filtering
  • Balance between query performance and storage overhead
  • Clean up orphaned links when entries are deleted
  • Use link tags for additional metadata when needed
  • Consider link direction for optimal query patterns

This link type reference provides the complete relationship structure for the Holochain application, enabling efficient querying and data discovery.

Backend Zome Functions API

Complete reference for all Holochain zome functions across all domains.

Zome Function Architecture

All zome functions follow consistent patterns for input validation, error handling, and return types.

Standard Function Pattern

#![allow(unused)]
fn main() {
#[hdk_extern]
pub fn create_entity(input: CreateEntityInput) -> ExternResult<Record> {
    let entity_hash = create_entry(&EntryTypes::Entity(input.clone()))?;

    let record = get(entity_hash.clone(), GetOptions::default())?
        .ok_or(wasm_error!(WasmErrorInner::Guest(String::from("Could not find the just created Entity"))))?;

    let path = Path::from("all_entities");
    create_link(path.path_entry_hash()?, entity_hash.clone(), LinkTypes::AllEntities, ())?;

    Ok(record)
}
}

Service Types Zome

DNA: requests_and_offers
Zome: service_types

Functions

create_service_type(input: CreateServiceTypeInput) -> ExternResult<Record>

Creates a new service type with pending status.

Input:

#![allow(unused)]
fn main() {
pub struct CreateServiceTypeInput {
    pub name: String,
    pub description: String,
    pub tags: Vec<String>,
}
}

Output: Record containing the created service type entry

get_all_service_types() -> ExternResult<Vec<Record>>

Retrieves all service types from the DHT.

Output: Vector of all service type records

get_service_type(service_type_hash: ActionHash) -> ExternResult<Option<Record>>

Retrieves a specific service type by hash.

approve_service_type(service_type_hash: ActionHash) -> ExternResult<Record>

Updates service type status to approved.

reject_service_type(service_type_hash: ActionHash) -> ExternResult<Record>

Updates service type status to rejected.

search_service_types_by_tag(tag: String) -> ExternResult<Vec<Record>>

Searches service types by tag.

Requests Zome

DNA: requests_and_offers
Zome: requests

Functions

create_request(input: CreateRequestInput) -> ExternResult<Record>

Creates a new request.

Input:

#![allow(unused)]
fn main() {
pub struct CreateRequestInput {
    pub title: String,
    pub description: String,
    pub service_type_hash: ActionHash,
    pub urgency: UrgencyLevel,
    pub time_preference: TimePreference,
    pub interaction_type: InteractionType,
}
}

get_active_requests() -> ExternResult<Vec<Record>>

Retrieves all active requests from the "requests.active" path.

get_archived_requests() -> ExternResult<Vec<Record>>

Retrieves all archived requests from the "requests.archived" path.

archive_request(request_hash: ActionHash) -> ExternResult<bool>

Archives a request by moving it from active to archived path and updating status.

update_request(input: UpdateRequestInput) -> ExternResult<Record>

Updates an existing request.

fulfill_request(request_hash: ActionHash) -> ExternResult<Record>

Marks a request as fulfilled.

close_request(request_hash: ActionHash) -> ExternResult<Record>

Closes a request.

Offers Zome

DNA: requests_and_offers
Zome: offers

Functions

create_offer(input: CreateOfferInput) -> ExternResult<Record>

Creates a new offer.

get_active_offers() -> ExternResult<Vec<Record>>

Retrieves all active offers from the "offers.active" path.

get_archived_offers() -> ExternResult<Vec<Record>>

Retrieves all archived offers from the "offers.archived" path.

archive_offer(offer_hash: ActionHash) -> ExternResult<bool>

Archives an offer by moving it from active to archived path and updating status.

accept_offer(offer_hash: ActionHash) -> ExternResult<Record>

Accepts an offer.

close_offer(offer_hash: ActionHash) -> ExternResult<Record>

Closes an offer.

Users Zome

DNA: requests_and_offers
Zome: users

Functions

create_user_profile(input: CreateUserProfileInput) -> ExternResult<Record>

Creates a user profile.

get_all_user_profiles() -> ExternResult<Vec<Record>>

Retrieves all user profiles.

get_user_profile(agent_hash: AgentPubKey) -> ExternResult<Option<Record>>

Gets a specific user profile by agent hash.

update_user_profile(input: UpdateUserProfileInput) -> ExternResult<Record>

Updates a user profile.

Organizations Zome

DNA: requests_and_offers
Zome: organizations

Functions

create_organization(input: CreateOrganizationInput) -> ExternResult<Record>

Creates a new organization.

get_all_organizations() -> ExternResult<Vec<Record>>

Retrieves all organizations.

add_organization_member(input: AddMemberInput) -> ExternResult<()>

Adds a member to an organization.

remove_organization_member(input: RemoveMemberInput) -> ExternResult<()>

Removes a member from an organization.

Administration Zome

DNA: requests_and_offers
Zome: administration

For the full administration design including the progenitor bootstrap mechanism see The Progenitor Pattern and Administration Zome Specification.

Functions

is_progenitor(_: ()) -> ExternResult<bool>

Returns true if the calling agent is the network progenitor. Compares the caller's genesis agent key against the progenitor_pubkey embedded in DNA properties.

Authorization: none required — any agent may call this to check their own status.

add_administrator(input: EntityActionHashAgents) -> ExternResult<bool>

Adds one or more agents to the administrator list.

Input:

#![allow(unused)]
fn main() {
pub struct EntityActionHashAgents {
    pub original_action_hash: ActionHash,
    pub previous_action_hash: ActionHash,
    pub agents: Vec<AgentPubKey>,
}
}

Output: true when a new admin link was created; false if the agent is already an administrator (idempotent).

Authorization: caller must be the network progenitor or an existing administrator.

remove_administrator(input: EntityActionHashAgents) -> ExternResult<bool>

Removes an agent from the administrator list.

Authorization: caller must be an existing administrator. Returns LastAdminError if removing would leave the network with no administrators.

get_all_administrators() -> ExternResult<Vec<Record>>

Returns all current administrator records.

get_agent_administration(agent: AgentPubKey) -> ExternResult<Option<Record>>

Returns the administration record for a specific agent, or None if they are not an administrator.

suspend_user(input: SuspendUserInput) -> ExternResult<Record>

Suspends a user with a reason. Caller must be an administrator.

unsuspend_user(agent: AgentPubKey) -> ExternResult<Record>

Lifts a suspension. Caller must be an administrator.

update_entity_status(input: UpdateEntityStatusInput) -> ExternResult<Record>

Updates the status (Pending, Accepted, Rejected, SuspendedIndefinitely, SuspendedTemporarily) of a user or organization. Caller must be an administrator.

Error Handling

All zome functions use consistent error handling patterns:

#![allow(unused)]
fn main() {
// Standard error pattern
pub fn example_function() -> ExternResult<Record> {
    let result = some_operation()
        .map_err(|e| wasm_error!(WasmErrorInner::Guest(format!("Operation failed: {}", e))))?;

    Ok(result)
}
}

Validation Rules

Input Validation

  • All string inputs are validated for minimum length
  • Required fields are checked for presence
  • Enum values are validated against allowed options

Entry Validation

  • All entries must pass integrity validation
  • References to other entries are verified
  • Status transitions follow defined workflows
  • Link creation follows proper authorization
  • Link targets are validated for existence
  • Link types are checked for correctness

Integration Points

hREA Integration

Service types can be automatically mapped to hREA ResourceSpecifications when approved.

Cross-Zome Communication

Zomes communicate through links and shared entry types for maintaining consistency.

This zome function reference provides the complete backend API for the Holochain application.

Backend Integration API

Integration patterns for hREA and external system connectivity.

hREA Integration

The application integrates with hREA (Holochain Resource-Event-Agent) for economic resource management.

ResourceSpecification Mapping

Service types automatically map to hREA ResourceSpecifications when approved:

#![allow(unused)]
fn main() {
// Automatic mapping on service type approval
pub fn approve_service_type(service_type_hash: ActionHash) -> ExternResult<Record> {
    // Update service type status
    let updated_record = update_service_type_status(service_type_hash, ServiceTypeStatus::Approved)?;

    // Create corresponding ResourceSpecification in hREA
    let resource_spec_input = CreateResourceSpecificationInput {
        name: service_type.name.clone(),
        resource_classified_as: vec![service_type.name.clone()],
        default_unit_of_effort: Some("hour".to_string()),
        // ... other mappings
    };

    call_hrea_zome("create_resource_specification", resource_spec_input)?;

    Ok(updated_record)
}
}

Intent/Proposal Mapping

Requests and offers map to hREA Intents and Proposals:

  • Requests → hREA Intents (expressions of need)
  • Offers → hREA Proposals (expressions of availability)

Event Recording

Exchange completions create hREA Economic Events for resource flow tracking.

External System Integration

GraphQL API

The application exposes a GraphQL API for external system integration:

type Query {
  serviceTypes: [ServiceType]
  requests: [Request]
  offers: [Offer]
  users: [UserProfile]
  organizations: [Organization]
}

type Mutation {
  createRequest(input: CreateRequestInput!): Request
  createOffer(input: CreateOfferInput!): Offer
  # ... other mutations
}

Webhook System

Configurable webhooks for external system notifications:

#![allow(unused)]
fn main() {
// Webhook configuration
pub struct WebhookConfig {
    pub url: String,
    pub events: Vec<String>,
    pub authentication: Option<String>,
}

// Webhook trigger on events
pub fn trigger_webhook(event: &str, payload: serde_json::Value) -> ExternResult<()> {
    // Send HTTP request to configured webhooks
    // Handle authentication and retries
}
}

Cross-DNA Communication

DNA Bridging

Communication between the main DNA and hREA DNA through bridging:

#![allow(unused)]
fn main() {
// Bridge configuration
pub fn setup_hrea_bridge() -> ExternResult<()> {
    let bridge_config = BridgeConfig {
        dna_hash: hrea_dna_hash(),
        zome_name: "resource_specification".to_string(),
    };

    create_bridge(bridge_config)?;
    Ok(())
}

// Cross-DNA function calls
pub fn call_hrea_zome<I, O>(function_name: &str, input: I) -> ExternResult<O>
where
    I: Serialize,
    O: for<'de> Deserialize<'de>,
{
    let result = call(
        CallTargetCell::OtherRole("hrea".into()),
        ZomeName::from("resource_specification"),
        FunctionName::from(function_name),
        None,
        input,
    )?;

    Ok(result)
}
}

Signal Handling

Cross-DNA communication through signals:

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize, Debug)]
pub enum CrossDNASignal {
    ServiceTypeApproved {
        service_type_hash: ActionHash,
        resource_spec_id: String,
    },
    RequestCreated {
        request_hash: ActionHash,
        intent_id: String,
    },
    OfferCreated {
        offer_hash: ActionHash,
        proposal_id: String,
    },
}

// Signal emission
pub fn emit_service_type_approved_signal(
    service_type_hash: ActionHash,
    resource_spec_id: String,
) -> ExternResult<()> {
    let signal = CrossDNASignal::ServiceTypeApproved {
        service_type_hash,
        resource_spec_id,
    };

    emit_signal(&signal)?;
    Ok(())
}
}

API Versioning

Version Management

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize)]
pub struct APIVersion {
    pub major: u32,
    pub minor: u32,
    pub patch: u32,
}

pub const CURRENT_API_VERSION: APIVersion = APIVersion {
    major: 1,
    minor: 0,
    patch: 0,
};

// Version compatibility checking
pub fn check_api_compatibility(client_version: APIVersion) -> bool {
    // Semantic versioning compatibility rules
    client_version.major == CURRENT_API_VERSION.major
}
}

Backward Compatibility

Maintaining backward compatibility through versioned entry types and migration functions.

Security and Authentication

Cross-System Authentication

#![allow(unused)]
fn main() {
// JWT token validation for external API access
pub fn validate_external_token(token: &str) -> ExternResult<AgentPubKey> {
    // Validate JWT token
    // Extract agent information
    // Return authenticated agent
}

// API key management
pub struct APIKey {
    pub key: String,
    pub permissions: Vec<String>,
    pub expires_at: Option<Timestamp>,
}
}

Permission Management

Role-based access control for external integrations with granular permissions for different API endpoints and operations.

This integration API enables the application to work seamlessly with external systems while maintaining security and data integrity.

Frontend Composables API

API reference for business logic composables that abstract domain operations and provide component-ready interfaces.

Composable Architecture

Composables sit between components and stores, providing business logic abstraction and error boundary management.

Base Composable Pattern

export function useDomainManagement() {
  // Create domain store
  const store = createDomainStore();

  // Error boundaries for different operations
  const loadingErrorBoundary = useErrorBoundary({
    context: DOMAIN_CONTEXTS.GET_ALL_ENTITIES,
    enableLogging: true,
    enableFallback: true,
    maxRetries: 2,
    retryDelay: 1000,
  });

  // Reactive state for components
  let state = $state({
    entities: store.entities,
    isLoading: store.isLoading,
    error: store.error,

    // Derived state
    approvedEntities: () =>
      store.entities().filter((e) => e.status === "approved"),
    pendingEntities: () =>
      store.entities().filter((e) => e.status === "pending"),
  });

  // Business operations with error handling
  const operations = {
    async loadEntities() {
      await loadingErrorBoundary.execute(store.fetchEntities, []);
    },

    async createEntity(input: CreateEntityInput) {
      return await createErrorBoundary.execute(store.createEntity(input));
    },
  };

  // Lifecycle management
  $effect(() => {
    operations.loadEntities();
  });

  return {
    state,
    operations,
    loadingErrorBoundary,
  };
}

Domain Composables

Service Types Management

File: ui/src/lib/composables/domain/service-types/useServiceTypesManagement.svelte.ts

export function useServiceTypesManagement() {
  const store = createServiceTypesStore();

  const { loadingErrorBoundary, createErrorBoundary, updateErrorBoundary } =
    useServiceTypeErrorBoundaries();

  const state = $state({
    entities: store.entities,
    isLoading: store.isLoading,
    error: store.error,
    approvedServiceTypes: () =>
      store.entities().filter((st) => st.status === "approved"),
    pendingServiceTypes: () =>
      store.entities().filter((st) => st.status === "pending"),
    rejectedServiceTypes: () =>
      store.entities().filter((st) => st.status === "rejected"),
  });

  const operations = {
    async loadServiceTypes() {
      /* Implementation */
    },
    async createServiceType(input: CreateServiceTypeInput) {
      /* Implementation */
    },
    async approveServiceType(hash: ActionHash) {
      /* Implementation */
    },
    async rejectServiceType(hash: ActionHash) {
      /* Implementation */
    },
    async searchServiceTypes(query: string) {
      /* Implementation */
    },
  };

  return {
    state,
    operations,
    loadingErrorBoundary,
    createErrorBoundary,
    updateErrorBoundary,
  };
}

Service Type Sorting ✨

File: ui/src/lib/composables/search/useServiceTypeSorting.svelte.ts

Purpose: Provides multi-field sorting functionality for service types with intelligent defaults and state management.

export interface ServiceTypeSortReturn {
  sortState: ServiceTypeSortState;
  sortServiceTypes: (serviceTypes: UIServiceType[]) => UIServiceType[];
  updateSort: (
    field: ServiceTypeSortField,
    direction?: ServiceTypeSortDirection,
  ) => void;
  toggleSort: (field: ServiceTypeSortField) => void;
  getSortIcon: (field: ServiceTypeSortField) => string;
  isSortedBy: (field: ServiceTypeSortField) => boolean;
}

export function useServiceTypeSorting(
  initialField: ServiceTypeSortField = "type",
  initialDirection: ServiceTypeSortDirection = "asc",
): ServiceTypeSortReturn {
  const state = $state<ServiceTypeSortState>({
    field: initialField,
    direction: initialDirection,
  });

  const sortServiceTypes = (serviceTypes: UIServiceType[]): UIServiceType[] => {
    return [...serviceTypes].sort((a, b) => {
      let result = 0;

      switch (state.field) {
        case "name":
          result = a.name.localeCompare(b.name);
          break;
        case "type":
          // Non-technical first, then by name as secondary sort
          if (a.technical === b.technical) {
            result = a.name.localeCompare(b.name);
          } else {
            result = a.technical ? 1 : -1;
          }
          break;
        case "created_at":
          result = (a.created_at || 0) - (b.created_at || 0);
          break;
        case "updated_at":
          result = (a.updated_at || 0) - (b.updated_at || 0);
          break;
      }

      return state.direction === "desc" ? -result : result;
    });
  };

  return {
    sortState: state,
    sortServiceTypes,
    updateSort: (field, direction) => {
      /* Implementation */
    },
    toggleSort: (field) => {
      /* Implementation */
    },
    getSortIcon: (field) => {
      /* Implementation */
    },
    isSortedBy: (field) => state.field === field,
  };
}

Key Features:

  • Multi-field Sorting: Supports name, type, created_at, updated_at
  • Secondary Sort: Automatic fallback to name sorting for type field
  • Smart Defaults: Type field defaults to 'asc' (non-technical first), others to 'desc'
  • Icon Management: Dynamic sort direction indicators (↑, ↓, ↕️)
  • State Persistence: Maintains sort preferences within component lifecycle

Requests Management

File: ui/src/lib/composables/domain/requests/useRequestsManagement.svelte.ts

Similar pattern with request-specific operations and state management.

Error Boundary Composables

File: ui/src/lib/composables/useErrorBoundary.svelte.ts

export function useErrorBoundary(config: ErrorBoundaryConfig) {
  let state = $state({
    error: null as DomainError | null,
    isRetrying: false,
    retryCount: 0,
  });

  const execute = async <T>(operation: Effect.Effect<T, DomainError>) => {
    try {
      const result = await Effect.runPromise(operation);
      state.error = null;
      state.retryCount = 0;
      return result;
    } catch (error) {
      handleError(error);
      throw error;
    }
  };

  const clearError = () => {
    state.error = null;
    state.retryCount = 0;
  };

  return { state, execute, clearError };
}

This composable layer provides clean business logic abstraction for components while maintaining proper error handling and state management.

Frontend Services API

Complete API reference for Effect-TS service layer implementations across all domains.

Service Architecture

All services follow the standardized Effect-TS pattern with dependency injection using Context.Tag.

Base Service Interface Pattern

export interface DomainService {
  readonly createEntity: (
    input: CreateEntityInput,
  ) => Effect.Effect<UIEntity, DomainError>;
  readonly getAllEntities: () => Effect.Effect<UIEntity[], DomainError>;
  readonly getEntity: (
    hash: ActionHash,
  ) => Effect.Effect<UIEntity | null, DomainError>;
  readonly updateEntity: (
    hash: ActionHash,
    input: UpdateEntityInput,
  ) => Effect.Effect<UIEntity, DomainError>;
  readonly deleteEntity: (hash: ActionHash) => Effect.Effect<void, DomainError>;
}

Service Implementation Pattern

export const makeDomainService = Effect.gen(function* () {
  const client = yield* HolochainClientService;

  const createEntity = (input: CreateEntityInput) =>
    Effect.gen(function* () {
      const record = yield* client.callZome({
        zome_name: "domain",
        fn_name: "create_entity",
        payload: input,
      });

      const entity = createUIEntity(record);
      if (!entity) {
        yield* Effect.fail(
          DomainError.create("Failed to create UI entity from record"),
        );
      }

      return entity;
    }).pipe(
      Effect.mapError((error) =>
        DomainError.fromError(error, DOMAIN_CONTEXTS.CREATE_ENTITY),
      ),
      Effect.withSpan("DomainService.createEntity"),
    );

  return { createEntity /* ... other methods */ };
});

Domain Services

Service Types Service

File: ui/src/lib/services/zomes/serviceTypes.service.ts

Interface

export interface ServiceTypeService {
  readonly createServiceType: (
    input: CreateServiceTypeInput,
  ) => Effect.Effect<UIServiceType, ServiceTypeError>;
  readonly getAllServiceTypes: () => Effect.Effect<
    UIServiceType[],
    ServiceTypeError
  >;
  readonly getServiceType: (
    hash: ActionHash,
  ) => Effect.Effect<UIServiceType | null, ServiceTypeError>;
  readonly updateServiceType: (
    hash: ActionHash,
    input: UpdateServiceTypeInput,
  ) => Effect.Effect<UIServiceType, ServiceTypeError>;
  readonly deleteServiceType: (
    hash: ActionHash,
  ) => Effect.Effect<void, ServiceTypeError>;
  readonly approveServiceType: (
    hash: ActionHash,
  ) => Effect.Effect<UIServiceType, ServiceTypeError>;
  readonly rejectServiceType: (
    hash: ActionHash,
  ) => Effect.Effect<UIServiceType, ServiceTypeError>;
  readonly searchServiceTypes: (
    query: string,
  ) => Effect.Effect<UIServiceType[], ServiceTypeError>;
  readonly getServiceTypesByTag: (
    tag: string,
  ) => Effect.Effect<UIServiceType[], ServiceTypeError>;
}

Usage

// Dependency injection
export const ServiceTypeService =
  Context.GenericTag<ServiceTypeService>("ServiceTypeService");

// Layer definition
export const ServiceTypeServiceLive = Layer.effect(
  ServiceTypeService,
  makeServiceTypeService,
).pipe(Layer.provide(HolochainClientServiceLive));

// Usage in stores or composables
const serviceTypes = await Effect.runPromise(
  Effect.gen(function* () {
    const service = yield* ServiceTypeService;
    return yield* service.getAllServiceTypes();
  }).pipe(Effect.provide(ServiceTypeServiceLive)),
);

Requests Service

File: ui/src/lib/services/zomes/requests.service.ts

Interface

export interface RequestService {
  readonly createRequest: (
    input: CreateRequestInput,
  ) => Effect.Effect<UIRequest, RequestError>;
  readonly getAllRequests: () => Effect.Effect<UIRequest[], RequestError>;
  readonly getRequest: (
    hash: ActionHash,
  ) => Effect.Effect<UIRequest | null, RequestError>;
  readonly updateRequest: (
    hash: ActionHash,
    input: UpdateRequestInput,
  ) => Effect.Effect<UIRequest, RequestError>;
  readonly deleteRequest: (
    hash: ActionHash,
  ) => Effect.Effect<void, RequestError>;
  readonly fulfillRequest: (
    hash: ActionHash,
  ) => Effect.Effect<UIRequest, RequestError>;
  readonly closeRequest: (
    hash: ActionHash,
  ) => Effect.Effect<UIRequest, RequestError>;
  readonly searchRequests: (
    query: string,
  ) => Effect.Effect<UIRequest[], RequestError>;
  readonly getRequestsByServiceType: (
    serviceTypeHash: ActionHash,
  ) => Effect.Effect<UIRequest[], RequestError>;
}

Offers Service

File: ui/src/lib/services/zomes/offers.service.ts

Interface

export interface OfferService {
  readonly createOffer: (
    input: CreateOfferInput,
  ) => Effect.Effect<UIOffer, OfferError>;
  readonly getAllOffers: () => Effect.Effect<UIOffer[], OfferError>;
  readonly getOffer: (
    hash: ActionHash,
  ) => Effect.Effect<UIOffer | null, OfferError>;
  readonly updateOffer: (
    hash: ActionHash,
    input: UpdateOfferInput,
  ) => Effect.Effect<UIOffer, OfferError>;
  readonly deleteOffer: (hash: ActionHash) => Effect.Effect<void, OfferError>;
  readonly acceptOffer: (
    hash: ActionHash,
  ) => Effect.Effect<UIOffer, OfferError>;
  readonly closeOffer: (hash: ActionHash) => Effect.Effect<UIOffer, OfferError>;
  readonly searchOffers: (
    query: string,
  ) => Effect.Effect<UIOffer[], OfferError>;
  readonly getOffersByServiceType: (
    serviceTypeHash: ActionHash,
  ) => Effect.Effect<UIOffer[], OfferError>;
}

Users Service

File: ui/src/lib/services/zomes/users.service.ts

Interface

export interface UsersService {
  readonly createUser: (
    input: CreateUserInput,
  ) => Effect.Effect<UIUser, UserError>;
  readonly getAllUsers: () => Effect.Effect<UIUser[], UserError>;
  readonly getUser: (
    hash: ActionHash,
  ) => Effect.Effect<UIUser | null, UserError>;
  readonly updateUser: (
    hash: ActionHash,
    input: UpdateUserInput,
  ) => Effect.Effect<UIUser, UserError>;
  readonly deleteUser: (hash: ActionHash) => Effect.Effect<void, UserError>;
  readonly getUserProfile: (
    agentHash: AgentPubKey,
  ) => Effect.Effect<UIUser | null, UserError>;
  readonly searchUsers: (query: string) => Effect.Effect<UIUser[], UserError>;
}

Organizations Service

File: ui/src/lib/services/zomes/organizations.service.ts

Interface

export interface OrganizationService {
  readonly createOrganization: (
    input: CreateOrganizationInput,
  ) => Effect.Effect<UIOrganization, OrganizationError>;
  readonly getAllOrganizations: () => Effect.Effect<
    UIOrganization[],
    OrganizationError
  >;
  readonly getOrganization: (
    hash: ActionHash,
  ) => Effect.Effect<UIOrganization | null, OrganizationError>;
  readonly updateOrganization: (
    hash: ActionHash,
    input: UpdateOrganizationInput,
  ) => Effect.Effect<UIOrganization, OrganizationError>;
  readonly deleteOrganization: (
    hash: ActionHash,
  ) => Effect.Effect<void, OrganizationError>;
  readonly addMember: (
    orgHash: ActionHash,
    userHash: ActionHash,
  ) => Effect.Effect<void, OrganizationError>;
  readonly removeMember: (
    orgHash: ActionHash,
    userHash: ActionHash,
  ) => Effect.Effect<void, OrganizationError>;
  readonly searchOrganizations: (
    query: string,
  ) => Effect.Effect<UIOrganization[], OrganizationError>;
}

Administration Service

File: ui/src/lib/services/zomes/administration.service.ts

Interface

export interface AdministrationService {
  readonly promoteToAdmin: (
    userHash: ActionHash,
  ) => Effect.Effect<void, AdministrationError>;
  readonly demoteFromAdmin: (
    userHash: ActionHash,
  ) => Effect.Effect<void, AdministrationError>;
  readonly promoteToModerator: (
    userHash: ActionHash,
  ) => Effect.Effect<void, AdministrationError>;
  readonly demoteFromModerator: (
    userHash: ActionHash,
  ) => Effect.Effect<void, AdministrationError>;
  readonly suspendUser: (
    userHash: ActionHash,
    reason: string,
  ) => Effect.Effect<void, AdministrationError>;
  readonly unsuspendUser: (
    userHash: ActionHash,
  ) => Effect.Effect<void, AdministrationError>;
  readonly getAllAdmins: () => Effect.Effect<UIUser[], AdministrationError>;
  readonly getAllModerators: () => Effect.Effect<UIUser[], AdministrationError>;
  readonly getAllSuspendedUsers: () => Effect.Effect<
    UIUser[],
    AdministrationError
  >;
}

Base Services

Holochain Client Service

File: ui/src/lib/services/HolochainClientService.svelte.ts

Interface

export interface HolochainClientService {
  readonly callZome: <T>(
    args: CallZomeRequest,
  ) => Effect.Effect<T, HolochainError>;
  readonly callZomeRaw: <T>(
    args: CallZomeRequest,
  ) => Effect.Effect<T, HolochainError>;
  readonly adminClient: () => AdminWebsocket;
  readonly appClient: () => AppWebsocket;
  readonly isConnected: () => boolean;
  readonly disconnect: () => Effect.Effect<void, never>;
  readonly reconnect: () => Effect.Effect<void, HolochainError>;
}

Usage

export const HolochainClientService =
  Context.GenericTag<HolochainClientService>("HolochainClientService");

// Usage in domain services
const result = await Effect.runPromise(
  Effect.gen(function* () {
    const client = yield* HolochainClientService;
    return yield* client.callZome({
      zome_name: "service_types",
      fn_name: "get_all_service_types",
      payload: null,
    });
  }).pipe(Effect.provide(HolochainClientServiceLive)),
);

hREA Service

File: ui/src/lib/services/hrea.service.ts

Interface

export interface HreaService {
  readonly getResourceSpecifications: () => Effect.Effect<
    ResourceSpecification[],
    HreaError
  >;
  readonly createResourceSpecification: (
    input: CreateResourceSpecificationInput,
  ) => Effect.Effect<ResourceSpecification, HreaError>;
  readonly updateResourceSpecification: (
    id: string,
    input: UpdateResourceSpecificationInput,
  ) => Effect.Effect<ResourceSpecification, HreaError>;
  readonly deleteResourceSpecification: (
    id: string,
  ) => Effect.Effect<void, HreaError>;
  readonly getIntents: () => Effect.Effect<Intent[], HreaError>;
  readonly createIntent: (
    input: CreateIntentInput,
  ) => Effect.Effect<Intent, HreaError>;
}

Error Handling

All services use domain-specific tagged errors following the pattern:

export class DomainError extends Data.TaggedError("DomainError")<{
  readonly message: string;
  readonly cause?: unknown;
  readonly context?: string;
  readonly entityId?: string;
  readonly operation?: string;
}> {
  static fromError(
    error: unknown,
    context: string,
    entityId?: string,
    operation?: string,
  ): DomainError {
    const message = error instanceof Error ? error.message : String(error);
    return new DomainError({
      message,
      cause: error,
      context,
      entityId,
      operation,
    });
  }
}

Common Patterns

Effect.gen vs .pipe Usage

Use Effect.gen for:

  • Dependency injection
  • Sequential operations
  • Conditional logic
  • Complex business logic

Use .pipe for:

  • Error handling and transformation
  • Adding spans and tracing
  • Simple transformations
  • Layer composition

Service Composition

// Composing multiple services
const complexOperation = Effect.gen(function* () {
  const serviceTypeService = yield* ServiceTypeService;
  const requestService = yield* RequestService;

  const serviceType = yield* serviceTypeService.getServiceType(serviceTypeHash);
  if (!serviceType) {
    yield* Effect.fail(DomainError.create("Service type not found"));
  }

  return yield* requestService.createRequest({
    ...requestInput,
    serviceTypeHash,
  });
});

Error Context Usage

// Using standardized error contexts
export const DOMAIN_CONTEXTS = {
  CREATE_ENTITY: "Failed to create entity",
  GET_ENTITY: "Failed to get entity",
  UPDATE_ENTITY: "Failed to update entity",
  DELETE_ENTITY: "Failed to delete entity",
  GET_ALL_ENTITIES: "Failed to fetch entities",
} as const;

// Apply in service methods
const result = operation.pipe(
  Effect.mapError((error) =>
    DomainError.fromError(error, DOMAIN_CONTEXTS.CREATE_ENTITY, input.id),
  ),
  Effect.withSpan("DomainService.createEntity"),
);

Testing Services

describe("DomainService", () => {
  it("should create entity with proper error handling", async () => {
    const MockHolochainClient = Layer.succeed(HolochainClientService, {
      callZome: () => Effect.succeed(mockRecord),
    });

    const TestDomainServiceLive = Layer.provide(
      DomainServiceLive,
      MockHolochainClient,
    );

    const result = await Effect.runPromise(
      Effect.gen(function* () {
        const service = yield* DomainService;
        return yield* service.createEntity(mockInput);
      }).pipe(Effect.provide(TestDomainServiceLive)),
    );

    expect(result.name).toBe(mockInput.name);
  });
});

This service layer provides the foundation for all data operations in the application, with consistent patterns for error handling, dependency injection, and Effect-TS integration.

Frontend Stores API

Complete API reference for Svelte 5 Runes + Effect-TS store implementations with comprehensive store-helpers utilities.

Store Architecture

All stores follow the factory pattern with reactive state management using Svelte 5 Runes and standardized helper functions from $lib/utils/store-helpers for consistency, performance, and maintainability.

Store-Helpers Utilities

The project includes a comprehensive set of utilities organized into 5 modules to standardize store patterns:

Module Structure

  • core.ts: Loading state management, error handling, operation wrapping
  • cache-helpers.ts: Cache synchronization, status transitions, batch operations
  • event-helpers.ts: Event emission, domain-specific emitters, cross-domain communication
  • record-helpers.ts: Entity creation, record processing, batch operations
  • fetching-helpers.ts: Data fetching patterns, caching integration, pagination

Store Factory Pattern

export const createDomainStore = () => {
  // Reactive state with Svelte 5 Runes
  let entities = $state<UIDomainEntity[]>([]);
  let isLoading = $state(false);
  let error = $state<string | null>(null);

  // Cache management
  const cache = createModuleCache<ActionHash, UIDomainEntity>(
    "domain",
    5 * 60 * 1000,
  );

  // Implement all 9 helper functions
  // ... helper function implementations

  // Main operations using Effect-TS
  const fetchEntities = Effect.gen(function* () {
    const domainService = yield* DomainService;
    const result = yield* domainService.getAllEntities();
    entities = mapRecordsToUIEntities(result);
    return entities;
  });

  return {
    // Reactive state accessors
    entities: () => entities,
    isLoading: () => isLoading,
    error: () => error,

    // Operations
    fetchEntities,
    // ... other operations

    // Helper functions (exposed for composables)
    createUIEntity,
    mapRecordsToUIEntities,
    // ... other helpers
  };
};

Core Store-Helpers API

Loading State Management

withLoadingState

Higher-order function to wrap operations with loading state management.

// Definition
type OperationWrapper = <T, E>(
  operation: () => Effect<T, E>,
) => (setters: LoadingStateSetter) => Effect<T, E>;

// Usage
const fetchData = withLoadingState(() =>
  pipe(
    service.getData(),
    E.map((data) => {
      entities.splice(0, entities.length, ...data);
      return data;
    }),
  ),
);

createLoadingStateSetter

Creates standardized state setters for loading and error states.

const setters = createLoadingStateSetter(loadingState, errorState);
// Returns: { setLoading: (value: boolean) => void, setError: (value: string | null) => void }

Error Handling

createErrorHandler

Creates domain-specific error handlers with contextual information.

const handleServiceError = createErrorHandler(
  ServiceError.fromError,
  "Failed to fetch entities",
);

// Usage in Effect chain
pipe(service.getData(), E.catchAll(handleServiceError));

createGenericErrorHandler

Simple error handler for generic string errors.

const handleError = createGenericErrorHandler("Operation failed");

Cache Management

createGenericCacheSyncHelper

Synchronizes cache with reactive state arrays for CRUD operations.

interface CacheArrays<T> {
  all: T[];
  pending?: T[];
  approved?: T[];
  rejected?: T[];
}

const { syncCacheToState } = createGenericCacheSyncHelper({
  all: entities,
  pending: pendingEntities,
  approved: approvedEntities,
  rejected: rejectedEntities,
});

// Usage
syncCacheToState(newEntity, "add"); // Add entity to appropriate arrays
syncCacheToState(entity, "update"); // Update entity in arrays
syncCacheToState(entity, "remove"); // Remove entity from arrays

createStatusTransitionHelper

Manages status changes with atomic updates between pending/approved/rejected arrays.

const { transitionEntityStatus } = createStatusTransitionHelper(
  {
    pending: pendingEntities,
    approved: approvedEntities,
    rejected: rejectedEntities,
  },
  cache,
);

// Usage
transitionEntityStatus(entityHash, "approved"); // Moves from pending to approved

processMultipleRecordCollections

Handles complex API responses with multiple collections efficiently.

const processedData = processMultipleRecordCollections(
  {
    converter: createUIEntity,
    cache,
    targetArrays: {
      all: entities,
      pending: pendingEntities,
      approved: approvedEntities,
      rejected: rejectedEntities,
    },
  },
  apiResponse, // { pending: Record[], approved: Record[], rejected: Record[] }
);

Event System

createStandardEventEmitters

Standard CRUD event emitters for basic entities.

const eventEmitters = createStandardEventEmitters<UIEntity>("entityType");

// Available methods
eventEmitters.emitCreated(entity);
eventEmitters.emitUpdated(entity);
eventEmitters.emitDeleted(entityHash);
eventEmitters.emitLoaded(entities);

createStatusAwareEventEmitters

Enhanced event emitters with status change support for approval workflows.

const eventEmitters = createStatusAwareEventEmitters<UIEntity>("entityType");

// Additional methods beyond standard emitters
eventEmitters.emitStatusChanged(entity);
eventEmitters.emitApproved(entity);
eventEmitters.emitRejected(entity);
eventEmitters.emitBatchStatusChanged(entities);

Record Processing

createUIEntityFromRecord

Higher-order function to create UI entities from Holochain records with error recovery.

const createUIEntity = createUIEntityFromRecord<RecordType, UIType>(
  (entry, actionHash, timestamp, additionalData) => ({
    ...entry,
    original_action_hash: actionHash,
    created_at: timestamp,
    status: additionalData?.status || "pending",
  }),
);

// Usage
const entity = createUIEntity(record, { status: "approved" });

mapRecordsToUIEntities

Maps arrays of Records to UI entities with null safety and error handling.

const entities = mapRecordsToUIEntities(records, createUIEntity);
// Returns: UIEntity[] with null values filtered out

createEntityCreationHelper

Standardized entity creation with validation and error handling.

const { createEntity } = createEntityCreationHelper(createUIEntity);

// Handles validation, error recovery, and consistency
const newEntity = createEntity(record, additionalData);

Data Fetching

createEntityFetcher

Higher-order fetching function with loading/error state and caching integration.

const entityFetcher = createEntityFetcher<UIEntity, EntityError>(
  handleEntityError,
);

// Returns fetcher with integrated loading state management
const fetchWithState = entityFetcher(
  fetchOperation,
  processingFunction,
  loadingStateSetter,
);

createCacheIntegratedFetcher

Advanced fetcher with cache-first strategy and service fallback.

const cacheFetcher = createCacheIntegratedFetcher(
  cache,
  serviceOperation,
  createUIEntity,
);

// Automatically checks cache first, falls back to service
const entity = await Effect.runPromise(cacheFetcher(entityHash));

The 9 Standardized Store Patterns

Every store implements these 9 patterns using the store-helpers utilities:

1. Entity Creation Pattern

Uses createUIEntityFromRecord helper to convert Holochain Records to UI entities with error recovery.

// Implementation using store-helpers
const createUIEntity = createUIEntityFromRecord<RecordType, UIType>(
  (entry, actionHash, timestamp, additionalData) => ({
    ...entry,
    original_action_hash: actionHash,
    created_at: timestamp,
    status: additionalData?.status || "pending",
  }),
);

2. Record Mapping Pattern

Uses mapRecordsToUIEntities helper for safe array mapping with null filtering.

// Implementation using store-helpers
const entities = mapRecordsToUIEntities(records, createUIEntity);
// Automatically handles null safety and error recovery

3. Cache Synchronization Pattern

Uses createGenericCacheSyncHelper for cache-to-state synchronization.

// Implementation using store-helpers
const { syncCacheToState } = createGenericCacheSyncHelper({
  all: entities,
  pending: pendingEntities,
  approved: approvedEntities,
  rejected: rejectedEntities,
});

// Usage across CRUD operations
syncCacheToState(newEntity, "add");
syncCacheToState(updatedEntity, "update");
syncCacheToState(deletedEntity, "remove");

4. Event Emission Pattern

Uses domain-specific event emitters from store-helpers.

// Choose appropriate emitter based on entity requirements
const eventEmitters = createStandardEventEmitters<UIEntity>("domain");
// OR for approval workflow entities
const eventEmitters = createStatusAwareEventEmitters<UIEntity>("domain");

// Automatic event broadcasting
eventEmitters.emitCreated(entity);
eventEmitters.emitStatusChanged(entity); // Status-aware only

5. Data Fetching Pattern

Uses createEntityFetcher and withLoadingState for consistent fetching.

// Implementation using store-helpers
const entityFetcher = createEntityFetcher<UIEntity, EntityError>(
  handleEntityError,
);

const fetchEntities = withLoadingState(() =>
  pipe(
    service.getAllEntities(),
    E.map((records) => mapRecordsToUIEntities(records, createUIEntity)),
  ),
);

6. Loading State Pattern

Uses withLoadingState wrapper for consistent state management.

// Implementation using store-helpers
const operation = withLoadingState(() =>
  pipe(
    serviceOperation(),
    E.tap((result) =>
      E.sync(() => {
        // Update reactive state
        entities.splice(0, entities.length, ...result);
      }),
    ),
  ),
);

// Usage
operation(setters); // Automatically manages loading/error state

7. Entity Creation Pattern

Uses createEntityCreationHelper for standardized creation workflows.

// Implementation using store-helpers
const { createEntity } = createEntityCreationHelper(createUIEntity);

// Handles validation, error recovery, and state updates
const newEntity = createEntity(record, { status: "approved" });

8. Status Transition Pattern

Uses createStatusTransitionHelper for approval workflow management.

// Implementation using store-helpers
const { transitionEntityStatus } = createStatusTransitionHelper(
  {
    pending: pendingEntities,
    approved: approvedEntities,
    rejected: rejectedEntities,
  },
  cache,
);

// Atomic status transitions
transitionEntityStatus(entityHash, "approved");

9. Collection Processing Pattern

Uses processMultipleRecordCollections for complex API responses.

// Implementation using store-helpers
const processedData = processMultipleRecordCollections(
  {
    converter: createUIEntity,
    cache,
    targetArrays: {
      all: entities,
      pending: pendingEntities,
      approved: approvedEntities,
      rejected: rejectedEntities,
    },
  },
  complexApiResponse,
);

Complete Store-Helpers Reference

For detailed documentation of all store-helpers utilities, including advanced usage patterns and comprehensive API reference, see Store-Helpers API Documentation.

Domain Stores

Service Types Store

File: ui/src/lib/stores/serviceTypes.store.svelte.ts

API Reference

interface ServiceTypesStore {
  // Reactive state accessors
  entities: () => UIServiceType[];
  isLoading: () => boolean;
  error: () => string | null;

  // Operations
  fetchEntities: Effect.Effect<UIServiceType[], ServiceTypeError>;
  createEntity: (
    input: CreateServiceTypeInput,
  ) => Effect.Effect<UIServiceType, ServiceTypeError>;
  updateEntity: (
    hash: ActionHash,
    input: UpdateServiceTypeInput,
  ) => Effect.Effect<UIServiceType, ServiceTypeError>;
  deleteEntity: (hash: ActionHash) => Effect.Effect<void, ServiceTypeError>;
  approveEntity: (
    hash: ActionHash,
  ) => Effect.Effect<UIServiceType, ServiceTypeError>;
  rejectEntity: (
    hash: ActionHash,
  ) => Effect.Effect<UIServiceType, ServiceTypeError>;
  searchEntities: (
    query: string,
  ) => Effect.Effect<UIServiceType[], ServiceTypeError>;

  // Status transitions
  updateEntityStatus: (hash: ActionHash, status: ServiceTypeStatus) => void;
  batchUpdateStatus: (
    updates: { hash: ActionHash; status: ServiceTypeStatus }[],
  ) => void;

  // Helper functions (exposed for composables)
  createUIEntity: (record: Record) => UIServiceType | null;
  mapRecordsToUIEntities: (records: Record[]) => UIServiceType[];
  syncEntityWithCache: (entity: UIServiceType) => void;
  processMultipleRecordCollections: (response: any) => any;
  eventEmitters: EventEmitters<UIServiceType>;

  // Cache access
  getCachedEntity: (hash: ActionHash) => UIServiceType | null;
  clearCache: () => void;
}

Usage

// Create store instance
const serviceTypesStore = createServiceTypesStore();

// Access reactive state
const entities = serviceTypesStore.entities();
const isLoading = serviceTypesStore.isLoading();

// Execute operations
await Effect.runPromise(
  serviceTypesStore.fetchEntities.pipe(Effect.provide(ServiceTypeServiceLive)),
);

// Create new entity
await Effect.runPromise(
  serviceTypesStore
    .createEntity({
      name: "Web Development",
      description: "Frontend and backend web development services",
    })
    .pipe(Effect.provide(ServiceTypeServiceLive)),
);

Requests Store

File: ui/src/lib/stores/requests.store.svelte.ts

API Reference

interface RequestsStore {
  // Reactive state accessors
  entities: () => UIRequest[];
  isLoading: () => boolean;
  error: () => string | null;

  // Operations
  fetchEntities: Effect.Effect<UIRequest[], RequestError>;
  createEntity: (
    input: CreateRequestInput,
  ) => Effect.Effect<UIRequest, RequestError>;
  updateEntity: (
    hash: ActionHash,
    input: UpdateRequestInput,
  ) => Effect.Effect<UIRequest, RequestError>;
  deleteEntity: (hash: ActionHash) => Effect.Effect<void, RequestError>;
  fulfillRequest: (hash: ActionHash) => Effect.Effect<UIRequest, RequestError>;
  closeRequest: (hash: ActionHash) => Effect.Effect<void, RequestError>;

  // Specialized operations
  getRequestsByServiceType: (
    serviceTypeHash: ActionHash,
  ) => Effect.Effect<UIRequest[], RequestError>;
  searchRequests: (query: string) => Effect.Effect<UIRequest[], RequestError>;

  // All 9 helper functions implemented
  // ... (same pattern as Service Types)
}

Offers Store

File: ui/src/lib/stores/offers.store.svelte.ts

Similar structure to Requests Store with offer-specific operations like acceptOffer and offer status management.

Users Store

File: ui/src/lib/stores/users.store.svelte.ts

User profile management with authentication and profile update operations.

Organizations Store

File: ui/src/lib/stores/organizations.store.svelte.ts

Organization management with member operations and organizational relationships.

Administration Store

File: ui/src/lib/stores/administration.store.svelte.ts

Administrative operations for user role management and system moderation.

Cache Management

Module-Level Cache Pattern

// Cache configuration
const cache = createModuleCache<ActionHash, UIEntity>(
  "domainName", // Cache namespace
  5 * 60 * 1000, // TTL: 5 minutes
);

// Cache operations
const getCachedEntity = (hash: ActionHash): UIEntity | null => {
  return cache.get(hash) || null;
};

const setCachedEntity = (entity: UIEntity): void => {
  cache.set(entity.hash, entity);
};

const invalidateCache = (hash?: ActionHash): void => {
  if (hash) {
    cache.delete(hash);
  } else {
    cache.clear();
  }
};

Cache Integration with Reactive State

// Cache-first loading pattern
const loadEntity = (hash: ActionHash) =>
  Effect.gen(function* () {
    // Check cache first
    const cached = getCachedEntity(hash);
    if (cached) {
      return cached;
    }

    // Fetch from service
    const service = yield* DomainService;
    const entity = yield* service.getEntity(hash);

    // Update cache and state
    setCachedEntity(entity);
    return entity;
  });

Event System Integration

Event Emission

// Store emits events for cross-domain communication
const eventEmitters = createEventEmitters<UIServiceType>("serviceTypes");

// Usage in store operations
const createEntity = (input: CreateServiceTypeInput) =>
  withLoadingState(
    Effect.gen(function* () {
      const service = yield* ServiceTypeService;
      const newEntity = yield* service.createServiceType(input);
      handleNewRecord(newEntity);
      eventEmitters.entityCreated(newEntity); // Event emission
      return newEntity;
    }),
  );

Event Listening

// Stores can listen to events from other domains
$effect(() => {
  const unsubscribe = eventBus.on(
    "requests:entity:created",
    (request: UIRequest) => {
      // Handle request creation in this store
      if (request.serviceTypeHash) {
        // Update related service type usage count
        updateServiceTypeUsage(request.serviceTypeHash);
      }
    },
  );

  return unsubscribe;
});

Testing Stores

Helper Function Testing

describe("ServiceTypes Store - Helper Functions", () => {
  let store: ReturnType<typeof createServiceTypesStore>;

  beforeEach(() => {
    store = createServiceTypesStore();
  });

  it("should implement all 9 helper functions", () => {
    expect(typeof store.createUIEntity).toBe("function");
    expect(typeof store.mapRecordsToUIEntities).toBe("function");
    expect(typeof store.syncEntityWithCache).toBe("function");
    expect(typeof store.eventEmitters).toBe("object");
    expect(typeof store.fetchEntities).toBe("object"); // Effect object
    expect(typeof store.createEntity).toBe("function");
    expect(typeof store.updateEntity).toBe("function");
    expect(typeof store.updateEntityStatus).toBe("function");
    expect(typeof store.processMultipleRecordCollections).toBe("function");
  });

  it("should create UI entity correctly", () => {
    const mockRecord = createMockRecord();
    const entity = store.createUIEntity(mockRecord);

    expect(entity).toBeDefined();
    expect(entity?.hash).toBe(mockRecord.signed_action.hashed.hash);
    expect(entity?.name).toBe("Test Service Type");
  });
});

Effect Operations Testing

describe("ServiceTypes Store - Effect Operations", () => {
  it("should fetch entities successfully", async () => {
    const MockServiceTypeService = Layer.succeed(ServiceTypeService, {
      getAllServiceTypes: () => Effect.succeed([createMockUIServiceType()]),
    });

    const store = createServiceTypesStore();
    const result = await Effect.runPromise(
      store.fetchEntities.pipe(Effect.provide(MockServiceTypeService)),
    );

    expect(result).toHaveLength(1);
    expect(store.entities()).toHaveLength(1);
  });
});

Best Practices

Do's ✅

  • Use store-helpers utilities: Leverage the comprehensive store-helpers for consistency
  • Follow the 9 standardized patterns: Implement all patterns using appropriate helpers
  • Use proper event emitters: Choose between standard and status-aware emitters based on entity needs
  • Maintain cache synchronization: Use createGenericCacheSyncHelper for cache-state sync
  • Handle errors gracefully: Use createErrorHandler for domain-specific error handling
  • Wrap operations: Use withLoadingState for consistent loading/error state management

Don'ts ❌

  • Mix old and new patterns: Consistently use store-helpers utilities throughout
  • Skip error handling: Always use appropriate error handlers
  • Direct state mutation: Use helper functions for all state updates
  • Inconsistent event patterns: Use the same event emitter type throughout a store
  • Cache inconsistency: Never allow cache and state to diverge

Migration Path

  1. Start with Service Types: Use as reference implementation for all patterns
  2. Implement store-helpers: Replace manual implementations with utilities
  3. Standardize event emitters: Use appropriate emitters for each domain
  4. Add status management: Implement approval workflows where needed
  5. Complete testing: Ensure all helper functions are properly tested

Reference Implementation

The Service Types Store (serviceTypes.store.svelte.ts) serves as the complete reference implementation, demonstrating all store-helpers utilities and patterns in their fully realized form. Use this store as the architectural template for all other domain implementations.

This store layer provides reactive state management with consistent patterns across all domains, ensuring maintainability, performance, and scalability of the application.

Store-Helpers Utilities API

Comprehensive utilities for standardizing store implementations in the Requests & Offers application using Effect-TS and Svelte 5 Runes.

Overview

The store-helpers provide a collection of reusable utilities organized into 5 modules to ensure consistency, performance, and maintainability across all domain stores.

Module Structure

Core Module (core.ts)

  • Loading state management: withLoadingState, createLoadingStateSetter
  • Error handling: createErrorHandler, createGenericErrorHandler
  • Operation wrapping: Safe operation execution with connection fallback
  • Validation: Field and hash validation utilities

Cache Module (cache-helpers.ts)

  • Cache synchronization: createGenericCacheSyncHelper
  • Status transitions: createStatusTransitionHelper
  • Collection processing: processMultipleRecordCollections
  • Batch operations: Efficient batch cache updates
  • Cache lookup: createCacheLookupFunction

Event Module (event-helpers.ts)

  • Standard emitters: createStandardEventEmitters
  • Status-aware emitters: createStatusAwareEventEmitters
  • Domain-specific emitters: Specialized emitters for each domain
  • Cross-domain communication: Event bridging between domains
  • Batch and conditional emitters: Advanced event patterns

Record Module (record-helpers.ts)

  • Entity creation: createUIEntityFromRecord
  • Record mapping: mapRecordsToUIEntities
  • Entity factories: Higher-order entity creation functions
  • Batch processing: Efficient bulk record processing
  • Validation: Record structure validation

Fetching Module (fetching-helpers.ts)

  • Basic fetching: createEntityFetcher
  • Cache integration: createCacheIntegratedFetcher
  • Specialized fetchers: Status-aware, paginated, filtered fetchers
  • Dependency management: Fetchers with dependency resolution
  • Fallback strategies: Robust fetching with error recovery

API Reference

Core Utilities

withLoadingState<T, E>(operation: () => Effect<T, E>): (setters: LoadingStateSetter) => Effect<T, E>

Higher-order function that wraps operations with loading state management.

Parameters:

  • operation: Function returning an Effect to be wrapped
  • setters: Object with setLoading and setError functions

Returns: Wrapped operation that manages loading/error state

Example:

const fetchData = withLoadingState(() =>
  pipe(
    service.getData(),
    E.map((data) => {
      entities.splice(0, entities.length, ...data);
      return data;
    }),
  ),
);

// Usage with state setters
fetchData({
  setLoading: (loading) => (isLoading = loading),
  setError: (error) => (errorMessage = error),
});

createErrorHandler<TError>(errorFactory, context): (error: unknown) => Effect<never, TError>

Creates standardized error handlers with contextual information.

Parameters:

  • errorFactory: Function to create domain-specific errors (e.g., ServiceError.fromError)
  • context: Context string for error messages

Returns: Error handler function for use in Effect chains

Example:

const handleServiceError = createErrorHandler(
  ServiceError.fromError,
  "Failed to fetch service types",
);

pipe(serviceOperation(), E.catchAll(handleServiceError));

createLoadingStateSetter(loadingState, errorState): LoadingStateSetter

Creates standardized state setters for reactive loading and error state.

Parameters:

  • loadingState: Reactive loading state variable
  • errorState: Reactive error state variable

Returns: Object with setLoading and setError methods

Cache Utilities

createGenericCacheSyncHelper<T>(arrays: CacheArrays<T>): { syncCacheToState: Function }

Synchronizes cache with reactive state arrays for CRUD operations.

Parameters:

  • arrays: Object containing reactive arrays (all, pending, approved, rejected)

Returns: Object with syncCacheToState function

Example:

const { syncCacheToState } = createGenericCacheSyncHelper({
  all: entities,
  pending: pendingEntities,
  approved: approvedEntities,
  rejected: rejectedEntities,
});

// Add entity to appropriate arrays based on status
syncCacheToState(newEntity, "add");

// Update entity in all relevant arrays
syncCacheToState(updatedEntity, "update");

// Remove entity from all arrays
syncCacheToState(deletedEntity, "remove");

createStatusTransitionHelper<T>(statusArrays, cache): { transitionEntityStatus: Function }

Manages status changes with atomic updates between status arrays.

Parameters:

  • statusArrays: Object with pending, approved, rejected arrays
  • cache: Cache instance for synchronization

Returns: Object with transitionEntityStatus function

Example:

const { transitionEntityStatus } = createStatusTransitionHelper(
  {
    pending: pendingEntities,
    approved: approvedEntities,
    rejected: rejectedEntities,
  },
  cache,
);

// Move entity from pending to approved
transitionEntityStatus(entityHash, "approved");

processMultipleRecordCollections<T>(config, response): T[]

Processes complex API responses with multiple record collections.

Parameters:

  • config: Configuration object with converter, cache, and target arrays
  • response: API response with multiple collections

Returns: Processed entities

Example:

const entities = processMultipleRecordCollections(
  {
    converter: createUIEntity,
    cache,
    targetArrays: {
      all: allEntities,
      pending: pendingEntities,
      approved: approvedEntities,
      rejected: rejectedEntities
    }
  },
  { pending: [...], approved: [...], rejected: [...] }
);

Event Utilities

createStandardEventEmitters<T>(domain: string): StandardEventEmitters<T>

Creates standard CRUD event emitters for basic entities.

Parameters:

  • domain: Domain name for event namespacing

Returns: Object with event emission methods

Methods:

  • emitCreated(entity: T): Emit entity creation event
  • emitUpdated(entity: T): Emit entity update event
  • emitDeleted(hash: ActionHash): Emit entity deletion event
  • emitLoaded(entities: T[]): Emit entities loaded event

Example:

const eventEmitters = createStandardEventEmitters<UIRequest>("request");

// Emit events during CRUD operations
eventEmitters.emitCreated(newRequest);
eventEmitters.emitUpdated(updatedRequest);
eventEmitters.emitDeleted(requestHash);
eventEmitters.emitLoaded(allRequests);

createStatusAwareEventEmitters<T>(domain: string): StatusAwareEventEmitters<T>

Creates enhanced event emitters with status change support for approval workflows.

Parameters:

  • domain: Domain name for event namespacing

Returns: Object with standard and status-aware event methods

Additional Methods:

  • emitStatusChanged(entity: T): Emit status change event
  • emitApproved(entity: T): Emit entity approval event
  • emitRejected(entity: T): Emit entity rejection event
  • emitBatchStatusChanged(entities: T[]): Emit batch status change event

Example:

const eventEmitters =
  createStatusAwareEventEmitters<UIServiceType>("serviceType");

// Standard events
eventEmitters.emitCreated(serviceType);

// Status-specific events
eventEmitters.emitStatusChanged(serviceType);
eventEmitters.emitApproved(serviceType);
eventEmitters.emitRejected(serviceType);

Record Utilities

createUIEntityFromRecord<TRecord, TEntity>(converter): (record: Record, additionalData?) => TEntity | null

Higher-order function to create UI entities from Holochain records with error recovery.

Parameters:

  • converter: Function that converts record data to UI entity

Returns: Function that safely converts records to entities

Example:

const createUIServiceType = createUIEntityFromRecord<
  ServiceTypeInDHT,
  UIServiceType
>((entry, actionHash, timestamp, additionalData) => ({
  ...entry,
  original_action_hash: actionHash,
  created_at: timestamp,
  status: additionalData?.status || "pending",
}));

// Usage
const entity = createUIServiceType(record, { status: "approved" });

mapRecordsToUIEntities<T>(records: Record[], converter): T[]

Maps arrays of Records to UI entities with null safety and error handling.

Parameters:

  • records: Array of Holochain records
  • converter: Entity creation function

Returns: Array of UI entities with null values filtered out

Example:

const entities = mapRecordsToUIEntities(records, createUIServiceType);
// Automatically handles errors and filters null values

Fetching Utilities

createEntityFetcher<T, E>(errorHandler): EntityFetcher<T, E>

Creates standardized entity fetcher with error handling integration.

Parameters:

  • errorHandler: Error handling function for failed operations

Returns: Function that creates fetching operations with state management

Example:

const entityFetcher = createEntityFetcher<UIServiceType, ServiceTypeError>(
  handleServiceTypeError,
);

const fetchOperation = entityFetcher(
  serviceOperation,
  processingFunction,
  loadingStateSetter,
);

createCacheIntegratedFetcher<T>(cache, serviceOperation, converter): (key: string) => Effect<T | null, E>

Creates advanced fetcher with cache-first strategy and service fallback.

Parameters:

  • cache: Cache instance for data storage
  • serviceOperation: Service function for data fetching
  • converter: Function to convert service response to UI entity

Returns: Function that fetches with cache integration

Example:

const cacheFetcher = createCacheIntegratedFetcher(
  cache,
  (hash) => service.getEntity(hash),
  createUIEntity,
);

// Automatically checks cache first, falls back to service
const entity = await Effect.runPromise(cacheFetcher(entityHash));

Usage Patterns

Basic Store Implementation

export const createDomainStore = () => {
  // 1. State initialization with Svelte 5 Runes
  let entities = $state<UIDomainEntity[]>([]);
  let loading = $state(false);
  let error = $state<string | null>(null);

  // 2. Helper initialization
  const createUIEntity = createUIEntityFromRecord<RecordType, UIType>(
    converter,
  );
  const eventEmitters = createStandardEventEmitters<UIType>("domain");
  const { syncCacheToState } = createGenericCacheSyncHelper({ all: entities });
  const setters = createLoadingStateSetter(loading, error);

  // 3. Operations with helpers
  const fetchEntities = withLoadingState(() =>
    pipe(
      domainService.getAllEntities(),
      E.map((records) => {
        const processed = mapRecordsToUIEntities(records, createUIEntity);
        entities.splice(0, entities.length, ...processed);
        eventEmitters.emitLoaded(processed);
        return processed;
      }),
    ),
  );

  // 4. Return store interface
  return {
    entities: () => entities,
    loading: () => loading,
    error: () => error,
    fetchEntities: () => fetchEntities(setters),
  };
};

Advanced Store with Status Management

export const createAdvancedStore = () => {
  // State with status arrays
  let allEntities = $state<UIEntity[]>([]);
  let pendingEntities = $state<UIEntity[]>([]);
  let approvedEntities = $state<UIEntity[]>([]);
  let rejectedEntities = $state<UIEntity[]>([]);

  // Advanced helpers
  const createUIEntity = createUIEntityFromRecord<RecordType, UIType>(
    converter,
  );
  const eventEmitters = createStatusAwareEventEmitters<UIType>("domain");
  const { syncCacheToState } = createGenericCacheSyncHelper({
    all: allEntities,
    pending: pendingEntities,
    approved: approvedEntities,
    rejected: rejectedEntities,
  });
  const { transitionEntityStatus } = createStatusTransitionHelper(
    {
      pending: pendingEntities,
      approved: approvedEntities,
      rejected: rejectedEntities,
    },
    cache,
  );

  // Complex operations
  const getAllEntities = withLoadingState(() =>
    pipe(
      domainService.getAllEntities(),
      E.map((response) =>
        processMultipleRecordCollections(
          {
            converter: createUIEntity,
            cache,
            targetArrays: {
              all: allEntities,
              pending: pendingEntities,
              approved: approvedEntities,
              rejected: rejectedEntities,
            },
          },
          response,
        ),
      ),
    ),
  );

  const approveEntity = (hash: ActionHash) =>
    withLoadingState(() =>
      pipe(
        domainService.approveEntity(hash),
        E.tap(() =>
          E.sync(() => {
            transitionEntityStatus(hash, "approved");
            const entity = approvedEntities.find((e) => e.hash === hash);
            if (entity) eventEmitters.emitApproved(entity);
          }),
        ),
      ),
    );

  return {
    // State accessors
    allEntities: () => allEntities,
    pendingEntities: () => pendingEntities,
    approvedEntities: () => approvedEntities,
    rejectedEntities: () => rejectedEntities,

    // Operations
    getAllEntities: () => getAllEntities(setters),
    approveEntity: (hash: ActionHash) => approveEntity(hash)(setters),
  };
};

Best Practices

Do's ✅

  • Use appropriate helpers: Choose the right helper for each use case
  • Maintain consistency: Use the same patterns across all stores
  • Handle errors gracefully: Always use error handlers for Effect operations
  • Cache synchronization: Keep cache and state synchronized
  • Event emission: Emit events for cross-domain communication

Don'ts ❌

  • Mix patterns: Don't mix old patterns with new helpers
  • Skip error handling: Always handle errors with appropriate helpers
  • Direct state mutation: Use helpers for all state updates
  • Cache inconsistency: Never allow cache and state to diverge
  • Silent failures: Always provide feedback for failed operations

Migration Guide

From Legacy Patterns

  1. Replace manual loading state: Use withLoadingState wrapper
  2. Replace manual cache sync: Use createGenericCacheSyncHelper
  3. Replace manual event emission: Use appropriate event emitters
  4. Replace manual entity creation: Use createUIEntityFromRecord
  5. Replace manual error handling: Use createErrorHandler

Service Types as Reference

The Service Types store (serviceTypes.store.svelte.ts) serves as the complete reference implementation demonstrating all helpers in action. Use it as a template for implementing or upgrading other stores.

This comprehensive utilities library ensures consistent, maintainable, and performant store implementations across the entire application.

Frontend Schema Validation API

Effect Schema validation system for type-safe data validation across all layers.

Schema Architecture

Strategic validation boundaries using Effect Schema for input validation, data transformation, and type safety.

Base Schema Patterns

import { Schema } from "@effect/schema";

// Entity schemas
export const EntityStatusSchema = Schema.Union(
  Schema.Literal("pending"),
  Schema.Literal("approved"),
  Schema.Literal("rejected"),
);

export const BaseEntitySchema = Schema.Struct({
  name: Schema.String,
  description: Schema.String,
  status: EntityStatusSchema,
});

// Input schemas with validation
export const CreateEntityInputSchema = Schema.Struct({
  name: Schema.String.pipe(Schema.minLength(1)),
  description: Schema.String.pipe(Schema.minLength(1)),
});

// UI entity schemas (includes UI-specific fields)
export const UIEntitySchema = BaseEntitySchema.extend(
  Schema.Struct({
    hash: Schema.String, // ActionHash
    createdAt: Schema.Date,
  }),
);

Domain Schemas

Service Type Schemas

File: ui/src/lib/schemas/service-type.schemas.ts

Complete schema definitions for service type validation and transformation.

Request Schemas

File: ui/src/lib/schemas/request.schemas.ts

Request-specific schemas with proper validation boundaries.

User Schemas

File: ui/src/lib/schemas/users.schemas.ts

User profile schemas, plus the form-collection split that separates the DHT name field into given and family name inputs. UserInDHT is unchanged — name remains a single string — so there is no DNA bump and no data migration. The split lives only at the form boundary.

// Form-collection shape: name split into two required fields.
// Mononymous users enter "." in family_name as an explicit declaration.
export const UserFormInputSchema = S.Struct({
  given_name: S.String.pipe(S.minLength(1), S.maxLength(100)),
  family_name: S.String.pipe(S.minLength(1), S.maxLength(100)),
  nickname: S.String.pipe(S.minLength(1), S.maxLength(50)),
  // ...remaining fields identical to UserInDHTSchema
});

/** Form → DHT: joins the two fields with a single space. Mononyms become "Sting ." */
export const formInputToDHT = (input: UserFormInput): UserInDHT => /* ... */;

/** DHT → form, for edit-mode pre-fill: first-space split, so compound family
 *  names ("del Carmen Rodriguez") are preserved. Mononyms get an empty
 *  family_name rather than an inherited dot. */
export const dhtToFormInput = (user: UserInDHT): UserFormInput => /* ... */;

/** Display helper: strips the " ." sentinel from a stored name.
 *  Embedded dots ("Dr. Smith", "J. R. R. Tolkien") are preserved. */
export const formatUserName = (name: string | null | undefined): string => /* ... */;

The trailing dot in a stored mononym ("Sting .") is a vetting marker only; formatUserName strips it at every display site so it never reaches the UI. The action hash on a user record remains the canonical identity reference — names are display labels.

Common Schemas

File: ui/src/lib/schemas/common.schemas.ts

// Shared schemas used across domains
export const TimePreferenceSchema = Schema.Union(
  Schema.Literal("asap"),
  Schema.Literal("flexible"),
  Schema.Literal("specific"),
);

export const InteractionTypeSchema = Schema.Union(
  Schema.Literal("in-person"),
  Schema.Literal("remote"),
  Schema.Literal("hybrid"),
);

Branded Hash Types

File: ui/src/lib/schemas/holochain.schemas.ts

Compile-time distinct types that prevent accidental swapping of original and previous action hashes:

// Branded action hash types — zero runtime cost
export type OriginalActionHash = ActionHash & { readonly __brand: 'OriginalActionHash' };
export type PreviousActionHash = ActionHash & { readonly __brand: 'PreviousActionHash' };

/** Cast an ActionHash as an OriginalActionHash (zero-cost, compile-time only) */
export const asOriginalActionHash = (hash: ActionHash): OriginalActionHash =>
  hash as OriginalActionHash;

/** Cast an ActionHash as a PreviousActionHash (zero-cost, compile-time only) */
export const asPreviousActionHash = (hash: ActionHash): PreviousActionHash =>
  hash as PreviousActionHash;

These types correspond to Rust newtypes OriginalActionHash and PreviousActionHash in dnas/requests_and_offers/utils/src/types.rs. The #[serde(transparent)] attribute on the Rust side ensures wire format compatibility — no migration needed.

See Action Hash Type Safety for the full specification.

Validation Integration

Service Layer Validation

Schemas integrate with services for input/output validation and type safety.

Store Layer Validation

Strategic validation at store boundaries for data integrity.

This schema system provides comprehensive type safety and validation across the application.

Frontend Event System API

Cross-domain event bus for coordinated state management and domain communication.

Event Architecture

Standardized event system enabling loose coupling between domains while maintaining data consistency.

Event Type Definitions

// Domain event types
type ServiceTypeEvents = {
  "service-types:entity:created": UIServiceType;
  "service-types:entity:updated": UIServiceType;
  "service-types:entity:deleted": ActionHash;
  "service-types:entities:loaded": UIServiceType[];
  "service-types:status:changed": {
    hash: ActionHash;
    status: ServiceTypeStatus;
  };
};

type RequestEvents = {
  "requests:entity:created": UIRequest;
  "requests:entity:updated": UIRequest;
  "requests:entity:deleted": ActionHash;
  "requests:entities:loaded": UIRequest[];
  "requests:status:changed": { hash: ActionHash; status: RequestStatus };
};

Event Emission

// Store event emission
const eventEmitters = createEventEmitters<UIServiceType>("serviceTypes");

// Usage in store operations
const createEntity = (input: CreateServiceTypeInput) =>
  withLoadingState(
    Effect.gen(function* () {
      const service = yield* ServiceTypeService;
      const newEntity = yield* service.createServiceType(input);
      handleNewRecord(newEntity);
      eventEmitters.entityCreated(newEntity); // Event emission
      return newEntity;
    }),
  );

Event Listening

// Cross-domain event listening
$effect(() => {
  const unsubscribe = eventBus.on(
    "service-types:entity:updated",
    (serviceType) => {
      // Update requests that reference this service type
      updateServiceTypeReferences(serviceType);
    },
  );

  return unsubscribe;
});

Event Bus Implementation

File: ui/src/lib/utils/eventBus.effect.ts

export const createEventEmitters = <T>(domain: string) => {
  const entityCreated = (entity: T) => {
    eventBus.emit(`${domain}:entity:created`, entity);
  };

  const entityUpdated = (entity: T) => {
    eventBus.emit(`${domain}:entity:updated`, entity);
  };

  const entityDeleted = (hash: ActionHash) => {
    eventBus.emit(`${domain}:entity:deleted`, hash);
  };

  const entitiesLoaded = (entities: T[]) => {
    eventBus.emit(`${domain}:entities:loaded`, entities);
  };

  return { entityCreated, entityUpdated, entityDeleted, entitiesLoaded };
};

Cross-Domain Communication Patterns

Service Type → Request Synchronization

When service types are updated, requests that reference them are automatically synchronized.

Request → Offer Coordination

Request and offer lifecycle events coordinate to maintain system consistency.

This event system enables reactive, loosely-coupled domain interactions while maintaining data integrity.

Frontend Error Handling API

Comprehensive error handling system using Effect-TS tagged errors with domain-specific contexts.

Error Architecture

The application uses a standardized tagged error system with domain-specific error types and centralized error contexts.

Base Error Pattern

import { Data } from "effect";

export class DomainError extends Data.TaggedError("DomainError")<{
  readonly message: string;
  readonly cause?: unknown;
  readonly context?: string;
  readonly entityId?: string;
  readonly operation?: string;
}> {
  static fromError(
    error: unknown,
    context: string,
    entityId?: string,
    operation?: string,
  ): DomainError {
    const message = error instanceof Error ? error.message : String(error);
    return new DomainError({
      message,
      cause: error,
      context,
      entityId,
      operation,
    });
  }

  static create(
    message: string,
    context?: string,
    entityId?: string,
    operation?: string,
  ): DomainError {
    return new DomainError({
      message,
      context,
      entityId,
      operation,
    });
  }
}

Domain-Specific Errors

Service Type Errors

File: ui/src/lib/errors/service-type.errors.ts

export class ServiceTypeError extends Data.TaggedError("ServiceTypeError")<{
  readonly message: string;
  readonly cause?: unknown;
  readonly context?: string;
  readonly entityId?: string;
  readonly operation?: string;
}> {
  /* Implementation */
}

export const SERVICE_TYPE_CONTEXTS = {
  CREATE_SERVICE_TYPE: "Failed to create service type",
  GET_SERVICE_TYPE: "Failed to get service type",
  UPDATE_SERVICE_TYPE: "Failed to update service type",
  DELETE_SERVICE_TYPE: "Failed to delete service type",
  GET_ALL_SERVICE_TYPES: "Failed to fetch service types",
  APPROVE_SERVICE_TYPE: "Failed to approve service type",
  REJECT_SERVICE_TYPE: "Failed to reject service type",
  SEARCH_SERVICE_TYPES: "Failed to search service types",
} as const;

Request Errors

Similar pattern for request-specific error handling with appropriate contexts.

Error Context Management

File: ui/src/lib/errors/error-contexts.ts

Centralized error contexts for consistent error messaging across the application.

Error Boundary System

Composable Error Boundaries

export function useErrorBoundary(config: ErrorBoundaryConfig) {
  let state = $state({
    error: null as DomainError | null,
    isRetrying: false,
    retryCount: 0,
  });

  const execute = async <T>(operation: Effect.Effect<T, DomainError>) => {
    // Error handling implementation with retry logic
  };

  return { state, execute, clearError, retry };
}

Component Error Display

Error boundaries integrate with UI components for consistent error display and user feedback.

This error handling system ensures robust error management across all layers of the application.

Zome Specifications

This section provides detailed specifications for each Holochain zome within the DNA.

For a higher-level overview of the system's technical foundation, see the main Technical Specifications.

Zomes

  • Users & Organizations: Manages user profiles, organization profiles, and their relationships.
  • Service Types: Manages the definition, validation workflow (pending, approved, rejected), and tag-based indexing of service types used in requests and offers.
  • Requests: Handles the creation, management, and lifecycle of requests, integrating with the Service Types zome for defining the nature of requests using approved service types.
  • Offers: Handles the creation, management, and lifecycle of offers, integrating with the Service Types zome for defining capabilities offered using approved service types.
  • Administration: Covers administrator roles, status management, entity verification, and system moderation.
  • Exchanges: Manages exchange proposals, agreements, completion validation, and exchange lifecycle between parties.
  • Mediums of Exchange: Manages payment methods and value exchange mechanisms with approval workflow, supporting both traditional currencies and alternative exchange systems (time banking, LETS, etc.).

(Internal Note: Guidelines below are for maintaining documentation consistency)

Documentation Structure

Each zome's documentation follows this structure:

  1. Overview
  2. Technical Implementation
    • Entry Types
    • Link Types
    • Core Functions
  3. Validation Rules
  4. Access Control
  5. Integration Points
  6. Usage Examples

Development Guidelines

  1. Function Documentation:

    • Document all public functions
    • Include parameter and return type descriptions
    • Provide error conditions and handling
  2. Link Types:

    • Document all link types
    • Explain link creation conditions
    • Describe link validation rules
  3. Entry Types:

    • Document all entry fields
    • Include validation rules
    • Provide example entries
  4. Examples:

    • Show common use cases
    • Include error handling
    • Demonstrate integration points

Users Organizations Zome Specification

Overview

The Users Organizations Zome manages user profiles, agent relationships, and profile status within the system. It consists of two parts:

  1. Integrity Zome: Defines entry and link types, validation rules
  2. Coordinator Zome: Implements business logic and external functions

Technical Implementation

1. Entry Types

User Entry

#![allow(unused)]
fn main() {
#[hdk_entry_helper]
pub struct User {
    /// The full name of the user
    pub name: String,

    /// Display name for the user
    pub nickname: String,

    /// User's biographical information (supports markdown, rendered on frontend with `marked` + `DOMPurify`)
    pub bio: String,

    /// Optional profile picture (serialized)
    pub picture: Option<SerializedBytes>,

    /// User type: 'advocate' or 'creator'
    pub user_type: String,

    /// User's skills
    pub skills: Vec<String>,

    /// Contact information
    pub email: String,
    pub phone: Option<String>,

    /// Location details
    pub time_zone: String,
    pub location: String,
}
}
#![allow(unused)]
fn main() {
pub enum LinkTypes {
    UserUpdates,    // Links user profile updates
    AllUsers,       // Global user index
    MyUser,         // Agent to user profile link
    UserStatus,     // User to status link
    UserAgents,     // User to agent link
}
}

3. Profile Management

Core Functions

create_user
#![allow(unused)]
fn main() {
pub fn create_user(user: User) -> ExternResult<Record>
}
  • Creates new user profile
  • Verifies no existing profile for agent
  • Creates necessary links:
    • AllUsers link for global index
    • MyUser link from agent to profile
    • UserAgents link from profile to agent
    • UserStatus link to initial status
  • Progenitor side-effect: if the calling agent is the network progenitor AND a progenitor_pubkey is configured in DNA properties, they are automatically registered as the first network administrator via a cross-zome call to add_administrator in the administration coordinator. In dev mode (no progenitor key set), the first user to call create_user is registered instead.
  • Returns created profile record
update_user
#![allow(unused)]
fn main() {
pub fn update_user(input: UpdateUserInput) -> ExternResult<Record>
}
  • Updates existing user profile
  • Verifies update permissions
  • Creates update links
  • Returns updated profile record

Profile Retrieval

get_latest_user_record
#![allow(unused)]
fn main() {
pub fn get_latest_user_record(original_action_hash: ActionHash) -> ExternResult<Option<Record>>
}
  • Retrieves most recent profile record
  • Follows update links
  • Returns optional record
get_latest_user
#![allow(unused)]
fn main() {
pub fn get_latest_user(original_action_hash: ActionHash) -> ExternResult<User>
}
  • Retrieves most recent profile entry
  • Returns user data or error
get_agent_user
#![allow(unused)]
fn main() {
pub fn get_agent_user(author: AgentPubKey) -> ExternResult<Vec<Link>>
}
  • Retrieves user profile links for agent
  • Returns vector of MyUser links
get_user_agents
#![allow(unused)]
fn main() {
pub fn get_user_agents(user_original_action_hash: ActionHash) -> ExternResult<Vec<AgentPubKey>>
}
  • Retrieves agents associated with profile
  • Returns vector of agent public keys

4. Validation Rules

Profile Validation

#![allow(unused)]
fn main() {
pub fn validate_user(user: User) -> ExternResult<ValidateCallbackResult>
}
  • Validates user type ('advocate' or 'creator')
  • Verifies picture format if present
  • Validates email address format

Update Validation

#![allow(unused)]
fn main() {
pub fn validate_update_user(
    _action: Update,
    _user: User,
    _original_action: EntryCreationAction,
    _original_user: User,
) -> ExternResult<ValidateCallbackResult>
}
  • Currently allows all valid updates
  • Maintains base validation rules

Delete Prevention

#![allow(unused)]
fn main() {
pub fn validate_delete_user(
    _action: Delete,
    _original_action: EntryCreationAction,
    _original_user: User,
) -> ExternResult<ValidateCallbackResult>
}
  • Prevents profile deletion
  • Returns Invalid result

5. Access Control

  • Profile creation limited to one per agent
  • Profile updates restricted to profile owner
  • Profile queries available to all users
  • Profile deletion not allowed

6. Integration Points

With Administration Zome

  • Status management
  • Profile verification
  • Administrative actions

With Organization Management

  • Organization membership
  • Project participation
  • Resource association

Usage Examples

Profile Creation

#![allow(unused)]
fn main() {
let user = User {
    name: "John Doe".to_string(),
    nickname: "JD".to_string(),
    bio: "Holochain Developer".to_string(),
    picture: None,
    user_type: "creator".to_string(),
    skills: vec!["Rust".to_string(), "Holochain".to_string()],
    email: "john@example.com".to_string(),
    phone: None,
    time_zone: "UTC+0".to_string(),
    location: "Global".to_string(),
};
let record = create_user(user)?;
}

Profile Update

#![allow(unused)]
fn main() {
let input = UpdateUserInput {
    original_action_hash: original_hash,
    previous_action_hash: previous_hash,
    updated_user: updated_profile,
};
let new_record = update_user(input)?;
}

Profile Retrieval

#![allow(unused)]
fn main() {
// Get agent's profile
let links = get_agent_user(agent_key)?;
if let Some(link) = links.first() {
    let user = get_latest_user(link.target.clone().into())?;
}

// Get profile's agents
let agents = get_user_agents(profile_hash)?;
}

Organizations Zome Specification

Overview

The Organizations Zome manages organization profiles and their relationships with users within the system. It consists of two parts:

  1. Integrity Zome: Defines entry and link types, validation rules
  2. Coordinator Zome: Implements business logic and external functions

Technical Implementation

1. Entry Types

Organization Entry

#![allow(unused)]
fn main() {
#[hdk_entry_helper]
pub struct Organization {
    /// Display name of the organization
    pub name: String,

    /// Organization's vision and mission statement (supports markdown, rendered on frontend with `marked` + `DOMPurify`)
    pub description: String,

    /// Full legal name for business registration compliance
    /// Required field for formal business entity identification
    pub full_legal_name: String,

    /// Optional organization logo (serialized)
    pub logo: Option<SerializedBytes>,

    /// Contact email for the organization
    pub email: String,

    /// Related URLs (website, social media, etc.)
    pub urls: Vec<String>,

    /// Organization's location
    pub location: String,
}
}
#![allow(unused)]
fn main() {
pub enum LinkTypes {
    AllOrganizations,          // Global organization index
    OrganizationStatus,        // Links organization to status
    UserOrganizations,         // Links users to organizations
    OrganizationMembers,       // Links organizations to members
    OrganizationCoordinators,  // Links organizations to coordinators
    OrganizationUpdates,       // Links organization updates
    OrganizationContacts,      // Links organization to contact person (tag = role)
}
}

3. Organization Management

Core Functions

create_organization
#![allow(unused)]
fn main() {
pub fn create_organization(organization: Organization) -> ExternResult<Record>
}
  • Creates new organization profile
  • Verifies agent has user profile
  • Creates necessary links:
    • AllOrganizations link for global index
    • OrganizationStatus link to initial status
    • UserOrganizations link from creator
    • OrganizationMembers link to creator
    • OrganizationCoordinators link to creator
  • Returns created organization record
update_organization
#![allow(unused)]
fn main() {
pub fn update_organization(input: UpdateOrganizationInput) -> ExternResult<Record>
}
  • Updates existing organization profile
  • Verifies coordinator permissions
  • Creates update links
  • Returns updated organization record
delete_organization
#![allow(unused)]
fn main() {
pub fn delete_organization(organization_original_action_hash: ActionHash) -> ExternResult<bool>
}
  • Deletes organization profile
  • Verifies coordinator permissions
  • Removes all associated links (members, coordinators, contacts, status)
  • Returns success boolean

Organization Retrieval

get_latest_organization_record
#![allow(unused)]
fn main() {
pub fn get_latest_organization_record(original_action_hash: ActionHash) -> ExternResult<Option<Record>>
}
  • Retrieves most recent organization record
  • Follows update links
  • Returns optional record
get_latest_organization
#![allow(unused)]
fn main() {
pub fn get_latest_organization(original_action_hash: ActionHash) -> ExternResult<Organization>
}
  • Retrieves most recent organization entry
  • Returns organization data or error

4. Member Management

Core Functions

add_member_to_organization
#![allow(unused)]
fn main() {
pub fn add_member_to_organization(input: OrganizationUserInput) -> ExternResult<bool>
}
  • Adds member to organization
  • Verifies coordinator permissions
  • Creates member links
  • Returns success boolean
remove_organization_member
#![allow(unused)]
fn main() {
pub fn remove_organization_member(input: OrganizationUserInput) -> ExternResult<bool>
}
  • Removes member from organization
  • Verifies coordinator permissions
  • Removes member and coordinator links
  • Removes contact link if the removed member is the contact person
  • Returns success boolean
leave_organization
#![allow(unused)]
fn main() {
pub fn leave_organization(original_action_hash: ActionHash) -> ExternResult<bool>
}
  • Allows member to leave organization
  • Removes member and coordinator links
  • Removes contact link if the leaving member is the contact person
  • Returns success boolean

Query Functions

get_organization_members
#![allow(unused)]
fn main() {
pub fn get_organization_members(organization_original_action_hash: ActionHash) -> ExternResult<Vec<User>>
}
  • Retrieves all organization members
  • Returns vector of user entries
get_user_organizations
#![allow(unused)]
fn main() {
pub fn get_user_organizations(user_original_action_hash: ActionHash) -> ExternResult<Vec<Organization>>
}
  • Retrieves all organizations for user
  • Returns vector of organization entries
is_organization_member
#![allow(unused)]
fn main() {
pub fn is_organization_member(input: OrganizationUserInput) -> ExternResult<bool>
}
  • Verifies if user is member
  • Returns boolean status

5. Coordinator Management

Core Functions

add_coordinator_to_organization
#![allow(unused)]
fn main() {
pub fn add_coordinator_to_organization(input: OrganizationUserInput) -> ExternResult<bool>
}
  • Promotes member to coordinator
  • Verifies existing coordinator permissions
  • Creates coordinator links
  • Returns success boolean
remove_organization_coordinator
#![allow(unused)]
fn main() {
pub fn remove_organization_coordinator(input: OrganizationUserInput) -> ExternResult<bool>
}
  • Removes coordinator role
  • Verifies coordinator permissions
  • Removes coordinator links
  • Returns success boolean

Query Functions

get_organization_coordinators
#![allow(unused)]
fn main() {
pub fn get_organization_coordinators(organization_original_action_hash: ActionHash) -> ExternResult<Vec<User>>
}
  • Retrieves all organization coordinators
  • Returns vector of user entries
is_organization_coordinator
#![allow(unused)]
fn main() {
pub fn is_organization_coordinator(input: OrganizationUserInput) -> ExternResult<bool>
}
  • Verifies if user is coordinator
  • Returns boolean status
check_if_agent_is_organization_coordinator
#![allow(unused)]
fn main() {
pub fn check_if_agent_is_organization_coordinator(organization_original_action_hash: ActionHash) -> ExternResult<bool>
}
  • Verifies if current agent is coordinator
  • Returns boolean status

6. Contact Management

Core Functions

#![allow(unused)]
fn main() {
pub fn get_organization_contacts_links(organization_original_action_hash: ActionHash) -> ExternResult<Vec<Link>>
}
  • Retrieves OrganizationContacts links for an organization
  • Returns vector of links (at most one due to single-contact enforcement)
get_organization_contact
#![allow(unused)]
fn main() {
pub fn get_organization_contact(organization_original_action_hash: ActionHash) -> ExternResult<Option<(User, String)>>
}
  • Retrieves the contact person's User entry and role string
  • Returns None if no contact is set
set_organization_contact
#![allow(unused)]
fn main() {
pub fn set_organization_contact(input: OrganizationContactInput) -> ExternResult<bool>
}
  • Sets the contact person for an organization
  • Verifies caller is a coordinator
  • Verifies target user is a coordinator
  • Removes any existing contact link (single-contact enforcement)
  • Creates OrganizationContacts link with role as tag
  • Returns success boolean
remove_organization_contact
#![allow(unused)]
fn main() {
pub fn remove_organization_contact(organization_original_action_hash: ActionHash) -> ExternResult<bool>
}
  • Removes the contact person from an organization
  • Verifies coordinator permissions
  • Returns NotContact error if no contact exists
  • Returns success boolean
is_organization_contact
#![allow(unused)]
fn main() {
pub fn is_organization_contact(input: OrganizationUserInput) -> ExternResult<bool>
}
  • Checks if a specific user is the contact person
  • Returns boolean status

Cleanup Behavior

Contact links are automatically cleaned up in:

  • leave_organization: Removes contact link if the leaving member is the contact
  • remove_organization_member: Removes contact link if the removed member is the contact
  • delete_organization: Removes all contact links as part of organization cleanup

7. Status Integration

Query Functions

is_organization_accepted
#![allow(unused)]
fn main() {
pub fn is_organization_accepted(organization_original_action_hash: &ActionHash) -> ExternResult<bool>
}
  • Checks organization status
  • Verifies if status is "accepted"
  • Returns boolean status

Users and Organizations Zome

Overview

The Users and Organizations zome is a core component that manages user profiles, organization profiles, and their relationships within the system. It provides comprehensive functionality for handling both individual users and organizational entities, including their creation, management, and interconnections.

Components

User Management

Handles individual user profiles and relationships:

  • User profile creation and updates
  • Agent-user relationships
  • Profile status management
  • User validation rules
  • User queries and retrieval

Organization Management

Manages organization profiles and their member relationships:

  • Organization profile creation and updates
  • Member and coordinator management
  • Organization contact person designation
  • Organization status tracking
  • Organization-user relationships
  • Organization validation rules

Technical Implementation

Entry Types

The zome defines two primary entry types:

#![allow(unused)]
fn main() {
// User entry for individual profiles
pub struct User {
    pub name: String,
    pub nickname: String,
    pub bio: String,  // supports markdown (rendered with `marked` + `DOMPurify`)
    pub picture: Option<SerializedBytes>,
    pub user_type: String,
    pub skills: Vec<String>,
    pub email: String,
    pub phone: Option<String>,
    pub time_zone: String,
    pub location: String,
}

// Organization entry for group profiles
pub struct Organization {
    pub name: String,
    pub description: String,
    pub logo: Option<SerializedBytes>,
    pub email: String,
    pub urls: Vec<String>,
    pub location: String,
}
}

The zome uses various link types to maintain relationships:

#![allow(unused)]
fn main() {
pub enum LinkTypes {
    // User-related links
    UserUpdates,
    AllUsers,
    MyUser,
    UserStatus,
    UserAgents,

    // Organization-related links
    AllOrganizations,
    OrganizationStatus,
    UserOrganizations,
    OrganizationMembers,
    OrganizationCoordinators,
    OrganizationUpdates,
    OrganizationContacts,    // Organization → contact person (tag = role)
}
}

Integration Points

  1. Administration Zome

    • Status management for both users and organizations
    • Administrator verification
    • Entity moderation
  2. Status Management

    • Both users and organizations have associated statuses
    • Status types: Created, Accepted, Rejected
    • Status tracking through administration zome

Implementation Location

  • Integrity: dnas/requests_and_offers/zomes/integrity/users_organizations
  • Coordinator: dnas/requests_and_offers/zomes/coordinator/users_organizations

Detailed Documentation

For more detailed information about specific components:

Requests Zome

Overview

The Requests zome provides core functionality for creating, managing, and finding support requests in the Requests and Offers application. Requests represent needs expressed by users, projects, or organizations seeking assistance, skills, or resources from other members of the community.

Technical Implementation

The Requests zome is implemented in two parts:

  • Integrity: dnas/requests_and_offers/zomes/integrity/requests
  • Coordinator: dnas/requests_and_offers/zomes/coordinator/requests

Entry Types

Request

The Request entry represents a request for support or resources with the following structure:

#![allow(unused)]
fn main() {
#[hdk_entry_helper]
#[derive(Clone, PartialEq)]
pub struct Request {
  /// The title of the request
  pub title: String,
  /// A detailed description of the request (max 1000 characters, supports markdown)
  pub description: String,
  /// ActionHashes of approved ServiceType entries that define the nature of the request.
  /// These are validated against the `service_types` zome.
  pub service_type_action_hashes: Vec<ActionHash>,
  /// How the requester prefers to be contacted (Email, Phone, Other)
  pub contact_preference: ContactPreference,
  /// The date range when the request is valid/needed
  pub date_range: Option<DateRange>,
  /// Estimated time needed in hours
  pub time_estimate_hours: Option<f32>,
  /// Preferred time of day for the work/interaction
  pub time_preference: TimePreference,
  /// The requester's time zone
  pub time_zone: Option<TimeZone>,
  /// Preferred method of exchange (Exchange, Arranged, PayItForward, Open)
  pub exchange_preference: ExchangePreference,
  /// Type of interaction preferred (Virtual, InPerson)
  pub interaction_type: InteractionType,
  /// Additional links or resources related to the request
  pub links: Vec<String>,
}
}

Where the supporting types are defined as:

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum ContactPreference {
  Email,
  Phone,
  Other
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum TimePreference {
  Morning,
  Afternoon,
  Evening,
  NoPreference,
  Other
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum ExchangePreference {
  Exchange,
  Arranged,
  PayItForward,
  Open
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum InteractionType {
  Virtual,
  InPerson
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct DateRange {
  pub start: Option<Timestamp>,
  pub end: Option<Timestamp>,
}

// TimeZone is implemented as a String
pub type TimeZone = String;
}

The following link types are used to create relationships between requests and other entries:

  • RequestUpdates: Links from the original request action to update actions (tracking chain).
  • AllRequests: Legacy link type (still present for backward compatibility).
  • ActiveRequests: Links from "requests.active" path to active request entries only.
  • ArchivedRequests: Links from "requests.archived" path to archived request entries only.
  • UserRequests: Links from a user profile (Agent PubKey) to the requests created by that user.
  • OrganizationRequests: Links from an organization's ActionHash to requests associated with it.
  • RequestCreator: Links from a request's ActionHash to its creator's user profile (Agent PubKey).
  • RequestOrganization: Links from a request's ActionHash to its associated organization's ActionHash (if any).
  • RequestToServiceType: Links from a request's ActionHash to an approved ServiceType ActionHash. This defines the type of service being requested.
    • Base: Request ActionHash
    • Target: ServiceType ActionHash (must be an approved ServiceType)
    • Link Tag: e.g., "defines_service_type" or the ServiceType ActionHash itself.

Active/Archived Path Pattern

The application uses separate DHT paths for active and archived requests to optimize query performance:

  • ActiveRequests: Path("requests.active") → Request (for visible/active requests)
  • ArchivedRequests: Path("requests.archived") → Request (for archived requests)

Benefits:

  • Queries fetch only relevant items (no client-side filtering needed)
  • Performance remains optimal as archived requests accumulate
  • Clear semantic separation of data states
  • Reduced DHT load for common queries

Archive Flow:

  1. New requests are created in requests.active path with ActiveRequests link type
  2. When archived, the link is deleted from requests.active and created in requests.archived with ArchivedRequests link type
  3. Entry status is also updated to ListingStatus::Archived for backward compatibility

Core Functions

Create Request

#![allow(unused)]
fn main() {
pub fn create_request(input: RequestInput) -> ExternResult<Record>
}

Creates a new request entry with the provided information.

During creation, the requests_coordinator zome must:

  • Validate that each ActionHash in input.request.service_type_action_hashes corresponds to an existing and approved ServiceType by calling the service_types_coordinator zome.
  • Create RequestToServiceType links for each valid and approved ServiceType ActionHash.

Parameters:

  • input: A RequestInput struct containing:
    • request: The request data
    • organization: Optional organization hash to associate with the request

Returns:

  • Record: The created request record
  • Error: If creation fails

Access Control:

  • Requires a valid user profile for the agent

Get Latest Request Record

#![allow(unused)]
fn main() {
pub fn get_latest_request_record(original_action_hash: ActionHash) -> ExternResult<Option<Record>>
}

Retrieves the latest record for a request, following any update links.

Parameters:

  • original_action_hash: The original action hash of the request

Returns:

  • Option<Record>: The latest request record if found, None otherwise

Get Latest Request

#![allow(unused)]
fn main() {
pub fn get_latest_request(original_action_hash: ActionHash) -> ExternResult<Request>
}

Retrieves the latest request entry data.

Parameters:

  • original_action_hash: The original action hash of the request

Returns:

  • Request: The latest request data
  • Error: If request is not found or cannot be deserialized

Update Request

#![allow(unused)]
fn main() {
pub fn update_request(input: UpdateRequestInput) -> ExternResult<Record>
}

Updates an existing request with new data.

During an update, if service_type_action_hashes are modified, the requests_coordinator zome must:

  • Validate new ServiceType ActionHashes against approved types in the service_types_coordinator zome.
  • Remove old RequestToServiceType links and create new ones as necessary.

Parameters:

  • input: An UpdateRequestInput struct containing:
    • original_action_hash: The original action hash
    • previous_action_hash: The most recent action hash
    • updated_request: The updated request data

Returns:

  • Record: The updated request record
  • Error: If update fails

Access Control:

  • Only the original author or an administrator can update a request

Delete Request

#![allow(unused)]
fn main() {
pub fn delete_request(original_action_hash: ActionHash) -> ExternResult<Record>
}

Deletes a request and all associated links, including RequestToServiceType links.

Parameters:

  • original_action_hash: The action hash of the request to delete

Returns:

  • Record: The deleted request record
  • Error: If deletion fails

Access Control:

  • Only the original author or an administrator can delete a request

Get Active Requests

#![allow(unused)]
fn main() {
pub fn get_active_requests(_: ()) -> ExternResult<Vec<Record>>
}

Retrieves all active requests from the "requests.active" path.

Parameters:

  • None (empty tuple)

Returns:

  • Vec<Record>: Array of active request records only
  • Error: If retrieval fails

Implementation Details:

  • Queries Path("requests.active") with LinkTypes::ActiveRequests filter
  • Uses GetStrategy::Network for DHT-wide queries
  • Fetches only active items, no client-side filtering needed

Get Archived Requests

#![allow(unused)]
fn main() {
pub fn get_archived_requests(_: ()) -> ExternResult<Vec<Record>>
}

Retrieves all archived requests from the "requests.archived" path.

Parameters:

  • None (empty tuple)

Returns:

  • Vec<Record>: Array of archived request records only
  • Error: If retrieval fails

Implementation Details:

  • Queries Path("requests.archived") with LinkTypes::ArchivedRequests filter
  • Uses GetStrategy::Network for DHT-wide queries

Archive Request

#![allow(unused)]
fn main() {
pub fn archive_request(original_action_hash: ActionHash) -> ExternResult<bool>
}

Archives a request by moving it from the active path to the archived path.

Parameters:

  • original_action_hash: The action hash of the request to archive

Returns:

  • bool: true if successful
  • Error: If archival fails

Implementation Details:

  1. Gets the latest request record (follows update chain)
  2. Checks permission (author or administrator only)
  3. Updates entry status to ListingStatus::Archived
  4. Deletes link from "requests.active" path
  5. Creates new link in "requests.archived" path
  6. Creates update tracking link from original to new action

Access Control:

  • Only the original author or an administrator can archive a request

Get User Requests

#![allow(unused)]
fn main() {
pub fn get_user_requests(user_hash: ActionHash) -> ExternResult<Vec<Record>>
}

Retrieves all requests created by a specific user.

Parameters:

  • user_hash: The action hash of the user profile

Returns:

  • Vec<Record>: Array of request records created by the user
  • Error: If retrieval fails

Get Organization Requests

#![allow(unused)]
fn main() {
pub fn get_organization_requests(organization_hash: ActionHash) -> ExternResult<Vec<Record>>
}

Retrieves all requests associated with a specific organization.

Parameters:

  • organization_hash: The action hash of the organization

Returns:

  • Vec<Record>: Array of request records associated with the organization
  • Error: If retrieval fails

Get Request Creator

#![allow(unused)]
fn main() {
pub fn get_request_creator(request_hash: ActionHash) -> ExternResult<Option<ActionHash>>
}

Retrieves the creator of a request.

Parameters:

  • request_hash: The action hash of the request

Returns:

  • Option<ActionHash>: The action hash of the creator user profile, if found
  • Error: If retrieval fails

Get Request Organization

#![allow(unused)]
fn main() {
pub fn get_request_organization(request_hash: ActionHash) -> ExternResult<Option<ActionHash>>
}

Retrieves the organization associated with a request, if any.

Parameters:

  • request_hash: The action hash of the request

Returns:

  • Option<ActionHash>: The action hash of the associated organization, if any
  • Error: If retrieval fails

Validation Rules

Request Validation

  • Title must be between 3 and 50 characters.
  • Description must be between 10 and 1000 characters. Supports markdown formatting (rendered on frontend with marked + DOMPurify).
  • service_type_action_hashes array must not be empty.
  • Each ActionHash in service_type_action_hashes must point to a valid and approved ServiceType entry, validated by calling the service_types_coordinator zome.
  • Links array must be present (can be empty).

Client Integration

The UI integrates with the Requests zome through a layered architecture designed for clarity, testability, and robust asynchronous operations using Effect TS:

  1. UI Components (.svelte files): Users interact with Svelte components. These components subscribe to reactive state from a RequestsStore and trigger actions by calling methods on this store.
  2. Requests Store (requests.store.svelte.ts): This Svelte 5 store, built with runes ($state, $derived, $effect), manages the reactive state related to requests (e.g., lists of requests, loading states, errors). It orchestrates user actions by creating and running Effect pipelines. These pipelines typically involve:
    • Calling methods on the RequestsService.
    • Handling success and error outcomes.
    • Updating the store's reactive state.
    • Managing caching (EntityCache) and cross-store communication (storeEventBus). The store is instantiated as a singleton using a factory pattern, as detailed in effect-patterns.md.
  3. Requests Service (requests.service.ts): This service encapsulates all direct communication with the Holochain "requests" zome. It wraps zome calls within Effect computations, providing typed errors (RequestError) and abstracting the raw Holochain client interactions. It depends on the HolochainClientServiceTag for actual zome calls.
  4. Holochain Zome (requests_coordinator): The backend Rust zome executes the core business logic.

This pattern ensures a clean separation of concerns and leverages Effect TS for managing all side effects, asynchronous flows, and dependencies.

... (rest of the code remains the same)

Requests Service

The RequestsService acts as a crucial bridge between the UI's state management layer (stores) and the Holochain backend. It encapsulates all direct calls to the "requests" zome functions. Key characteristics include:

  • Effect-based Operations: All methods return Effect types, allowing for composable, lazy, and robust asynchronous operations. This aligns with the patterns in effect-patterns.md.
  • Typed Errors: Zome call failures or business logic errors are mapped to a specific RequestError type, providing clear and typed error handling.
  • Abstraction of Holochain Client: It hides the complexities of AppWebsocket.callZome, offering a cleaner API to the rest of the frontend.
  • Dependency Injection: The service itself is typically provided as an Effect Layer and depends on the HolochainClientServiceTag (which provides the actual Holochain client instance) for its operations.

The service interface is defined as follows:

export type RequestsService = {
  createRequest: (
    request: RequestInDHT,
    organizationHash?: ActionHash,
  ) => Effect<never, RequestError, Record>;
  getLatestRequestRecord: (
    originalActionHash: ActionHash,
  ) => Effect<never, RequestError, Record | null>;
  getLatestRequest: (
    originalActionHash: ActionHash,
  ) => Effect<never, RequestError, RequestInDHT | null>;
  updateRequest: (
    originalActionHash: ActionHash,
    previousActionHash: ActionHash,
    updatedRequest: RequestInDHT,
  ) => Effect<never, RequestError, Record>;
  // Active/Archived queries
  getActiveRequestsRecords: () => Effect<never, RequestError, Record[]>;
  getArchivedRequestsRecords: () => Effect<never, RequestError, Record[]>;
  getUserActiveRequestsRecords: (
    userHash: ActionHash,
  ) => Effect<never, RequestError, Record[]>;
  getUserArchivedRequestsRecords: (
    userHash: ActionHash,
  ) => Effect<never, RequestError, Record[]>;
  // User and organization queries
  getUserRequestsRecords: (
    userHash: ActionHash,
  ) => Effect<never, RequestError, Record[]>;
  getOrganizationRequestsRecords: (
    organizationHash: ActionHash,
  ) => Effect<never, RequestError, Record[]>;
  // Management operations
  deleteRequest: (requestHash: ActionHash) => Effect<never, RequestError, void>;
  archiveRequest: (requestHash: ActionHash) => Effect<never, RequestError, boolean>;
};

Requests Store

The RequestsStore is the primary interface for UI components to interact with request-related data and operations. It follows the Effect-driven Svelte store pattern detailed in effect-patterns.md:

  • Factory Pattern & Singleton Instantiation: The store is created by a factory function that returns an Effect. This Effect, when run once with necessary dependencies (like RequestsServiceTag, EntityCacheTag, StoreEventBusTag), produces a singleton store instance.
  • Reactive State with Svelte 5 Runes: Internal state (e.g., requests: $state([]), loading: $state(false), error: $state(null)) is managed using Svelte 5 runes for fine-grained reactivity.
  • Effect-returning Methods: Public methods (e.g., createRequest, getAllRequests) return Effect types. UI components call these methods and then run the returned Effect (e.g., using E.runPromise(store.createRequest(...))).
  • Orchestration: The store methods orchestrate calls to the RequestsService, handle caching logic using EntityCache, manage loading/error states, and emit/listen to events via storeEventBus for cross-store synchronization.
  • ServiceType Handling:
    • When creating or updating requests, the RequestInDHT object passed to store methods will include service_type_action_hashes. These hashes are typically sourced from UI components like a ServiceTypeSelector which might interact with a ServiceTypesStore.
    • For displaying requests, the store might fetch ServiceType details (names, descriptions) based on the stored service_type_action_hashes, potentially by coordinating with a ServiceTypesStore or by including resolved data in its UIRequest type.

The store interface is defined as:

// Assuming UIRequest is a type that might include resolved ServiceType names for display
// and RequestInDHT is the TypeScript equivalent of the Rust Request struct.
// ServiceType would be imported from service_types zome's types.
import type { ActionHash, Record } from "@holochain/client";
import type { Effect } from "@effect/io/Effect";
import type {
  EntityCache,
  EntityCacheTag,
} from "$lib/utils/entityCache.effect"; // Example path
import type { StoreEventBusTag } from "$lib/utils/eventBus.effect"; // Example path
import type {
  RequestsServiceTag,
  RequestError,
} from "$lib/services/zomes/requests.service"; // Example path
import type { ServiceType } from "$lib/types/holochain/service_types"; // Example path
import type {
  RequestInDHT,
  ContactPreference,
  TimePreference,
  ExchangePreference,
  InteractionType,
} from "$lib/types/holochain/requests"; // Example path

export type RequestStoreError =
  | RequestError
  | /* other store-specific errors */ Error;

export type UIRequest = RequestInDHT & {
  original_action_hash: ActionHash; // Ensure original_action_hash is part of UIRequest
  resolvedServiceTypes?: ServiceType[];
  // Potentially other UI-specific fields like creator profile, organization details
};

export type RequestsStore = {
  // Reactive State (actual implementation uses $state internally, accessed via store.requests() etc.)
  readonly requests: UIRequest[];
  readonly activeRequests: UIRequest[];      // Active requests only
  readonly archivedRequests: UIRequest[];    // Archived requests only
  readonly loading: boolean;
  readonly error: string | null;
  readonly cache: EntityCache<UIRequest>;

  // Methods returning Effects
  getLatestRequest: (
    originalActionHash: ActionHash,
  ) => Effect<
    RequestsServiceTag | EntityCacheTag,
    RequestStoreError,
    UIRequest | null
  >;
  getActiveRequests: () => Effect<
    RequestsServiceTag | EntityCacheTag | StoreEventBusTag,
    RequestStoreError,
    UIRequest[]
  >;
  getArchivedRequests: () => Effect<
    RequestsServiceTag | EntityCacheTag | StoreEventBusTag,
    RequestStoreError,
    UIRequest[]
  >;
  getUserActiveRequests: (
    userHash: ActionHash,
  ) => Effect<
    RequestsServiceTag | EntityCacheTag,
    RequestStoreError,
    UIRequest[]
  >;
  getUserArchivedRequests: (
    userHash: ActionHash,
  ) => Effect<
    RequestsServiceTag | EntityCacheTag,
    RequestStoreError,
    UIRequest[]
  >;
  getOrganizationRequests: (
    organizationHash: ActionHash,
  ) => Effect<
    RequestsServiceTag | EntityCacheTag,
    RequestStoreError,
    UIRequest[]
  >;
  createRequest: (
    request: RequestInDHT,
    organizationHash?: ActionHash,
  ) => Effect<RequestsServiceTag | StoreEventBusTag, RequestStoreError, Record>;
  updateRequest: (
    originalActionHash: ActionHash,
    previousActionHash: ActionHash,
    updatedRequest: RequestInDHT,
  ) => Effect<RequestsServiceTag | StoreEventBusTag, RequestStoreError, Record>;
  archiveRequest: (
    requestHash: ActionHash,
  ) => Effect<RequestsServiceTag | StoreEventBusTag, RequestStoreError, Record>;
  deleteRequest: (
    requestHash: ActionHash,
  ) => Effect<RequestsServiceTag | StoreEventBusTag, RequestStoreError, void>;
  invalidateCache: () => Effect<never, never, void>; // Example: might be an Effect if it involves async ops
};

Key implementation aspects include:

  • Svelte 5 runes ($state, $derived, $effect) for reactive state management.
  • EntityCache for caching fetched data to reduce backend calls and manage data consistency.
  • storeEventBus for cross-store communication (e.g., invalidating related caches in other stores upon creation/update/deletion of a request) and state synchronization.
  • Effect for robust error handling, composable asynchronous operations, and managing dependencies via Context.Tag and Layer.

hREA Integration

Requests are designed to integrate with the hREA economic model as follows:

  • Requests are mapped to hREA Intents.
  • The ServiceType entries linked to a Request (via service_type_action_hashes) are mapped to hREA ResourceSpecifications. These ServiceTypes define the skills or services being requested.
  • Request process states align with hREA economic process states

Usage Examples

Creating a Request

// Using the requests store
// Assume serviceTypeActionHash1, serviceTypeActionHash2 are ActionHashes of approved ServiceTypes
// obtained from a ServiceTypeSelector component or ServiceTypesStore.
// RequestInDHT should match the Rust struct definition, excluding fields auto-set by the zome (like creator, timestamp).
const newRequestData: RequestInDHT = {
  title: "Development assistance needed for UI components",
  description:
    "Looking for a Svelte expert to help build reusable UI components for our Holochain app, focusing on accessibility and performance. Experience with Effect TS is a plus.",
  service_type_action_hashes: [serviceTypeActionHash1, serviceTypeActionHash2],
  contact_preference: ContactPreference.Email, // Ensure ContactPreference enum/type is imported/available
  time_preference: TimePreference.NoPreference, // Ensure TimePreference enum/type is imported/available
  exchange_preference: ExchangePreference.Arranged, // Ensure ExchangePreference enum/type is imported/available
  interaction_type: InteractionType.Virtual, // Ensure InteractionType enum/type is imported/available
  // date_range, time_estimate_hours, time_zone, links are optional or can be set as needed
  date_range: { start: new Date().toISOString(), end: null }, // Example: using ISOString for Timestamps
  time_estimate_hours: 20.5,
  links: ["https://github.com/project-repo/issues/123"],
};

// For a personal request
const result = await pipe(
  requestsStore.createRequest(newRequest),
  E.runPromise,
);

// For an organization request
const result = await pipe(
  requestsStore.createRequest(newRequest, organizationHash),
  E.runPromise,
);

Getting Active Requests

// Using the requests store
const activeRequests = await pipe(requestsStore.getActiveRequests(), E.runPromise);

Getting Archived Requests

// Using the requests store
const archivedRequests = await pipe(requestsStore.getArchivedRequests(), E.runPromise);

Getting User Requests (Active)

// Get active requests for a specific user
const userActiveRequests = await pipe(
  requestsStore.getUserActiveRequests(userAgentPubKey),
  E.runPromise,
);

Getting User Requests (Archived)

// Get archived requests for a specific user
const userArchivedRequests = await pipe(
  requestsStore.getUserArchivedRequests(userAgentPubKey),
  E.runPromise,
);

Archiving a Request

// Archive a request (moves it from active to archived)
const result = await pipe(
  requestsStore.archiveRequest(requestHash),
  E.runPromise,
);

Updating a Request

// Using the requests store
const updatedRequest: RequestInDHT = {
  ...existingRequest,
  title: "Updated title",
  description: "Updated description",
};

const result = await pipe(
  requestsStore.updateRequest(
    existingRequest.original_action_hash,
    existingRequest.previous_action_hash,
    updatedRequest,
  ),
  E.runPromise,
);

Deleting a Request

// Using the requests store
await pipe(requestsStore.deleteRequest(requestHash), E.runPromise);

Offers Zome

Overview

The Offers zome provides core functionality for creating, managing, and finding support offers in the Requests and Offers application. Offers represent capabilities, skills, or resources that users, projects, or organizations can provide to other members of the community.

Technical Implementation

The Offers zome is implemented in two parts:

  • Integrity: dnas/requests_and_offers/zomes/integrity/offers
  • Coordinator: dnas/requests_and_offers/zomes/coordinator/offers

Entry Types

Offer

The Offer entry represents an offer of support or resources with the following structure:

#![allow(unused)]
fn main() {
#[hdk_entry_helper]
#[derive(Clone, PartialEq)]
pub struct Offer {
  /// The title of the offer
  pub title: String,
  /// A detailed description of the offer (max 1000 characters, supports markdown)
  pub description: String,
  /// ActionHashes of approved ServiceType entries that define the nature of the offer.
  /// These are validated against the `service_types` zome.
  pub service_type_action_hashes: Vec<ActionHash>,
  /// Preferred time of day for the work/interaction
  pub time_preference: TimePreference,
  /// The offerer's time zone
  pub time_zone: Option<TimeZone>,
  /// Preferred method of exchange (Exchange, Arranged, PayItForward, Open)
  pub exchange_preference: ExchangePreference,
  /// Type of interaction offered (Virtual, InPerson)
  pub interaction_type: InteractionType,
  /// Additional links or resources related to the offer
  pub links: Vec<String>,
}
}

Where the supporting types are defined as:

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum TimePreference {
  Morning,
  Afternoon,
  Evening,
  NoPreference,
  Other
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum ExchangePreference {
  Exchange,
  Arranged,
  PayItForward,
  Open
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum InteractionType {
  Virtual,
  InPerson
}

// TimeZone is implemented as a String
pub type TimeZone = String;
}

The following link types are used to create relationships between offers and other entries:

  • OfferUpdates: Links from the original offer action to update actions (tracking chain).
  • AllOffers: Legacy link type (still present for backward compatibility).
  • ActiveOffers: Links from "offers.active" path to active offer entries only.
  • ArchivedOffers: Links from "offers.archived" path to archived offer entries only.
  • UserOffers: Links from a user profile (Agent PubKey) to the offers created by that user.
  • OrganizationOffers: Links from an organization's ActionHash to offers associated with it.
  • OfferCreator: Links from an offer's ActionHash to its creator's user profile (Agent PubKey).
  • OfferOrganization: Links from an offer's ActionHash to its associated organization's ActionHash (if any).
  • OfferToServiceType: Links from an offer's ActionHash to an approved ServiceType ActionHash. This defines the type of service being offered.
    • Base: Offer ActionHash
    • Target: ServiceType ActionHash (must be an approved ServiceType)
    • Link Tag: e.g., "defines_service_type" or the ServiceType ActionHash itself.

Active/Archived Path Pattern

The application uses separate DHT paths for active and archived offers to optimize query performance:

  • ActiveOffers: Path("offers.active") → Offer (for visible/active offers)
  • ArchivedOffers: Path("offers.archived") → Offer (for archived offers)

Benefits:

  • Queries fetch only relevant items (no client-side filtering needed)
  • Performance remains optimal as archived offers accumulate
  • Clear semantic separation of data states
  • Reduced DHT load for common queries

Archive Flow:

  1. New offers are created in offers.active path with ActiveOffers link type
  2. When archived, the link is deleted from offers.active and created in offers.archived with ArchivedOffers link type
  3. Entry status is also updated to ListingStatus::Archived for backward compatibility

Core Functions

Create Offer

#![allow(unused)]
fn main() {
pub fn create_offer(input: OfferInput) -> ExternResult<Record>
}

Creates a new offer entry with the provided information.

During creation, the offers_coordinator zome must:

  • Validate that each ActionHash in input.offer.service_type_action_hashes corresponds to an existing and approved ServiceType by calling the service_types_coordinator zome.
  • Create OfferToServiceType links for each valid and approved ServiceType ActionHash.

Parameters:

  • input: An OfferInput struct containing:
    • offer: The offer data
    • organization: Optional organization hash to associate with the offer

Returns:

  • Record: The created offer record
  • Error: If creation fails

Access Control:

  • Requires a valid user profile for the agent

Get Latest Offer Record

#![allow(unused)]
fn main() {
pub fn get_latest_offer_record(original_action_hash: ActionHash) -> ExternResult<Option<Record>>
}

Retrieves the latest record for an offer, following any update links.

Parameters:

  • original_action_hash: The original action hash of the offer

Returns:

  • Option<Record>: The latest offer record if found, None otherwise

Get Latest Offer

#![allow(unused)]
fn main() {
pub fn get_latest_offer(original_action_hash: ActionHash) -> ExternResult<Offer>
}

Retrieves the latest offer entry data.

Parameters:

  • original_action_hash: The original action hash of the offer

Returns:

  • Offer: The latest offer data
  • Error: If offer is not found or cannot be deserialized

Update Offer

#![allow(unused)]
fn main() {
pub fn update_offer(input: UpdateOfferInput) -> ExternResult<Record>
}

Updates an existing offer with new data.

During an update, if service_type_action_hashes are modified, the offers_coordinator zome must:

  • Validate new ServiceType ActionHashes against approved types in the service_types_coordinator zome.
  • Remove old OfferToServiceType links and create new ones as necessary.

Parameters:

  • input: An UpdateOfferInput struct containing:
    • original_action_hash: The original action hash
    • previous_action_hash: The most recent action hash
    • updated_offer: The updated offer data

Returns:

  • Record: The updated offer record
  • Error: If update fails

Access Control:

  • Only the original author or an administrator can update an offer

Delete Offer

#![allow(unused)]
fn main() {
pub fn delete_offer(original_action_hash: ActionHash) -> ExternResult<Record>
}

Deletes an offer and all associated links, including OfferToServiceType links.

Parameters:

  • original_action_hash: The action hash of the offer to delete

Returns:

  • Record: The deleted offer record
  • Error: If deletion fails

Access Control:

  • Only the original author or an administrator can delete an offer

Get Active Offers

#![allow(unused)]
fn main() {
pub fn get_active_offers(_: ()) -> ExternResult<Vec<Record>>
}

Retrieves all active offers from the "offers.active" path.

Parameters:

  • None (empty tuple)

Returns:

  • Vec<Record>: Array of active offer records only
  • Error: If retrieval fails

Implementation Details:

  • Queries Path("offers.active") with LinkTypes::ActiveOffers filter
  • Uses GetStrategy::Network for DHT-wide queries
  • Fetches only active items, no client-side filtering needed

Get Archived Offers

#![allow(unused)]
fn main() {
pub fn get_archived_offers(_: ()) -> ExternResult<Vec<Record>>
}

Retrieves all archived offers from the "offers.archived" path.

Parameters:

  • None (empty tuple)

Returns:

  • Vec<Record>: Array of archived offer records only
  • Error: If retrieval fails

Implementation Details:

  • Queries Path("offers.archived") with LinkTypes::ArchivedOffers filter
  • Uses GetStrategy::Network for DHT-wide queries

Archive Offer

#![allow(unused)]
fn main() {
pub fn archive_offer(original_action_hash: ActionHash) -> ExternResult<bool>
}

Archives an offer by moving it from the active path to the archived path.

Parameters:

  • original_action_hash: The action hash of the offer to archive

Returns:

  • bool: true if successful
  • Error: If archival fails

Implementation Details:

  1. Gets the latest offer record (follows update chain)
  2. Checks permission (author or administrator only)
  3. Updates entry status to ListingStatus::Archived
  4. Deletes link from "offers.active" path
  5. Creates new link in "offers.archived" path
  6. Creates update tracking link from original to new action

Access Control:

  • Only the original author or an administrator can archive an offer

Get User Offers

#![allow(unused)]
fn main() {
pub fn get_user_offers(user_hash: ActionHash) -> ExternResult<Vec<Record>>
}

Retrieves all offers created by a specific user.

Parameters:

  • user_hash: The action hash of the user profile

Returns:

  • Vec<Record>: Array of offer records created by the user
  • Error: If retrieval fails

Get Organization Offers

#![allow(unused)]
fn main() {
pub fn get_organization_offers(organization_hash: ActionHash) -> ExternResult<Vec<Record>>
}

Retrieves all offers associated with a specific organization.

Parameters:

  • organization_hash: The action hash of the organization

Returns:

  • Vec<Record>: Array of offer records associated with the organization
  • Error: If retrieval fails

Get User Active Offers

#![allow(unused)]
fn main() {
pub fn get_user_active_offers(user_hash: ActionHash) -> ExternResult<Vec<Record>>
}

Retrieves all active offers created by a specific user.

Parameters:

  • user_hash: The action hash of the user profile

Returns:

  • Vec<Record>: Array of active offer records created by the user
  • Error: If retrieval fails

Implementation Details:

  • Queries user profile links (LinkTypes::UserOffers)
  • Filters results to return only active status offers
  • Used in "My Listings" views

Get User Archived Offers

#![allow(unused)]
fn main() {
pub fn get_user_archived_offers(user_hash: ActionHash) -> ExternResult<Vec<Record>>
}

Retrieves all archived offers created by a specific user.

Parameters:

  • user_hash: The action hash of the user profile

Returns:

  • Vec<Record>: Array of archived offer records created by the user
  • Error: If retrieval fails

Implementation Details:

  • Queries user profile links (LinkTypes::UserOffers)
  • Filters results to return only archived status offers
  • Used in "My Listings" archived tab

Get Offer Creator

#![allow(unused)]
fn main() {
pub fn get_offer_creator(offer_hash: ActionHash) -> ExternResult<Option<ActionHash>>
}

Retrieves the creator of an offer.

Parameters:

  • offer_hash: The action hash of the offer

Returns:

  • Option<ActionHash>: The action hash of the creator user profile, if found
  • Error: If retrieval fails

Get Offer Organization

#![allow(unused)]
fn main() {
pub fn get_offer_organization(offer_hash: ActionHash) -> ExternResult<Option<ActionHash>>
}

Retrieves the organization associated with an offer, if any.

Parameters:

  • offer_hash: The action hash of the offer

Returns:

  • Option<ActionHash>: The action hash of the associated organization, if any
  • Error: If retrieval fails

Validation Rules

Offer Validation

  • Title must be between 3 and 50 characters.
  • Description must be between 10 and 1000 characters. Supports markdown formatting (rendered on frontend with marked + DOMPurify).
  • service_type_action_hashes array must not be empty.
  • Each ActionHash in service_type_action_hashes must point to a valid and approved ServiceType entry, validated by calling the service_types_coordinator zome.
  • Time preference must be specified.
  • Exchange preference must be specified.
  • Interaction type must be specified.
  • Links array must be present (can be empty).

Client Integration

The UI integrates with the Offers zome through a layered architecture designed for clarity, testability, and robust asynchronous operations using Effect TS:

  1. UI Components (.svelte files): Users interact with Svelte components. These components subscribe to reactive state from an OffersStore and trigger actions by calling methods on this store.
  2. Offers Store (offers.store.svelte.ts): This Svelte 5 store, built with runes ($state, $derived, $effect), manages the reactive state related to offers (e.g., lists of offers, loading states, errors). It orchestrates user actions by creating and running Effect pipelines. These pipelines typically involve:
    • Calling methods on the OffersService.
    • Handling success and error outcomes.
    • Updating the store's reactive state.
    • Managing caching (EntityCache) and cross-store communication (storeEventBus). The store is instantiated as a singleton using a factory pattern, as detailed in effect-patterns.md.
  3. Offers Service (offers.service.ts): This service encapsulates all direct communication with the Holochain "offers" zome. It wraps zome calls within Effect computations, providing typed errors (OfferError) and abstracting the raw Holochain client interactions. It depends on the HolochainClientServiceTag for actual zome calls.
  4. Holochain Zome (offers_coordinator): The backend Rust zome executes the core business logic.

This pattern ensures a clean separation of concerns and leverages Effect TS for managing all side effects, asynchronous flows, and dependencies.

Offers Service

The OffersService acts as a crucial bridge between the UI's state management layer (stores) and the Holochain backend. It encapsulates all direct calls to the "offers" zome functions. Key characteristics include:

  • Effect-based Operations: All methods return Effect types, allowing for composable, lazy, and robust asynchronous operations. This aligns with the patterns in effect-patterns.md.
  • Typed Errors: Zome call failures or business logic errors are mapped to a specific OfferError type, providing clear and typed error handling.
  • Abstraction of Holochain Client: It hides the complexities of AppWebsocket.callZome, offering a cleaner API to the rest of the frontend.
  • Dependency Injection: The service itself is typically provided as an Effect Layer and depends on the HolochainClientServiceTag (which provides the actual Holochain client instance) for its operations.

The service interface is defined as follows:

export type OffersService = {
  createOffer: (
    offer: OfferInDHT,
    organizationHash?: ActionHash,
  ) => Effect<never, OfferError, Record>;
  getLatestOfferRecord: (
    originalActionHash: ActionHash,
  ) => Effect<never, OfferError, Record | null>;
  getLatestOffer: (
    originalActionHash: ActionHash,
  ) => Effect<never, OfferError, OfferInDHT | null>;
  updateOffer: (
    originalActionHash: ActionHash,
    previousActionHash: ActionHash,
    updatedOffer: OfferInDHT,
  ) => Effect<never, OfferError, Record>;
  // Active/Archived queries
  getActiveOffersRecords: () => Effect<never, OfferError, Record[]>;
  getArchivedOffersRecords: () => Effect<never, OfferError, Record[]>;
  getUserActiveOffersRecords: (
    userHash: ActionHash,
  ) => Effect<never, OfferError, Record[]>;
  getUserArchivedOffersRecords: (
    userHash: ActionHash,
  ) => Effect<never, OfferError, Record[]>;
  // User and organization queries
  getUserOffersRecords: (
    userHash: ActionHash,
  ) => Effect<never, OfferError, Record[]>;
  getOrganizationOffersRecords: (
    organizationHash: ActionHash,
  ) => Effect<never, OfferError, Record[]>;
  // Management operations
  deleteOffer: (offerHash: ActionHash) => Effect<never, OfferError, void>;
  archiveOffer: (offerHash: ActionHash) => Effect<never, OfferError, boolean>;
};

Offers Store

The OffersStore is the primary interface for UI components to interact with offer-related data and operations. It follows the Effect-driven Svelte store pattern detailed in effect-patterns.md:

  • Factory Pattern & Singleton Instantiation: The store is created by a factory function that returns an Effect. This Effect, when run once with necessary dependencies (like OffersServiceTag, EntityCacheTag, StoreEventBusTag), produces a singleton store instance.
  • Reactive State with Svelte 5 Runes: Internal state (e.g., offers: $state([]), loading: $state(false), error: $state(null)) is managed using Svelte 5 runes for fine-grained reactivity.
  • Effect-returning Methods: Public methods (e.g., createOffer, getAllOffers) return Effect types. UI components call these methods and then run the returned Effect (e.g., using E.runPromise(store.createOffer(...))).
  • Orchestration: The store methods orchestrate calls to the OffersService, handle caching logic using EntityCache, manage loading/error states, and emit/listen to events via storeEventBus for cross-store synchronization.
  • ServiceType Handling:
    • When creating or updating offers, the OfferInDHT object passed to store methods will include service_type_action_hashes. These hashes are typically sourced from UI components like a ServiceTypeSelector which might interact with a ServiceTypesStore.
    • For displaying offers, the store might fetch ServiceType details (names, descriptions) based on the stored service_type_action_hashes, potentially by coordinating with a ServiceTypesStore or by including resolved data in its UIOffer type.

The store interface is defined as:

// Assuming UIOffer is a type that might include resolved ServiceType names for display
// and OfferInDHT is the TypeScript equivalent of the Rust Offer struct.
import type { ActionHash, Record } from "@holochain/client";
import type { Effect } from "@effect/io/Effect";
import type {
  EntityCache,
  EntityCacheTag,
} from "$lib/utils/entityCache.effect"; // Example path
import type { StoreEventBusTag } from "$lib/utils/eventBus.effect"; // Example path
import type {
  OffersServiceTag,
  OfferError,
} from "$lib/services/zomes/offers.service"; // Example path
import type { ServiceType } from "$lib/types/holochain/service_types"; // Example path
import type {
  OfferInDHT,
  TimePreference,
  ExchangePreference,
  InteractionType,
} from "$lib/types/holochain/offers"; // Example path

export type OfferStoreError =
  | OfferError
  | /* other store-specific errors */ Error;

export type UIOffer = OfferInDHT & {
  original_action_hash: ActionHash; // Ensure original_action_hash is part of UIOffer
  resolvedServiceTypes?: ServiceType[];
  // Potentially other UI-specific fields like creator profile, organization details
};

export type OffersStore = {
  // Reactive State (actual implementation uses $state internally, accessed via store.offers() etc.)
  readonly offers: UIOffer[];
  readonly activeOffers: UIOffer[];      // Active offers only
  readonly archivedOffers: UIOffer[];    // Archived offers only
  readonly loading: boolean;
  readonly error: string | null;
  readonly cache: EntityCache<UIOffer>;

  // Active/Archived query methods
  getActiveOffers: () => Effect<
    OffersServiceTag | EntityCacheTag | StoreEventBusTag,
    OfferStoreError,
    UIOffer[]
  >;
  getArchivedOffers: () => Effect<
    OffersServiceTag | EntityCacheTag | StoreEventBusTag,
    OfferStoreError,
    UIOffer[]
  >;

  // Methods returning Effects
  getLatestOffer: (
    originalActionHash: ActionHash,
  ) => Effect<
    OffersServiceTag | EntityCacheTag,
    OfferStoreError,
    UIOffer | null
  >;
  getUserOffers: (
    userHash: ActionHash,
  ) => Effect<OffersServiceTag | EntityCacheTag, OfferStoreError, UIOffer[]>;
  getUserActiveOffers: (
    userHash: ActionHash,
  ) => Effect<OffersServiceTag | EntityCacheTag, OfferStoreError, UIOffer[]>;
  getUserArchivedOffers: (
    userHash: ActionHash,
  ) => Effect<OffersServiceTag | EntityCacheTag, OfferStoreError, UIOffer[]>;
  getOrganizationOffers: (
    organizationHash: ActionHash,
  ) => Effect<OffersServiceTag | EntityCacheTag, OfferStoreError, UIOffer[]>;
  createOffer: (
    offer: OfferInDHT,
    organizationHash?: ActionHash,
  ) => Effect<OffersServiceTag | StoreEventBusTag, OfferStoreError, Record>;
  updateOffer: (
    originalActionHash: ActionHash,
    previousActionHash: ActionHash,
    updatedOffer: OfferInDHT,
  ) => Effect<OffersServiceTag | StoreEventBusTag, OfferStoreError, Record>;
  deleteOffer: (
    offerHash: ActionHash,
  ) => Effect<OffersServiceTag | StoreEventBusTag, OfferStoreError, void>;
  archiveOffer: (
    offerHash: ActionHash,
  ) => Effect<OffersServiceTag | StoreEventBusTag, OfferStoreError, void>;
  invalidateCache: () => Effect<never, never, void>;
};

Key implementation aspects include:

  • Svelte 5 runes ($state, $derived, $effect) for reactive state management.
  • EntityCache for caching fetched data to reduce backend calls and manage data consistency.
  • storeEventBus for cross-store communication (e.g., invalidating related caches in other stores upon creation/update/deletion of an offer) and state synchronization.
  • Effect for robust error handling, composable asynchronous operations, and managing dependencies via Context.Tag and Layer.

hREA Integration

Offers are designed to integrate with the hREA economic model as follows:

  • Offers are mapped to hREA Proposals (or Intents depending on the specific hREA mapping interpretation, typically Proposals).
  • The ServiceType entries linked to an Offer (via service_type_action_hashes) are mapped to hREA ResourceSpecifications. These ServiceTypes define the skills or services being offered.
  • Offer process states align with hREA economic process states

Usage Examples

Creating an Offer

// Using the offers store
// Assume serviceTypeActionHash1, serviceTypeActionHash2 are ActionHashes of approved ServiceTypes
// obtained from a ServiceTypeSelector component or ServiceTypesStore.
// OfferInDHT should match the Rust struct definition, excluding fields auto-set by the zome (like creator, timestamp).
const newOfferData: OfferInDHT = {
  title: "Svelte & Effect TS Expertise Available",
  description:
    "Offering development services for Holochain frontends using Svelte 5, Effect TS, and TailwindCSS. Can help build reactive UIs and integrate with Holochain zomes.",
  service_type_action_hashes: [serviceTypeActionHash1, serviceTypeActionHash2],
  time_preference: TimePreference.Afternoon, // Ensure TimePreference enum/type is imported/available
  exchange_preference: ExchangePreference.Exchange, // Ensure ExchangePreference enum/type is imported/available
  interaction_type: InteractionType.Virtual, // Ensure InteractionType enum/type is imported/available
  // time_zone, links are optional or can be set as needed
  time_zone: "America/New_York",
  links: ["https://linkedin.com/in/myprofile"],
};

// For a personal offer
const result = await pipe(offersStore.createOffer(newOffer), E.runPromise);

// For an organization offer
const result = await pipe(
  offersStore.createOffer(newOffer, organizationHash),
  E.runPromise,
);

Getting Active Offers

// Using the offers store
const activeOffers = await pipe(offersStore.getActiveOffers(), E.runPromise);

Getting Archived Offers

// Using the offers store
const archivedOffers = await pipe(offersStore.getArchivedOffers(), E.runPromise);

Archiving an Offer

// Using the offers store
await pipe(offersStore.archiveOffer(offerHash), E.runPromise);

Updating an Offer

// Using the offers store
const updatedOffer: OfferInDHT = {
  ...existingOffer,
  title: "Updated title",
  description: "Updated description",
};

const result = await pipe(
  offersStore.updateOffer(
    existingOffer.original_action_hash,
    existingOffer.previous_action_hash,
    updatedOffer,
  ),
  E.runPromise,
);

Deleting an Offer

// Using the offers store
await pipe(offersStore.deleteOffer(offerHash), E.runPromise);

Mediums of Exchange Zome (mediums_of_exchange_integrity, mediums_of_exchange_coordinator)

1. Overview

  • Purpose: Manages the lifecycle of MediumOfExchange entries, which define various payment methods and value exchange mechanisms within the requests and offers ecosystem. Supports both traditional currencies (USD, EUR) and alternative exchange systems (Pay It Forward, Local Exchange Trading Systems, Time Banking, etc.).
  • Status: Complete implementation with approval workflow, entity linking, and cross-zome integration operational
  • Approval Workflow: Implements a moderation process for MediumOfExchange entries, involving user suggestions, admin review, and approval/rejection states (pending, approved, rejected). Only approved mediums can be actively used in requests and offers.
  • Exchange Type Classification: Supports two types of mediums - "base" categories (foundational exchange systems) and "currency" types (specific monetary units)
  • Cross-Zome Integration: Full integration with requests_coordinator and offers_coordinator for bidirectional linking
  • Zome Structure:
    • mediums_of_exchange_integrity: Handles data validation, entry definitions, and validation rules.
    • mediums_of_exchange_coordinator: Provides externally callable functions for business logic, interacting with the integrity zome and managing data flows.

2. Integrity Zome (mediums_of_exchange_integrity)

2.1. Entry Types

MediumOfExchange

The MediumOfExchange entry defines a specific payment method or value exchange mechanism.

#![allow(unused)]
fn main() {
#[hdk_entry_helper]
#[derive(Clone, PartialEq)]
pub struct MediumOfExchange {
  /// Unique identifier (e.g., 'EUR', 'USD', 'TIME', 'LOCAL')
  pub code: String,
  /// Human-readable name (e.g., 'Euro', 'US Dollar', 'Time Banking', 'Local Currency')
  pub name: String,
  /// Detailed description of the medium of exchange
  pub description: Option<String>,
  /// Exchange type: "base" (foundational categories) or "currency" (specific monetary units)
  pub exchange_type: String,
  /// ID of corresponding hREA ResourceSpecification (only for approved)
  pub resource_spec_hrea_id: Option<String>,
}
}

2.2. Validation Rules

  • MediumOfExchange Entry:
    • code: Must not be empty. Serves as unique identifier.
    • name: Must not be empty. Human-readable display name.
    • description: Optional field for detailed explanation.
    • exchange_type: Must be either "base" or "currency". Validated strictly.
    • resource_spec_hrea_id: Optional initially (for suggested state), set during approval.
  • Updates/Deletes: Only original author can update/delete entries (author validation enforced).
  • AllMediumsOfExchange

    • Base: Path anchor (e.g., mediums_of_exchange).
    • Target: ActionHash of a MediumOfExchange entry.
    • Purpose: Links all medium of exchange entries for global access.
  • MediumOfExchangeUpdates

    • Base: ActionHash of original MediumOfExchange entry.
    • Target: ActionHash of updated MediumOfExchange entry.
    • Purpose: Links original entries to their updates for version tracking.
  • MediumOfExchangeToRequest

    • Base: ActionHash of a MediumOfExchange entry.
    • Target: ActionHash of a Request entry.
    • Purpose: Links approved mediums to requests that accept them.
  • RequestToMediumOfExchange

    • Base: ActionHash of a Request entry.
    • Target: ActionHash of a MediumOfExchange entry.
    • Purpose: Bidirectional link from requests to their accepted mediums.
  • MediumOfExchangeToOffer

    • Base: ActionHash of a MediumOfExchange entry.
    • Target: ActionHash of an Offer entry.
    • Purpose: Links approved mediums to offers that provide them.
  • OfferToMediumOfExchange

    • Base: ActionHash of an Offer entry.
    • Target: ActionHash of a MediumOfExchange entry.
    • Purpose: Bidirectional link from offers to their provided mediums.

3. Coordinator Zome (mediums_of_exchange_coordinator)

3.1. Path Anchors Used

  • Global Anchor (Static Path):
    • mediums_of_exchange: For linking all medium of exchange entries.
  • Status Anchors (Static Paths):
    • mediums_of_exchange.status.pending: For MediumOfExchange entries awaiting admin review.
    • mediums_of_exchange.status.approved: For MediumOfExchange entries approved by an admin and usable in requests/offers.
    • mediums_of_exchange.status.rejected: For MediumOfExchange entries rejected by an admin.

3.2. Key Functions

Input/Output structs are defined in Rust for type safety and clarity.

User-Facing Functions

  • suggest_medium_of_exchange(input: MediumOfExchangeInput) -> ExternResult<Record>
    • Description: Allows accepted users to suggest new MediumOfExchange entries. Non-admin users can only suggest "currency" types.
    • Actions:
      1. Validates user is accepted or administrator.
      2. Ensures non-admin users can only suggest "currency" exchange types.
      3. Creates a new MediumOfExchange entry with resource_spec_hrea_id set to None.
      4. Links the entry to the global mediums_of_exchange anchor.
      5. Links the entry to the mediums_of_exchange.status.pending anchor.
    • Access Control: Accepted users and administrators.
    • Returns: The Record of the newly created MediumOfExchange entry.

Administrative Functions

  • create_medium_of_exchange(input: MediumOfExchangeInput) -> ExternResult<Record>

    • Description: Allows administrators to directly create and approve MediumOfExchange entries of any type.
    • Actions:
      1. Creates a new MediumOfExchange entry.
      2. Links the entry to the global mediums_of_exchange anchor.
      3. Links the entry directly to the mediums_of_exchange.status.approved anchor.
    • Access Control: Admin only.
    • Returns: The Record of the newly created and approved MediumOfExchange entry.
  • approve_medium_of_exchange(medium_of_exchange_hash: ActionHash) -> ExternResult<()>

    • Description: Allows an administrator to approve a MediumOfExchange that is currently pending.
    • Actions:
      1. Validates that medium_of_exchange_hash points to an existing MediumOfExchange entry.
      2. Creates placeholder hREA ResourceSpecification ID (format: hrea_resource_spec_{code}).
      3. Updates the entry with the hREA ResourceSpecification ID.
      4. Removes from all status paths and links to mediums_of_exchange.status.approved.
    • Access Control: Admin only.
    • Returns: Success confirmation.
  • reject_medium_of_exchange(medium_of_exchange_hash: ActionHash) -> ExternResult<()>

    • Description: Allows an administrator to reject a MediumOfExchange.
    • Actions:
      1. Validates medium_of_exchange_hash.
      2. Removes from all status paths.
      3. Links to mediums_of_exchange.status.rejected.
    • Access Control: Admin only.
    • Returns: Success confirmation.
  • update_medium_of_exchange(input: UpdateMediumOfExchangeInput) -> ExternResult<Record>

    • Description: Allows an administrator to update an existing MediumOfExchange.
    • Actions:
      1. Validates admin permissions and entry existence.
      2. Creates update entry.
      3. Creates update link for version tracking.
    • Access Control: Admin only.
    • Returns: The Record of the updated MediumOfExchange entry.
  • delete_medium_of_exchange(medium_of_exchange_hash: ActionHash) -> ExternResult<()>

    • Description: Allows an administrator to delete a MediumOfExchange entry.
    • Actions:
      1. Validates admin permissions and entry existence.
      2. Removes from all status paths.
      3. Deletes the entry.
    • Access Control: Admin only.
    • Returns: Success confirmation.

Getter Functions

  • get_medium_of_exchange(medium_of_exchange_hash: ActionHash) -> ExternResult<Option<Record>>

    • Description: Retrieves a specific MediumOfExchange entry by its ActionHash.
    • Access Control: Public.
    • Returns: Some(Record) if found, None otherwise.
  • get_latest_medium_of_exchange_record(original_action_hash: ActionHash) -> ExternResult<Option<Record>>

    • Description: Gets the latest version of a medium of exchange given the original action hash.
    • Access Control: Public.
    • Returns: Latest version Record or original if no updates.
  • get_all_mediums_of_exchange() -> ExternResult<Vec<Record>>

    • Description: Retrieves all MediumOfExchange entries regardless of status.
    • Access Control: Public.
    • Returns: A vector of Records.
  • get_pending_mediums_of_exchange() -> ExternResult<Vec<Record>>

    • Description: Retrieves all MediumOfExchange entries currently linked to the mediums_of_exchange.status.pending anchor.
    • Access Control: Admin only.
    • Returns: A vector of Records.
  • get_approved_mediums_of_exchange() -> ExternResult<Vec<Record>>

    • Description: Retrieves all MediumOfExchange entries currently linked to the mediums_of_exchange.status.approved anchor.
    • Access Control: Public (essential for UI selectors).
    • Returns: A vector of Records.
  • get_rejected_mediums_of_exchange() -> ExternResult<Vec<Record>>

    • Description: Retrieves all MediumOfExchange entries currently linked to the mediums_of_exchange.status.rejected anchor.
    • Access Control: Admin only.
    • Returns: A vector of Records.

Entity Linking Functions

  • link_to_medium_of_exchange(input: MediumOfExchangeLinkInput) -> ExternResult<()>

    • Description: Creates bidirectional links between an approved medium of exchange and a request or offer.
    • Validation: Verifies the medium of exchange is approved before linking.
    • Access Control: Public (for linking approved mediums).
    • Returns: Success confirmation.
  • unlink_from_medium_of_exchange(input: MediumOfExchangeLinkInput) -> ExternResult<()>

    • Description: Removes bidirectional links between a medium of exchange and a request or offer.
    • Access Control: Public.
    • Returns: Success confirmation.
  • update_medium_of_exchange_links(input: UpdateMediumOfExchangeLinksInput) -> ExternResult<()>

    • Description: Updates all medium of exchange links for a request or offer, efficiently adding new links and removing outdated ones.
    • Access Control: Public.
    • Returns: Success confirmation.
  • get_mediums_of_exchange_for_entity(input: GetMediumOfExchangeForEntityInput) -> ExternResult<Vec<ActionHash>>

    • Description: Gets all medium of exchange hashes linked to a request or offer.
    • Access Control: Public.
    • Returns: Vector of ActionHashes.
  • delete_all_medium_of_exchange_links_for_entity(input: GetMediumOfExchangeForEntityInput) -> ExternResult<()>

    • Description: Deletes all medium of exchange links for a request or offer (used when deleting the entity).
    • Access Control: Public.
    • Returns: Success confirmation.

Cross-Entity Discovery Functions

  • get_requests_for_medium_of_exchange(medium_of_exchange_hash: ActionHash) -> ExternResult<Vec<Record>>

    • Description: Retrieves all requests linked to a specific medium of exchange.
    • Access Control: Public.
    • Returns: A vector of Request Records.
  • get_offers_for_medium_of_exchange(medium_of_exchange_hash: ActionHash) -> ExternResult<Vec<Record>>

    • Description: Retrieves all offers linked to a specific medium of exchange.
    • Access Control: Public.
    • Returns: A vector of Offer Records.

Utility Functions

  • is_medium_of_exchange_approved(medium_of_exchange_hash: ActionHash) -> ExternResult<bool>
    • Description: Checks if a medium of exchange is approved (for internal use and validation).
    • Access Control: Public.
    • Returns: Boolean indicating approval status.

3.3. Cross-Zome Interactions

  • Called by requests_coordinator and offers_coordinator:

    • To validate that MediumOfExchange ActionHashes provided during Request/Offer creation/update correspond to approved MediumOfExchanges.
    • Entity linking operations to associate requests/offers with their accepted/provided mediums.
  • Integration with administration zome:

    • Uses admin validation functions to check permissions for approval/rejection operations.
    • Leverages user acceptance status for suggestion permissions.

4. Testing Status

Backend Tests (Sweettest)

  • ✅ Medium of exchange CRUD operations and status management
  • ✅ Exchange type validation ("base" vs "currency")
  • ✅ Approval workflow (suggest, approve, reject)
  • ✅ Entity linking with requests and offers
  • ✅ Bidirectional link management and cleanup
  • ✅ Admin permission validation and access control
  • ✅ Cross-entity discovery functionality
  • ✅ Update and version tracking

Frontend Integration

  • Effect-TS Service Layer: Complete with all zome functions exposed
  • Reactive Svelte Store: Full state management with caching and event bus
  • UI Components: Enhanced form and selector components with categorization
  • Cross-Store Integration: Requests and offers stores enhanced with medium linking
  • Test Coverage: All unit tests passing (part of 343 total test suite across 20 files)

5. Implementation Notes

Performance Considerations

  • Path anchor indexing provides efficient status-based queries
  • Bidirectional linking enables fast cross-entity discovery
  • Caching implemented at service and store levels
  • Status transition operations optimized to remove from all paths before setting new status

Data Integrity

  • Only approved medium of exchange entries can be linked to requests/offers
  • Exchange type validation ensures consistency ("base" vs "currency")
  • Author validation prevents unauthorized updates/deletes
  • Cross-zome validation ensures link consistency

User Experience

  • Exchange Type Classification: Clear distinction between foundational categories and specific currencies
  • Suggestion Workflow: Users can contribute new mediums for community approval
  • Permission-Based Access: Different capabilities for users vs administrators
  • Visual Categorization: Enhanced UI with base categories (📂) and currencies (💰)

hREA Integration

  • MediumOfExchange entries map to hREA ResourceSpecification (not EconomicResource). A ResourceSpecification defines the type of value exchange — it is a template referenced by the reciprocal Intent inside a Proposal. Concrete resource instances (EconomicResource) are never created from Mediums of Exchange directly; they arise only from Economic Events in a completed exchange lifecycle.
  • The resource_spec_hrea_id field stores the ID of the corresponding hREA ResourceSpecification, set during approval by the hREA store.

Exchange Type System

  • "base": Foundational exchange categories (Pay It Forward, LETS, Time Banking)
  • "currency": Specific monetary units (USD, EUR, Bitcoin, Local Currency)
  • User Restrictions: Non-admin users can only suggest "currency" types
  • Admin Capabilities: Full access to create both "base" and "currency" types

6. Security Considerations

Access Control Matrix

OperationUserAccepted UserAdministrator
Suggest Currency
Suggest Base Type
Create Direct
View Approved
View Pending
Approve/Reject
Update/Delete
Link to Requests/Offers

Validation Layers

  • Entry Validation: Field presence and format validation
  • Permission Validation: User role and acceptance status checks
  • Business Logic Validation: Exchange type restrictions and approval status verification
  • Link Validation: Only approved mediums can be linked to entities

This comprehensive documentation provides complete technical coverage of the Mediums of Exchange zome, from backend Rust implementation through validation rules and cross-zome integration patterns.

Service Types Zome (service_types_integrity, service_types_coordinator)

1. Overview

  • Purpose: Manages the lifecycle of ServiceType entries. These entries define the categories or types of services, skills, or resources that can be requested or offered within the application. This zome is crucial for classifying and discovering requests and offers.
  • Status: Full validation workflow, tag-based discovery, and cross-zome integration operational
  • Validation Workflow: Implements a moderation process for ServiceType entries, involving user suggestions, admin review, and approval/rejection states (pending, approved, rejected). Only approved service types can be actively used in new requests and offers.
  • Tag-Based Discovery: Comprehensive tagging system with path anchor indexing for efficient search, filtering, and cross-entity discovery
  • Cross-Zome Integration: Full integration with requests_coordinator and offers_coordinator for tag-based discovery
  • Zome Structure:
    • service_types_integrity: Handles data validation, entry definitions, and link type rules.
    • service_types_coordinator: Provides externally callable functions for business logic, interacting with the integrity zome and managing data flows.

hREA Mapping

In the hREA/ValueFlows ontology, ServiceType entries map to ResourceSpecification (not EconomicResource). A ResourceSpecification defines the type of service — it is a template referenced by Intents inside Proposals. Concrete resource instances (EconomicResource) are never created directly from Service Types; they only arise as the result of Economic Events in a completed exchange lifecycle.

2. Integrity Zome (service_types_integrity)

2.1. Entry Types

ServiceType

The ServiceType entry defines a specific type of service or skill with technical classification.

#![allow(unused)]
fn main() {
#[hdk_entry_helper]
#[derive(Clone, PartialEq)]
pub struct ServiceType {
    pub name: String,        // E.g., "Web Development", "Graphic Design", "Childcare"
    pub description: String, // A brief explanation of the service type (supports markdown, rendered on frontend with `marked` + `DOMPurify`)
    pub technical: bool,     // Technical vs non-technical classification
    // REMOVED: pub tags: Vec<String> - Tags functionality has been removed as per issue #49
    // pub category: Option<String>, // Optional: A broader category, e.g., "Technology", "Creative Arts"
                                  // Decision: Currently not implemented, technical field provides classification.
}
}

2.2. Validation Rules

  • ServiceType Entry:
    • name: Must not be empty. Max length (e.g., 100 chars) can be enforced.
    • description: Must not be empty. Max length (e.g., 500 chars) can be enforced.
    • technical: Boolean field for technical classification (no validation needed).
  • Updates/Deletes: Standard Holochain author validation (original author or agent with specific capabilities can update/delete).
  • ServiceTypeStatusAnchorToServiceType

    • Base: Path anchor (e.g., service_types.status.pending).
    • Target: ActionHash of a ServiceType entry.
    • Link Tag: ActionHash of the ServiceType (for easy retrieval/deduplication) or a Timestamp for ordering.
    • Purpose: Connects ServiceType entries to their current validation status (pending, approved, rejected).
  • TechnicalClassificationAnchorToServiceType (NEW)

    • Base: Path anchor (e.g., service_types.classification.technical or service_types.classification.non_technical).
    • Target: ActionHash of an approved ServiceType entry.
    • Link Tag: ActionHash of the ServiceType.
    • Purpose: Enables filtering approved service types by technical classification.
  • (Implicit) ServiceTypeUpdates

    • Holochain automatically links original entry actions to their updates.

REMOVED LINK TYPES :

  • TagAnchorToServiceType - Tags functionality completely removed
  • AllTagsAnchorToTagString - Tags functionality completely removed

(Note: RequestToServiceType and OfferToServiceType links are defined in their respective zomes but are crucial for understanding how ServiceTypes are consumed. These links should only target approved ServiceType entries.)

3. Coordinator Zome (service_types_coordinator)

3.1. Path Anchors Used

  • Status Anchors (Static Paths):
    • service_types.status.pending: For ServiceType entries awaiting admin review.
    • service_types.status.approved: For ServiceType entries approved by an admin and usable in requests/offers.
    • service_types.status.rejected: For ServiceType entries rejected by an admin.
  • Technical Classification Anchors (Static Paths - NEW):
    • service_types.classification.technical: For indexing technical service types.
    • service_types.classification.non_technical: For indexing non-technical service types.

REMOVED ANCHORS :

  • service_types.tags.{url_encoded_tag_string} - Tags functionality completely removed
  • service_types.all_tags - Tags functionality completely removed

3.2. Key Functions

Input/Output structs (e.g., SuggestServiceTypeInput) are defined in Rust for clarity.

User-Facing Functions

  • suggest_service_type(input: ServiceType) -> ExternResult<Record>
    • Description: Allows any authenticated user to suggest a new ServiceType.
    • Actions:
      1. Creates a new ServiceType entry with the provided name, description, and tags.
      2. Links the new ServiceType entry's ActionHash to the service_types.status.pending anchor.
    • Access Control: Any valid, authenticated agent.
    • Returns: The Record of the newly created ServiceType entry.

Admin-Facing Functions

(Admin role is determined by a separate mechanism, e.g., membership in an admin group or a capability grant.)

-   **`admin_create_service_type(input: ServiceType) -> ExternResult<Record>`**
-   **Description**: Allows an administrator to directly create and approve a `ServiceType`.
-   **Actions**:
    1.  Creates a new `ServiceType` entry.
    2.  Links the `ServiceType`'s `ActionHash` to the `service_types.status.approved` anchor.
    3.  Links the `ServiceType`'s `ActionHash` to the appropriate technical classification anchor:
        -   `service_types.classification.technical` if `input.technical` is true
        -   `service_types.classification.non_technical` if `input.technical` is false
-   **Access Control**: Admin only.
-   **Returns**: The `Record` of the newly created and approved `ServiceType` entry.
  • approve_service_type(service_type_ah: ActionHash) -> ExternResult<ActionHash>

    • Description: Allows an administrator to approve a ServiceType that is currently pending (or rejected).
    • Actions:
      1. Validates that service_type_ah points to an existing ServiceType entry.
      2. Removes any existing links from service_types.status.pending or service_types.status.rejected to service_type_ah.
      3. Creates a link from service_types.status.approved to service_type_ah.
      4. Fetches the ServiceType entry to get its technical classification.
      5. Links service_type_ah from the appropriate technical classification anchor:
        • service_types.classification.technical if technical is true
        • service_types.classification.non_technical if technical is false
    • Access Control: Admin only.
    • Returns: The ActionHash of the approved ServiceType.
  • reject_service_type(service_type_ah: ActionHash, reason: Option<String>) -> ExternResult<ActionHash>

    • Description: Allows an administrator to reject a ServiceType (whether pending or approved).
    • reason is optional and currently not stored directly on DHT, but could be logged or emitted as a signal.
    • Actions:
      1. Validates service_type_ah.
      2. Removes links from service_types.status.pending or service_types.status.approved to service_type_ah.
      3. Creates a link from service_types.status.rejected to service_type_ah.
      4. If the ServiceType was previously approved:
        • Fetches the ServiceType entry to get its technical classification.
        • Removes links from appropriate technical classification anchors to service_type_ah.
        • Crucially: Triggers cross-zome calls or signals to requests_coordinator and offers_coordinator to handle cleanup of links from existing Request and Offer entries that referenced this service_type_ah. (Requires granted capabilities).
    • Access Control: Admin only.
    • Returns: The ActionHash of the rejected ServiceType.
  • admin_update_service_type(original_action_hash: ActionHash, updated_input: ServiceType) -> ExternResult<Record>

    • Description: Allows an administrator to update an existing ServiceType.
    • Actions:
      1. Creates an update for the ServiceType entry pointed to by original_action_hash.
      2. The status of the ServiceType (e.g., approved) is generally maintained unless explicitly changed by a separate call to approve_service_type or reject_service_type.
      3. If the ServiceType is approved and technical classification has changed:
        • Removes links from the old technical classification anchor.
        • Adds links to the new technical classification anchor based on updated_input.technical.
    • Access Control: Admin only.
    • Returns: The Record of the updated ServiceType entry (new ActionHash, same EntryHash).
  • admin_delete_service_type(original_action_hash: ActionHash) -> ExternResult<ActionHash>

    • Description: Allows an administrator to delete a ServiceType entry.
    • Actions:
      1. Creates a delete action for the entry identified by original_action_hash.
      2. Removes all links from status anchors (pending, approved, rejected) to this ServiceType's ActionHash(es).
      3. If the ServiceType was approved:
        • Removes all links from technical classification anchors.
        • Triggers cross-zome calls/signals for Request/Offer link cleanup if it was approved.
    • Access Control: Admin only.
    • Returns: The ActionHash of the delete action.

Getter Functions

  • get_service_type(service_type_ah: ActionHash) -> ExternResult<Option<Record>>

    • Description: Retrieves a specific ServiceType entry by its ActionHash.
    • Access Control: Public.
    • Returns: Some(Record) if found, None otherwise.
  • get_pending_service_types() -> ExternResult<Vec<Record>>

    • Description: Retrieves all ServiceType entries currently linked to the service_types.status.pending anchor.

    • Access Control: Admin only.

    • Returns: A vector of Records.

    • get_approved_service_types() -> ExternResult<Vec<Record>>

    • Description: Retrieves all ServiceType entries currently linked to the service_types.status.approved anchor.

    • Access Control: Public (essential for UI selectors, general browsing).

    • Returns: A vector of Records.

    • get_rejected_service_types() -> ExternResult<Vec<Record>>

    • Description: Retrieves all ServiceType entries currently linked to the service_types.status.rejected anchor.

    • Access Control: Admin only.

    • Returns: A vector of Records.

    • get_all_service_types_admin() -> ExternResult<Vec<Record>>

    • Description: Retrieves all ServiceType entries regardless of status (combines pending, approved, rejected, or fetches all known entries).

    • Access Control: Admin only.

    • Returns: A vector of Records.

Technical Classification Functions

  • get_technical_service_types() -> ExternResult<Vec<Record>>

    • Description: Retrieves all approved ServiceType entries that are classified as technical.
    • Access Control: Public.
    • Returns: A vector of Records.
  • get_non_technical_service_types() -> ExternResult<Vec<Record>>

    • Description: Retrieves all approved ServiceType entries that are classified as non-technical.
    • Access Control: Public.
    • Returns: A vector of Records.
  • get_service_types_by_classification(technical: bool) -> ExternResult<Vec<Record>>

    • Description: Retrieves all approved ServiceType entries filtered by technical classification.
    • Access Control: Public.
    • Returns: A vector of Records.
  • get_classification_statistics() -> ExternResult<(u32, u32)>

    • Description: Returns count statistics for technical vs non-technical service types.
    • Access Control: Public.
    • Returns: A tuple containing (technical_count, non_technical_count).

REMOVED FUNCTIONS :

  • get_service_types_by_tag - Tags functionality completely removed
  • get_service_types_by_tags - Tags functionality completely removed
  • search_service_types_by_tag_prefix - Tags functionality completely removed
  • get_tag_statistics - Tags functionality completely removed
  • get_all_service_type_tags - Tags functionality completely removed

Cross-Entity Discovery Functions

  • get_requests_by_classification(technical: bool) -> ExternResult<Vec<Record>> (NEW)

    • Description: Discovers requests associated with service types that match the technical classification.
    • Implementation: Calls get_service_types_by_classification then finds all requests linked to those service types.
    • Access Control: Public.
    • Returns: A vector of Request Records.
  • get_offers_by_classification(technical: bool) -> ExternResult<Vec<Record>> (NEW)

    • Description: Discovers offers associated with service types that match the technical classification.
    • Implementation: Calls get_service_types_by_classification then finds all offers linked to those service types.
    • Access Control: Public.
    • Returns: A vector of Offer Records.

REMOVED FUNCTIONS :

  • get_requests_by_tag - Tags functionality completely removed
  • get_offers_by_tag - Tags functionality completely removed

3.3. Cross-Zome Interactions

  • Called by requests_coordinator and offers_coordinator:

    • To validate that ServiceType ActionHashes provided during Request/Offer creation/update correspond to approved ServiceTypes. This is typically done by calling get_service_type and then checking its linkage to the service_types.status.approved anchor, or by ensuring the ActionHash is part of the list returned by get_approved_service_types.
  • Calls to requests_coordinator and offers_coordinator:

    • During reject_service_type or admin_delete_service_type, cross-zome calls are made to clean up links from existing Request and Offer entries that referenced the affected ServiceType.
    • Tag-based discovery functions coordinate with both coordinators to provide cross-entity discovery.

4. Testing Status

Backend Tests (Sweettest)

  • ✅ Service type CRUD operations and status management
  • ✅ Technical classification filtering and indexing
  • ✅ Classification statistics and counting
  • ✅ Cross-entity discovery by technical classification
  • ✅ Admin workflow (suggest, approve, reject)
  • ✅ Permission validation and access control

Frontend Integration

  • Effect-TS Service Layer: Complete with all zome functions exposed
  • Reactive Svelte Store: Full state management with caching and event bus
  • UI Components: Complete table layout implementation (ServiceTypesTable.svelte)
  • Technical Classification: Filter and sort controls for technical vs non-technical
  • Admin Interface: Table-based management replacing card layout
  • Cross-Store Integration: Requests and offers stores enhanced with classification-based discovery
  • Test Coverage: All unit tests passing (part of 343 total test suite across 20 files)

5. Implementation Notes

Performance Considerations

  • Path anchor indexing provides efficient classification-based queries
  • Table layout improves data density and scanning ability
  • Caching implemented at service and store levels
  • Lazy loading and pagination patterns available for large datasets

Data Integrity

  • Only approved service types can be linked to requests/offers
  • Technical classification cleanup on service type deletion/rejection
  • Cross-zome validation ensures consistency

User Experience

  • Table Layout: Improved data density and administrative efficiency
  • Technical Classification: Clear filtering between technical and non-technical services
  • Responsive Design: Table works correctly on mobile devices
  • Intuitive Admin Workflow: Streamlined service type moderation interface
  • Performance: Maintained good performance with table display
  • Accessibility: Standards maintained in new table implementation

UI/UX Improvements

  • Card to Table Conversion: Enhanced administrative interface with better data scanning
  • Filter/Sort Controls: Technical classification filtering for better organization
  • Mobile Responsiveness: Table adapts correctly to different screen sizes
  • Action Integration: Edit, delete, and view actions seamlessly integrated into table

Administration Zome Specification

Overview

The Administration Zome manages system-wide administrative functions, including user verification, status management, and administrative access control. It consists of two parts:

  1. Integrity Zome: Defines entry and link types, validation rules
  2. Coordinator Zome: Implements business logic and external functions

Progenitor Pattern

The first agent to call create_user after installing the DNA is the network progenitor. Their public key is embedded in the DNA properties at install time via progenitor_pubkey. When the progenitor creates their user profile, they are automatically registered as the first network administrator — no explicit administrator registration call is required.

Key properties:

  • The progenitor pubkey is fixed at DNA install time (in workdir/happ.yaml and at test setup via rolesSettings).
  • Auto-registration happens inside create_user via a cross-zome call to add_administrator in the administration coordinator.
  • In production (progenitor key configured): ONLY the progenitor is auto-registered. Any user who creates a profile before the progenitor receives standard pending status — they are NOT made admin.
  • In dev mode (no progenitor_pubkey set): the first user to call create_user becomes admin automatically as a convenience bootstrap.
  • The progenitor still receives "pending" status like any other user after create_user.
  • The progenitor is revocable — they can be removed from the administrator list by any other administrator.

Progenitor Externs

One public function is available in the administration coordinator for checking progenitor status:

#![allow(unused)]
fn main() {
// Check if the current calling agent is the network progenitor
pub fn is_progenitor(_: ()) -> ExternResult<bool>
}

Note: check_if_agent_is_progenitor(agent: AgentPubKey) was removed in favour of is_progenitor(). Callers that previously checked another agent's progenitor status should use is_progenitor from the target agent's own conductor context.

Administrator Registration

The first administrator is registered automatically when the progenitor calls create_user (production mode). In dev mode (no progenitor key configured), the first user to call create_user is registered instead. Subsequent administrators are added explicitly via add_administrator (which requires an existing administrator or the progenitor to call it).

Technical Implementation

1. Entry Types

Status Entry

#![allow(unused)]
fn main() {
pub struct Status {
    pub status_type: String,
    pub reason: Option<String>,
    pub suspended_until: Option<String>,
}
}

Status types are defined through an enumeration:

#![allow(unused)]
fn main() {
pub enum StatusType {
    Pending,
    Accepted,
    Rejected,
    SuspendedIndefinitely,
    SuspendedTemporarily,
}
}
#![allow(unused)]
fn main() {
pub enum LinkTypes {
    AllAdministrators,     // Links administrators to entities
    AgentAdministrators,   // Links agents to administrator roles
    StatusUpdates,         // Links status updates
    AllStatuses,          // Global status index
    EntityStatus,         // Links entities to their status
    AcceptedEntity,       // Links accepted entities
}
}

3. Administrator Management

Core Functions

add_administrator
#![allow(unused)]
fn main() {
pub fn add_administrator(input: EntityActionHashAgents) -> ExternResult<bool>
}

The sole public extern for administrator registration. Callers must be either an existing administrator or the network progenitor. Internally calls the private register_administrator helper, which is idempotent (returns false without error if the entity is already an administrator).

  • Requires caller to be the progenitor or an existing administrator
  • Idempotent: safe to call when already an administrator
  • Returns true when a new admin link was created, false when already admin
remove_administrator
#![allow(unused)]
fn main() {
pub fn remove_administrator(input: EntityActionHashAgents) -> ExternResult<bool>
}
  • Verifies caller is administrator
  • Ensures at least one administrator remains
  • Removes administrator links
  • Returns success boolean

Query Functions

#![allow(unused)]
fn main() {
pub fn get_all_administrators_links(entity: String) -> ExternResult<Vec<Link>>
}
  • Retrieves all administrator links for entity
  • Returns vector of links
check_if_entity_is_administrator
#![allow(unused)]
fn main() {
pub fn check_if_entity_is_administrator(input: EntityActionHash) -> ExternResult<bool>
}
  • Verifies if entity is administrator
  • Checks administrator links
  • Returns boolean status
check_if_agent_is_administrator
#![allow(unused)]
fn main() {
pub fn check_if_agent_is_administrator(input: EntityAgent) -> ExternResult<bool>
}
  • Verifies if agent is administrator
  • Checks agent administrator links
  • Returns boolean status

4. Status Management

Core Functions

create_status
#![allow(unused)]
fn main() {
pub fn create_status(input: EntityActionHash) -> ExternResult<Record>
}
  • Creates pending status for entity
  • Verifies no existing status
  • Creates status links
  • Returns status record
update_entity_status
#![allow(unused)]
fn main() {
pub fn update_entity_status(input: UpdateEntityActionHash) -> ExternResult<Record>
}
  • Updates entity's status
  • Creates status update links
  • Handles accepted status links
  • Returns updated record

Status Query Functions

#![allow(unused)]
fn main() {
pub fn get_entity_status_link(input: EntityActionHash) -> ExternResult<Link>
}
  • Retrieves status link for entity
  • Returns link or error
get_latest_status_record
#![allow(unused)]
fn main() {
pub fn get_latest_status_record(original_action_hash: ActionHash) -> ExternResult<Option<Record>>
}
  • Gets most recent status record
  • Returns optional record
get_latest_status
#![allow(unused)]
fn main() {
pub fn get_latest_status(original_action_hash: ActionHash) -> ExternResult<Option<Status>>
}
  • Gets most recent status entry
  • Returns optional status
get_latest_status_record_for_entity
#![allow(unused)]
fn main() {
pub fn get_latest_status_record_for_entity(input: EntityActionHash) -> ExternResult<Option<Record>>
}
  • Gets entity's latest status record
  • Returns optional record
get_latest_status_for_entity
#![allow(unused)]
fn main() {
pub fn get_latest_status_for_entity(input: EntityActionHash) -> ExternResult<Option<Status>>
}
  • Gets entity's latest status entry
  • Returns optional status

Status Management Functions

suspend_entity_temporarily
#![allow(unused)]
fn main() {
pub fn suspend_entity_temporarily(input: SuspendEntityInput) -> ExternResult<bool>
}
  • Temporarily suspends entity
  • Sets suspension duration
  • Returns success boolean
suspend_entity_indefinitely
#![allow(unused)]
fn main() {
pub fn suspend_entity_indefinitely(input: SuspendEntityInput) -> ExternResult<bool>
}
  • Indefinitely suspends entity
  • Returns success boolean
unsuspend_entity_if_time_passed
#![allow(unused)]
fn main() {
pub fn unsuspend_entity_if_time_passed(input: UpdateInput) -> ExternResult<bool>
}
  • Checks suspension duration
  • Auto-unsuspends if time passed
  • Returns success boolean
unsuspend_entity
#![allow(unused)]
fn main() {
pub fn unsuspend_entity(input: UpdateInput) -> ExternResult<bool>
}
  • Manually unsuspends entity
  • Returns success boolean

Accepted Entity Management

#![allow(unused)]
fn main() {
pub fn create_accepted_entity_link(input: EntityActionHash) -> ExternResult<bool>
}
  • Creates link for accepted entity
  • Returns success boolean
#![allow(unused)]
fn main() {
pub fn delete_accepted_entity_link(input: EntityActionHash) -> ExternResult<bool>
}
  • Removes accepted entity link
  • Returns success boolean
get_accepted_entities
#![allow(unused)]
fn main() {
pub fn get_accepted_entities(entity: String) -> ExternResult<Vec<Link>>
}
  • Retrieves all accepted entities
  • Returns vector of links
check_if_entity_is_accepted
#![allow(unused)]
fn main() {
pub fn check_if_entity_is_accepted(input: EntityActionHash) -> ExternResult<bool>
}
  • Verifies if entity is accepted
  • Returns boolean status

5. Access Control

  • Administrator functions require administrator privileges
  • Status management restricted to administrators
  • Status queries available to all users
  • Entity acceptance management restricted to administrators

Integrity Validation

The integrity zome adds a validate callback that dispatches FlatOp variants for AllAdministrators and AgentAdministrators link types. Its primary guarantee is the progenitor bootstrap: only the network progenitor can write the very first admin links without any pre-existing authority.

Design note: HDI vs HDK

Integrity zomes use hdi::prelude::*. The HDI crate does not expose get_links, LinkQuery, or GetStrategy — only must_get_* variants (which require a known action hash). Dynamic "is this agent currently an administrator?" checks therefore cannot be performed inside integrity validation. That authorization is enforced by the coordinator layer (check_if_agent_is_administrator / check_if_entity_is_administrator) on every mutating call.

What the integrity zome validates

OperationIntegrity guarantee
Create AllAdministrators link by the progenitorCryptographically verified (deterministic DNA property comparison)
Create AllAdministrators link by any other agentDefault-allow; coordinator enforces admin-membership check
Delete AllAdministrators linkDefault-allow; coordinator enforces admin-membership check
Create AgentAdministrators link by the progenitorCryptographically verified
Create AgentAdministrators link by any other agentDefault-allow; coordinator enforces admin-membership check
Delete AgentAdministrators linkDefault-allow; coordinator enforces admin-membership check

Validation Helper

One private helper is used inside the validate callback:

#![allow(unused)]
fn main() {
// Deterministic: compares agent to the DNA progenitor_pubkey property
fn is_progenitor(agent: &AgentPubKey) -> ExternResult<bool>
}

The validate extern uses op.flattened::<EntryTypes, LinkTypes>() to dispatch each FlatOp variant to the appropriate validation function. All unrecognised ops return ValidateCallbackResult::Valid (default-allow pattern).

Status Entry Validation

The existing validate_status function continues to enforce that:

  • Status type must be one of the defined StatusType variants.
  • Suspended statuses must include a reason.
  • Temporarily suspended statuses must include a suspended_until timestamp.

Usage Examples

Administrator Management

#![allow(unused)]
fn main() {
// Register first administrator (progenitor auto-registration via create_user)
// or add a new administrator (requires caller to be admin or progenitor)
let input = EntityActionHashAgents {
    entity: "network".to_string(),
    entity_original_action_hash: hash,
    agent_pubkeys: vec![agent_key],
};
add_administrator(input)?; // idempotent — safe to call even if already admin
}

Status Management

#![allow(unused)]
fn main() {
// Create entity status
let status = create_status(entity_hash)?;

// Suspend temporarily
let suspend_input = SuspendEntityInput {
    entity,
    entity_original_action_hash: hash,
    status_original_action_hash: status_hash,
    status_previous_action_hash: prev_hash,
    reason: "Violation".to_string(),
    duration: Some(Duration::days(7)),
};
suspend_entity_temporarily(suspend_input)?;

// Check and unsuspend
unsuspend_entity_if_time_passed(update_input)?;
}

hREA Integration Specification

Overview

This document details the integration of hREA (Holochain Resource-Event-Agent) within the Requests & Offers application. hREA provides the foundation for managing economic flows, resource tracking, and agent relationships in our Holochain-based system.

Economic Flow Model

The application implements the following hREA economic flow with integrated feedback mechanisms:

Agent -> Proposal (Requests/Offers) -> Intent -> Agreement -> Commitment -> Economic Event -> Resource
                                                      ↓
                                              Feedback Process ← Agent

Note: Resource in the flow above refers to EconomicResource — a concrete instance produced or transferred as the result of an Economic Event. This is distinct from ResourceSpecification, which describes the type of resource. Service Types and Mediums of Exchange are ResourceSpecifications referenced by Intents — they are never EconomicResources.

Each step serves a specific purpose in the collaborative ecosystem:

  • Agent: Participants in the ecosystem (individuals or organizations)
  • Proposal: Encompasses both Requests (Intents for receiving) and Offers (Intents for providing)
  • Intent: The underlying purpose or goal of the proposal (service or medium of exchange)
  • Agreement: Mutual acceptance and alignment between parties
  • Commitment: Confirmed obligation to fulfill the agreed terms
  • Economic Event: Actual record of fulfillment or action taken (conditional on positive feedback)
  • Resource: The tangible or intangible outcome affected by the event
  • Feedback Process: Critical validation mechanism that conditionally enables fulfillment

For MVP simplicity, we merge Requests/Offers with Proposals and Intents in the user experience while maintaining the underlying hREA structural complexity.

Feedback-Driven Economic Flow

Feedback Process Rules

Based on the exchange process clarifications and hREA mapping diagram, the feedback mechanism operates as follows:

  1. Feedback Initiation Rights:

    • The agent that initiates a request has the right to provide feedback
    • The agent that accepts an offer has the right to provide feedback
    • The agent performing work (on request) or providing service (from offer) can request feedback
  2. Feedback-Conditional Fulfillment:

    • Economic events fulfill commitments conditionally based on positive feedback
    • Fulfillment implementation can be delayed to optimize the feedback process
    • This creates a quality assurance layer in the economic flow
  3. Feedback Process States:

    • Pending Feedback: Work completed, awaiting feedback
    • Feedback Requested: Worker/provider has requested feedback from recipient
    • Positive Feedback: Enables commitment fulfillment and economic event creation
    • Negative Feedback: Triggers resolution process before fulfillment

hREA Mapping Implementation

Based on the provided hREA mapping diagram, the flow operates as:

Alice (Agent) --make--> Proposal (Request/Offer) --represent--> Resource Specifications
                                    ↓ bundle
Bob (Agent) --make--> Agreement --bundle--> Commitments --fulfills--> Economic Event
                         ↑                                                ↓
                    Feedback ←--ask for--← Bob                    Commitment Completion
                         ↓
              if feedback is positive → fulfills commitments

This ensures that:

  • Alice creates requests that represent resource specifications (Service Types)
  • Bob can make offers and later request feedback from Alice
  • Agreements bundle commitments from both parties
  • Economic Events are created only when feedback is positive
  • Feedback validation acts as a quality gate for commitment fulfillment

Economic Flow Visualization

graph TD
    %% Agents and Initial Actions
    A[Agent Alice] -- make --> R[Request Proposal]
    B[Agent Bob] -- make --> O[Offer Proposal]

    %% Resource Specifications
    R -- represent --> RS[Resource Specification<br/>Service Types]
    O -- represent --> RS

    %% Agreement Formation
    R -- bundle --> AG[Agreement]
    O -- bundle --> AG
    AG -- bundle --> C1[Commitment from Bob]
    AG -- bundle --> C2[Commitment to Alice]

    %% Work Completion and Feedback Request
    C1 -- Work Performed --> WC[Work Completion]
    B -- ask for --> FP[Feedback Process]

    %% Feedback Validation
    A -- provides --> FB[Feedback]
    FB -- if positive --> EE[Economic Event]
    FB -- if negative --> RP[Resolution Process]

    %% Fulfillment
    EE -- fulfills --> C1
    EE -- fulfills --> C2
    EE -- affects --> RES[Resource]

    %% Alternative Resolution
    RP -- may lead to --> EE

    class A,B agent
    class R,O proposal
    class AG,C1,C2 agreement
    class FP,FB feedback
    class EE event

Core Components Integration

1. Agent Ecosystem

Agent Types

  • Individual Agents: Users with specific skills and capabilities
  • Organizational Agents: Collectives with collective resources and needs
  • Project Agents: Specialized organizations with specific goals

Agent Feedback Responsibilities

  • Request Initiators: Provide feedback on received services
  • Offer Acceptors: Provide feedback on delivered outcomes
  • Service Providers: Can request feedback to enable fulfillment
  • Quality Assurance: Participate in resolution processes for negative feedback

2. Enhanced Proposal System

Dual Nature of Proposals

Proposals in our system serve a dual purpose:

  • Requests: Map to hREA proposals bundling intents expressing the need to receive resources or services
  • Offers: Map to hREA proposals bundling intents expressing the willingness to provide resources or services

Resource Specification Integration

Both Requests and Offers reference hREA ResourceSpecifications:

  • Service Types: Standardized categories of services and skills (our Service Types system)
  • Medium of Exchange: Methods of value transfer (time, money, barter, etc.)
  • Quality Metrics: Standards for evaluating service delivery

Important: Service Types and Mediums of Exchange are hREA ResourceSpecifications only — they define categories and types, not concrete resource instances. EconomicResource entries are not created for Service Types or Mediums of Exchange; those arise only from Economic Events in a completed exchange.

3. Agreement and Commitment Workflow

Agreement Formation

  • Mutual acceptance between requesting and offering agents
  • Bundling of complementary commitments
  • Definition of feedback criteria and success metrics
  • Establishment of quality assurance framework

Commitment Management with Feedback Integration

  • Commitment Creation: Formalized obligations with feedback requirements
  • Progress Tracking: Monitor commitment fulfillment stages
  • Feedback Triggers: Automatic prompts for feedback at completion milestones
  • Conditional Fulfillment: Economic events created only after positive feedback

4. Feedback-Enhanced Economic Events

Economic Event Creation Process

  1. Work Completion: Service provider completes committed work
  2. Feedback Request: Provider can request feedback from recipient
  3. Feedback Evaluation: Recipient provides positive/negative feedback
  4. Conditional Event: Economic event created only with positive feedback
  5. Resource Impact: Resources affected based on successful completion

Quality Assurance Integration

  • Feedback Validation: Ensure feedback quality and authenticity
  • Dispute Resolution: Handle negative feedback through mediation processes
  • Reputation Tracking: Build agent reputation based on feedback history
  • Continuous Improvement: Use feedback data for system optimization

5. Resource and Value Flow Management

Resource Specifications as Service Types

  • Map traditional "skills" to hREA ResourceSpecifications
  • Enable standardized service categorization
  • Support skill matching and discovery
  • Facilitate quality benchmarking

Value Exchange Mechanisms

  • Direct Service Exchange: Skills for skills
  • Mediated Exchange: Services for tokens/credits
  • Hybrid Models: Combination of direct and mediated exchange
  • Reputation-Based: Quality feedback influences exchange rates

Technical Implementation Strategies

Feedback System Architecture

Feedback Data Structures

interface FeedbackProcess {
  id: string;
  commitmentId: string;
  requesterId: AgentId; // Who can request feedback
  providerId: AgentId; // Who provides feedback
  status: "pending" | "requested" | "completed";
  feedback?: FeedbackEntry;
}

interface FeedbackEntry {
  rating: "positive" | "negative";
  comments: string;
  timestamp: Date;
  providedBy: AgentId;
}

Conditional Economic Events

  • Economic events include feedback validation
  • Delayed fulfillment patterns for quality assurance
  • Automated triggers based on feedback outcomes
  • Integration with existing hREA event structures

Matching and Discovery

Enhanced Matching Algorithms

  • Quality-Weighted Matching: Consider agent feedback history
  • Service Type Alignment: Match based on ResourceSpecification compatibility
  • Feedback-Informed Recommendations: Prioritize high-rated providers
  • Risk Assessment: Factor feedback patterns into matching decisions

Search and Filtering

  • Feedback-Enhanced Search: Include quality metrics in search results
  • Reputation Filtering: Filter by agent feedback scores
  • Service Quality Indicators: Display historical performance data
  • Trust Network Navigation: Leverage feedback networks for discovery

Performance and Scalability

Feedback Data Optimization

  • Efficient Feedback Storage: Optimized data structures for feedback queries
  • Aggregated Reputation Scores: Pre-computed quality metrics
  • Feedback Indexing: Fast retrieval of feedback history
  • Privacy-Preserving Analytics: Aggregate insights while protecting individual privacy

Privacy Architecture

Exchange-related hREA entities are accessible only to the agents named as participants in those entities. This is a core architectural constraint, not a UI-layer concern.

Scope of Private Exchange Data

The following entities are private to exchange participants only:

  • Agreement — accessible only to the two agents named in the agreement
  • Commitment — accessible only to the commitment parties
  • EconomicEvent — accessible only to the agents who created and received it
  • Fulfillment links — traversable only by exchange participants
  • Conversations and counter-proposals (see conversations zome in requests_and_offers DNA)

Agent-Centric Enforcement

Holochain's agent-centric model provides the underlying enforcement:

  • Entries are stored in participants' source chains, not in a shared public DHT
  • get_* zome calls are validated against the calling agent's identity
  • Capability tokens on exchange zome functions restrict access to named participants
  • No admin-level capability token grants access to exchange, commitment, or economic event data

Administrator Exclusion

Administrator agents are explicitly excluded from exchange data access:

  • The admin zome in requests_and_offers DNA handles platform-level operations only: user approval/suspension, service type and organization management
  • Admin capability tokens do NOT grant access to Agreement, Commitment, EconomicEvent, or Fulfillment queries
  • This separation is intentional — peer-to-peer exchange contracts are private to their parties

For the full privacy model including conversation privacy and the ConversationToAgreement link architecture, see Exchange Process — Privacy Model.

Future Evolution

Planned Enhancements

  1. Machine Learning Integration: AI-powered feedback analysis and quality prediction
  2. Advanced Reputation Systems: Multi-dimensional trust scoring
  3. Automated Quality Assurance: Smart contracts for feedback validation
  4. Cross-Platform Feedback: Integration with external reputation systems

Integration Roadmap

  1. Enhanced Feedback Analytics: Detailed insights into service quality trends
  2. Predictive Quality Scoring: AI-driven quality predictions
  3. Automated Dispute Resolution: Smart mediation for negative feedback
  4. Ecosystem Health Monitoring: System-wide quality and satisfaction metrics

MVP Implementation Priorities

For the initial release, focus on:

  1. Basic Feedback Workflows: Simple positive/negative feedback with comments
  2. Conditional Fulfillment: Economic events triggered by positive feedback
  3. Quality Indicators: Basic reputation scoring and display
  4. Feedback Request System: Allow service providers to request feedback
  5. Dispute Handling: Manual process for negative feedback resolution

This feedback-driven approach ensures quality while maintaining the decentralized nature of the hREA economic model, creating a self-regulating ecosystem where quality service delivery is incentivized and validated through peer feedback.

hREA Mapping Diagram

Getting Started with Requests & Offers

Welcome to the Requests & Offers project! This guide will help you get started with our Holochain-based platform for facilitating exchanges within the hAppenings.community.

What Makes This Project Unique

This project implements a sophisticated 7-layer Effect-TS architecture that provides:

  • Type-safe async operations with Effect-TS
  • Reactive state management with Svelte 5 Runes
  • Centralized error handling with domain-specific errors
  • Composable business logic abstraction
  • Comprehensive testing across all layers

Prerequisites

Before you begin, ensure you have:

  • Holochain Development Environment installed
  • Basic understanding of Holochain concepts
  • Bun 1.0.0 or later
  • Recommended: Familiarity with TypeScript and functional programming concepts

Quick Start

  1. Clone the repository:

    git clone https://github.com/Happening-Community/requests-and-offers.git
    cd requests-and-offers
    
  2. Enter the nix shell:

    nix develop
    
  3. Install dependencies:

    bun install
    
  4. Start the development environment:

    bun start
    
  5. Open your browser to see multiple agent instances running

Understanding the Architecture

7-Layer Effect-TS Pattern

Our codebase follows a standardized pattern across all domains:

  1. Service Layer: Effect-native services with Context.Tag dependency injection
  2. Store Layer: Factory functions with Svelte 5 Runes + 9 standardized helper functions
  3. Schema Validation: Effect Schema with strategic validation boundaries
  4. Error Handling: Domain-specific tagged errors with centralized management
  5. Composables: Component logic abstraction using Effect-based functions
  6. Components: Svelte 5 + accessibility focus, using composables for business logic
  7. Testing: Comprehensive Effect-TS coverage across all layers

Example: Service Types Domain (Fully Implemented)

The Service Types domain serves as the architectural template. Here's how the layers work together:

// 1. Service Layer (Effect-native with dependency injection)
export const ServiceTypeService =
  Context.GenericTag<ServiceTypeService>("ServiceTypeService");

// 2. Store Layer (Factory function with Svelte 5 Runes)
export const createServiceTypesStore = () => {
  let entities = $state<UIServiceType[]>([]);

  const fetchEntities = Effect.gen(function* () {
    const records = yield* serviceTypeService.getAllServiceTypes();
    entities = mapRecordsToUIEntities(records); // Helper function #2
  });

  return { entities: () => entities, fetchEntities };
};

// 3. Composable (Business logic abstraction)
export function useServiceTypesManagement() {
  const store = createServiceTypesStore();
  const errorBoundary = useErrorBoundary({
    context: SERVICE_TYPE_CONTEXTS.FETCH_SERVICE_TYPES,
  });

  return { ...store, errorBoundary };
}

// 4. Component (Using composable for business logic)
// ServiceTypesGrid.svelte uses the composable

The 9 Standardized Store Helper Functions

Each domain store implements these helpers for consistency:

  1. Entity Creation Helper: createUIEntity - Converts Holochain records to UI entities
  2. Record Mapping Helper: mapRecordsToUIEntities - Maps arrays with null safety
  3. Cache Sync Helper: createCacheSyncHelper - Synchronizes cache with state arrays
  4. Event Emission Helpers: createEventEmitters - Standardized event broadcasting
  5. Data Fetching Helper: createEntityFetcher - Higher-order fetching with loading state
  6. Loading State Helper: withLoadingState - Consistent loading/error patterns
  7. Record Creation Helper: createRecordCreationHelper - Processes new records
  8. Status Transition Helper: createStatusTransitionHelper - Atomic status updates
  9. Collection Processor: processMultipleRecordCollections - Complex response handling

Project Structure Deep Dive

requests-and-offers/
├── dnas/requests_and_offers/
│   ├── zomes/
│   │   ├── coordinator/     # Business logic zomes
│   │   │   ├── service_types/    # ✅ Complete domain template
│   │   │   ├── requests/         # ✅ Complete domain
│   │   │   ├── offers/           # ✅ Complete domain
│   │   │   └── ...
│   │   └── integrity/       # Data validation zomes
├── ui/                      # SvelteKit frontend
│   ├── src/lib/
│   │   ├── services/        # Service layer (Effect-TS services)
│   │   ├── stores/          # Store layer (Svelte 5 + Effect-TS)
│   │   ├── composables/     # Business logic abstraction
│   │   ├── components/      # UI components (by feature)
│   │   ├── schemas/         # Effect Schema validation
│   │   ├── errors/          # Domain-specific error handling
│   │   └── utils/           # Shared utilities
│   └── src/routes/          # SvelteKit pages
├── tests/sweettest/         # Sweettest integration tests (Rust)
└── documentation/           # Comprehensive project docs

Core Technologies in Practice

Effect-TS Integration

  • Service Layer: All async operations use Effect for composability
  • Error Handling: Domain-specific tagged errors with Effect's error model
  • State Management: Effect services integrate with Svelte stores
  • Testing: Effect's testing utilities for reliable async tests

Svelte 5 Runes + Effect-TS

// Store pattern combining Svelte 5 Runes with Effect-TS
export const createServiceTypesStore = () => {
  let entities = $state<UIServiceType[]>([]);
  let isLoading = $state(false);

  const loadEntities = Effect.gen(function* () {
    isLoading = true;
    const result = yield* serviceTypeService.getAllServiceTypes();
    entities = mapRecordsToUIEntities(result);
    isLoading = false;
  });

  return {
    entities: () => entities,
    isLoading: () => isLoading,
    loadEntities,
  };
};

hREA Integration

The application integrates with hREA (Holochain Resource-Event-Agent) framework:

  • Requests map to hREA Intents
  • Offers map to hREA Proposals
  • Service Types map to ResourceSpecifications
  • Users/Organizations map to Agents

Your First Development Task

Verification: Explore a Complete Domain

  1. Examine Service Types domain (fully implemented):

    # Look at the service layer
    code ui/src/lib/services/zomes/serviceTypes.service.ts
    
    # Check the store implementation
    code ui/src/lib/stores/serviceTypes.store.svelte.ts
    
    # See the composable pattern
    code ui/src/lib/composables/domain/service-types/useServiceTypesManagement.svelte.ts
    
    # View the components
    code ui/src/lib/components/service-types/
    
  2. Run domain-specific tests:

    # Backend tests
    bun test:service-types
    
    # Frontend tests
    cd ui && bun test:unit -- service-types
    
  3. See it in action:

    • Start the app: bun start
    • Navigate to Service Types section
    • Try creating, editing, and managing service types

Next: Learn the Patterns

  1. Effect-TS Primer: Read our Effect-TS guide for project-specific patterns
  2. Development Workflow: Follow development workflow for implementing features
  3. Architectural Patterns: Understand our established patterns

Implementation Status

  • Service Types Domain: ✅ Fully completed (100%) - serves as template
  • Requests Domain: ✅ Fully completed (100%) - patterns applied
  • Offers Domain: ✅ Fully completed (100%) - all 9 helpers implemented
  • Other Domains: Effect-based, queued for standardization

Next Steps

  1. Deep Dive

  2. Development

  3. Understanding the System

Need Help?

What Sets This Apart

This isn't just a typical SvelteKit + Holochain app. You're working with:

  • Functional programming patterns with Effect-TS
  • Type-safe async operations throughout
  • Standardized architecture across all domains
  • Comprehensive error handling with recovery strategies
  • Advanced state management combining reactive and functional paradigms
  • Production-ready patterns proven across multiple domains

Ready to dive deeper? Continue with our Effect-TS Primer to understand the core patterns that power this architecture.

Installation Guide

This guide provides detailed instructions for installing and setting up the Requests & Offers application.

System Requirements

  • Linux, macOS, or Windows with WSL2
  • Holochain Development Environment
  • Bun 1.0.0 or later

Installation Steps

1. Development Environment Setup

Install Holochain

Follow the official Holochain installation guide for your operating system.

You can quickly install Holochain using this command:

bash <(curl https://holochain.github.io/holochain/setup.sh)

This will set up the complete Holochain development environment, including Nix and all necessary components.

2. Project Setup

Clone the Repository

git clone https://github.com/Happening-Community/requests-and-offers.git
cd requests-and-offers

Enter Nix Shell

nix develop

Install Dependencies

bun install

This will also download the hREA suite as part of the postinstall script.

Environment Configuration

The project uses environment variables to control development features. Three environment files are configured:

  • .env.development - Full development features enabled
  • .env.test - Limited dev features for alpha testing
  • .env.production - All dev features disabled

Key Environment Variables:

VITE_APP_ENV=development|test|production       # Core environment setting
VITE_DEV_FEATURES_ENABLED=true|false          # Master dev features toggle
VITE_MOCK_BUTTONS_ENABLED=true|false          # Form mock buttons

Development Features Include:

  • Mock data buttons in forms for rapid testing
  • Development utilities and debug tools
  • Enhanced error reporting and logging
  • Component boundary visualization (future)

Tree-Shaking: In production mode, all development code is completely removed from the final build through Vite's build-time optimization, ensuring zero overhead.

3. Development Setup

The application consists of two main parts:

  1. Frontend (SvelteKit application with 7-layer Effect-TS architecture)
  2. Backend (Holochain DNA with multiple zomes)

Build and Verify Setup

Before starting the development environment, build the components to verify everything works:

# Build Holochain zomes (requires Nix environment)
bun build:zomes

Verification: This should complete without errors and create compiled zomes in the target directory.

# Build complete hApp
bun build:happ

Verification: Should create workdir/requests_and_offers.happ file.

Start Development Environment

The project includes three deployment modes for different development scenarios:

🧑‍💻 Development Mode (Full dev features + mock buttons):

# Start with default configuration (2 agents)
bun start

# Start with custom number of agents
AGENTS=3 bun start

# Start with Tauri (desktop application)
bun start:tauri

🧪 Test Mode (Alpha testing without mock buttons):

# Start in test mode - simulates production environment for testing
bun start:test

# Custom agents in test mode
AGENTS=3 bun start:test

🚀 Production Mode (Clean production build):

# Start in production mode - all dev features tree-shaken out
bun start:prod

# Custom agents in production mode
AGENTS=3 bun start:prod

Choosing the Right Mode:

  • Development: Use for feature development, debugging, and learning (includes mock data buttons)
  • Test: Use for alpha testing and production simulation (realistic environment without dev tools)
  • Production: Use for actual deployment and performance testing (optimized builds)

This will:

  1. Clean the Holochain sandbox
  2. Build the hApp
  3. Start the UI server
  4. Launch the Holochain environment
  5. Start the Holochain Playground

Verify Development Environment

After running bun start, you should see:

  1. Terminal Output: Multiple URLs displayed:

    • UI servers for each agent (e.g., http://localhost:5173, http://localhost:5174)
    • Bootstrap server URL
    • Signal server URL
    • Holochain Playground URL
  2. Browser Windows: Automatically opened browser windows for each agent

  3. Successful Connection: Each UI should show the main interface without connection errors

If you see errors:

  • Check the Troubleshooting section below
  • Ensure Nix environment is properly activated: nix develop
  • Verify all dependencies are installed: bun install

4. Testing & Verification

Verify Installation with Tests

Run tests to ensure everything is working correctly:

# Run all tests (comprehensive verification)
bun test

This runs:

  • Zome builds and compilation
  • Backend Sweettest tests
  • Frontend unit and integration tests
  • Status module tests

Expected Result: All tests should pass. If tests fail, check dependencies and environment.

Component Tests for Verification

# Frontend tests only (verify UI layer)
bun test:ui

# Individual zome tests (verify specific domains)
bun test:service-types  # Service types functionality (complete reference implementation)
bun test:users          # Users Organizations zome
bun test:organizations  # Organizations functionality
bun test:requests       # Requests functionality
bun test:offers         # Offers functionality
bun test:administration # Administration zome
bun test:status        # Status module

Advanced Testing (Optional)

# Frontend unit tests (requires Nix for hREA integration)
nix develop --command bun test:unit

# Integration tests
cd ui && bun test:integration

# E2E tests with Holochain
cd ui && bun test:e2e:holochain

4.5. First Development Task

Validate Your Setup: Complete this task to confirm your environment is ready for development.

Task: Explore Service Types Domain

The Service Types domain is 100% complete and serves as the architectural template. Use it to verify your understanding:

  1. Examine the Implementation:

    # Look at the service layer (Effect-TS with dependency injection)
    cat ui/src/lib/services/zomes/serviceTypes.service.ts | head -50
    
    # Check the store implementation (Svelte 5 Runes + Effect-TS)
    cat ui/src/lib/stores/serviceTypes.store.svelte.ts | head -50
    
    # See the composable pattern (business logic abstraction)
    cat ui/src/lib/composables/domain/service-types/useServiceTypesManagement.svelte.ts
    
    # View component organization
    ls -la ui/src/lib/components/service-types/
    
  2. Run Domain-Specific Tests:

    # Backend tests (Sweettest multi-agent)
    bun test:service-types
    
    # Frontend tests (Effect-TS integration)
    cd ui && bun test:unit -- service-types
    
  3. See It In Action:

    • Navigate to Service Types section in the running app
    • Try creating a new service type (use mock button in development mode)
    • Edit an existing service type
    • Notice the error handling and loading states
    • Observe the Effect-TS patterns in developer tools
    • Development Mode: Notice the "Create Mock Data" button in forms
    • Test Mode: Run bun start:test and note the absence of mock buttons

Success Criteria:

  • All commands run without errors
  • Service type CRUD operations work in the UI
  • Tests pass for service-types domain
  • You can identify the 7-layer architecture in the code
  • Mock data buttons appear in development mode (bun start)
  • Mock data buttons are hidden in test mode (bun start:test)
  • Development features are completely absent in production mode (bun start:prod)

Next Steps After Setup

Once your installation is verified:

  1. Learn the Architecture: Read our Getting Started Guide for architecture overview
  2. Understand Patterns: Study Effect-TS Primer for project-specific patterns
  3. Practice Implementation: Follow Development Workflow for feature development
  4. Join Community: Connect on Discord for support

5. Building

Development Builds

# Build Holochain zomes
bun build:zomes

# Build complete hApp (includes zome builds)
bun build:happ

Production Package

# Create production package (includes hApp and UI)
bun package

6. hREA Integration

The project integrates with hREA (Holochain Resource-Event-Agent). The hREA suite is automatically downloaded during installation, but you can manage it with:

# Re-download hREA suite
bun run download-hrea-suite

# Remove hREA suite
bun run clean:hrea-suite

Development Resources

Troubleshooting

Common Setup Issues

1. Nix Environment Problems

Symptoms: command not found: holochain or build failures

Solutions:

# Ensure Nix is properly installed
nix --version

# Enter development environment
nix develop

# If still issues, try rebuilding the environment
nix develop --rebuild

2. Port Conflicts

Symptoms: "Port already in use" errors

Solution: The application automatically finds available ports for:

  • UI servers (starts from 5173)
  • Bootstrap server
  • Signal server

If you still have conflicts, close other development servers or restart your terminal.

3. Build Issues

Symptoms: Compilation errors or missing files

Solutions:

# Clean and rebuild zomes
bun run build:zomes

# If zome build fails, check Nix environment
nix develop --command bun run build:zomes

# Clean and rebuild hApp
bun run build:happ

# Clean Holochain sandbox if needed
rm -rf .hc*

4. Dependencies Issues

Symptoms: Module not found or version conflicts

Solutions:

# Reinstall dependencies
rm -rf node_modules bun.lockb
bun install

# Reinstall UI dependencies
cd ui
rm -rf node_modules bun.lockb
bun install
cd ..

5. hREA Integration Issues

Symptoms: hREA-related test failures or missing DNA files

Solutions:

# Reinstall hREA suite
bun run clean:hrea-suite
bun run download-hrea-suite

# Verify hREA installation
ls -la workdir/hrea.dna

6. Effect-TS Runtime Issues

Symptoms: Runtime errors in Effect operations or service injection failures

Solutions:

  • Check that all services are properly provided in layers
  • Verify Context.Tag usage in service definitions
  • Ensure proper error handling in Effect operations
  • Review Effect-TS patterns in our Effect-TS Primer

7. Frontend Issues

Symptoms: UI not loading, component errors, or state management issues

Solutions:

# Check TypeScript compilation
cd ui && bun run check

# Run linting
cd ui && bun run lint

# Clear browser cache and restart
# Check browser console for specific errors

8. Test Failures

Symptoms: Tests failing during verification

Detailed Solutions:

# If backend tests fail
bun test:service-types  # Test specific domain
nix develop --command cargo test --manifest-path tests/sweettest/Cargo.toml  # Run Sweettest directly

# If frontend tests fail
cd ui && bun test:unit -- --reporter=verbose

# If hREA integration tests fail
nix develop --command bun test:unit

Environment Verification Checklist

If you're having persistent issues, verify your environment:

# Check all required tools
nix --version                    # Should show Nix version
bun --version                    # Should show Bun 1.0.0+
node --version                   # Should show Node 18+

# Check Nix environment
nix develop --command which holochain  # Should show holochain path
nix develop --command cargo --version  # Should show Rust toolchain

# Check project structure
ls -la workdir/                  # Should contain DNA/hApp files
ls -la ui/node_modules/          # Should contain dependencies

Performance Issues

Slow Builds

# Enable parallel builds
export CARGO_BUILD_JOBS=4

# Use release mode for faster zome builds
bun run build:zomes -- --release

High Memory Usage

# Limit concurrent operations
export NODE_OPTIONS="--max-old-space-size=4096"

# Run with fewer agents for testing
AGENTS=1 bun start

Getting Help

When reporting issues, please include:

  1. Error Output: Full error messages and stack traces
  2. Environment Info: OS, Nix version, Bun version
  3. Steps to Reproduce: What commands led to the issue
  4. Context: What were you trying to accomplish

Support Channels:

Quick Recovery Commands

If you need to completely reset your environment:

# Nuclear option - clean everything
rm -rf .hc* node_modules ui/node_modules workdir/*.dna workdir/*.happ
bun install
bun build:zomes
bun build:happ
bun start

Warning: This will remove all local data and require rebuilding everything.

Production Network Setup

By default, workdir/happ.yaml ships with progenitor_pubkey: ~ (null), which means the first agent to create a profile becomes the network administrator — convenient for development but unsuitable for production.

For a production deployment you must set the progenitor's agent public key before building the hApp:

Using Kangaroo (recommended): The Kangaroo Electron app handles this automatically. It reads the creator's agent public key from the Holochain conductor admin WebSocket and injects it into DNA properties before installing the hApp. No manual configuration is needed.

Manual configuration:

  1. Obtain the agent public key from the Holochain conductor admin API (base64-encoded AgentPubKey).

  2. Set it in workdir/happ.yaml:

properties:
  progenitor_pubkey: uhCAkXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  1. Rebuild the hApp:
bun build:happ

The agent whose key is set will be automatically registered as the first network administrator when they call create_user. All other agents receive standard Pending status regardless of join order.

For a full explanation of the progenitor pattern see The Progenitor Pattern.

Documentation

Development Workflow Guide

This guide provides practical patterns for implementing features in the Requests & Offers project, following our established 7-layer Effect-TS architecture.

Constitution Compliance

All development activities must comply with the Requests and Offers Constitution. This workflow implements the 7 Core Principles:

  • I. Evidence-Based Development: All patterns reference established implementations
  • II. 7-Layer Effect-TS Architecture: This workflow enforces the standardized architecture
  • III. Type Safety First: Effect-TS patterns with schema validation boundaries
  • IV. Comprehensive Testing: TDD approach with Sweettest and Vitest testing
  • V. Peer-to-Peer First: Holochain DHT functionality prioritized throughout
  • VI. Standardized Patterns: Service Types domain template replication
  • VII. Community-Driven Design: Simplified MVP approach for hAppenings.community

Overview

Our development workflow follows a standardized pattern across all domains, ensuring consistency and maintainability. Every feature implementation follows the same 7-layer structure with established patterns.

Development Cycle

1. Domain Analysis & Planning

Before implementing any feature, understand the domain structure:

# Explore existing domains for patterns
ls ui/src/lib/services/zomes/     # Service layer implementations
ls ui/src/lib/stores/             # Store layer implementations
ls ui/src/lib/composables/domain/ # Composable implementations
ls ui/src/lib/components/         # Component implementations

Use Service Types as Template: The service-types domain is 100% complete and serves as the architectural template for all new implementations.

2. Layer Implementation Order

Always implement layers in this order to maintain dependencies:

  1. Zome Layer (Backend) → 2. Service Layer → 3. Store Layer → 4. Composable Layer → 5. Component Layer → 6. Error Handling → 7. Testing

3. Backend Development (Zomes)

Coordinator Zome Implementation

# Create new zome structure
cd dnas/requests_and_offers/zomes/coordinator/
mkdir my_domain && cd my_domain

Pattern: Follow the coordinator/integrity separation:

#![allow(unused)]
fn main() {
// coordinator/my_domain/src/lib.rs
use hdk::prelude::*;
use my_domain_integrity::*;

#[hdk_extern]
pub fn create_my_entity(input: CreateMyEntityInput) -> ExternResult<Record> {
    // Business logic implementation
}

#[hdk_extern]
pub fn get_my_entities() -> ExternResult<Vec<Record>> {
    // Query implementation
}
}

Testing Pattern

# Create and run zome tests
bun test:my-domain

Test all CRUD operations and business logic before moving to frontend.

4. Service Layer Implementation

Create Effect-native services with dependency injection:

// ui/src/lib/services/zomes/myDomain.service.ts
import { Context, Effect, pipe } from "effect";
import { HolochainClientService } from "../HolochainClientService.svelte";

export interface MyDomainService {
  readonly createMyEntity: (
    input: CreateMyEntityInput,
  ) => Effect.Effect<UIMyEntity, MyDomainError>;
  readonly getAllMyEntities: () => Effect.Effect<UIMyEntity[], MyDomainError>;
  readonly updateMyEntity: (
    hash: ActionHash,
    input: UpdateMyEntityInput,
  ) => Effect.Effect<UIMyEntity, MyDomainError>;
  readonly deleteMyEntity: (
    hash: ActionHash,
  ) => Effect.Effect<void, MyDomainError>;
}

export const MyDomainService =
  Context.GenericTag<MyDomainService>("MyDomainService");

export const makeMyDomainService = Effect.gen(function* () {
  const client = yield* HolochainClientService;

  const createMyEntity = (input: CreateMyEntityInput) =>
    Effect.gen(function* () {
      const record = yield* client.callZome({
        zome_name: "my_domain",
        fn_name: "create_my_entity",
        payload: input,
      });
      return createUIMyEntity(record);
    }).pipe(
      Effect.mapError((error) =>
        MyDomainError.fromError(error, MY_DOMAIN_CONTEXTS.CREATE_MY_ENTITY),
      ),
      Effect.withSpan("MyDomainService.createMyEntity"),
    );

  return { createMyEntity, getAllMyEntities, updateMyEntity, deleteMyEntity };
});

Key Patterns:

  • Use Effect.gen for complex operations with dependencies
  • Use .pipe for error handling and tracing
  • Apply consistent error transformation with domain contexts
  • Include telemetry with Effect.withSpan

5. Store Layer Implementation

Create factory functions combining Svelte 5 Runes with Effect-TS:

// ui/src/lib/stores/myDomain.store.svelte.ts
import { Effect, Context } from "effect";
import { MyDomainService } from "$lib/services";

export const createMyDomainStore = () => {
  // Reactive state with Svelte 5 Runes
  let entities = $state<UIMyEntity[]>([]);
  let isLoading = $state(false);
  let error = $state<string | null>(null);

  // Cache management
  const cache = createModuleCache<ActionHash, UIMyEntity>(
    "myDomain",
    5 * 60 * 1000,
  );

  // 1. Entity Creation Helper
  const createUIEntity = (record: Record): UIMyEntity | null => {
    try {
      const decoded = decode(record.entry);
      return {
        hash: record.signed_action.hashed.hash,
        ...decoded,
        createdAt: new Date(
          record.signed_action.hashed.content.timestamp / 1000,
        ),
      };
    } catch (error) {
      console.error("Failed to create UI entity:", error);
      return null;
    }
  };

  // 2. Record Mapping Helper
  const mapRecordsToUIEntities = (records: Record[]): UIMyEntity[] => {
    return records
      .map(createUIEntity)
      .filter((entity): entity is UIMyEntity => entity !== null);
  };

  // 3. Cache Sync Helper
  const syncCacheWithEntities = () => {
    entities.forEach((entity) => cache.set(entity.hash, entity));
  };

  // 4. Event Emission Helpers
  const eventEmitters = createEventEmitters<UIMyEntity>("myDomain");

  // 5. Data Fetching Helper
  const fetchEntities = Effect.gen(function* () {
    const myDomainService = yield* MyDomainService;
    isLoading = true;
    error = null;

    const result = yield* myDomainService.getAllMyEntities();
    entities = mapRecordsToUIEntities(result);
    syncCacheWithEntities();

    isLoading = false;
    return entities;
  }).pipe(
    Effect.catchAll((err) =>
      Effect.sync(() => {
        error = err.message;
        isLoading = false;
        return [];
      }),
    ),
  );

  // 6. Loading State Helper
  const withLoadingState = <T>(operation: Effect.Effect<T, any>) =>
    Effect.gen(function* () {
      isLoading = true;
      error = null;
      const result = yield* operation;
      isLoading = false;
      return result;
    }).pipe(
      Effect.catchAll((err) =>
        Effect.sync(() => {
          error = err.message;
          isLoading = false;
          throw err;
        }),
      ),
    );

  // 7. Record Creation Helper
  const handleNewRecord = (newEntity: UIMyEntity) => {
    entities = [...entities, newEntity];
    cache.set(newEntity.hash, newEntity);
    eventEmitters.entityCreated(newEntity);
  };

  // 8. Status Transition Helper (if applicable)
  const updateEntityStatus = (hash: ActionHash, newStatus: EntityStatus) => {
    const index = entities.findIndex((e) => e.hash === hash);
    if (index !== -1) {
      entities[index] = { ...entities[index], status: newStatus };
      cache.set(hash, entities[index]);
      eventEmitters.entityUpdated(entities[index]);
    }
  };

  // 9. Collection Processor
  const processMultipleCollections = (response: ComplexResponse) => {
    if (response.myEntities) {
      entities = mapRecordsToUIEntities(response.myEntities);
    }
    // Handle related collections...
  };

  return {
    // Reactive state
    entities: () => entities,
    isLoading: () => isLoading,
    error: () => error,

    // Actions
    fetchEntities,
    createEntity: (input: CreateMyEntityInput) =>
      withLoadingState(
        Effect.gen(function* () {
          const myDomainService = yield* MyDomainService;
          const newEntity = yield* myDomainService.createMyEntity(input);
          handleNewRecord(newEntity);
          return newEntity;
        }),
      ),

    // Helpers (exposed for composables)
    createUIEntity,
    mapRecordsToUIEntities,
    eventEmitters,
  };
};

Implementation Notes:

  • Always implement all 9 standardized helper functions
  • Use Svelte 5 Runes for reactive state
  • Integrate Effect-TS for async operations
  • Include proper error handling and loading states
  • Implement cache management with TTL

6. Composable Layer Implementation

Create composables that abstract business logic from components:

// ui/src/lib/composables/domain/my-domain/useMyDomainManagement.svelte.ts
import { Effect } from "effect";
import { useErrorBoundary } from "$lib/composables";
import { createMyDomainStore } from "$lib/stores";
import { MY_DOMAIN_CONTEXTS } from "$lib/errors";

export function useMyDomainManagement() {
  // Create domain store
  const store = createMyDomainStore();

  // Error boundaries for different operations
  const loadingErrorBoundary = useErrorBoundary({
    context: MY_DOMAIN_CONTEXTS.FETCH_MY_ENTITIES,
    enableLogging: true,
    enableFallback: true,
    maxRetries: 2,
  });

  const createErrorBoundary = useErrorBoundary({
    context: MY_DOMAIN_CONTEXTS.CREATE_MY_ENTITY,
    enableLogging: true,
    maxRetries: 1,
  });

  // Enhanced operations with error handling
  const loadEntities = async () => {
    await loadingErrorBoundary.execute(store.fetchEntities, []);
  };

  const createEntity = async (input: CreateMyEntityInput) => {
    await createErrorBoundary.execute(store.createEntity(input));
  };

  // Reactive state that components can use
  let state = $state({
    entities: store.entities,
    isLoading: store.isLoading,
    error: store.error,
    loadingError: () => loadingErrorBoundary.state.error,
    createError: () => createErrorBoundary.state.error,
  });

  return {
    // State
    state,

    // Actions
    loadEntities,
    createEntity,

    // Error boundaries
    loadingErrorBoundary,
    createErrorBoundary,

    // Store methods (if needed directly)
    store,
  };
}

7. Component Layer Implementation

Create Svelte components that use composables for business logic:

<!-- ui/src/lib/components/my-domain/MyDomainGrid.svelte -->
<script>
  import { useMyDomainManagement } from '$lib/composables';
  import ErrorDisplay from '$lib/components/shared/ErrorDisplay.svelte';

  const {
    state,
    loadEntities,
    createEntity,
    loadingErrorBoundary,
    createErrorBoundary
  } = useMyDomainManagement();

  // Load entities on mount
  $effect(() => {
    loadEntities();
  });
</script>

<!-- Error Displays -->
{#if state.loadingError()}
  <ErrorDisplay
    error={state.loadingError()}
    context="Loading entities"
    variant="inline"
    showRetry={true}
    onretry={() => loadEntities()}
    ondismiss={() => loadingErrorBoundary.clearError()}
  />
{/if}

<!-- Loading State -->
{#if state.isLoading()}
  <div>Loading entities...</div>
{/if}

<!-- Entity Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  {#each state.entities() as entity (entity.hash)}
    <MyDomainCard {entity} />
  {/each}
</div>

8. Error Handling Implementation

Create domain-specific error handling:

// ui/src/lib/errors/my-domain.errors.ts
import { Data } from "effect";

export class MyDomainError extends Data.TaggedError("MyDomainError")<{
  readonly message: string;
  readonly cause?: unknown;
  readonly context?: string;
  readonly entityId?: string;
  readonly operation?: string;
}> {
  static fromError(
    error: unknown,
    context: string,
    entityId?: string,
    operation?: string,
  ): MyDomainError {
    const message = error instanceof Error ? error.message : String(error);
    return new MyDomainError({
      message,
      cause: error,
      context,
      entityId,
      operation,
    });
  }
}

// Add to error-contexts.ts
export const MY_DOMAIN_CONTEXTS = {
  CREATE_MY_ENTITY: "Failed to create entity",
  GET_MY_ENTITY: "Failed to get entity",
  UPDATE_MY_ENTITY: "Failed to update entity",
  DELETE_MY_ENTITY: "Failed to delete entity",
  FETCH_MY_ENTITIES: "Failed to fetch entities",
} as const;

9. Testing Implementation

Create comprehensive tests for each layer:

# Backend tests (Sweettest, requires Nix)
nix develop --command cargo test --manifest-path tests/sweettest/Cargo.toml

# Frontend unit tests
cd ui && bun test:unit -- my-domain

# Integration tests
cd ui && bun test:integration -- my-domain

Test Structure:

// ui/tests/unit/stores/myDomain.store.test.ts
import { describe, it, expect } from "vitest";
import { createMyDomainStore } from "$lib/stores";

describe("MyDomain Store", () => {
  it("should create UI entities correctly", () => {
    const store = createMyDomainStore();
    const mockRecord = createMockRecord();
    const entity = store.createUIEntity(mockRecord);

    expect(entity).toBeDefined();
    expect(entity.hash).toBe(mockRecord.signed_action.hashed.hash);
  });

  it("should handle all 9 helper functions", () => {
    const store = createMyDomainStore();

    // Test each helper function
    expect(typeof store.createUIEntity).toBe("function");
    expect(typeof store.mapRecordsToUIEntities).toBe("function");
    // ... test all 9 helpers
  });
});

Best Practices

Effect-TS Patterns

When to Use Effect.gen vs .pipe

// Use Effect.gen for:
// 1. Injecting dependencies
const serviceOperation = Effect.gen(function* () {
  const service = yield* MyDomainService;
  const result = yield* service.getAllEntities();
  return result;
});

// 2. Sequential operations with error handling
const complexOperation = Effect.gen(function* () {
  const step1 = yield* operation1();
  const step2 = yield* operation2(step1);
  return yield* operation3(step2);
});

// Use .pipe for:
// 1. Error handling
const withErrorHandling = operation.pipe(
  Effect.mapError((error) => MyDomainError.fromError(error, context)),
  Effect.catchAll(fallbackOperation),
);

// 2. Transformations
const transformed = operation.pipe(
  Effect.map((result) => transformResult(result)),
  Effect.withSpan("operation-name"),
);

Store Patterns

Always Implement the 9 Helper Functions

  1. createUIEntity: Convert Holochain Record to UI entity
  2. mapRecordsToUIEntities: Map array of Records with null safety
  3. createCacheSyncHelper: Sync cache with state arrays
  4. createEventEmitters: Standardized event broadcasting
  5. createEntityFetcher: Higher-order fetching with loading state
  6. withLoadingState: Wrap operations with loading/error patterns
  7. createRecordCreationHelper: Process new records and update cache
  8. createStatusTransitionHelper: Handle status changes atomically
  9. processMultipleRecordCollections: Handle complex responses

Component Patterns

Use Composables for Business Logic

<script>
  // ✅ Good - Use composable for business logic
  const { state, actions, errorBoundaries } = useMyDomainManagement();

  // ❌ Avoid - Direct store usage in components
  // const store = createMyDomainStore();
</script>

Consistent Error Display

<!-- Always include error displays for user feedback -->
{#if errorBoundary.state.error}
  <ErrorDisplay
    error={errorBoundary.state.error}
    context="Operation description"
    showRetry={true}
    onretry={() => retryOperation()}
    ondismiss={() => errorBoundary.clearError()}
  />
{/if}

Common Patterns

Domain Implementation Checklist

  • Zome implemented with coordinator/integrity pattern
  • Service layer with Effect-TS and dependency injection
  • Store layer with all 9 helper functions
  • Composable layer abstracting business logic
  • Component layer using composables
  • Error handling with domain-specific errors and contexts
  • Tests covering all layers (backend + frontend)
  • Documentation updated with new domain

Performance Considerations

  • Cache Management: Implement TTL-based caching for frequently accessed data
  • Event Bus: Use event emitters for cross-domain communication
  • Loading States: Always provide loading feedback for async operations
  • Error Recovery: Implement retry strategies and fallback mechanisms

Maintenance Guidelines

  • Follow Service Types: Use as reference implementation for all patterns
  • Consistent Naming: Follow established naming conventions across domains
  • Documentation: Update guides when adding new patterns
  • Testing: Maintain test coverage for all new functionality

Next Steps

  1. Study Complete Examples: Examine service-types, requests, and offers domains
  2. Practice Implementation: Try implementing a simple domain following this workflow
  3. Read Architecture Guides: Architectural Patterns and Effect-TS Primer
  4. Join Community: Connect with other developers on Discord

This workflow ensures consistency, maintainability, and scalability across all feature implementations in the project.

Architectural Patterns Guide

This guide documents the established architectural patterns used throughout the Requests & Offers project. These patterns ensure consistency, maintainability, and scalability across all domains.

Overview

Our architecture follows proven patterns that have been refined through implementing multiple domains. Every pattern documented here is used in production code and has been validated through the complete Service Types, Requests, and Offers domains.

Core Architectural Principles

1. 7-Layer Architecture Pattern

Each domain follows the same 7-layer structure:

7. Testing Layer     ← Comprehensive coverage across all layers
6. Component Layer   ← Svelte 5 components using composables
5. Composable Layer  ← Business logic abstraction
4. Error Layer       ← Domain-specific error handling
3. Schema Layer      ← Effect Schema validation
2. Store Layer       ← Svelte 5 Runes + Effect-TS integration
1. Service Layer     ← Effect-native services with dependency injection

Key Benefits:

  • Consistency: Same structure across all domains
  • Testability: Each layer can be tested in isolation
  • Maintainability: Clear separation of concerns
  • Scalability: Easy to add new domains following the pattern

2. Dependency Flow Pattern

Dependencies always flow downward through the layers:

Components → Composables → Stores → Services → Holochain
     ↓           ↓          ↓         ↓          ↓
   UI Logic → Business → State → Network → Backend
            Logic     Mgmt    Calls

Rules:

  • Components never directly access stores or services
  • Composables orchestrate store and service interactions
  • Stores manage reactive state and coordinate service calls
  • Services handle all Holochain communication
  • Each layer only depends on layers below it

The 9 Standardized Store Helper Functions

Every domain store implements these 9 helper functions for consistency and functionality:

1. Entity Creation Helper

const createUIEntity = (record: Record): UIEntity | null => {
  try {
    const decoded = decode(record.entry);
    return {
      hash: record.signed_action.hashed.hash,
      ...decoded,
      createdAt: new Date(record.signed_action.hashed.content.timestamp / 1000),
    };
  } catch (error) {
    console.error("Failed to create UI entity:", error);
    return null;
  }
};

Purpose: Converts Holochain Records to UI entities with error recovery Usage: Primary conversion function for all incoming data

2. Record Mapping Helper

const mapRecordsToUIEntities = (records: Record[]): UIEntity[] => {
  return records
    .map(createUIEntity)
    .filter((entity): entity is UIEntity => entity !== null);
};

Purpose: Maps arrays of Records to UI entities with null safety Usage: Used in all list operations and bulk data processing

3. Cache Sync Helper

const createCacheSyncHelper = () => {
  const syncCacheWithEntities = () => {
    entities.forEach((entity) => cache.set(entity.hash, entity));
  };

  const syncEntityWithCache = (entity: UIEntity) => {
    cache.set(entity.hash, entity);
    const index = entities.findIndex((e) => e.hash === entity.hash);
    if (index !== -1) {
      entities[index] = entity;
    } else {
      entities = [...entities, entity];
    }
  };

  return { syncCacheWithEntities, syncEntityWithCache };
};

Purpose: Synchronizes cache with state arrays for CRUD operations Usage: Maintains consistency between reactive state and cached data

4. Event Emission Helpers

const createEventEmitters = <T>(domain: string) => {
  const entityCreated = (entity: T) => {
    eventBus.emit(`${domain}:entity:created`, entity);
  };

  const entityUpdated = (entity: T) => {
    eventBus.emit(`${domain}:entity:updated`, entity);
  };

  const entityDeleted = (hash: ActionHash) => {
    eventBus.emit(`${domain}:entity:deleted`, hash);
  };

  const entitiesLoaded = (entities: T[]) => {
    eventBus.emit(`${domain}:entities:loaded`, entities);
  };

  return { entityCreated, entityUpdated, entityDeleted, entitiesLoaded };
};

Purpose: Standardized event broadcasting for domain operations Usage: Cross-domain communication and UI synchronization

5. Data Fetching Helper

const createEntityFetcher = <T, E>(
  fetchOperation: Effect.Effect<T[], E>,
  processingFn: (records: any[]) => T[],
) => {
  const fetchWithState = Effect.gen(function* () {
    isLoading = true;
    error = null;

    const result = yield* fetchOperation;
    const processed = processingFn(result);
    entities = processed;

    isLoading = false;
    return processed;
  }).pipe(
    Effect.catchAll((err) =>
      Effect.sync(() => {
        error = err.message;
        isLoading = false;
        return [];
      }),
    ),
  );

  return fetchWithState;
};

Purpose: Higher-order fetching function with loading/error state management Usage: All data loading operations use this pattern

6. Loading State Helper

const withLoadingState = <T, E>(operation: Effect.Effect<T, E>) =>
  Effect.gen(function* () {
    isLoading = true;
    error = null;

    const result = yield* operation;

    isLoading = false;
    return result;
  }).pipe(
    Effect.catchAll((err) =>
      Effect.sync(() => {
        error = err.message;
        isLoading = false;
        throw err;
      }),
    ),
  );

Purpose: Wraps operations with consistent loading/error patterns Usage: Applied to all async operations that affect UI state

7. Record Creation Helper

const createRecordCreationHelper = () => {
  const handleNewRecord = (newEntity: UIEntity) => {
    entities = [...entities, newEntity];
    cache.set(newEntity.hash, newEntity);
    eventEmitters.entityCreated(newEntity);
  };

  const handleUpdatedRecord = (updatedEntity: UIEntity) => {
    const index = entities.findIndex((e) => e.hash === updatedEntity.hash);
    if (index !== -1) {
      entities[index] = updatedEntity;
      cache.set(updatedEntity.hash, updatedEntity);
      eventEmitters.entityUpdated(updatedEntity);
    }
  };

  return { handleNewRecord, handleUpdatedRecord };
};

Purpose: Processes newly created records and updates cache/state Usage: All create and update operations use these helpers

8. Status Transition Helper

const createStatusTransitionHelper = () => {
  const updateEntityStatus = (hash: ActionHash, newStatus: EntityStatus) => {
    const index = entities.findIndex((e) => e.hash === hash);
    if (index !== -1) {
      const updatedEntity = { ...entities[index], status: newStatus };
      entities[index] = updatedEntity;
      cache.set(hash, updatedEntity);
      eventEmitters.entityUpdated(updatedEntity);
    }
  };

  const batchUpdateStatus = (
    updates: { hash: ActionHash; status: EntityStatus }[],
  ) => {
    const updatedEntities = entities.map((entity) => {
      const update = updates.find((u) => u.hash === entity.hash);
      return update ? { ...entity, status: update.status } : entity;
    });

    entities = updatedEntities;
    updatedEntities.forEach((entity) => cache.set(entity.hash, entity));
    eventEmitters.entitiesLoaded(updatedEntities);
  };

  return { updateEntityStatus, batchUpdateStatus };
};

Purpose: Manages status changes with atomic updates Usage: Status workflows and bulk status operations

9. Collection Processor

const processMultipleRecordCollections = (response: ComplexResponse) => {
  const processCollections = (collections: Record<string, Record[]>) => {
    const processed: Record<string, UIEntity[]> = {};

    for (const [key, records] of Object.entries(collections)) {
      processed[key] = mapRecordsToUIEntities(records);
    }

    return processed;
  };

  const mergeCollections = (
    primary: UIEntity[],
    related: Record<string, UIEntity[]>
  ) => {
    // Merge related entities into primary entities
    return primary.map(entity => ({
      ...entity,
      ...Object.keys(related).reduce((acc, key) => ({
        ...acc,
        [key]: related[key].filter(relatedEntity =>
          /* relationship logic based on domain */
        )
      }), {})
    }));
  };

  return { processCollections, mergeCollections };
};

Purpose: Handles complex responses with multiple collections Usage: Complex queries returning multiple related entity types

Cache Management Patterns

Module-Level Cache Pattern

// Cache configuration
const cache = createModuleCache<ActionHash, UIEntity>(
  "domainName", // Cache namespace
  5 * 60 * 1000, // TTL: 5 minutes
);

// Cache strategies
const getCachedEntity = (hash: ActionHash): UIEntity | null => {
  return cache.get(hash) || null;
};

const setCachedEntity = (entity: UIEntity): void => {
  cache.set(entity.hash, entity);
};

const invalidateCache = (hash?: ActionHash): void => {
  if (hash) {
    cache.delete(hash);
  } else {
    cache.clear();
  }
};

Cache Integration with Reactive State

// Cache-first loading pattern
const loadEntity = (hash: ActionHash) =>
  Effect.gen(function* () {
    // Check cache first
    const cached = getCachedEntity(hash);
    if (cached) {
      return cached;
    }

    // Fetch from service
    const service = yield* DomainService;
    const entity = yield* service.getEntity(hash);

    // Update cache and state
    setCachedEntity(entity);
    return entity;
  });

Error Boundary Patterns

Composable Error Boundaries

export function useDomainManagement() {
  // Separate error boundaries for different operation types
  const loadingErrorBoundary = useErrorBoundary({
    context: DOMAIN_CONTEXTS.FETCH_ENTITIES,
    enableLogging: true,
    enableFallback: true,
    maxRetries: 2,
    retryDelay: 1000,
  });

  const mutationErrorBoundary = useErrorBoundary({
    context: DOMAIN_CONTEXTS.CREATE_ENTITY,
    enableLogging: true,
    maxRetries: 1,
    retryDelay: 500,
  });

  const criticalErrorBoundary = useErrorBoundary({
    context: DOMAIN_CONTEXTS.DELETE_ENTITY,
    enableLogging: true,
    maxRetries: 0, // No auto-retry for destructive operations
    enableToast: true,
  });

  return {
    loadingErrorBoundary,
    mutationErrorBoundary,
    criticalErrorBoundary,
  };
}

Error Context Patterns

// Standardized error contexts per domain
export const DOMAIN_CONTEXTS = {
  // CRUD operations
  CREATE_ENTITY: "Failed to create entity",
  GET_ENTITY: "Failed to get entity",
  UPDATE_ENTITY: "Failed to update entity",
  DELETE_ENTITY: "Failed to delete entity",

  // List operations
  FETCH_ENTITIES: "Failed to fetch entities",
  SEARCH_ENTITIES: "Failed to search entities",
  FILTER_ENTITIES: "Failed to filter entities",

  // Specialized operations
  APPROVE_ENTITY: "Failed to approve entity",
  REJECT_ENTITY: "Failed to reject entity",
  PUBLISH_ENTITY: "Failed to publish entity",
} as const;

Event Bus Patterns

Domain Event System

// Event type definitions
type DomainEvents = {
  "service-types:entity:created": UIServiceType;
  "service-types:entity:updated": UIServiceType;
  "service-types:entity:deleted": ActionHash;
  "service-types:entities:loaded": UIServiceType[];
  "service-types:status:changed": {
    hash: ActionHash;
    status: ServiceTypeStatus;
  };
};

// Event emission in stores
const emitEntityCreated = (entity: UIServiceType) => {
  eventBus.emit("service-types:entity:created", entity);
};

// Event listening in composables
const setupEventListeners = () => {
  eventBus.on("service-types:entity:created", (entity) => {
    // Handle cross-domain updates
  });

  eventBus.on("service-types:status:changed", ({ hash, status }) => {
    // Update related entities
  });
};

Cross-Domain Communication

// Example: Requests listening to Service Type changes
export function useRequestsManagement() {
  const store = createRequestsStore();

  // Listen for service type updates that affect requests
  eventBus.on("service-types:entity:updated", (serviceType) => {
    // Update requests that reference this service type
    store.updateServiceTypeReferences(serviceType);
  });

  eventBus.on("service-types:entity:deleted", (serviceTypeHash) => {
    // Handle deletion of referenced service type
    store.handleServiceTypeDeletion(serviceTypeHash);
  });

  return store;
}

Composable Abstraction Patterns

Business Logic Separation

export function useDomainManagement() {
  const store = createDomainStore();
  const { loadingErrorBoundary, mutationErrorBoundary } = useErrorBoundaries();

  // Reactive state (read-only for components)
  let state = $state({
    entities: store.entities,
    isLoading: store.isLoading,
    error: store.error,

    // Computed derived state
    approvedEntities: () =>
      store.entities().filter((e) => e.status === "approved"),
    pendingEntities: () =>
      store.entities().filter((e) => e.status === "pending"),

    // Error states from boundaries
    loadingError: () => loadingErrorBoundary.state.error,
    mutationError: () => mutationErrorBoundary.state.error,
  });

  // Business operations (with error handling)
  const operations = {
    async loadEntities() {
      await loadingErrorBoundary.execute(store.fetchEntities, []);
    },

    async createEntity(input: CreateEntityInput) {
      await mutationErrorBoundary.execute(store.createEntity(input));
    },

    async updateEntityStatus(hash: ActionHash, status: EntityStatus) {
      await mutationErrorBoundary.execute(
        store.updateEntityStatus(hash, status),
      );
    },
  };

  // Lifecycle management
  onMount(() => {
    operations.loadEntities();
  });

  return {
    state,
    operations,

    // Expose error boundaries for component error handling
    loadingErrorBoundary,
    mutationErrorBoundary,
  };
}

Composable Composition Pattern

// Specialized composables that compose domain management
export function useEntitySelection() {
  const { state } = useDomainManagement();

  let selectedEntities = $state<Set<ActionHash>>(new Set());

  const selection = {
    selectedEntities: () => selectedEntities,
    isSelected: (hash: ActionHash) => selectedEntities.has(hash),
    toggleSelection: (hash: ActionHash) => {
      if (selectedEntities.has(hash)) {
        selectedEntities.delete(hash);
      } else {
        selectedEntities.add(hash);
      }
      selectedEntities = new Set(selectedEntities);
    },
    selectAll: () => {
      selectedEntities = new Set(state.entities().map((e) => e.hash));
    },
    clearSelection: () => {
      selectedEntities = new Set();
    },
  };

  return { selection };
}

Component Integration Patterns

Component-Composable Integration

<!-- DomainManagementPage.svelte -->
<script>
  import { useDomainManagement, useEntitySelection } from '$lib/composables';
  import ErrorDisplay from '$lib/components/shared/ErrorDisplay.svelte';
  import EntityCard from './EntityCard.svelte';

  const { state, operations, loadingErrorBoundary, mutationErrorBoundary } = useDomainManagement();
  const { selection } = useEntitySelection();
</script>

<!-- Error displays for different operations -->
{#if state.loadingError()}
  <ErrorDisplay
    error={state.loadingError()}
    context="Loading entities"
    variant="inline"
    showRetry={true}
    onretry={() => operations.loadEntities()}
    ondismiss={() => loadingErrorBoundary.clearError()}
  />
{/if}

{#if state.mutationError()}
  <ErrorDisplay
    error={state.mutationError()}
    context="Entity operation"
    variant="banner"
    ondismiss={() => mutationErrorBoundary.clearError()}
  />
{/if}

<!-- Entity grid with selection -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  {#each state.entities() as entity (entity.hash)}
    <EntityCard
      {entity}
      isSelected={selection.isSelected(entity.hash)}
      onToggleSelection={() => selection.toggleSelection(entity.hash)}
      onUpdate={(input) => operations.updateEntity(entity.hash, input)}
    />
  {/each}
</div>

Service Layer Patterns

Effect-TS Service Pattern

export const makeDomainService = Effect.gen(function* () {
  const client = yield* HolochainClientService;

  // CRUD operations with consistent error handling
  const createEntity = (input: CreateEntityInput) =>
    Effect.gen(function* () {
      const record = yield* client.callZome({
        zome_name: "domain",
        fn_name: "create_entity",
        payload: input,
      });

      return createUIEntity(record);
    }).pipe(
      Effect.mapError((error) =>
        DomainError.fromError(error, DOMAIN_CONTEXTS.CREATE_ENTITY),
      ),
      Effect.withSpan("DomainService.createEntity"),
    );

  const getAllEntities = () =>
    Effect.gen(function* () {
      const records = yield* client.callZome({
        zome_name: "domain",
        fn_name: "get_all_entities",
        payload: null,
      });

      return mapRecordsToUIEntities(records);
    }).pipe(
      Effect.mapError((error) =>
        DomainError.fromError(error, DOMAIN_CONTEXTS.FETCH_ENTITIES),
      ),
      Effect.withSpan("DomainService.getAllEntities"),
    );

  // Specialized operations
  const approveEntity = (hash: ActionHash) =>
    Effect.gen(function* () {
      const record = yield* client.callZome({
        zome_name: "domain",
        fn_name: "approve_entity",
        payload: hash,
      });

      return createUIEntity(record);
    }).pipe(
      Effect.mapError((error) =>
        DomainError.fromError(error, DOMAIN_CONTEXTS.APPROVE_ENTITY, hash),
      ),
      Effect.withSpan("DomainService.approveEntity"),
    );

  return {
    createEntity,
    getAllEntities,
    updateEntity,
    deleteEntity,
    approveEntity,
  };
});

Testing Patterns

Layer-Specific Testing

// Service layer testing
describe("DomainService", () => {
  it("should create entity with proper error handling", async () => {
    const MockHolochainClient = Layer.succeed(HolochainClientService, {
      callZome: () => Effect.succeed(mockRecord),
    });

    const TestDomainServiceLive = Layer.provide(
      DomainServiceLive,
      MockHolochainClient,
    );

    const result = await Effect.runPromise(
      Effect.gen(function* () {
        const service = yield* DomainService;
        return yield* service.createEntity(mockInput);
      }).pipe(Effect.provide(TestDomainServiceLive)),
    );

    expect(result.name).toBe(mockInput.name);
  });
});

// Store layer testing
describe("DomainStore", () => {
  it("should implement all 9 helper functions", () => {
    const store = createDomainStore();

    expect(typeof store.createUIEntity).toBe("function");
    expect(typeof store.mapRecordsToUIEntities).toBe("function");
    // ... test all 9 helpers
  });

  it("should handle entity creation with cache sync", () => {
    const store = createDomainStore();
    const entity = store.createUIEntity(mockRecord);

    expect(entity).toBeDefined();
    expect(store.getCachedEntity(entity.hash)).toEqual(entity);
  });
});

Performance Patterns

Optimization Strategies

// Lazy loading pattern
const lazyLoadEntity = (hash: ActionHash) =>
  Effect.lazy(() =>
    Effect.gen(function* () {
      const cached = getCachedEntity(hash);
      if (cached) return cached;

      const service = yield* DomainService;
      return yield* service.getEntity(hash);
    }),
  );

// Batch operations pattern
const batchCreateEntities = (inputs: CreateEntityInput[]) =>
  Effect.gen(function* () {
    const service = yield* DomainService;

    // Process in batches to avoid overwhelming the network
    const batches = chunk(inputs, 10);
    const results = [];

    for (const batch of batches) {
      const batchResults = yield* Effect.all(
        batch.map((input) => service.createEntity(input)),
        { concurrency: 5 },
      );
      results.push(...batchResults);
    }

    return results;
  });

Best Practices Summary

Do's ✅

  • Follow the 9 Helper Functions: Implement all helpers in every store
  • Use Error Boundaries: Separate boundaries for different operation types
  • Implement Caching: Use module-level caching with TTL
  • Event Communication: Use event bus for cross-domain communication
  • Layer Separation: Maintain clear dependency flow between layers
  • Effect-TS Patterns: Use Effect.gen for dependencies, .pipe for transformations
  • Comprehensive Testing: Test each layer independently

Don'ts ❌

  • Skip Helper Functions: Never implement only partial helper functions
  • Direct Store Access: Components should never directly access stores
  • Mixed Error Contexts: Don't reuse error contexts across domains
  • Cache Inconsistency: Always sync cache with reactive state changes
  • Layer Violations: Never skip layers or create circular dependencies
  • Manual Error Handling: Always use error boundaries and tagged errors
  • State Mutation: Never mutate state outside of designated helper functions

Migration Guide

When implementing a new domain or updating an existing one:

  1. Start with Service Layer: Implement Effect-TS service with proper dependency injection
  2. Create Store with All 9 Helpers: Don't skip any helper functions
  3. Add Error Handling: Implement domain-specific errors and contexts
  4. Build Composable: Abstract business logic from components
  5. Create Components: Use composables, never direct store access
  6. Add Comprehensive Tests: Test each layer independently
  7. Document Patterns: Update this guide if you establish new patterns

This architectural patterns guide ensures consistency and quality across all domains in the project. Follow these patterns to maintain the high standards established in the Service Types, Requests, and Offers domains.

Adopting the 7-Layer Effect-TS Architecture in Holochain Projects

A comprehensive guide for implementing the proven 7-layer Effect-TS architecture in new or existing Holochain projects.

📋 Overview

This guide explains how to adopt the sophisticated 7-layer Effect-TS architecture from the Requests & Offers project in your own Holochain applications. This architecture has been proven to provide exceptional maintainability, developer experience, and production reliability.

Target Audience

  • New Holochain Projects: Starting fresh with best practices
  • Existing Projects: Migrating from simpler architectures
  • Development Teams: Establishing consistent patterns
  • Enterprise Applications: Production-grade reliability requirements

🏗️ Architecture Overview

The 7 Layers

  1. Service Layer: Effect-native services with Context.Tag dependency injection
  2. Store Layer: Svelte 5 Runes with standardized helper functions
  3. Schema Layer: Effect Schema validation with strategic boundaries
  4. Error Layer: Domain-specific tagged errors with centralized management
  5. Composable Layer: Effect-based business logic abstraction
  6. Component Layer: Svelte 5 components with accessibility focus
  7. Test Layer: Comprehensive testing across all layers

Key Benefits

  • 🔄 Consistency: Predictable patterns across all domains
  • 🧪 Type Safety: End-to-end typing from zome to UI
  • 🚀 Developer Experience: Rapid onboarding and feature development
  • 🛡️ Reliability: Graceful error handling and recovery
  • 📊 Maintainability: Clear separation of concerns

🎯 Decision Guide: Should You Adopt This Architecture?

✅ Perfect Fit For

Project TypeComplexityTeam SizeTimelineRecommendation
Multi-domain hAppHigh3+ developers6+ monthsFull Adoption
Enterprise IntegrationHigh2+ developers4+ monthsFull Adoption
Production ApplicationMedium-High1+ developers3+ monthsStrong Consider
hREA IntegrationMedium1+ developers2+ monthsRecommended

⚠️ Consider Alternatives For

Project TypeComplexityTeam SizeTimelineRecommendation
Simple CRUD AppLow1 developer<1 monthSimplified Approach
Quick PrototypeLow1 developer<2 weeksDirect Svelte
Learning ProjectLow1 developerVariableStart Simple
Single DomainLow-Medium1-2 developers<3 monthsHybrid Approach

🚀 Implementation Strategies

Strategy 1: Full Adoption (New Projects)

Phase 1: Foundation Setup (1-2 weeks)

# 1. Initialize project structure
mkdir -p my-happ/ui/src/lib/{services,stores,composables,components,schemas,errors,utils}
mkdir -p my-happ/ui/src/lib/utils/store-helpers
mkdir -p my-happ/ui/src/lib/composables/domain
mkdir -p my-happ/ui/src/lib/components/ui

# 2. Install core dependencies
cd my-happ/ui
bun add effect @effect/schema @holochain/client
bun add svelte @sveltejs/kit
bun add vitest @vitest/ui @playwright/test

# 3. Copy essential utilities
# From this project, copy:
# - ui/src/lib/utils/store-helpers/ (entire directory)
# - ui/src/lib/errors/ (error handling patterns)
# - ui/src/lib/utils/effect.ts (Effect utilities)

Phase 2: Service Layer Implementation (1 week per domain)

// 1. Create service template (reference: serviceTypes.service.ts)
export const MyDomainService = Context.GenericTag<MyDomainService>("MyDomainService");

export const makeMyDomainService = Effect.gen(function* () {
  const client = yield* HolochainClientService;
  
  const createMyEntity = (input: CreateMyEntityInput) =>
    Effect.gen(function* () {
      // Implementation
    });
    
  return { createMyEntity };
});

Phase 3: Store Layer Implementation (1 week per domain)

// 1. Implement 9 standardized helpers
export const createMyDomainStore = () => {
  let entities = $state<MyEntity[]>([]);
  
  // 1. Entity Creation Helper
  const createUIEntity = createUIEntityFromRecord<MyEntityInDHT, UIMyEntity>(
    (entry, actionHash, timestamp) => ({ ...entry, original_action_hash: actionHash, created_at: timestamp })
  );
  
  // 2. Record Mapping Helper  
  const mapRecordsToUIEntities = (records: HolochainRecord[]) =>
    records.map(record => createUIEntity(record)).filter(Boolean);
    
  // 3-9. Implement remaining helpers...
  
  return { entities: () => entities, /* other methods */ };
};

Phase 4: Composable Layer (3-5 days per domain)

// 1. Create domain composable
export function useMyDomainManagement() {
  const store = myDomainStore;
  const service = yield* MyDomainService;
  
  const createEntity = (input: CreateMyEntityInput) =>
    Effect.gen(function* () {
      const result = yield* service.createMyEntity(input);
      // Update store, emit events, etc.
    });
    
  return { createEntity };
}

Strategy 2: Hybrid Approach (Existing Projects)

Phase 1: Assessment (2-3 days)

  1. Audit Current Architecture

    // Document existing patterns
    - Current state management approach
    - Error handling strategies
    - Component organization
    - Testing practices
    
  2. Identify Migration Candidates

    // Start with most complex domain
    - Domains with multiple state interactions
    - Areas with frequent bugs
    - New features being added
    

Phase 2: Incremental Migration (2-3 weeks per domain)

// Step 1: Add Effect-TS dependencies
bun add effect @effect/schema

// Step 2: Implement service layer for one domain
export const ExistingDomainService = Context.GenericTag<ExistingDomainService>("ExistingDomainService");

// Step 3: Keep existing components, add service integration
function ExistingComponent() {
  // Gradually replace old patterns with new
  const { createEntity } = useExistingDomainService();
}

Phase 3: Pattern Standardization (1-2 weeks)

// 1. Adopt store helpers gradually
import { createGenericCacheSyncHelper } from '$lib/utils/store-helpers';

// 2. Implement error boundaries
import { useErrorBoundary } from '$lib/composables/ui/useErrorBoundary.svelte';

// 3. Add testing infrastructure
import { createMockService } from '$lib/utils/mocks';

Strategy 3: Minimal Integration (Quick Start)

Essential Elements Only (1 week implementation)

// 1. Add only critical utilities
// Service layer pattern
export const SimpleService = Context.Tag<SimpleService>();

// Basic store helper
const withLoadingState = (operation) => (setLoading, setError) => 
  pipe(
    operation,
    tap(() => setLoading(true)),
    catchError((error) => {
      setError(error.message);
      return E.fail(error);
    }),
    finally(() => setLoading(false))
  );

// 2. Keep existing component structure
function MyComponent() {
  const loading = $state(false);
  const error = $state(null);
  
  const createEntity = withLoadingState(
    service.createEntity(input)
  )(setLoading, setError);
}

📁 File Structure Templates

New Project Structure

my-happ/
├── dnas/
│   └── my_domain/
│       ├── zomes/
│       │   ├── coordinator/
│       │   └── integrity/
└── ui/
    ├── src/
    │   ├── lib/
    │   │   ├── services/
    │   │   │   ├── HolochainClientService.ts
    │   │   │   ├── zomes/
    │   │   │   │   └── myDomain.service.ts
    │   │   ├── stores/
    │   │   │   └── myDomain.store.svelte.ts
    │   │   ├── composables/
    │   │   │   ├── domain/
    │   │   │   │   └── myDomain/
    │   │   │   │       └── useMyDomainManagement.svelte.ts
    │   │   ├── components/
    │   │   │   ├── ui/
    │   │   │   └── myDomain/
    │   │   ├── schemas/
    │   │   │   └── myDomain.schema.ts
    │   │   ├── errors/
    │   │   │   ├── myDomain.error.ts
    │   │   │   └── error-contexts.ts
    │   │   └── utils/
    │   │       ├── store-helpers/
    │   │       └── effect.ts
    │   └── routes/
    └── tests/
        ├── unit/
        ├── integration/
        └── e2e/

Essential Files to Copy

Copy these core files from the Requests & Offers project:

# Core utilities
cp -r ui/src/lib/utils/store-helpers/ my-project/ui/src/lib/utils/
cp ui/src/lib/utils/effect.ts my-project/ui/src/lib/utils/
cp ui/src/lib/utils/mocks.ts my-project/ui/src/lib/utils/

# Error handling foundation
cp ui/src/lib/errors/error-contexts.ts my-project/ui/src/lib/errors/
cp ui/src/lib/errors/base.error.ts my-project/ui/src/lib/errors/

# Service patterns
cp ui/src/lib/services/HolochainClientService.ts my-project/ui/src/lib/services/

# Store events
cp ui/src/lib/stores/storeEvents.ts my-project/ui/src/lib/stores/

🛠️ Implementation Checklist

Pre-Implementation

  • Architecture Decision: Choose adoption strategy (Full/Hybrid/Minimal)
  • Team Alignment: Ensure team understands Effect-TS concepts
  • Dependency Planning: Budget 2-4 weeks for learning curve
  • Tool Setup: Install Bun, Effect-TS, testing infrastructure

Foundation Layer

  • Package Structure: Establish consistent directory layout
  • Core Dependencies: Install Effect-TS, schemas, testing tools
  • Utility Functions: Copy store-helpers and Effect utilities
  • Error Framework: Implement base error classes and contexts

Service Layer

  • Service Template: Create Context.Tag service pattern
  • Dependency Injection: Implement service composition
  • Error Handling: Add domain-specific error types
  • Holochain Integration: Connect service layer to zome functions

Store Layer

  • State Management: Implement Svelte 5 Runes pattern
  • Helper Functions: Implement all 9 standardized helpers
  • Cache Management: Add EntityCache with TTL
  • Event Integration: Connect to store event system

Composable Layer

  • Business Logic: Extract to Effect-based composables
  • Error Boundaries: Implement retry mechanisms
  • State Synchronization: Ensure cache/state consistency
  • User Experience: Add loading states and feedback

Component Layer

  • Accessibility: Implement ARIA patterns and keyboard navigation
  • Composable Integration: Use composables for business logic
  • Responsive Design: Mobile-first approach
  • Error Display: User-friendly error messages

Test Layer

  • Unit Tests: Service layer with comprehensive mocking
  • Integration Tests: Store and composable interactions
  • E2E Tests: User workflows with Playwright
  • Backend Tests: Holochain zome testing with Sweettest

🧪 Migration Patterns

Existing State Migration

// Before: Simple Svelte store
let entities = writable([]);

// After: Effect-based store with helpers
export const createMyDomainStore = () => {
  let entities = $state<MyEntity[]>([]);
  
  const { syncCacheToState } = createGenericCacheSyncHelper({
    all: entities
  });
  
  const fetchEntities = withLoadingState(() =>
    pipe(
      myDomainService.getAllEntities(),
      E.map(mapRecordsToUIEntities),
      E.tap((processed) => {
        entities.splice(0, entities.length, ...processed);
      })
    )
  );
  
  return { entities: () => entities, fetchEntities };
};

Error Handling Migration

// Before: Basic try/catch
try {
  await createEntity(input);
} catch (error) {
  console.error('Failed:', error);
}

// After: Effect-based error handling
const createEntityEffect = (input: CreateEntityInput) =>
  pipe(
    myDomainService.createEntity(input),
    E.catchAll((error) =>
      E.fail(MyDomainError.fromError(error, ERROR_CONTEXTS.CREATE_ENTITY))
    )
  );

📊 Success Metrics

Quality Metrics

  • Test Coverage: ≥80% unit, ≥70% integration
  • Type Safety: 100% TypeScript coverage
  • Error Boundaries: All user operations covered
  • Performance: <100ms response time for operations

Developer Experience

  • Onboarding Time: <1 week for new developers
  • Feature Addition: <3 days for new domain implementation
  • Bug Rate: <50% reduction in production bugs
  • Code Review Time: <30 minutes per PR

Production Metrics

  • Uptime: ≥99.9% for critical operations
  • Error Recovery: Automatic recovery for 90% of errors
  • Bundle Size: <500KB initial load
  • Memory Usage: <100MB for typical sessions

🎓 Learning Resources

Essential Concepts

  1. Effect-TS Fundamentals

    • Effect.gen vs .pipe decision matrix
    • Context.Tag dependency injection
    • Error handling with Either and Exit
  2. Svelte 5 Runes

    • $state, $derived, $effect reactivity
    • Component composition patterns
    • Accessibility best practices
  3. Holochain Integration

    • Zome function patterns
    • DHT operation understanding
    • hREA integration concepts

Practice Exercises

  1. Start with Simple Domain

    • Implement basic CRUD operations
    • Add store helpers incrementally
    • Build components with composables
  2. Progress to Complex Domain

    • Multi-entity relationships
    • Cross-domain communication
    • Advanced error scenarios
  3. Master Production Patterns

    • Performance optimization
    • Error recovery strategies
    • Testing comprehensive coverage

🔧 Troubleshooting

Common Challenges

Effect-TS Learning Curve

// Problem: Understanding when to use Effect.gen vs .pipe
// Solution: Use decision matrix from development guidelines

// Effect.gen: Dependencies, conditional logic, sequential operations
Effect.gen(function* () {
  const service = yield* MyService;
  if (condition) {
    return yield* service.methodA();
  } else {
    return yield* service.methodB();
  }
});

// .pipe: Error handling, tracing, simple transforms
pipe(
  service.method(),
  E.map(transform),
  E.catchAll(handleError)
);

Type Complexity

// Problem: Complex Effect types can be intimidating
// Solution: Let TypeScript inference do the work

// Instead of explicit types:
const myFunction: Effect<string, Error, MyService> = ...

// Let inference work:
const myFunction = Effect.gen(function* () {
  const service = yield* MyService;
  return service.getString();
});

Performance Issues

// Problem: Too many re-renders or slow updates
// Solution: Use derived values and batch operations

// Good: $derived for computed values
const filteredEntities = $derived(
  entities.filter(e => e.status === 'active')
);

// Good: Batch updates with cache helpers
const { syncCacheToState } = createGenericCacheSyncHelper({
  all: entities,
  pending: pendingEntities
});

Performance Optimization

  1. Bundle Size

    // Use dynamic imports for large features
    const HeavyComponent = lazy(() => import('./HeavyComponent.svelte'));
    
  2. Memory Management

    // Clear cache on navigation
    $effect(() => {
      return () => {
        cache.clear();
      };
    });
    
  3. Response Time

    // Use optimistic updates
    const createEntity = (input) =>
      pipe(
        service.createEntity(input),
        E.tap((entity) => {
          // Update UI immediately
          entities.push(entity);
        })
      );
    

🎯 Conclusion

The 7-layer Effect-TS architecture represents a significant investment in code quality and maintainability. While it adds complexity compared to simpler approaches, the dividends in long-term maintainability, developer experience, and production reliability make it an excellent choice for serious Holochain applications.

Start small, iterate, and gradually adopt patterns as you understand their value. The architecture is designed to be adopted incrementally, allowing you to benefit from its advantages without requiring a complete rewrite of existing code.

For projects planning to scale, having multiple domains, or requiring production reliability, this architecture provides a solid foundation that will serve your project well throughout its lifecycle.


Next Steps:

  1. Assess your project's complexity and requirements
  2. Choose an adoption strategy
  3. Implement the foundation layer
  4. Gradually adopt patterns domain by domain
  5. Establish testing and quality metrics
  6. Iterate and improve based on experience

Remember: The goal is not perfection, but consistent improvement in code quality and developer experience.

Domain Implementation Guide

This guide provides a step-by-step template for implementing new domains in the Requests & Offers project, following our established 7-layer Effect-TS architecture. Use this as a practical walkthrough for creating consistent, maintainable domain implementations.

Overview

Every domain implementation follows the same pattern, ensuring consistency and maintainability. This guide uses the Service Types domain as a concrete example, since it's 100% complete and serves as our architectural template.

Implementation Checklist

Use this checklist for every new domain:

  • 1. Zome Layer - Holochain backend implementation
  • 2. Service Layer - Effect-TS service with dependency injection
  • 3. Store Layer - Svelte 5 Runes + Effect-TS with 9 helper functions
  • 4. Error Layer - Domain-specific error handling and contexts
  • 5. Schema Layer - Effect Schema validation
  • 6. Composable Layer - Business logic abstraction
  • 7. Component Layer - Svelte 5 components using composables
  • 8. Testing Layer - Comprehensive test coverage

Step-by-Step Implementation

Step 1: Zome Layer (Backend)

1.1 Create Zome Structure

# Create coordinator zome
cd dnas/requests_and_offers/zomes/coordinator/
mkdir my_domain && cd my_domain

# Create integrity zome
cd ../../../integrity/
mkdir my_domain && cd my_domain

1.2 Implement Coordinator Zome

File: dnas/requests_and_offers/zomes/coordinator/my_domain/src/lib.rs

#![allow(unused)]
fn main() {
use hdk::prelude::*;
use my_domain_integrity::*;

#[hdk_extern]
pub fn create_my_entity(input: CreateMyEntityInput) -> ExternResult<Record> {
    let my_entity_hash = create_entry(&EntryTypes::MyEntity(input.clone()))?;

    let record = get(my_entity_hash.clone(), GetOptions::default())?
        .ok_or(wasm_error!(WasmErrorInner::Guest(String::from("Could not find the just created MyEntity"))))?;

    let path = Path::from("all_my_entities");
    create_link(path.path_entry_hash()?, my_entity_hash.clone(), LinkTypes::AllMyEntities, ())?;

    Ok(record)
}

#[hdk_extern]
pub fn get_all_my_entities(_: ()) -> ExternResult<Vec<Record>> {
    let path = Path::from("all_my_entities");
    let links = get_links(path.path_entry_hash()?, LinkTypes::AllMyEntities, None)?;

    let get_input: Vec<GetInput> = links
        .into_iter()
        .map(|link| GetInput::new(
            ActionHash::from(link.target).into(),
            GetOptions::default(),
        ))
        .collect();

    let records = HDK.with(|hdk| hdk.borrow().get(get_input))?;
    let records: Vec<Record> = records.into_iter().filter_map(|r| r).collect();

    Ok(records)
}

#[hdk_extern]
pub fn get_my_entity(my_entity_hash: ActionHash) -> ExternResult<Option<Record>> {
    get(my_entity_hash, GetOptions::default())
}

#[hdk_extern]
pub fn update_my_entity(input: UpdateMyEntityInput) -> ExternResult<Record> {
    let updated_my_entity_hash = update_entry(input.original_my_entity_hash.clone(), &input.my_entity)?;

    let record = get(updated_my_entity_hash, GetOptions::default())?
        .ok_or(wasm_error!(WasmErrorInner::Guest(String::from("Could not find the just updated MyEntity"))))?;

    Ok(record)
}

#[hdk_extern]
pub fn delete_my_entity(original_my_entity_hash: ActionHash) -> ExternResult<ActionHash> {
    delete_entry(original_my_entity_hash)
}
}

1.3 Implement Integrity Zome

File: dnas/requests_and_offers/zomes/integrity/my_domain/src/lib.rs

#![allow(unused)]
fn main() {
use hdi::prelude::*;

#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
#[hdk_entry_types]
#[unit_enum(UnitEntryTypes)]
pub enum EntryTypes {
    MyEntity(MyEntity),
}

#[derive(Serialize, Deserialize)]
#[hdk_link_types]
pub enum LinkTypes {
    AllMyEntities,
}

#[hdk_entry_helper]
#[derive(Clone, PartialEq)]
pub struct MyEntity {
    pub name: String,
    pub description: String,
    pub status: MyEntityStatus,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum MyEntityStatus {
    Pending,
    Approved,
    Rejected,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct CreateMyEntityInput {
    pub name: String,
    pub description: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct UpdateMyEntityInput {
    pub original_my_entity_hash: ActionHash,
    pub my_entity: MyEntity,
}
}

1.4 Test the Zome

# Build and test the zome
bun build:zomes
bun test:my-domain

Step 2: Service Layer (Effect-TS)

File: ui/src/lib/services/zomes/myDomain.service.ts

import { Context, Effect, pipe } from "effect";
import type { ActionHash, Record } from "@holochain/client";
import { HolochainClientService } from "../HolochainClientService.svelte";
import { MyDomainError, MY_DOMAIN_CONTEXTS } from "$lib/errors";
import type {
  CreateMyEntityInput,
  UpdateMyEntityInput,
  UIMyEntity,
} from "$lib/types";

// 1. Define service interface
export interface MyDomainService {
  readonly createMyEntity: (
    input: CreateMyEntityInput,
  ) => Effect.Effect<UIMyEntity, MyDomainError>;
  readonly getAllMyEntities: () => Effect.Effect<UIMyEntity[], MyDomainError>;
  readonly getMyEntity: (
    hash: ActionHash,
  ) => Effect.Effect<UIMyEntity | null, MyDomainError>;
  readonly updateMyEntity: (
    hash: ActionHash,
    input: UpdateMyEntityInput,
  ) => Effect.Effect<UIMyEntity, MyDomainError>;
  readonly deleteMyEntity: (
    hash: ActionHash,
  ) => Effect.Effect<void, MyDomainError>;
}

// 2. Create service tag for dependency injection
export const MyDomainService =
  Context.GenericTag<MyDomainService>("MyDomainService");

// 3. Implement service with dependencies
export const makeMyDomainService = Effect.gen(function* () {
  const client = yield* HolochainClientService;

  const createMyEntity = (input: CreateMyEntityInput) =>
    Effect.gen(function* () {
      const record = yield* client.callZome({
        zome_name: "my_domain",
        fn_name: "create_my_entity",
        payload: input,
      });

      const entity = createUIMyEntity(record);
      if (!entity) {
        yield* Effect.fail(
          MyDomainError.create("Failed to create UI entity from record"),
        );
      }

      return entity;
    }).pipe(
      Effect.mapError((error) =>
        MyDomainError.fromError(error, MY_DOMAIN_CONTEXTS.CREATE_MY_ENTITY),
      ),
      Effect.withSpan("MyDomainService.createMyEntity"),
    );

  const getAllMyEntities = () =>
    Effect.gen(function* () {
      const records = yield* client.callZome({
        zome_name: "my_domain",
        fn_name: "get_all_my_entities",
        payload: null,
      });

      return mapRecordsToUIMyEntities(records);
    }).pipe(
      Effect.mapError((error) =>
        MyDomainError.fromError(error, MY_DOMAIN_CONTEXTS.GET_ALL_MY_ENTITIES),
      ),
      Effect.withSpan("MyDomainService.getAllMyEntities"),
    );

  const getMyEntity = (hash: ActionHash) =>
    Effect.gen(function* () {
      const record = yield* client.callZome({
        zome_name: "my_domain",
        fn_name: "get_my_entity",
        payload: hash,
      });

      return record ? createUIMyEntity(record) : null;
    }).pipe(
      Effect.mapError((error) =>
        MyDomainError.fromError(error, MY_DOMAIN_CONTEXTS.GET_MY_ENTITY, hash),
      ),
      Effect.withSpan("MyDomainService.getMyEntity"),
    );

  const updateMyEntity = (hash: ActionHash, input: UpdateMyEntityInput) =>
    Effect.gen(function* () {
      const record = yield* client.callZome({
        zome_name: "my_domain",
        fn_name: "update_my_entity",
        payload: { ...input, original_my_entity_hash: hash },
      });

      const entity = createUIMyEntity(record);
      if (!entity) {
        yield* Effect.fail(
          MyDomainError.create(
            "Failed to create UI entity from updated record",
          ),
        );
      }

      return entity;
    }).pipe(
      Effect.mapError((error) =>
        MyDomainError.fromError(
          error,
          MY_DOMAIN_CONTEXTS.UPDATE_MY_ENTITY,
          hash,
        ),
      ),
      Effect.withSpan("MyDomainService.updateMyEntity"),
    );

  const deleteMyEntity = (hash: ActionHash) =>
    Effect.gen(function* () {
      yield* client.callZome({
        zome_name: "my_domain",
        fn_name: "delete_my_entity",
        payload: hash,
      });
    }).pipe(
      Effect.mapError((error) =>
        MyDomainError.fromError(
          error,
          MY_DOMAIN_CONTEXTS.DELETE_MY_ENTITY,
          hash,
        ),
      ),
      Effect.withSpan("MyDomainService.deleteMyEntity"),
    );

  // Helper functions for UI entity creation
  const createUIMyEntity = (record: Record): UIMyEntity | null => {
    try {
      const decoded = decode(record.entry);
      return {
        hash: record.signed_action.hashed.hash,
        ...decoded,
        createdAt: new Date(
          record.signed_action.hashed.content.timestamp / 1000,
        ),
      };
    } catch (error) {
      console.error("Failed to create UI entity:", error);
      return null;
    }
  };

  const mapRecordsToUIMyEntities = (records: Record[]): UIMyEntity[] => {
    return records
      .map(createUIMyEntity)
      .filter((entity): entity is UIMyEntity => entity !== null);
  };

  return {
    createMyEntity,
    getAllMyEntities,
    getMyEntity,
    updateMyEntity,
    deleteMyEntity,
  };
});

// 4. Create service layer for dependency injection
export const MyDomainServiceLive = Layer.effect(
  MyDomainService,
  makeMyDomainService,
).pipe(Layer.provide(HolochainClientServiceLive));

Step 3: Store Layer (Svelte 5 Runes + Effect-TS)

File: ui/src/lib/stores/myDomain.store.svelte.ts

import { Effect } from "effect";
import { createModuleCache } from "$lib/utils/cache.svelte";
import { MyDomainService } from "$lib/services";
import { createEventEmitters } from "$lib/utils/eventBus.effect";
import type {
  ActionHash,
  UIMyEntity,
  CreateMyEntityInput,
  UpdateMyEntityInput,
} from "$lib/types";

export const createMyDomainStore = () => {
  // Reactive state with Svelte 5 Runes
  let entities = $state<UIMyEntity[]>([]);
  let isLoading = $state(false);
  let error = $state<string | null>(null);

  // Cache management
  const cache = createModuleCache<ActionHash, UIMyEntity>(
    "myDomain",
    5 * 60 * 1000,
  );

  // 1. Entity Creation Helper
  const createUIEntity = (record: Record): UIMyEntity | null => {
    try {
      const decoded = decode(record.entry);
      return {
        hash: record.signed_action.hashed.hash,
        ...decoded,
        createdAt: new Date(
          record.signed_action.hashed.content.timestamp / 1000,
        ),
      };
    } catch (error) {
      console.error("Failed to create UI entity:", error);
      return null;
    }
  };

  // 2. Record Mapping Helper
  const mapRecordsToUIEntities = (records: Record[]): UIMyEntity[] => {
    return records
      .map(createUIEntity)
      .filter((entity): entity is UIMyEntity => entity !== null);
  };

  // 3. Cache Sync Helper
  const createCacheSyncHelper = () => {
    const syncCacheWithEntities = () => {
      entities.forEach((entity) => cache.set(entity.hash, entity));
    };

    const syncEntityWithCache = (entity: UIMyEntity) => {
      cache.set(entity.hash, entity);
      const index = entities.findIndex((e) => e.hash === entity.hash);
      if (index !== -1) {
        entities[index] = entity;
      } else {
        entities = [...entities, entity];
      }
    };

    return { syncCacheWithEntities, syncEntityWithCache };
  };

  const { syncCacheWithEntities, syncEntityWithCache } =
    createCacheSyncHelper();

  // 4. Event Emission Helpers
  const eventEmitters = createEventEmitters<UIMyEntity>("myDomain");

  // 5. Data Fetching Helper
  const createEntityFetcher = <T, E>(
    fetchOperation: Effect.Effect<T[], E>,
    processingFn: (records: any[]) => T[],
  ) => {
    const fetchWithState = Effect.gen(function* () {
      isLoading = true;
      error = null;

      const result = yield* fetchOperation;
      const processed = processingFn(result);
      entities = processed;
      syncCacheWithEntities();
      eventEmitters.entitiesLoaded(processed);

      isLoading = false;
      return processed;
    }).pipe(
      Effect.catchAll((err) =>
        Effect.sync(() => {
          error = err.message;
          isLoading = false;
          return [];
        }),
      ),
    );

    return fetchWithState;
  };

  // 6. Loading State Helper
  const withLoadingState = <T, E>(operation: Effect.Effect<T, E>) =>
    Effect.gen(function* () {
      isLoading = true;
      error = null;

      const result = yield* operation;

      isLoading = false;
      return result;
    }).pipe(
      Effect.catchAll((err) =>
        Effect.sync(() => {
          error = err.message;
          isLoading = false;
          throw err;
        }),
      ),
    );

  // 7. Record Creation Helper
  const createRecordCreationHelper = () => {
    const handleNewRecord = (newEntity: UIMyEntity) => {
      entities = [...entities, newEntity];
      cache.set(newEntity.hash, newEntity);
      eventEmitters.entityCreated(newEntity);
    };

    const handleUpdatedRecord = (updatedEntity: UIMyEntity) => {
      const index = entities.findIndex((e) => e.hash === updatedEntity.hash);
      if (index !== -1) {
        entities[index] = updatedEntity;
        cache.set(updatedEntity.hash, updatedEntity);
        eventEmitters.entityUpdated(updatedEntity);
      }
    };

    return { handleNewRecord, handleUpdatedRecord };
  };

  const { handleNewRecord, handleUpdatedRecord } = createRecordCreationHelper();

  // 8. Status Transition Helper
  const createStatusTransitionHelper = () => {
    const updateEntityStatus = (
      hash: ActionHash,
      newStatus: MyEntityStatus,
    ) => {
      const index = entities.findIndex((e) => e.hash === hash);
      if (index !== -1) {
        const updatedEntity = { ...entities[index], status: newStatus };
        entities[index] = updatedEntity;
        cache.set(hash, updatedEntity);
        eventEmitters.entityUpdated(updatedEntity);
      }
    };

    return { updateEntityStatus };
  };

  const { updateEntityStatus } = createStatusTransitionHelper();

  // 9. Collection Processor
  const processMultipleRecordCollections = (response: any) => {
    if (response.myEntities) {
      entities = mapRecordsToUIEntities(response.myEntities);
      syncCacheWithEntities();
    }
    // Handle other collections if needed
  };

  // Main operations using Effect-TS
  const fetchEntities = createEntityFetcher(
    Effect.gen(function* () {
      const myDomainService = yield* MyDomainService;
      return yield* myDomainService.getAllMyEntities();
    }),
    (result) => result, // Already processed by service
  );

  const createEntity = (input: CreateMyEntityInput) =>
    withLoadingState(
      Effect.gen(function* () {
        const myDomainService = yield* MyDomainService;
        const newEntity = yield* myDomainService.createMyEntity(input);
        handleNewRecord(newEntity);
        return newEntity;
      }),
    );

  const updateEntity = (hash: ActionHash, input: UpdateMyEntityInput) =>
    withLoadingState(
      Effect.gen(function* () {
        const myDomainService = yield* MyDomainService;
        const updatedEntity = yield* myDomainService.updateMyEntity(
          hash,
          input,
        );
        handleUpdatedRecord(updatedEntity);
        return updatedEntity;
      }),
    );

  const deleteEntity = (hash: ActionHash) =>
    withLoadingState(
      Effect.gen(function* () {
        const myDomainService = yield* MyDomainService;
        yield* myDomainService.deleteMyEntity(hash);

        entities = entities.filter((e) => e.hash !== hash);
        cache.delete(hash);
        eventEmitters.entityDeleted(hash);
      }),
    );

  return {
    // Reactive state accessors
    entities: () => entities,
    isLoading: () => isLoading,
    error: () => error,

    // Operations
    fetchEntities,
    createEntity,
    updateEntity,
    deleteEntity,
    updateEntityStatus,

    // Helper functions (exposed for composables)
    createUIEntity,
    mapRecordsToUIEntities,
    syncEntityWithCache,
    processMultipleRecordCollections,
    eventEmitters,

    // Cache access
    getCachedEntity: (hash: ActionHash) => cache.get(hash) || null,
    clearCache: () => cache.clear(),
  };
};

Step 4: Error Layer

File: ui/src/lib/errors/my-domain.errors.ts

import { Data } from "effect";

export class MyDomainError extends Data.TaggedError("MyDomainError")<{
  readonly message: string;
  readonly cause?: unknown;
  readonly context?: string;
  readonly entityId?: string;
  readonly operation?: string;
}> {
  static fromError(
    error: unknown,
    context: string,
    entityId?: string,
    operation?: string,
  ): MyDomainError {
    const message = error instanceof Error ? error.message : String(error);
    return new MyDomainError({
      message,
      cause: error,
      context,
      entityId,
      operation,
    });
  }

  static create(
    message: string,
    context?: string,
    entityId?: string,
    operation?: string,
  ): MyDomainError {
    return new MyDomainError({
      message,
      context,
      entityId,
      operation,
    });
  }
}

// Add to error-contexts.ts
export const MY_DOMAIN_CONTEXTS = {
  CREATE_MY_ENTITY: "Failed to create entity",
  GET_MY_ENTITY: "Failed to get entity",
  UPDATE_MY_ENTITY: "Failed to update entity",
  DELETE_MY_ENTITY: "Failed to delete entity",
  GET_ALL_MY_ENTITIES: "Failed to fetch entities",
  APPROVE_MY_ENTITY: "Failed to approve entity",
  REJECT_MY_ENTITY: "Failed to reject entity",
} as const;

Step 5: Schema Layer

File: ui/src/lib/schemas/my-domain.schemas.ts

import { Schema } from "@effect/schema";

// Base entity schema
export const MyEntityStatusSchema = Schema.Union(
  Schema.Literal("pending"),
  Schema.Literal("approved"),
  Schema.Literal("rejected"),
);

export const MyEntitySchema = Schema.Struct({
  name: Schema.String,
  description: Schema.String,
  status: MyEntityStatusSchema,
});

// Input schemas
export const CreateMyEntityInputSchema = Schema.Struct({
  name: Schema.String.pipe(Schema.minLength(1)),
  description: Schema.String.pipe(Schema.minLength(1)),
});

export const UpdateMyEntityInputSchema = Schema.Struct({
  name: Schema.optional(Schema.String.pipe(Schema.minLength(1))),
  description: Schema.optional(Schema.String.pipe(Schema.minLength(1))),
  status: Schema.optional(MyEntityStatusSchema),
});

// UI entity schema (includes UI-specific fields)
export const UIMyEntitySchema = MyEntitySchema.extend(
  Schema.Struct({
    hash: Schema.String, // ActionHash
    createdAt: Schema.Date,
  }),
);

// Type exports
export type MyEntityStatus = Schema.Schema.Type<typeof MyEntityStatusSchema>;
export type MyEntity = Schema.Schema.Type<typeof MyEntitySchema>;
export type CreateMyEntityInput = Schema.Schema.Type<
  typeof CreateMyEntityInputSchema
>;
export type UpdateMyEntityInput = Schema.Schema.Type<
  typeof UpdateMyEntityInputSchema
>;
export type UIMyEntity = Schema.Schema.Type<typeof UIMyEntitySchema>;

Step 6: Composable Layer

File: ui/src/lib/composables/domain/my-domain/useMyDomainManagement.svelte.ts

import { Effect } from "effect";
import { useErrorBoundary } from "$lib/composables";
import { createMyDomainStore } from "$lib/stores";
import { MY_DOMAIN_CONTEXTS } from "$lib/errors";
import type {
  ActionHash,
  CreateMyEntityInput,
  UpdateMyEntityInput,
} from "$lib/types";

export function useMyDomainManagement() {
  // Create domain store
  const store = createMyDomainStore();

  // Error boundaries for different operations
  const loadingErrorBoundary = useErrorBoundary({
    context: MY_DOMAIN_CONTEXTS.GET_ALL_MY_ENTITIES,
    enableLogging: true,
    enableFallback: true,
    maxRetries: 2,
    retryDelay: 1000,
  });

  const createErrorBoundary = useErrorBoundary({
    context: MY_DOMAIN_CONTEXTS.CREATE_MY_ENTITY,
    enableLogging: true,
    maxRetries: 1,
    retryDelay: 500,
  });

  const updateErrorBoundary = useErrorBoundary({
    context: MY_DOMAIN_CONTEXTS.UPDATE_MY_ENTITY,
    enableLogging: true,
    maxRetries: 1,
    retryDelay: 500,
  });

  const deleteErrorBoundary = useErrorBoundary({
    context: MY_DOMAIN_CONTEXTS.DELETE_MY_ENTITY,
    enableLogging: true,
    maxRetries: 0, // No auto-retry for destructive operations
    enableToast: true,
  });

  // Reactive state for components
  let state = $state({
    entities: store.entities,
    isLoading: store.isLoading,
    error: store.error,

    // Derived state
    approvedEntities: () =>
      store.entities().filter((e) => e.status === "approved"),
    pendingEntities: () =>
      store.entities().filter((e) => e.status === "pending"),
    rejectedEntities: () =>
      store.entities().filter((e) => e.status === "rejected"),

    // Error states from boundaries
    loadingError: () => loadingErrorBoundary.state.error,
    createError: () => createErrorBoundary.state.error,
    updateError: () => updateErrorBoundary.state.error,
    deleteError: () => deleteErrorBoundary.state.error,
  });

  // Business operations with error handling
  const operations = {
    async loadEntities() {
      await loadingErrorBoundary.execute(store.fetchEntities, []);
    },

    async createEntity(input: CreateMyEntityInput) {
      return await createErrorBoundary.execute(store.createEntity(input));
    },

    async updateEntity(hash: ActionHash, input: UpdateMyEntityInput) {
      return await updateErrorBoundary.execute(store.updateEntity(hash, input));
    },

    async deleteEntity(hash: ActionHash) {
      await deleteErrorBoundary.execute(store.deleteEntity(hash));
    },

    async approveEntity(hash: ActionHash) {
      await updateErrorBoundary.execute(
        store.updateEntityStatus(hash, "approved"),
      );
    },

    async rejectEntity(hash: ActionHash) {
      await updateErrorBoundary.execute(
        store.updateEntityStatus(hash, "rejected"),
      );
    },
  };

  // Event listeners setup
  const setupEventListeners = () => {
    store.eventEmitters.onEntityCreated((entity) => {
      // Handle entity creation events
    });

    store.eventEmitters.onEntityUpdated((entity) => {
      // Handle entity update events
    });

    store.eventEmitters.onEntityDeleted((hash) => {
      // Handle entity deletion events
    });
  };

  // Lifecycle management
  $effect(() => {
    setupEventListeners();
    operations.loadEntities();
  });

  return {
    // State
    state,

    // Operations
    operations,

    // Error boundaries for component error handling
    loadingErrorBoundary,
    createErrorBoundary,
    updateErrorBoundary,
    deleteErrorBoundary,

    // Store access (if needed for advanced usage)
    store,
  };
}

Step 7: Component Layer

File: ui/src/lib/components/my-domain/MyDomainGrid.svelte

<script>
  import { useMyDomainManagement } from '$lib/composables';
  import ErrorDisplay from '$lib/components/shared/ErrorDisplay.svelte';
  import MyDomainCard from './MyDomainCard.svelte';
  import MyDomainForm from './MyDomainForm.svelte';

  const {
    state,
    operations,
    loadingErrorBoundary,
    createErrorBoundary,
    updateErrorBoundary,
    deleteErrorBoundary
  } = useMyDomainManagement();

  let showCreateForm = $state(false);
</script>

<!-- Error displays for different operations -->
{#if state.loadingError()}
  <ErrorDisplay
    error={state.loadingError()}
    context="Loading entities"
    variant="inline"
    showRetry={true}
    onretry={() => operations.loadEntities()}
    ondismiss={() => loadingErrorBoundary.clearError()}
  />
{/if}

{#if state.createError()}
  <ErrorDisplay
    error={state.createError()}
    context="Creating entity"
    variant="banner"
    ondismiss={() => createErrorBoundary.clearError()}
  />
{/if}

{#if state.updateError()}
  <ErrorDisplay
    error={state.updateError()}
    context="Updating entity"
    variant="inline"
    ondismiss={() => updateErrorBoundary.clearError()}
  />
{/if}

{#if state.deleteError()}
  <ErrorDisplay
    error={state.deleteError()}
    context="Deleting entity"
    variant="banner"
    ondismiss={() => deleteErrorBoundary.clearError()}
  />
{/if}

<!-- Action bar -->
<div class="flex justify-between items-center mb-6">
  <h1 class="text-2xl font-bold">My Entities</h1>
  <button
    class="btn variant-filled-primary"
    onclick={() => showCreateForm = true}
  >
    Create Entity
  </button>
</div>

<!-- Create form modal -->
{#if showCreateForm}
  <MyDomainForm
    onsubmit={async (input) => {
      await operations.createEntity(input);
      if (!state.createError()) {
        showCreateForm = false;
      }
    }}
    oncancel={() => showCreateForm = false}
  />
{/if}

<!-- Loading state -->
{#if state.isLoading()}
  <div class="flex justify-center items-center py-8">
    <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-500"></div>
    <span class="ml-2">Loading entities...</span>
  </div>
{/if}

<!-- Entity grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  {#each state.entities() as entity (entity.hash)}
    <MyDomainCard
      {entity}
      onupdate={(input) => operations.updateEntity(entity.hash, input)}
      ondelete={() => operations.deleteEntity(entity.hash)}
      onapprove={() => operations.approveEntity(entity.hash)}
      onreject={() => operations.rejectEntity(entity.hash)}
    />
  {/each}
</div>

<!-- Empty state -->
{#if !state.isLoading() && state.entities().length === 0}
  <div class="text-center py-8">
    <p class="text-gray-500">No entities found. Create your first entity to get started.</p>
  </div>
{/if}

Step 8: Testing Layer

File: ui/tests/unit/stores/myDomain.store.test.ts

import { describe, it, expect, beforeEach } from "vitest";
import { Effect, Layer } from "effect";
import { createMyDomainStore } from "$lib/stores";
import { MyDomainService } from "$lib/services";
import { HolochainClientService } from "$lib/services";

describe("MyDomain Store", () => {
  let store: ReturnType<typeof createMyDomainStore>;

  beforeEach(() => {
    store = createMyDomainStore();
  });

  describe("Helper Functions", () => {
    it("should implement all 9 helper functions", () => {
      expect(typeof store.createUIEntity).toBe("function");
      expect(typeof store.mapRecordsToUIEntities).toBe("function");
      expect(typeof store.syncEntityWithCache).toBe("function");
      expect(typeof store.eventEmitters).toBe("object");
      expect(typeof store.fetchEntities).toBe("object"); // Effect object
      expect(typeof store.createEntity).toBe("function");
      expect(typeof store.updateEntity).toBe("function");
      expect(typeof store.updateEntityStatus).toBe("function");
      expect(typeof store.processMultipleRecordCollections).toBe("function");
    });

    it("should create UI entity correctly", () => {
      const mockRecord = createMockRecord();
      const entity = store.createUIEntity(mockRecord);

      expect(entity).toBeDefined();
      expect(entity?.hash).toBe(mockRecord.signed_action.hashed.hash);
      expect(entity?.name).toBe("Test Entity");
    });

    it("should map records to UI entities with null safety", () => {
      const mockRecords = [createMockRecord(), createInvalidRecord()];
      const entities = store.mapRecordsToUIEntities(mockRecords);

      expect(entities).toHaveLength(1); // Invalid record filtered out
      expect(entities[0].name).toBe("Test Entity");
    });
  });

  describe("Effect Operations", () => {
    it("should fetch entities successfully", async () => {
      const MockMyDomainService = Layer.succeed(MyDomainService, {
        getAllMyEntities: () => Effect.succeed([createMockUIEntity()]),
      });

      const result = await Effect.runPromise(
        store.fetchEntities.pipe(Effect.provide(MockMyDomainService)),
      );

      expect(result).toHaveLength(1);
      expect(store.entities()).toHaveLength(1);
    });

    it("should handle create entity operation", async () => {
      const MockMyDomainService = Layer.succeed(MyDomainService, {
        createMyEntity: () => Effect.succeed(createMockUIEntity()),
      });

      const result = await Effect.runPromise(
        store
          .createEntity({ name: "New Entity", description: "Test" })
          .pipe(Effect.provide(MockMyDomainService)),
      );

      expect(result.name).toBe("Test Entity");
      expect(store.entities()).toContain(result);
    });
  });

  describe("Error Handling", () => {
    it("should handle fetch errors gracefully", async () => {
      const MockMyDomainService = Layer.succeed(MyDomainService, {
        getAllMyEntities: () => Effect.fail(new Error("Network error")),
      });

      const result = await Effect.runPromise(
        store.fetchEntities.pipe(Effect.provide(MockMyDomainService)),
      );

      expect(result).toEqual([]);
      expect(store.error()).toBe("Network error");
      expect(store.isLoading()).toBe(false);
    });
  });
});

// Helper functions for testing
function createMockRecord(): Record {
  return {
    signed_action: {
      hashed: {
        hash: "test-hash-123",
        content: {
          timestamp: Date.now() * 1000,
        },
      },
    },
    entry: {
      Present: {
        name: "Test Entity",
        description: "Test Description",
        status: "pending",
      },
    },
  } as any;
}

function createMockUIEntity() {
  return {
    hash: "test-hash-123",
    name: "Test Entity",
    description: "Test Description",
    status: "pending",
    createdAt: new Date(),
  };
}

Validation Checklist

After implementing all layers, validate your domain:

Functionality Validation

  • All CRUD operations work in the UI
  • Error handling displays proper messages
  • Loading states show during operations
  • Cache synchronization works correctly
  • Events are emitted and received properly

Code Quality Validation

  • All 9 helper functions implemented
  • Effect-TS patterns used consistently
  • Error boundaries handle all operation types
  • TypeScript types are properly defined
  • No direct store access from components

Testing Validation

  • Backend tests pass: bun test:my-domain
  • Frontend unit tests pass: cd ui && bun test:unit -- my-domain
  • Integration tests cover main workflows
  • Error scenarios are tested

Documentation Validation

  • Update work-in-progress.md with domain status
  • Add domain to architecture documentation
  • Document any new patterns established
  • Update API documentation if needed

Common Pitfalls to Avoid

Don't Skip Helper Functions

  • Never implement partial helper functions
  • All 9 helpers must be present and functional
  • Consistency is more important than optimization

Don't Violate Layer Dependencies

  • Components should never directly access stores
  • Use composables for all business logic
  • Services only communicate with Holochain

Don't Mix Error Handling

  • Use separate error boundaries for different operations
  • Don't reuse error contexts across domains
  • Always transform errors to domain-specific types

Don't Ignore Cache Consistency

  • Always sync cache with reactive state changes
  • Use helper functions for all cache operations
  • Clear cache on errors when appropriate

Next Steps

Once your domain is implemented and validated:

  1. Performance Testing: Test with larger datasets
  2. Integration Testing: Test cross-domain interactions
  3. User Acceptance: Validate with actual users
  4. Documentation: Update all relevant documentation
  5. Patterns Review: Document any new patterns discovered

This template ensures every domain follows the same high-quality patterns established in the Service Types, Requests, and Offers domains. Use it as your implementation checklist for consistent, maintainable domain development.

Effect-TS Primer for Requests & Offers

This guide explains how Effect-TS is used throughout the Requests & Offers project, providing practical patterns and examples specific to our architecture.

What is Effect-TS?

Effect-TS is a powerful TypeScript library for managing async operations, errors, and dependencies in a functional, composable way. In our project, it serves as the backbone for:

  • Type-safe async operations across all service layers
  • Dependency injection for services and contexts
  • Comprehensive error handling with tagged errors
  • Composable business logic that's easy to test and maintain

Why Effect-TS in This Project?

Before Effect-TS (Traditional Approach)

// ❌ Traditional async/await with manual error handling
async function createServiceType(
  input: CreateServiceTypeInput,
): Promise<UIServiceType> {
  try {
    const client = await getHolochainClient();
    const record = await client.callZome({
      zome_name: "service_types",
      fn_name: "create_service_type",
      payload: input,
    });

    return createUIServiceType(record);
  } catch (error) {
    console.error("Failed to create service type:", error);
    throw new ServiceTypeError("Failed to create service type", error);
  }
}

With Effect-TS (Our Approach)

// ✅ Effect-TS with composable error handling and dependency injection
const createServiceType = (input: CreateServiceTypeInput) =>
  Effect.gen(function* () {
    const client = yield* HolochainClientService;
    const record = yield* client.callZome({
      zome_name: "service_types",
      fn_name: "create_service_type",
      payload: input,
    });

    return createUIServiceType(record);
  }).pipe(
    Effect.mapError((error) =>
      ServiceTypeError.fromError(
        error,
        SERVICE_TYPE_CONTEXTS.CREATE_SERVICE_TYPE,
      ),
    ),
    Effect.withSpan("ServiceTypeService.createServiceType"),
  );

Benefits of Effect-TS approach:

  • Dependency injection: Services automatically get their dependencies
  • Composable error handling: Errors are transformed consistently
  • Type safety: Full type inference and safety
  • Testability: Easy to mock dependencies and test logic
  • Observability: Built-in tracing and telemetry

Core Effect-TS Patterns in Our Project

1. Effect.gen vs .pipe - When to Use Each

Use Effect.gen for:

Dependency Injection:

const serviceOperation = Effect.gen(function* () {
  // Inject dependencies
  const holochainClient = yield* HolochainClientService;
  const serviceTypeService = yield* ServiceTypeService;

  // Use services
  const result = yield* serviceTypeService.getAllServiceTypes();
  return result;
});

Sequential Operations with Conditional Logic:

const complexOperation = Effect.gen(function* () {
  const user = yield* getCurrentUser();

  if (user.role === "admin") {
    const adminData = yield* getAdminData();
    return yield* processAdminData(adminData);
  } else {
    const userData = yield* getUserData(user.id);
    return yield* processUserData(userData);
  }
});

Error Handling Within Operations:

const resilientOperation = Effect.gen(function* () {
  const primaryResult = yield* primaryOperation().pipe(
    Effect.catchAll(() => fallbackOperation()),
  );

  const processed = yield* processResult(primaryResult);
  return processed;
});

Use .pipe for:

Error Transformation:

const withErrorHandling = operation.pipe(
  Effect.mapError((error) => ServiceTypeError.fromError(error, context)),
  Effect.catchAll((error) => Effect.succeed(defaultValue)),
);

Operation Composition:

const composedOperation = baseOperation.pipe(
  Effect.map((result) => transformResult(result)),
  Effect.flatMap((transformed) => validateResult(transformed)),
  Effect.withSpan("composedOperation"),
  Effect.timeout("10 seconds"),
);

Layer Building:

const ServiceTypeServiceLive = Layer.effect(
  ServiceTypeService,
  makeServiceTypeService,
).pipe(Layer.provide(HolochainClientServiceLive));

2. Service Layer Pattern with Dependency Injection

Our services use Effect's Context system for dependency injection:

// 1. Define the service interface
export interface ServiceTypeService {
  readonly createServiceType: (
    input: CreateServiceTypeInput,
  ) => Effect.Effect<UIServiceType, ServiceTypeError>;
  readonly getAllServiceTypes: () => Effect.Effect<
    UIServiceType[],
    ServiceTypeError
  >;
  readonly updateServiceType: (
    hash: ActionHash,
    input: UpdateServiceTypeInput,
  ) => Effect.Effect<UIServiceType, ServiceTypeError>;
  readonly deleteServiceType: (
    hash: ActionHash,
  ) => Effect.Effect<void, ServiceTypeError>;
}

// 2. Create the service tag for dependency injection
export const ServiceTypeService =
  Context.GenericTag<ServiceTypeService>("ServiceTypeService");

// 3. Implement the service with dependencies
export const makeServiceTypeService = Effect.gen(function* () {
  // Inject HolochainClient dependency
  const client = yield* HolochainClientService;

  const createServiceType = (input: CreateServiceTypeInput) =>
    Effect.gen(function* () {
      const record = yield* client.callZome({
        zome_name: "service_types",
        fn_name: "create_service_type",
        payload: input,
      });

      return createUIServiceType(record);
    }).pipe(
      Effect.mapError((error) =>
        ServiceTypeError.fromError(
          error,
          SERVICE_TYPE_CONTEXTS.CREATE_SERVICE_TYPE,
        ),
      ),
      Effect.withSpan("ServiceTypeService.createServiceType"),
    );

  const getAllServiceTypes = () =>
    Effect.gen(function* () {
      const records = yield* client.callZome({
        zome_name: "service_types",
        fn_name: "get_all_service_types",
        payload: null,
      });

      return records
        .map(createUIServiceType)
        .filter(
          (serviceType): serviceType is UIServiceType => serviceType !== null,
        );
    }).pipe(
      Effect.mapError((error) =>
        ServiceTypeError.fromError(
          error,
          SERVICE_TYPE_CONTEXTS.GET_ALL_SERVICE_TYPES,
        ),
      ),
      Effect.withSpan("ServiceTypeService.getAllServiceTypes"),
    );

  return {
    createServiceType,
    getAllServiceTypes,
    updateServiceType,
    deleteServiceType,
  };
});

// 4. Create the service layer for dependency injection
export const ServiceTypeServiceLive = Layer.effect(
  ServiceTypeService,
  makeServiceTypeService,
).pipe(Layer.provide(HolochainClientServiceLive));

3. Store Integration with Svelte 5 Runes

Our stores combine Effect-TS operations with Svelte 5 Runes for reactivity:

// ui/src/lib/stores/serviceTypes.store.svelte.ts
export const createServiceTypesStore = () => {
  // Svelte 5 Runes for reactive state
  let entities = $state<UIServiceType[]>([]);
  let isLoading = $state(false);
  let error = $state<string | null>(null);

  // Effect-TS operations that update reactive state
  const fetchEntities = Effect.gen(function* () {
    const serviceTypeService = yield* ServiceTypeService;

    isLoading = true;
    error = null;

    const result = yield* serviceTypeService.getAllServiceTypes();
    entities = mapRecordsToUIEntities(result);

    isLoading = false;
    return entities;
  }).pipe(
    Effect.catchAll((err) =>
      Effect.sync(() => {
        error = err.message;
        isLoading = false;
        return [];
      }),
    ),
  );

  const createEntity = (input: CreateServiceTypeInput) =>
    Effect.gen(function* () {
      const serviceTypeService = yield* ServiceTypeService;

      isLoading = true;
      error = null;

      const newEntity = yield* serviceTypeService.createServiceType(input);
      entities = [...entities, newEntity];

      isLoading = false;
      return newEntity;
    }).pipe(
      Effect.catchAll((err) =>
        Effect.sync(() => {
          error = err.message;
          isLoading = false;
          throw err;
        }),
      ),
    );

  return {
    // Reactive state accessors
    entities: () => entities,
    isLoading: () => isLoading,
    error: () => error,

    // Effect operations
    fetchEntities,
    createEntity,
  };
};

4. Error Handling with Tagged Errors

Our error handling uses Effect's tagged error system:

// Domain-specific error class
export class ServiceTypeError extends Data.TaggedError("ServiceTypeError")<{
  readonly message: string;
  readonly cause?: unknown;
  readonly context?: string;
  readonly serviceTypeId?: string;
  readonly operation?: string;
}> {
  static fromError(
    error: unknown,
    context: string,
    serviceTypeId?: string,
    operation?: string,
  ): ServiceTypeError {
    const message = error instanceof Error ? error.message : String(error);
    return new ServiceTypeError({
      message,
      cause: error,
      context,
      serviceTypeId,
      operation,
    });
  }
}

// Usage in service operations
const createServiceType = (input: CreateServiceTypeInput) =>
  Effect.gen(function* () {
    // ... operation logic
  }).pipe(
    Effect.mapError((error) =>
      ServiceTypeError.fromError(
        error,
        SERVICE_TYPE_CONTEXTS.CREATE_SERVICE_TYPE,
        undefined,
        "create",
      ),
    ),
    Effect.withSpan("ServiceTypeService.createServiceType"),
  );

5. Effect Execution in Svelte Components

Components execute Effects through composables:

// ui/src/lib/composables/domain/service-types/useServiceTypesManagement.svelte.ts
export function useServiceTypesManagement() {
  const store = createServiceTypesStore();

  const errorBoundary = useErrorBoundary({
    context: SERVICE_TYPE_CONTEXTS.FETCH_SERVICE_TYPES,
    enableLogging: true,
    maxRetries: 2,
  });

  // Execute Effect operations with error boundary
  const loadServiceTypes = async () => {
    await errorBoundary.execute(store.fetchEntities, []);
  };

  const createServiceType = async (input: CreateServiceTypeInput) => {
    await errorBoundary.execute(store.createEntity(input));
  };

  return {
    // Reactive state
    serviceTypes: store.entities,
    isLoading: store.isLoading,
    error: store.error,

    // Actions
    loadServiceTypes,
    createServiceType,

    // Error boundary
    errorBoundary,
  };
}
<!-- Components use composables to access Effect operations -->
<script>
  import { useServiceTypesManagement } from '$lib/composables';

  const {
    serviceTypes,
    isLoading,
    loadServiceTypes,
    createServiceType,
    errorBoundary
  } = useServiceTypesManagement();

  // Execute Effect on mount
  $effect(() => {
    loadServiceTypes();
  });

  async function handleCreate(input) {
    await createServiceType(input);
  }
</script>

Advanced Effect-TS Patterns

1. Effect Composition and Pipelines

// Complex operation pipeline
const processServiceTypeWithValidation = (input: CreateServiceTypeInput) =>
  Effect.gen(function* () {
    // Step 1: Validate input
    const validatedInput = yield* validateServiceTypeInput(input);

    // Step 2: Check for duplicates
    const existingTypes = yield* serviceTypeService.getAllServiceTypes();
    const isDuplicate = existingTypes.some(
      (st) => st.name === validatedInput.name,
    );

    if (isDuplicate) {
      yield* Effect.fail(
        ServiceTypeError.create("Service type name already exists"),
      );
    }

    // Step 3: Create service type
    const newServiceType =
      yield* serviceTypeService.createServiceType(validatedInput);

    // Step 4: Update cache
    yield* updateServiceTypeCache(newServiceType);

    return newServiceType;
  }).pipe(
    Effect.withSpan("processServiceTypeWithValidation"),
    Effect.timeout("30 seconds"),
  );

2. Retry and Resilience Patterns

// Operation with retry and fallback
const resilientFetch = Effect.gen(function* () {
  const serviceTypeService = yield* ServiceTypeService;
  return yield* serviceTypeService.getAllServiceTypes();
}).pipe(
  // Retry with exponential backoff
  Effect.retry(
    pipe(
      Schedule.exponential("500 millis"),
      Schedule.intersect(Schedule.recurs(3)),
    ),
  ),
  // Fallback to cached data
  Effect.catchAll(() => getCachedServiceTypes()),
  // Final fallback to empty array
  Effect.catchAll(() => Effect.succeed([])),
);

3. Concurrent Operations

// Run multiple operations concurrently
const loadAllDomainData = Effect.gen(function* () {
  const [serviceTypes, requests, offers] = yield* Effect.all(
    [
      serviceTypeService.getAllServiceTypes(),
      requestService.getAllRequests(),
      offerService.getAllOffers(),
    ],
    { concurrency: 3 },
  );

  return { serviceTypes, requests, offers };
}).pipe(Effect.withSpan("loadAllDomainData"));

4. Resource Management

// Automatic resource cleanup
const withDatabaseConnection = <T>(
  operation: (
    connection: DatabaseConnection,
  ) => Effect.Effect<T, DatabaseError>,
) =>
  Effect.acquireUseRelease(openDatabaseConnection(), operation, (connection) =>
    closeDatabaseConnection(connection),
  );

Testing with Effect-TS

Our testing approach leverages Effect's testing utilities:

// ui/tests/unit/services/serviceTypes.service.test.ts
import { describe, it, expect } from "vitest";
import { Effect, Layer, TestServices } from "effect";
import { makeServiceTypeService, ServiceTypeService } from "$lib/services";

describe("ServiceTypeService", () => {
  it("should create service type successfully", async () => {
    // Create mock layer
    const MockHolochainClientService = Layer.succeed(HolochainClientService, {
      callZome: () => Effect.succeed(mockRecord),
    });

    // Create test layer with mock dependencies
    const TestServiceTypeServiceLive = Layer.provide(
      ServiceTypeServiceLive,
      MockHolochainClientService,
    );

    // Run test
    const result = await Effect.runPromise(
      Effect.gen(function* () {
        const service = yield* ServiceTypeService;
        return yield* service.createServiceType(mockInput);
      }).pipe(Effect.provide(TestServiceTypeServiceLive)),
    );

    expect(result.name).toBe(mockInput.name);
  });
});

Performance Considerations

1. Effect Caching

// Cache expensive operations
const getCachedServiceTypes = Effect.gen(function* () {
  const cache = yield* CacheService;
  const cached = yield* cache.get("service-types");

  if (cached) {
    return cached;
  }

  const serviceTypes = yield* serviceTypeService.getAllServiceTypes();
  yield* cache.set("service-types", serviceTypes, "5 minutes");

  return serviceTypes;
});

2. Lazy Evaluation

// Lazy service creation
const lazyServiceTypeService = Effect.lazy(() =>
  Effect.gen(function* () {
    const client = yield* HolochainClientService;
    return makeServiceTypeService(client);
  }),
);

Best Practices

1. Service Design

  • Single Responsibility: Each service handles one domain
  • Dependency Injection: Use Context.Tag for all dependencies
  • Error Consistency: Transform all errors to domain-specific types
  • Telemetry: Add spans for observability

2. Store Integration

  • Reactive State: Use Svelte 5 Runes for component reactivity
  • Effect Operations: Keep async logic in Effect operations
  • Error Handling: Provide loading and error states
  • Cache Management: Implement TTL-based caching

3. Component Usage

  • Composables: Use composables to abstract Effect operations
  • Error Boundaries: Implement proper error boundaries
  • Loading States: Always provide loading feedback
  • Clean Architecture: Keep Effect logic out of components

4. Testing

  • Mock Dependencies: Use Layer.succeed for mocking
  • Test Isolation: Each test should have independent state
  • Error Testing: Test both success and failure scenarios
  • Integration Testing: Test service interactions

Common Patterns Reference

Service Creation Pattern

export const makeMyService = Effect.gen(function* () {
  const dependency = yield* DependencyService;

  const operation = (input: Input) =>
    Effect.gen(function* () {
      // Implementation
    }).pipe(
      Effect.mapError((error) => MyError.fromError(error, context)),
      Effect.withSpan("MyService.operation"),
    );

  return { operation };
});

Store Operation Pattern

const storeOperation = Effect.gen(function* () {
  const service = yield* MyService;

  isLoading = true;
  error = null;

  const result = yield* service.operation(input);
  entities = processResult(result);

  isLoading = false;
  return result;
}).pipe(
  Effect.catchAll((err) =>
    Effect.sync(() => {
      error = err.message;
      isLoading = false;
      throw err;
    }),
  ),
);

Error Handling Pattern

const operation = Effect.gen(function* () {
  // Operation logic
}).pipe(
  Effect.mapError((error) => DomainError.fromError(error, context)),
  Effect.retry(Schedule.exponential("1 second")),
  Effect.catchAll((error) => Effect.succeed(fallbackValue)),
  Effect.withSpan("operation-name"),
);

Next Steps

  1. Study Examples: Examine service-types, requests, and offers services for complete examples
  2. Practice Patterns: Try implementing a simple service following these patterns
  3. Read Effect Docs: Visit effect.website for comprehensive documentation
  4. Join Community: Connect with Effect-TS community for advanced patterns

This primer provides the foundation for working with Effect-TS in our project. The patterns shown here are used consistently across all domains to ensure maintainable, type-safe, and composable code.

Testing Guide

Comprehensive testing strategy for the Requests & Offers application covering all layers of the 7-layer Effect-TS architecture.

Testing Architecture

Testing Stack

  • Backend: Rust unit tests + Sweettest multi-agent tests
  • Frontend: Vitest + @effect/vitest for Effect-TS testing
  • E2E: Playwright with Holochain integration
  • Coverage: All 343 unit tests passing across 20 test files with no unhandled Effect errors

Testing Philosophy

  • Layer-Specific: Each layer tested independently
  • Effect-TS Integration: Proper testing of Effect operations with dependency injection
  • Mock Implementations: Consistent mocking strategies across all domains
  • Error Boundary Testing: Comprehensive testing of tagged error handling

Frontend Testing

Unit Testing

Testing Effect-TS Services

import { describe, it, expect, beforeEach } from "vitest";
import { Effect, Layer } from "effect";
import { ServiceTypeService, makeServiceTypeService } from "$lib/services";
import { HolochainClientService } from "$lib/services";

describe("ServiceTypeService", () => {
  it("should create service type with proper error handling", async () => {
    const MockHolochainClient = Layer.succeed(HolochainClientService, {
      callZome: () => Effect.succeed(mockRecord),
    });

    const TestServiceTypeServiceLive = Layer.provide(
      ServiceTypeServiceLive,
      MockHolochainClient,
    );

    const result = await Effect.runPromise(
      Effect.gen(function* () {
        const service = yield* ServiceTypeService;
        return yield* service.createServiceType(mockInput);
      }).pipe(Effect.provide(TestServiceTypeServiceLive)),
    );

    expect(result.name).toBe(mockInput.name);
  });
});

Testing Store Helper Functions

describe("ServiceTypes Store - Helper Functions", () => {
  let store: ReturnType<typeof createServiceTypesStore>;

  beforeEach(() => {
    store = createServiceTypesStore();
  });

  it("should implement all 9 helper functions", () => {
    expect(typeof store.createUIEntity).toBe("function");
    expect(typeof store.mapRecordsToUIEntities).toBe("function");
    expect(typeof store.syncEntityWithCache).toBe("function");
    expect(typeof store.eventEmitters).toBe("object");
    expect(typeof store.fetchEntities).toBe("object"); // Effect object
    expect(typeof store.createEntity).toBe("function");
    expect(typeof store.updateEntity).toBe("function");
    expect(typeof store.updateEntityStatus).toBe("function");
    expect(typeof store.processMultipleRecordCollections).toBe("function");
  });

  it("should create UI entity correctly", () => {
    const mockRecord = createMockRecord();
    const entity = store.createUIEntity(mockRecord);

    expect(entity).toBeDefined();
    expect(entity?.hash).toBe(mockRecord.signed_action.hashed.hash);
    expect(entity?.name).toBe("Test Service Type");
  });

  it("should map records to UI entities with null safety", () => {
    const mockRecords = [createMockRecord(), createInvalidRecord()];
    const entities = store.mapRecordsToUIEntities(mockRecords);

    expect(entities).toHaveLength(1); // Invalid record filtered out
    expect(entities[0].name).toBe("Test Service Type");
  });
});

Testing Composables

describe("useServiceTypesManagement", () => {
  it("should provide proper error boundaries", () => {
    const { loadingErrorBoundary, createErrorBoundary } =
      useServiceTypesManagement();

    expect(loadingErrorBoundary.state.error).toBeNull();
    expect(createErrorBoundary.state.error).toBeNull();
    expect(typeof loadingErrorBoundary.execute).toBe("function");
    expect(typeof createErrorBoundary.clearError).toBe("function");
  });

  it("should handle entity creation with error boundaries", async () => {
    const { operations, createErrorBoundary } = useServiceTypesManagement();

    const mockInput = { name: "Test", description: "Test Description" };
    await operations.createEntity(mockInput);

    expect(createErrorBoundary.state.error).toBeNull();
  });
});

Integration Testing

Component Integration

// tests/integration/components/ServiceTypeGrid.test.ts
import { render, fireEvent } from "@testing-library/svelte";
import ServiceTypeGrid from "$lib/components/service-types/ServiceTypeGrid.svelte";

describe("ServiceTypeGrid Integration", () => {
  it("should load and display service types", async () => {
    const { getByText, findByText } = render(ServiceTypeGrid);

    // Wait for data to load
    await findByText("Test Service Type");

    expect(getByText("Test Service Type")).toBeInTheDocument();
  });

  it("should handle create operation", async () => {
    const { getByText, getByLabelText } = render(ServiceTypeGrid);

    // Trigger create form
    fireEvent.click(getByText("Create Service Type"));

    // Fill form
    fireEvent.input(getByLabelText("Name"), {
      target: { value: "New Service" },
    });
    fireEvent.input(getByLabelText("Description"), {
      target: { value: "Description" },
    });

    // Submit
    fireEvent.click(getByText("Create"));

    // Verify creation
    await findByText("New Service");
  });
});

E2E Testing

The e2e test suite uses Playwright against a real Holochain conductor. Tests run against a single-agent sandbox, which is sufficient for UI verification and basic zome integration.

The suite is intentionally lean — one spec file, core-flows.spec.ts, driving a single ordered story with test.describe.serial and seeding its own data via direct zome calls rather than relying on shared fixtures:

  1. Connects to the conductor and confirms the home page shows the "Create Profile" call to action for a fresh agent.
  2. Creates a profile via callZome and verifies it renders on /user.
  3. Accepts its own profile — the first user in the sandbox auto-registers as network administrator (dev-mode bootstrap), so it can approve itself.
  4. Seeds a service type and a medium of exchange via callZome (admin-created entries are auto-approved) and confirms both appear on their admin list pages.
  5. Drives the real /offers/create and /requests/create forms and confirms both listings show the new entry.

Global setup starts exactly one conductor for the whole Playwright run, so every test shares one agent identity — users_organizations::create_user rejects a second call for an already-registered agent, which is why this is one serial file rather than several independent spec files that would each try to register their own user.

Prerequisites

# Must be inside the Nix shell
nix develop

# Build the hApp before the first run, or after any zome change
bun build:happ

Running E2E Tests

# From ui/ directory, inside nix develop
bun test:e2e           # full suite
bun test:e2e:verbose   # full suite with conductor stderr visible

Set E2E_VERBOSE=true to stream conductor stderr to the terminal for startup debugging. If the Vite dev server is already running (bun start), tests reuse it automatically.

How It Works

The test infrastructure owns the full conductor lifecycle via tests/setup/conductor-manager.ts:

  1. Global setup — generates a Holochain sandbox in test-e2e-workdir/, patches the conductor config to a fixed admin port (55000), starts the conductor, installs the hApp via AdminWebsocket, attaches an app interface on a dynamic port, and issues an authentication token.
  2. Environment handoff — app port and base64-encoded token are written to .test-env.json and injected into process.env (HC_APP_PORT, HC_APP_TOKEN) for all test workers.
  3. Test helperstests/e2e/utils/e2e-helpers.ts exposes:
    • holochainUrl(path) — builds the full URL including ?hcPort=&hcToken= params that HolochainClientService reads to connect (mirrors what hc-spin injects)
    • gotoApp(page, path) — navigates and waits for the connection indicator to clear
    • createTestClient() — opens an AppWebsocket with the same token as the browser, so zome calls from test code are visible in the UI immediately (same agent, no DHT gossip delay)
    • callZome(client, zome, fn, payload) — typed zome call helper for test data seeding
  4. Global teardown — stops the conductor and removes test-e2e-workdir/.

Writing E2E Tests

Always use gotoApp rather than page.goto directly — it injects the Holochain connection params automatically:

import { test, expect } from '@playwright/test';
import { gotoApp, createTestClient, callZome } from '../utils/e2e-helpers.js';

test.describe('My feature', () => {
  test('should display seeded data', async ({ page }) => {
    const client = await createTestClient();
    await callZome(client, 'my_zome', 'create_entry', { name: 'Test' });

    await gotoApp(page, '/my-route');
    await expect(page.locator('text=Test')).toBeVisible();

    await client.client.close();
  });
});

Backend Testing

Zome Unit Tests

#![allow(unused)]
fn main() {
// dnas/requests_and_offers/zomes/coordinator/service_types/src/tests.rs
#[cfg(test)]
mod tests {
    use super::*;
    use hdk::prelude::*;

    #[test]
    fn test_create_service_type() {
        let input = CreateServiceTypeInput {
            name: "Test Service".to_string(),
            description: "Test Description".to_string(),
            tags: vec!["test".to_string()],
        };

        let result = create_service_type(input);
        assert!(result.is_ok());

        let record = result.unwrap();
        let service_type: ServiceType = record.entry().to_app_option().unwrap().unwrap();
        assert_eq!(service_type.name, "Test Service");
        assert_eq!(service_type.status, ServiceTypeStatus::Pending);
    }

    #[test]
    fn test_approve_service_type() {
        // Create service type first
        let create_input = CreateServiceTypeInput {
            name: "Test Service".to_string(),
            description: "Test Description".to_string(),
            tags: vec!["test".to_string()],
        };

        let create_result = create_service_type(create_input).unwrap();
        let service_type_hash = create_result.signed_action.hashed.hash.clone();

        // Approve it
        let approve_result = approve_service_type(service_type_hash);
        assert!(approve_result.is_ok());

        let updated_record = approve_result.unwrap();
        let updated_service_type: ServiceType = updated_record.entry().to_app_option().unwrap().unwrap();
        assert_eq!(updated_service_type.status, ServiceTypeStatus::Approved);
    }
}
}

Sweettest Multi-Agent Tests

Choosing the right setup helper

Two multi-agent setup functions exist in tests/sweettest/src/common/conductors.rs:

  • setup_two_agents_with_alice_as_progenitor() — Alice's key is pre-generated and embedded in DNA properties before app installation. Alice becomes admin automatically when she calls create_user. Use this for any test that requires Alice to be an administrator or that tests progenitor-gated behavior.
  • setup_two_agents() — uses a hardcoded key (HARDCODED_PROGENITOR_PUBKEY) that neither Alice nor Bob matches. Neither agent is auto-registered as admin. Use this when admin status is irrelevant to the test.

See The Progenitor Pattern for the full test-writing guide.

#![allow(unused)]
fn main() {
// tests/sweettest/tests/service_types.rs
use holochain::prelude::*;
use holochain::sweettest::*;
use requests_and_offers_sweettest::common::*;

#[tokio::test(flavor = "multi_thread")]
async fn basic_service_type_crud_operations() {
    let (conductors, alice, bob) = setup_two_agents_with_alice_as_progenitor().await;

    // Alice is auto-registered as admin via progenitor init callback
    conductors[0]
        .call::<_, Record>(&alice.zome("users_organizations"), "create_user", sample_user("Alice"))
        .await;
    conductors[1]
        .call::<_, Record>(&bob.zome("users_organizations"), "create_user", sample_user("Bob"))
        .await;

    await_consistency(15, [&alice, &bob]).await.unwrap();

    // Alice creates a service type (admin-only; auto-approved)
    let st_record: Record = conductors[0]
        .call(
            &alice.zome("service_types"),
            "create_service_type",
            sample_service_type("Web Development"),
        )
        .await;

    let st_hash = st_record.signed_action.hashed.hash.clone();

    await_consistency(15, [&alice, &bob]).await.unwrap();

    // Bob reads the service type
    let st_from_bob: Option<Record> = conductors[1]
        .call(&bob.zome("service_types"), "get_service_type", st_hash.clone())
        .await;
    assert!(st_from_bob.is_some());

    let st: ServiceType = st_from_bob
        .unwrap()
        .entry()
        .to_app_option()
        .unwrap()
        .expect("entry");
    assert_eq!(st.name, "Web Development");

    // Get all approved service types
    let all_types: Vec<Record> = conductors[0]
        .call(&alice.zome("service_types"), "get_approved_service_types", ())
        .await;
    assert!(!all_types.is_empty());
}
}

Testing Utilities

Mock Data Factories

// tests/utils/factories.ts
export function createMockRecord(): Record {
  return {
    signed_action: {
      hashed: {
        hash: "test-hash-123",
        content: {
          timestamp: Date.now() * 1000,
        },
      },
    },
    entry: {
      Present: {
        name: "Test Service Type",
        description: "Test Description",
        status: "pending",
      },
    },
  } as any;
}

export function createMockUIServiceType(): UIServiceType {
  return {
    hash: "test-hash-123",
    name: "Test Service Type",
    description: "Test Description",
    status: "pending",
    tags: ["test"],
    createdAt: new Date(),
  };
}

export function createMockServiceTypeService() {
  return {
    createServiceType: vi.fn().mockResolvedValue(createMockUIServiceType()),
    getAllServiceTypes: vi.fn().mockResolvedValue([createMockUIServiceType()]),
    getServiceType: vi.fn().mockResolvedValue(createMockUIServiceType()),
    updateServiceType: vi.fn().mockResolvedValue(createMockUIServiceType()),
    deleteServiceType: vi.fn().mockResolvedValue(undefined),
    approveServiceType: vi.fn().mockResolvedValue(createMockUIServiceType()),
    rejectServiceType: vi.fn().mockResolvedValue(createMockUIServiceType()),
  };
}

Effect-TS Test Utilities

// tests/utils/effect-helpers.ts
import { Effect, Layer } from "effect";

export function createMockLayer<T>(tag: any, implementation: T) {
  return Layer.succeed(tag, implementation);
}

export async function runEffectTest<T, E>(
  effect: Effect.Effect<T, E>,
  layers: Layer.Layer<any, any, any>[] = [],
) {
  const combinedLayers = layers.reduce((acc, layer) => Layer.merge(acc, layer));
  return await Effect.runPromise(effect.pipe(Effect.provide(combinedLayers)));
}

export function expectEffectToSucceed<T, E>(
  effect: Effect.Effect<T, E>,
  layers: Layer.Layer<any, any, any>[] = [],
) {
  return expect(runEffectTest(effect, layers)).resolves;
}

export function expectEffectToFail<T, E>(
  effect: Effect.Effect<T, E>,
  layers: Layer.Layer<any, any, any>[] = [],
) {
  return expect(runEffectTest(effect, layers)).rejects;
}

Test Commands

Running Tests

# All tests
bun test

# Frontend only
bun test:ui

# Unit tests (requires Nix for hREA integration)
nix develop --command bun test:unit

# Integration tests
cd ui && bun test:integration

# E2E tests
cd ui && bun test:e2e

# Domain-specific tests
bun test:service-types
bun test:requests
bun test:offers
bun test:users
bun test:organizations
bun test:administration

# Backend Sweettest tests (requires Nix)
nix develop --command cargo test --manifest-path tests/sweettest/Cargo.toml

Test Configuration

// vitest.config.ts
import { defineConfig } from "vitest/config";
import { sveltekit } from "@sveltejs/kit/vite";

export default defineConfig({
  plugins: [sveltekit()],
  test: {
    include: ["src/**/*.{test,spec}.{js,ts}"],
    environment: "jsdom",
    setupFiles: ["./tests/setup.ts"],
    coverage: {
      reporter: ["text", "json", "html"],
      exclude: ["node_modules/", "tests/"],
    },
  },
});

Testing Best Practices

Do's ✅

  • Test all 9 helper functions for every domain store
  • Use Effect-TS testing patterns with proper dependency injection
  • Test error boundaries and error handling paths
  • Mock external dependencies consistently
  • Test cross-domain interactions in integration tests
  • Use domain-specific test data that reflects real usage
  • Test both happy path and error cases

Don'ts ❌

  • Skip testing helper functions - they're critical for consistency
  • Mix testing patterns - use Effect-TS patterns consistently
  • Test implementation details - focus on behavior
  • Ignore error cases - test error boundaries thoroughly
  • Skip integration tests - they catch real-world issues
  • Use production data - always use controlled test data

Coverage Goals

Current Status

  • ✅ All 343 unit tests passing across 20 test files with no unhandled Effect errors
  • Backend: Comprehensive Sweettest coverage for all domains
  • Frontend: Unit and integration tests for all standardized domains
  • E2E: Basic coverage with Playwright + Holochain integration

Target Coverage

  • Unit Tests: >90% code coverage
  • Integration Tests: All critical user workflows
  • E2E Tests: Core user journeys across all domains
  • Error Boundary Tests: All error scenarios and recovery paths

This testing strategy ensures robust quality assurance across all layers of the 7-layer Effect-TS architecture.

Deployment Guide: Current Status & Working Process

Guide for deploying Requests and Offers across all platforms and repositories


🚨 Current Status: Manual Process Working, Automation Scripts Broken

What Actually Works (v0.1.9 Proven)

  • Manual 7-Step Process: Reliable deployment using documented manual procedures
  • Cross-Platform Builds: All 5 platforms building successfully (macOS ARM64/x64, Windows, Linux DEB/AppImage)
  • Manual GitHub CLI Uploads: More reliable than electron-builder auto-publishing
  • Wildcard Asset Patterns: Dynamic file discovery prevents filename mismatches
  • Cross-Repository Coordination: Established workflow between main repo → kangaroo submodule

What's Currently Broken

  • Automated Deployment Scripts: Non-functional due to path mismatches and structural issues
  • Package.json Deploy Commands: Refer to non-existent scripts
  • Single-Command Automation: Not available - requires manual 7-step process
  • CI/CD Integration: Partial - GitHub Actions work but require manual trigger

🎯 Recommendation

Use the Manual 7-Step Process documented in Release Checklist until automated deployment system is completely reworked. This process is proven, reliable, and successfully delivered v0.1.9.


🏗️ Current Architecture

Repository Structure (Actual)

requests-and-offers/                          # Main Repository
├── workdir/
│   └── requests_and_offers.webhapp     # Built WebHapp package
├── deployment/
│   ├── kangaroo-electron/                  # Submodule: Desktop App
│   │   ├── .github/workflows/
│   │   │   └── release.yaml          # CI/CD Pipeline (WORKING)
│   │   ├── pouch/                       # WebHapp staging
│   │   ├── scripts/                     # Build scripts
│   │   └── dist/                        # Build artifacts
│   └── homebrew/                       # Submodule: macOS Formula
└── documentation/
    └── RELEASE_CHECKLIST.md              # Working manual process

Working Deployment Flow (v0.1.9 Proven)

1. Environment Setup → 2. WebHapp Build → 3. Main Release Creation →
4. Kangaroo Update → 5. CI/CD Trigger → 6. Build Monitoring →
7. Asset Linking

Repository Coordination (Working)

  • Main Repository: happenings-community/requests-and-offers

    • ✅ WebHapp build and packaging (bun package)
    • ✅ GitHub release creation with webhapp asset
    • ✅ Version management (package.json, CHANGELOG.md)
  • Kangaroo Submodule: deployment/kangaroo-electron

    • ✅ Cross-platform builds via GitHub Actions
    • ✅ Manual GitHub CLI uploads (reliable pattern)
    • ✅ Wildcard file discovery (find dist -name "*.dmg")
    • ✅ Build monitoring and validation
  • Homebrew Submodule: deployment/homebrew

    • ✅ Formula updates with SHA256 checksums
    • ✅ Manual commit and push workflow

What's Missing (Broken Automation)

❌ scripts/deployment/deploy.sh               # Does not exist
❌ scripts/deployment/lib/validation.sh        # Does not exist
❌ scripts/deployment/lib/version-manager.sh     # Does not exist
❌ scripts/deployment/lib/webapp-builder.sh      # Does not exist
❌ scripts/deployment/lib/kangaroo-deployer.sh   # Does not exist
❌ scripts/deployment/lib/homebrew-updater.sh    # Does not exist
❌ package.json deploy commands                # Refer to missing scripts

🛠️ Current Setup & Prerequisites

Required Tools

# Essential tools (all working)
git --version
gh --version        # GitHub CLI (authenticated)
bun --version
nix --version      # Required for zome compilation

# Authentication check
gh auth status      # Must show write access to both repositories

Repository Setup (Working)

The project uses git submodules for unified management:

# Clone main repository with submodules
git clone --recurse-submodules https://github.com/happenings-community/requests-and-offers.git
cd requests-and-offers

# Initialize submodules in existing clone
git submodule update --init --recursive

# Update submodules to latest versions
git submodule update --remote

# Verify submodule status
git submodule status

Working Submodule Structure:

  • Main Repository: happenings-community/requests-and-offers (root)
  • Kangaroo Repository: deployment/kangaroo-electron (submodule) ✅
  • Homebrew Repository: deployment/homebrew (submodule) ✅

Pre-Flight Checks (Working)

# From main repository root
cd /home/soushi888/Projets/Holochain/requests-and-offers

# Verify clean working directory
git status

# Check submodules are initialized
ls -la deployment/kangaroo-electron/.git
ls -la deployment/homebrew/.git

# Verify GitHub CLI access
gh auth status

# Test Nix environment (for zome builds)
nix develop --command "echo 'Nix shell working'"

🚀 Current Working Deployment Process

Overview

The following manual 7-step process is proven to work reliably and successfully delivered v0.1.9. This should be used until automated deployment system is completely reworked.

Step 1: Environment Setup & Preparation

# Navigate to main repository
cd /home/soushi888/Projets/Holochain/requests-and-offers

# Verify clean working directory
git status

# Check submodules are initialized
git submodule status

# Update submodules if needed
git submodule update --init --recursive

# Verify GitHub CLI access
gh auth status

Step 2: WebHapp Build

# Enter Nix shell for zome compilation
nix develop

# Build WebHapp package
bun package

# Verify webhapp was created successfully
ls -la workdir/requests_and_offers.webhapp

# Ensure it was built in test mode (no dev features)
# The build should be ~5-10MB for test mode

Step 3: Main Repository Release

# Create git tag for new version
git tag v0.1.X

# Create GitHub release with webhapp
gh release create v0.1.X \
  --title "🚀 Requests and Offers v0.1.X" \
  --notes "### What's New

[Feature highlights from CHANGELOG.md]

### Desktop Apps 📱
🔄 Building cross-platform desktop applications...

### Installation
[WebApp installation instructions]

### Technical Specifications
- **Network**: Holostrap alpha network
- **Holochain Version**: 0.5.5
- **UI Framework**: SvelteKit + Svelte 5

---

⚠️ **Note**: Desktop applications are currently building. Download links will be added automatically when build process completes."

# Upload webhapp as first asset
gh release upload v0.1.X workdir/requests_and_offers.webhapp --clobber

Step 4: Kangaroo Repository Update

# Navigate to kangaroo submodule
cd deployment/kangaroo-electron

# Copy fresh webhapp to kangaroo pouch directory
cp ../../workdir/requests_and_offers.webhapp pouch/

# Verify version files are consistent
# Check package.json and kangaroo.config.ts have correct version

# Commit webhapp update (this triggers CI/CD)
git add pouch/requests_and_offers.webhapp
git commit -m "build: update webhapp for v0.1.X release"

# Push to release branch to trigger GitHub Actions
git checkout release
git merge main --no-edit
git push origin release

# Return to main repository root
cd ../..

Step 5: Build Monitoring

# Monitor GitHub Actions progress
gh run list --limit=5 --repo happenings-community/kangaroo-electron

# View specific run logs if needed
gh run view [RUN_ID] --log --repo happenings-community/kangaroo-electron

# Expected platforms: macOS ARM64, macOS x64, Windows, Linux DEB/AppImage
# Build time: ~2-4 minutes per platform
# Total time: ~10-15 minutes for all platforms

Step 6: Asset Upload & Verification

# After CI/CD completes, verify all assets exist
gh release view v0.1.X --repo happenings-community/kangaroo-electron

# Expected assets:
# - Requests-and-Offers-{version}-arm64-mac.dmg
# - Requests-and-Offers-{version}-x64-mac.dmg
# - Requests-and-Offers-{version}-x64-win.exe
# - Requests-and-Offers-{version}-x64-linux.deb
# - Requests-and-Offers-{version}.AppImage
# - checksums.txt

Step 7: Release Notes Finalization

# Update main repository release with working desktop links
gh release edit v0.1.X \
  --notes "### What's New

[Features from CHANGELOG.md]

### Desktop Apps 📱

#### macOS
- **Apple Silicon**: [Download .dmg](https://github.com/happenings-community/kangaroo-electron/releases/download/v0.1.X/Requests-and-Offers-0.1.X-arm64-mac.dmg)
- **Intel**: [Download .dmg](https://github.com/happenings-community/kangaroo-electron/releases/download/v0.1.X/Requests-and-Offers-0.1.X-x64-mac.dmg)

#### Windows
- [Download .exe](https://github.com/happenings-community/kangaroo-electron/releases/download/v0.1.X/Requests-and-Offers-0.1.X-x64-win.exe)

#### Linux
- **Debian/Ubuntu**: [Download .deb](https://github.com/happenings-community/kangaroo-electron/releases/download/v0.1.X/Requests-and-Offers-0.1.X-x64-linux.deb)
- **Universal Portable**: [Download AppImage](https://github.com/happenings-community/kangaroo-electron/releases/download/v0.1.X/Requests-and-Offers-0.1.X.AppImage)

### Installation
[Platform-specific installation instructions]

### Technical Specifications
- **Network**: Holostrap alpha network
- **Desktop App**: Cross-platform Electron application
- **WebApp**: Holochain hApp with SvelteKit frontend"

# Test download links work
curl -I https://github.com/happenings-community/kangaroo-electron/releases/download/v0.1.X/Requests-and-Offers-0.1.X-arm64-mac.dmg

🔧 Working Patterns from v0.1.9 Release

Manual GitHub CLI Uploads (Proven Pattern)

Key Discovery: Manual GitHub CLI uploads are more reliable than electron-builder auto-publishing for branch builds.

Working Asset Upload Pattern:

# From .github/workflows/release.yaml (lines 73-85)
# macOS Example (reliable):
- name: build and upload app WITHOUT code signing (macOS x86)
  run: |
    yarn build:mac-x64
    ls dist
    # Upload any .dmg file found in dist directory
    find dist -name "*.dmg" -exec gh release upload "v${{ steps.kangarooConfig.outputs.APP_VERSION }}" {} \;

Advantages over electron-builder auto-publishing:

  • ✅ Works reliably on branch builds (not just main releases)
  • ✅ Dynamic file discovery handles naming variations
  • ✅ No dependency on publish configuration settings
  • ✅ Clear error handling and visibility

Wildcard File Discovery

Problem Solved: Hardcoded filenames don't match actual electron-builder output.

Solution Pattern:

# Instead of this (fails):
gh release upload "v0.1.9" "dist/Requests-and-Offers-0.1.9-x64-mac.dmg"

# Use this (works):
find dist -name "*.dmg" -exec gh release upload "v0.1.9" {} \;
find dist -name "*.exe" -exec gh release upload "v0.1.9" {} \;

Benefits:

  • ✅ Handles filename variations automatically
  • ✅ Works across all platforms
  • ✅ No need to know exact naming patterns
  • ✅ Resilient to electron-builder version changes

CI/CD Workflow Analysis

The actual working CI/CD (.github/workflows/release.yaml) shows these patterns:

  1. Manual Upload Strategy: Lines 74, 85, 103, 118, 133, 149 all use manual gh release upload commands
  2. Wildcard Discovery: Uses find dist -name "*.dmg" patterns (lines 74, 85)
  3. Cross-Platform Matrix: Builds on windows-2022, macos-13, macos-latest, ubuntu-22.04 (line 12)
  4. Asset Generation: All platforms generate binaries and checksums automatically
  5. Build Verification: Each platform uploads assets with --clobber flag to handle duplicates

Cross-Repository Coordination Flow

Working Synchronization:

  1. Main → Kangaroo: Copy webhapp to deployment/kangaroo-electron/pouch/
  2. Version Consistency: Update package.json and kangaroo.config.ts in sync
  3. Trigger CI/CD: Commit to kangaroo release branch triggers GitHub Actions
  4. Asset Linking: Main release notes link to kangaroo release assets
  5. Repository Communication: Both repos reference each other

Critical Success Factors:

  • ✅ Fresh webhapp copied before triggering builds
  • ✅ Version numbers synchronized across all files
  • ✅ Correct branch management (main → release → main)
  • ✅ Proper GitHub CLI authentication and permissions

🚨 Troubleshooting Common Issues

Electron-Builder Publishing Failures

Problem: Builds complete successfully but assets don't upload to GitHub release Root Cause: electron-builder's publish configuration is disabled for branch builds Working Solution: Manual GitHub CLI uploads (as used in v0.1.9)

# Instead of relying on electron-builder publish:
# 1. Build each platform
yarn build:mac-x64
yarn build:mac-arm64
yarn build:win
yarn build:linux

# 2. Upload with wildcard discovery
find dist -name "*.dmg" -exec gh release upload "v0.1.9" {} \;
find dist -name "*.exe" -exec gh release upload "v0.1.9" {} \;
find dist -name "*.deb" -exec gh release upload "v0.1.9" {} \;
find dist -name "*.AppImage" -exec gh release upload "v0.1.9" {} \;

Filename Mismatch Issues

Problem: Upload commands fail because generated filenames don't match expected patterns Root Cause: electron-builder artifact naming differs from hardcoded expectations

Solution: Use dynamic file discovery with wildcards

# Discover actual files before upload
ls dist/
find dist -name "*.dmg" -exec echo "Found: {}" \;

# Upload with dynamic discovery
find dist -name "*.dmg" -exec gh release upload "v0.1.9" {} \;

Asset Upload Recovery

If assets fail to upload completely:

# 1. Verify release exists
gh release view v0.1.X

# 2. Check build completion
gh run list --repo happenings-community/kangaroo-electron

# 3. Re-trigger builds if needed
cd deployment/kangaroo-electron
echo "retrigger $(date)" > .trigger
git add .trigger
git commit -m "trigger: rebuild v0.1.X"
git push origin release

Platform-Specific Issues

macOS Builds

  • Issue: Code signing certificate conflicts
  • Solution: Set MACOS_CODE_SIGNING=false in kangaroo config for unsigned builds
  • Verification: Check .github/workflows/release.yaml conditions (lines 56-58)

Windows Builds

  • Issue: EV certificate setup complexity
  • Solution: Use unsigned builds for testing, enable signing for production
  • Reference: Lines 151-170 in release.yaml show AzureSignTool integration

Linux Builds

  • Issue: DEB post-install script failures
  • Solution: Check scripts/extend-deb-postinst.mjs (referenced line 131)
  • Verification: Ensure AppImage includes proper desktop integration

📋 Cross-Repository Workflow

Git Submodule Management

Current Working Structure:

# Verify submodule status
git submodule status

# Update to latest
git submodule update --remote deployment/kangaroo-electron
git submodule update --remote deployment/homebrew

# Initialize if needed
git submodule update --init --recursive

Version Synchronization

Critical Files to Keep in Sync:

  1. package.json (main repository) - Source of truth
  2. deployment/kangaroo-electron/package.json - Must match main
  3. deployment/kangaroo-electron/kangaroo.config.ts - Electron app version
  4. CHANGELOG.md - Documentation reference

Sync Process:

# Update main version
# Edit package.json: "version": "0.1.X"

# Update kangaroo version
cd deployment/kangaroo-electron
# Edit package.json: "version": "0.1.X"
# Edit kangaroo.config.ts: version: '0.1.X'

# Commit all changes
git add package.json kangaroo.config.ts
git commit -m "build: sync versions to v0.1.X"

Branch Management Strategy

Working Branch Strategy:

  • Main Repository: Work on dev branch (default); promote to main for releases only, then tag
  • Kangaroo Repository: Use release branch for CI/CD triggers (its own main is the development branch)
  • Homebrew Repository: Use main branch for formula updates

Synchronization Commands:

# For kangaroo submodule
cd deployment/kangaroo-electron
git checkout main
git pull origin main
git checkout release
git merge main --no-edit
git push origin release

# Return to main repository
cd ../..

📊 Performance & Metrics

v0.1.9 Success Metrics (Reference)

Build Performance:

  • Total Release Time: ~2.5 hours (including troubleshooting)
  • Build Success Rate: 100% (5/5 platforms)
  • Platform Build Times:
    • macOS ARM64: 1m46s
    • macOS x64: 3m2s
    • Windows x64: 2m54s
    • Linux x64: ~4m (includes post-install scripts)

Asset Upload Success:

  • Upload Method: Manual GitHub CLI commands (reliable)
  • File Discovery: Wildcard patterns (handles naming variations)
  • Retry Count: 1 retry needed (for macOS upload fixes)

Quality Metrics

Success Criteria Achieved:

  • ✅ All platform builds complete successfully
  • ✅ All assets uploaded and downloadable
  • ✅ Release notes complete and accurate with working links
  • ✅ Download links tested and working for all platforms
  • ✅ Branches synchronized between repositories
  • ✅ Basic functionality verified in released app

Key Success Factors

  1. Manual Process Reliability: Step-by-step execution with verification
  2. Wildcard File Discovery: Eliminates filename mismatch failures
  3. Cross-Platform CI/CD: GitHub Actions working consistently
  4. Repository Communication: Proper linking between main and kangaroo repos
  5. Asset Upload Strategy: Manual GitHub CLI more reliable than auto-publishing

🔮 Future Automation Development

What Needs to Be Built

To transition from manual to automated deployment, the following components need development:

  1. Script Development:

    • Create scripts/deployment/deploy.sh orchestrator
    • Implement version management system
    • Add validation and rollback capabilities
  2. Path Configuration:

    • Dynamic path resolution for repository structure
    • Environment-specific configuration handling
    • Cross-repository synchronization
  3. Asset Upload Automation:

    • Replicate working manual GitHub CLI patterns
    • Implement wildcard file discovery
    • Add error handling and retry logic
  4. CI/CD Integration:

    • Build monitoring and validation
    • Automated link generation for release notes
    • Cross-platform build coordination
  5. Homebrew Automation:

    • SHA256 checksum calculation
    • Formula updates and testing
    • Git commit and push automation

Development Approach

Phase 1: Working Script Extraction

  • Extract proven patterns from manual v0.1.9 process
  • Document reliable commands and error handling
  • Test with actual repository structure

Phase 2: Automation Scripting

  • Convert manual commands to automated scripts
  • Add comprehensive validation and error recovery
  • Implement rollback and backup capabilities

Phase 3: Integration Testing

  • End-to-end testing with real repositories
  • CI/CD integration and monitoring
  • Performance optimization and reliability testing

📞 Quick Reference

Working Commands Summary

# Complete deployment (7 steps)
# 1. Environment setup
cd /home/soushi888/Projets/Holochain/requests-and-offers
git status && git submodule status

# 2. WebHapp build
nix develop --command "bun package"

# 3. Main release
git tag v0.1.X
gh release create v0.1.X --title "Release Title" --notes "Release notes"
gh release upload v0.1.X workdir/requests_and_offers.webhapp

# 4. Kangaroo update
cd deployment/kangaroo-electron
cp ../../workdir/requests_and_offers.webhapp pouch/
git add pouch/requests_and_offers.webhapp
git commit -m "build: update webhapp for v0.1.X"
git checkout release && git merge main --no-edit && git push origin release

# 5. Build monitoring
gh run list --repo happenings-community/kangaroo-electron

# 6. Asset verification
gh release view v0.1.X --repo happenings-community/kangaroo-electron

# 7. Release notes update
gh release edit v0.1.X --notes "Updated notes with desktop links"

Essential File Locations

# Main repository
/home/soushi888/Projets/Holochain/requests-and-offers/package.json
/home/soushi888/Projets/Holochain/requests-and-offers/workdir/requests_and_offers.webhapp
/home/soushi888/Projets/Holochain/requests-and-offers/CHANGELOG.md

# Kangaroo submodule
/home/soushi888/Projets/Holochain/requests-and-offers/deployment/kangaroo-electron/.github/workflows/release.yaml
/home/soushi888/Projets/Holochain/requests-and-offers/deployment/kangaroo-electron/kangaroo.config.ts
/home/soushi888/Projets/Holochain/requests-and-offers/deployment/kangaroo-electron/package.json

Troubleshooting Commands

# Check git authentication
gh auth status

# Verify submodule status
git submodule status

# Monitor builds
gh run list --repo happenings-community/kangaroo-electron

# View build logs
gh run view [RUN_ID] --log --repo happenings-community/kangaroo-electron

# Check releases
gh release view v0.1.X --repo happenings-community/requests-and-offers
gh release view v0.1.X --repo happenings-community/kangaroo-electron

This guide reflects the current working reality as of v0.1.9. Use the manual 7-step process until automated deployment system is completely rebuilt with working patterns proven in this release.

Docker Compose Configurations for Holochain Bootstrap Server

This document provides different Docker Compose configurations for setting up a Holochain bootstrap server on your Digital Ocean droplet (1 vCPU, 512MB RAM, 10GB SSD).

Table of Contents

  1. Quick Start Testing
  2. Production with SSL
  3. Domain-Based Setup
  4. Complete Development Stack
  5. Resource-Optimized
  6. High Availability
  7. Monitoring & Debugging

1. Quick Start Testing

Best for: Initial testing, 10-20 users, minimal setup time

Resources: ~200MB RAM, ~50MB CPU

# docker-compose.yml
version: '3.8'

services:
  bootstrap:
    image: ghcr.io/holochain/kitsune2_bootstrap_srv:v0.2.16
    command:
      - kitsune2-bootstrap-srv
      - --development
      - --listen
      - "[::]:8080"
    environment:
      - RUST_LOG=info
    ports:
      - "8080:8080"
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 128M
        reservations:
          memory: 64M

Usage:

# Start server
docker-compose up -d

# Test
curl -X GET http://142.93.41.235:8080/

# Kangaroo config:
bootstrapUrl: 'http://142.93.41.235:8080/'

Pros: ✅ Simple, ✅ Fast setup, ✅ Low resource usage Cons: ❌ No SSL, ❌ HTTP only, ❌ Not production-ready


2. Production with SSL

Best for: Testing with HTTPS, SSL certificate validation

Resources: ~250MB RAM, ~75MB CPU

# docker-compose.yml
version: '3.8'

services:
  bootstrap:
    image: ghcr.io/holochain/kitsune2_bootstrap_srv:v0.2.16
    command:
      - kitsune2-bootstrap-srv
      - --production
      - --listen
      - "[::]:443"
      - --tls-cert
      - /certs/server.crt
      - --tls-key
      - /certs/server.key
    environment:
      - RUST_LOG=info
    ports:
      - "443:443"
    volumes:
      - ./certs:/certs:ro
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 160M
        reservations:
          memory: 80M

  # Self-signed certificate generator (run once)
  cert-generator:
    image: alpine:latest
    command: |
      sh -c "
        apk add --no-cache openssl
        mkdir -p /certs
        openssl req -x509 -newkey rsa:2048 -keyout /certs/server.key -out /certs/server.crt -days 365 -nodes -subj '/CN=142.93.41.235'
        chown 65535:65535 /certs/*
        chmod 600 /certs/server.key
        chmod 644 /certs/server.crt
        echo 'Self-signed certificates generated for 142.93.41.235'
        echo 'Use: curl -k https://142.93.41.235:443/'
      "
    volumes:
      - ./certs:/certs
    profiles:
      - setup

Usage:

# Generate certificates (run once)
docker-compose --profile setup up cert-generator

# Start bootstrap server
docker-compose up -d bootstrap

# Test (accept self-signed cert)
curl -k -X GET https://142.93.41.235:443/

# Kangaroo config:
bootstrapUrl: 'https://142.93.41.235:443/'

Pros: ✅ HTTPS support, ✅ Still uses IP address Cons: ❌ Self-signed cert (browser warnings), ❌ Extra setup step


3. Domain-Based Setup

Best for: Production deployment with proper SSL

Resources: ~300MB RAM, ~100MB CPU

# docker-compose.yml
version: '3.8'

services:
  bootstrap:
    image: ghcr.io/holochain/kitsune2_bootstrap_srv:v0.2.16
    command:
      - kitsune2-bootstrap-srv
      - --production
      - --listen
      - "[::]:443"
      - --tls-cert
      - /etc/letsencrypt/live/bootstrap.yourdomain.com/fullchain.pem
      - --tls-key
      - /etc/letsencrypt/live/bootstrap.yourdomain.com/privkey.pem
    environment:
      - RUST_LOG=info
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - /etc/letsencrypt:/etc/letsencrypt:ro
      - /var/www/html:/var/www/html:ro
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 200M
        reservations:
          memory: 100M

  # Certbot for SSL certificate management
  certbot:
    image: certbot/certbot:latest
    command: |
      sh -c "
        echo 'Setting up SSL certificates...'
        certbot certonly --webroot -w /var/www/html --email your-email@example.com --agree-tos --no-eff-email -d bootstrap.yourdomain.com --non-interactive
        echo 'Certificates obtained successfully'
        echo 'Setting up auto-renewal...'
        echo '0 12 * * * /usr/bin/certbot renew --quiet' | crontab -
      "
    volumes:
      - /etc/letsencrypt:/etc/letsencrypt
      - /var/www/html:/var/www/html
    profiles:
      - ssl

  # Nginx reverse proxy (optional, for better performance)
  nginx:
    image: nginx:alpine
    command: |
      sh -c "
        cat > /etc/nginx/nginx.conf << 'EOF'
events {
    worker_connections 1024;
}
http {
    server {
        listen 80;
        server_name bootstrap.yourdomain.com;
        location /.well-known/acme-challenge/ {
            root /var/www/html;
        }
        location / {
            return 301 https://$server_name$request_uri;
        }
    }
    server {
        listen 443 ssl http2;
        server_name bootstrap.yourdomain.com;
        ssl_certificate /etc/letsencrypt/live/bootstrap.yourdomain.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/bootstrap.yourdomain.com/privkey.pem;
        location / {
            proxy_pass http://bootstrap:8443;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
}
EOF
        nginx -g 'daemon off;'
      "
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /etc/letsencrypt:/etc/letsencrypt:ro
    depends_on:
      - bootstrap
    profiles:
      - nginx

Prerequisites:

  1. Point DNS A record: bootstrap.yourdomain.com → 142.93.41.235
  2. Open ports 80 and 443 in firewall

Usage:

# Initial SSL setup
docker-compose --profile ssl up certbot

# Start with Nginx (recommended)
docker-compose --profile nginx up -d

# Or start without Nginx
docker-compose up -d bootstrap

# Test
curl -X GET https://bootstrap.yourdomain.com/

# Kangaroo config:
bootstrapUrl: 'https://bootstrap.yourdomain.com/'

Pros: ✅ Production-ready, ✅ Proper SSL, ✅ Professional setup Cons: ❌ Requires domain, ❌ More complex setup


4. Complete Development Stack

Best for: Full testing with signal server

Resources: ~350MB RAM, ~150MB CPU

# docker-compose.yml
version: '3.8'

services:
  bootstrap:
    image: ghcr.io/holochain/kitsune2_bootstrap_srv:v0.2.16
    command:
      - kitsune2-bootstrap-srv
      - --development
      - --listen
      - "[::]:8443"
    environment:
      - RUST_LOG=debug
    ports:
      - "8443:8443"
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 128M
        reservations:
          memory: 64M

  # Simple WebSocket signal server
  signal-server:
    image: node:18-alpine
    working_dir: /app
    command: |
      sh -c "
        npm install -g ws
        cat > signal-server.js << 'EOF'
        const WebSocket = require('ws');
        const wss = new WebSocket.Server({
          port: 8080,
          perMessageDeflate: false  # Save CPU/memory
        });

        console.log('Signal server running on port 8080');
        console.log('Memory usage:', process.memoryUsage());

        let clientCount = 0;
        wss.on('connection', (ws) => {
          clientCount++;
          console.log(\`Client connected. Total: \${clientCount}\`);

          ws.on('message', (data) => {
            // Relay to all other clients
            wss.clients.forEach(client => {
              if (client !== ws && client.readyState === WebSocket.OPEN) {
                client.send(data);
              }
            });
          });

          ws.on('close', () => {
            clientCount--;
            console.log(\`Client disconnected. Total: \${clientCount}\`);
          });

          ws.on('error', (error) => {
            console.error('WebSocket error:', error.message);
          });
        });

        // Monitor memory usage
        setInterval(() => {
          const mem = process.memoryUsage();
          if (mem.heapUsed > 50 * 1024 * 1024) { // 50MB warning
            console.warn('High memory usage:', mem);
          }
        }, 30000);
EOF
        node signal-server.js
      "
    ports:
      - "8080:8080"
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 64M
        reservations:
          memory: 32M

  # Monitoring panel
  monitoring:
    image: nginx:alpine
    command: |
      sh -c "
        cat > /usr/share/nginx/html/index.html << 'EOF'
<!DOCTYPE html>
<html>
<head><title>Holochain Bootstrap Status</title></head>
<body>
<h1>Holochain Bootstrap Server Status</h1>
<h2>Services</h2>
<ul>
<li>Bootstrap Server: <a href='http://localhost:8443/'>http://localhost:8443/</a></li>
<li>Signal Server: ws://localhost:8080/</li>
</ul>
<h2>System Info</h2>
<pre id='stats'></pre>
<script>
setInterval(() => {
  fetch('/stats')
    .then(r => r.text())
    .then(data => document.getElementById('stats').textContent = data);
}, 5000);
</script>
</body>
</html>
EOF
        cat > /etc/nginx/nginx.conf << 'EOF'
events { worker_connections 64; }
http {
  server {
    listen 80;
    location / { root /usr/share/nginx/html; }
    location /stats {
      access_log off;
      return 200 'Uptime: $(cat /proc/uptime | cut -d' ' -f1)s\\nMemory: $(free -h | grep Mem | awk '{print $3}')\\nLoad: $(uptime | cut -d',' -f1 | cut -d':' -f5-)';
    }
  }
}
EOF
        nginx -g 'daemon off;'
      "
    ports:
      - "80:80"
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 32M
        reservations:
          memory: 16M

Usage:

# Start all services
docker-compose up -d

# Access monitoring panel
curl http://142.93.41.235/

# Test bootstrap
curl -X GET http://142.93.41.235:8443/

# Test signal server
websocat ws://142.93.41.235:8080/

# Kangaroo config:
bootstrapUrl: 'http://142.93.41.235:8443/'
signalUrl: 'ws://142.93.41.235:8080/'

Pros: ✅ Complete setup, ✅ Monitoring, ✅ Both services Cons: ❌ Higher resource usage, ❌ More complex


5. Resource-Optimized

Best for: Maximum efficiency on limited droplet

Resources: ~180MB RAM, ~50MB CPU

# docker-compose.yml
version: '3.8'

services:
  bootstrap:
    image: ghcr.io/holochain/kitsune2_bootstrap_srv:v0.2.16
    command:
      - kitsune2-bootstrap-srv
      - --development
      - --listen
      - "[::]:8080"
    environment:
      - RUST_LOG=warn  # Minimal logging
    ports:
      - "8080:8080"
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 96M
          cpus: '0.5'
        reservations:
          memory: 48M
    ulimits:
      nofile:
        soft: 1024
        hard: 2048
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

Additional Optimizations:

# Add to /etc/sysctl.conf for system optimization
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
echo 'fs.file-max=65536' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

# Create log rotation for Docker logs
sudo mkdir -p /etc/docker/logrotate.d
cat | sudo tee /etc/docker/logrotate.d/docker-containers << 'EOF'
/var/lib/docker/containers/*/*.log {
    daily
    rotate 3
    compress
    delaycompress
    missingok
    notifempty
    create 0644 root root
}
EOF

Usage:

# Start optimized server
docker-compose up -d

# Monitor resource usage
docker stats --no-stream bootstrap

# Test
curl -X GET http://142.93.41.235:8080/

Pros: ✅ Minimal resource usage, ✅ Optimized for 512MB droplet Cons: ❌ Minimal logging, ❌ Less monitoring data


6. High Availability

Best for: Production redundancy (requires 2+ droplets)

Resources: ~300MB RAM per instance

# docker-compose.yml
version: '3.8'

services:
  bootstrap-primary:
    image: ghcr.io/holochain/kitsune2_bootstrap_srv:v0.2.16
    command:
      - kitsune2-bootstrap-srv
      - --production
      - --listen
      - "[::]:8443"
      - --tls-cert
      - /certs/server.crt
      - --tls-key
      - /certs/server.key
    environment:
      - RUST_LOG=info
    ports:
      - "8443:8443"
    volumes:
      - ./certs:/certs:ro
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 128M
        reservations:
          memory: 64M

  # Nginx load balancer
  nginx:
    image: nginx:alpine
    command: |
      sh -c "
        cat > /etc/nginx/nginx.conf << 'EOF'
events {
    worker_connections 512;
}
http {
    upstream bootstrap_backend {
        server 142.93.41.235:8443 max_fails=3 fail_timeout=30s;
        # Add secondary servers here
        # server SECONDARY_IP:8443 max_fails=3 fail_timeout=30s;
    }
    server {
        listen 443 ssl http2;
        ssl_certificate /certs/server.crt;
        ssl_certificate_key /certs/server.key;
        location / {
            proxy_pass http://bootstrap_backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_connect_timeout 5s;
            proxy_send_timeout 10s;
            proxy_read_timeout 10s;
        }
    }
}
EOF
        nginx -g 'daemon off;'
      "
    ports:
      - "443:443"
    volumes:
      - ./certs:/certs:ro
    depends_on:
      - bootstrap-primary
    restart: unless-stopped

Usage:

# Deploy on primary server
docker-compose up -d

# Deploy same config on secondary server with different IP
# Update nginx config to include secondary servers

Pros: ✅ High availability, ✅ Load balancing Cons: ❌ Requires multiple droplets, ❌ Complex setup


7. Monitoring & Debugging

Best for: Development and troubleshooting

# docker-compose.monitoring.yml
version: '3.8'

services:
  bootstrap:
    image: ghcr.io/holochain/kitsune2_bootstrap_srv:v0.2.16
    command:
      - kitsune2-bootstrap-srv
      - --development
      - --listen
      - "[::]:8080"
    environment:
      - RUST_LOG=debug
    ports:
      - "8080:8080"
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 128M
        reservations:
          memory: 64M

  # System monitoring
  node-exporter:
    image: prom/node-exporter:latest
    command:
      - '--path.procfs=/host/proc'
      - '--path.rootfs=/rootfs'
      - '--path.sysfs=/host/sys'
      - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    ports:
      - "9100:9100"
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 32M

  # Log aggregation
  fluentd:
    image: fluent/fluent-bit:latest
    command: |
      sh -c "
        cat > /fluent-bit/etc/fluent-bit.conf << 'EOF'
        [SERVICE]
            Flush         1
            Log_Level     info
            Daemon        off
            Parsers_File  parsers.conf
            HTTP_Server   On
            HTTP_Listen   0.0.0.0
            HTTP_Port     2020

        [INPUT]
            Name              tail
            Path              /var/log/containers/*.log
            Parser            docker
            Tag               docker.*
            Refresh_Interval  5
            Mem_Buf_Limit     50MB
            Skip_Long_Lines   On

        [OUTPUT]
            Name  stdout
            Match *

        [OUTPUT]
            Name  file
            Match *
            Path  /var/log/fluent-bit
            File  processed.log
EOF
        cat > /fluent-bit/etc/parsers.conf << 'EOF'
        [PARSER]
            Name        docker
            Format      json
            Time_Key    time
            Time_Format %Y-%m-%dT%H:%M:%S.%L
            Time_Keep   On
EOF
        /fluent-bit/bin/fluent-bit --config=/fluent-bit/etc/fluent-bit.conf
      "
    volumes:
      - /var/log:/var/log:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
    ports:
      - "2020:2020"
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 64M

  # Grafana dashboard
  grafana:
    image: grafana/grafana:latest
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
      - GF_USERS_ALLOW_SIGN_UP=false
    ports:
      - "3000:3000"
    volumes:
      - grafana-storage:/var/lib/grafana
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 128M

volumes:
  grafana-storage:

Usage:

# Start with monitoring
docker-compose -f docker-compose.yml -f docker-compose.monitoring.yml up -d

# Access Grafana
# URL: http://142.93.41.235:3000
# Username: admin
# Password: admin

# View metrics
curl http://142.93.41.235:9100/metrics

# View logs
curl http://142.93.41.235:2020/api/v1/metrics

Pros: ✅ Comprehensive monitoring, ✅ Log aggregation, ✅ Metrics dashboard Cons: ❌ High resource usage, ❌ Complex setup


Selection Guide

ConfigurationUse CaseRAM UsageComplexitySSL SupportMonitoring
Quick StartInitial testing~200MB⭐ Simple❌ No❌ No
Production SSLHTTPS testing~250MB⭐⭐ Medium✅ Self-signed❌ No
Domain-BasedProduction~300MB⭐⭐⭐ Complex✅ Let's Encrypt❌ No
Complete StackFull development~350MB⭐⭐⭐ Complex❌ No✅ Basic
Resource-OptimizedLimited droplet~180MB⭐⭐ Medium❌ No❌ No
High AvailabilityProduction redundancy~300MB×N⭐⭐⭐⭐ Very Complex✅ Yes❌ No
MonitoringDevelopment/Debug~400MB⭐⭐⭐ Complex❌ No✅ Full

Recommendations

  1. Start with "Quick Start Testing" for initial validation
  2. Move to "Resource-Optimized" for longer testing periods
  3. Use "Domain-Based Setup" for production deployment
  4. Add "Monitoring & Debugging" when troubleshooting issues
  5. Consider "High Availability" only for critical production needs

Usage Commands

# Start any configuration
docker-compose up -d

# Check status
docker-compose ps

# View logs
docker-compose logs -f bootstrap

# Stop services
docker-compose down

# Remove all data
docker-compose down -v

# Monitor resources
docker stats --no-stream

# Clean up unused resources
docker system prune -f

Created for: Digital Ocean Basic Droplet (1 vCPU, 512MB RAM, 10GB SSD) Expected Capacity: 10-20 concurrent users for testing configurations Production Capacity: 50-100 concurrent users with domain-based setup

Contributing Guide

Thank you for your interest in contributing to the Requests & Offers project! This guide will help you get started with contributing to our codebase.

Code of Conduct

Please read and follow our Code of Conduct to maintain a welcoming and inclusive environment for all contributors.

Getting Started

  1. Fork the repository
  2. Clone your fork
  3. Set up the development environment following our Installation Guide

Development Workflow

1. Branches

  • main: Production-ready code
  • develop: Main development branch
  • Feature branches: feature/your-feature-name
  • Bug fix branches: fix/bug-description

2. Commit Messages

Follow the Conventional Commits specification:

type(scope): description

[optional body]

[optional footer]

Types:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation changes
  • style: Code style changes
  • refactor: Code refactoring
  • test: Adding or modifying tests
  • chore: Maintenance tasks

Scopes:

  • ui: Frontend changes
  • users: Users Organizations feature
  • admin: Administration feature
  • requests: Requests feature
  • organizations: Organizations feature
  • offers: Offers feature
  • status: Status module
  • test: Test infrastructure
  • build: Build system changes

3. Pull Requests

  1. Create a new branch for your changes
  2. Make your changes
  3. Write or update tests
  4. Update documentation
  5. Submit a pull request to the dev branch

4. Development Standards

Code Style

  • Follow Rust style guidelines for zomes
  • Use SvelteKit best practices for frontend
  • Maintain consistent code formatting
  • All code contributions must adhere to the most possible to the standards outlined in .windsurfrules and the specific rule files within .cursor/rules/.
  • To suggest code style changes, please open a GitHub issue or pull request labeled suggestion.

Testing

  • Write unit tests for zome functions
  • Include integration tests for complex features
  • Test frontend components
  • Verify documentation accuracy

Documentation

  • Update relevant documentation
  • Include code examples
  • Maintain cross-references
  • Follow documentation structure

Feature Development Workflow

We follow a systematic approach to feature development that ensures proper testing and integration at each level.

Step 1: DNA Development
  1. Zome Planning

    • Define entry types and validation rules
    • Plan link types and their relationships
    • Document expected behaviors
  2. Zome Implementation

    #![allow(unused)]
    fn main() {
    // Example: New entry type in integrity zome
    #[hdk_entry_helper]
    pub struct NewFeature {
        pub field1: String,
        pub field2: Vec<String>,
    }
    
    // Coordinator zome function
    #[hdk_extern]
    pub fn create_new_feature(input: NewFeature) -> ExternResult<Record> {
        // Implementation
    }
    }
  3. DNA Testing with Sweettest

    #![allow(unused)]
    fn main() {
    // tests/sweettest/tests/new_feature.rs
    #[tokio::test(flavor = "multi_thread")]
    async fn basic_new_feature_crud() {
        let (conductors, alice, bob) = setup_two_agents_with_alice_as_progenitor().await;
        conductors[0]
            .call::<_, Record>(&alice.zome("users_organizations"), "create_user", sample_user("Alice"))
            .await;
        await_consistency(15, [&alice, &bob]).await.unwrap();
    
        let record: Record = conductors[0]
            .call(&alice.zome("new_feature"), "create_new_feature", sample_new_feature())
            .await;
        assert!(record.signed_action.hashed.hash.get_raw_39().len() > 0);
    }
    }
Step 2: Service Layer
  1. Holochain Service

    // ui/src/services/zomes/new-feature.service.ts
    export class NewFeatureService {
      constructor(private client: AppAgentClient) {}
    
      async createNewFeature(input: NewFeature): Promise<Record> {
        return await this.client.callZome({
          zome_name: "new_feature",
          fn_name: "create_new_feature",
          payload: input,
        });
      }
    }
    
  2. Store Implementation

    // ui/src/stores/new-feature.store.ts
    export const newFeatureStore = writable<NewFeature[]>([]);
    
    export const createNewFeature = async (input: NewFeature) => {
      const result = await service.createNewFeature(input);
      newFeatureStore.update((features) => [...features, result]);
      return result;
    };
    
Step 3: UI Implementation
  1. Components

    <!-- ui/src/lib/components/NewFeature.svelte -->
    <script lang="ts">
      import { newFeatureStore, createNewFeature } from '$lib/stores/new-feature.store';
    
      async function handleSubmit(event) {
        const result = await createNewFeature({
          field1: event.detail.value,
          field2: event.detail.options,
        });
      }
    </script>
    
  2. Pages

    <!-- ui/src/routes/new-feature/+page.svelte -->
    <script lang="ts">
      import NewFeature from '$lib/components/NewFeature.svelte';
    </script>
    
    <NewFeature />
    
Development Order
  1. DNA First

    • Implement and test entry types
    • Create and verify zome functions
    • Write comprehensive Sweettest tests
  2. Services and Stores (Parallel)

    • Create Holochain service methods
    • Implement store with state management
    • Add store actions and subscriptions
  3. UI Components

    • Develop reusable components
    • Create feature pages
    • Implement user interactions
Testing Strategy
  1. DNA Testing

    # Test specific feature
    bun test:new-feature
    
    # Run all tests
    bun test
    
  2. UI Testing

    # Component tests
    bun test:ui
    
    # E2E tests (if applicable)
    bun test:e2e
    
  3. Manual Testing

    • Start development environment
    • Test with multiple agents
    • Verify all user flows
Documentation
  1. DNA Documentation

    • Update zome documentation
    • Document entry and link types
    • Add usage examples
  2. Frontend Documentation

    • Document services and stores
    • Add component documentation
    • Update user guides
  3. Testing Documentation

    • Document test scenarios
    • Add test data examples
    • Update test instructions

Project Structure

Frontend (ui/)

  • SvelteKit application
  • Component documentation
  • UI/UX guidelines

Backend (dnas/requests_and_offers/zomes/)

  • Users Organizations Zome
    • User management
    • Organization handling
  • Administration Zome
    • System administration
    • Status management

Documentation (documentation/)

  • Technical specifications
  • User guides
  • API documentation
  • Development guides

Getting Help

Development Support

🚀 Release Checklist

Comprehensive guide for deploying new versions of Requests and Offers

This checklist ensures consistent, reliable releases by following a systematic process that addresses common failure points and edge cases.

📋 Pre-Release Checklist

Environment Verification

  • Git Authentication: Ensure you can push to main repository and submodules
  • GitHub CLI Access: Verify gh auth status shows proper authentication
  • Branch Status: Confirm you're on the correct branch (dev for development, main for releases in main repo; release for kangaroo submodule)
  • Clean Working Directory: No uncommitted changes in main repository or submodules
  • Submodule Status: Verify submodules are initialized and up to date
    git submodule status
    git submodule update --init --recursive
    
  • Network Access: Confirm internet connectivity for GitHub operations

GitHub CLI Authentication Setup

Prerequisites: GitHub CLI requires proper authentication for release operations

Step 1: Install GitHub CLI

# macOS
brew install gh

# Ubuntu/Debian
sudo apt install gh

# Other platforms: https://github.com/cli/cli#installation

Step 2: Authenticate with GitHub

# Authenticate (will open browser for OAuth)
gh auth login

# Select scope:
# - What account do you want to log into? → GitHub.com
# - What is your preferred protocol for Git operations? → HTTPS
# - Authenticate Git with your GitHub credentials? → Yes
# - How would you like to authenticate GitHub CLI? → Login with a web browser

# Verify authentication
gh auth status
# Should show: "Logged in to github.com as username ✓"

Step 3: Verify Repository Access

# Test repository access
gh repo view happenings-community/requests-and-offers
gh repo view happenings-community/kangaroo-electron
gh repo view happenings-community/homebrew-requests-and-offers

# Test release permissions
gh release list --repo happenings-community/requests-and-offers --limit 1

Step 4: Configure Git Credentials (if needed)

# Configure git to use gh for authentication
git config --global credential.helper "!gh auth git-credential"

# Verify git authentication
git ls-remote https://github.com/happenings-community/requests-and-offers.git

Multi-Repository Management

Repository Structure:

# Main repository (primary working directory)
https://github.com/happenings-community/requests-and-offers

# Submodules (accessed via deployment/ directory)
https://github.com/happenings-community/kangaroo-electron    # deployment/kangaroo-electron
https://github.com/happenings-community/homebrew-requests-and-offers  # deployment/homebrew

Submodule Workflow:

# Initialize all submodules
git submodule update --init --recursive

# Navigate to specific submodule
cd deployment/kangaroo-electron

# Work in submodule (independent git operations)
git status
git checkout main
git pull origin main

# Return to main repository
cd ../..

# Update submodule reference in main repo
git add deployment/kangaroo-electron
git commit -m "submodule: Update kangaroo-electron to latest"
git push origin dev

Branch Management Strategy:

# Main Repository: work on dev branch (default); main is reserved for releases
git checkout dev
git pull origin dev

# Kangaroo Submodule:
# - Development on main branch
# - Releases trigger from release branch
cd deployment/kangaroo-electron
git checkout main      # Development
git checkout release   # Release builds

# Homebrew Submodule: work on main branch only
cd deployment/homebrew
git checkout main

Cross-Repository Operations:

# Create coordinated releases across repositories
gh release create v0.2.3 --repo happenings-community/requests-and-offers
gh release create v0.2.3 --repo happenings-community/kangaroo-electron

# Copy assets between repositories
gh release download v0.2.3 --repo happenings-community/kangaroo-electron --pattern "*.dmg"

# Synchronize tags across repositories
git tag -a v0.2.3 -m "Coordinated release v0.2.3"
git push origin v0.2.3

# In each submodule:
cd deployment/kangaroo-electron
git tag -a v0.2.3 -m "Desktop apps v0.2.3"
git push origin v0.2.3

Version Planning

  • Version Number: Determine next version following semantic versioning
  • Change Documentation: Update CHANGELOG.md with new version details
  • Breaking Changes: Identify any breaking changes requiring major version bump
  • Feature Scope: Confirm all intended features are complete and tested

Code Quality Checks

  • Tests Passing: Run full test suite (bun test in main repo)
  • Lint Clean: No linting errors (cd ui && bun run lint)
  • Type Safety: TypeScript compilation successful (cd ui && bun run check)
  • Build Verification: Ensure project builds without errors (bun build:happ)

🔧 Release Preparation

Main Repository Setup

cd /home/soushi888/Projets/Holochain/requests-and-offers
  • Update Version Files: Ensure version consistency across:
    • dnas/requests_and_offers/dna.yaml
    • ui/package.json
    • CHANGELOG.md
  • Set Test Mode Environment: Ensure test mode (no development features) before building:
    # This ensures development features are disabled in the build
    export VITE_MOCK_BUTTONS_ENABLED=false
    
  • Build WebHapp: bun package (creates workdir/requests_and_offers.webhapp)
  • Verify WebHapp: Confirm file exists and has reasonable size (>5MB)
  • Verify Test Mode: Ensure the webhapp was built with test mode (no development features)
  • Commit Changes: Commit any version updates with clear message

Kangaroo Repository Setup

# Option 1: Work in submodule directory
cd deployment/kangaroo-electron

# Option 2: Work from main repo with submodule commands
git submodule update --remote kangaroo-electron
cd deployment/kangaroo-electron
  • Update Version: Edit kangaroo.config.ts with new version number
  • Update Release Notes: Create/update RELEASE_BUILD_NOTES.md
  • Copy WebHapp: Copy latest webhapp from main repo to pouch/ directory
    cp ../../workdir/requests_and_offers.webhapp pouch/
    
  • Verify Correct WebHapp: Ensure the webhapp in pouch/ is built in test mode (no development features)
  • Verify Configuration: Ensure network servers are configured for the target network:
    • For dev-test: bootstrapUrl: 'https://dev-test-bootstrap2.holochain.org/' / signalUrl: 'wss://dev-test-bootstrap2.holochain.org/'
    • For production: bootstrapUrl: 'https://holostrap.elohim.host/' / signalUrl: 'wss://holostrap.elohim.host/'
  • Commit Changes: Commit version and configuration updates in submodule

🌿 Branch Synchronization

Main Repository Branch Sync

⚠️ CRITICAL: Complete ALL commits (including submodule reference updates) on dev BEFORE promoting to main and creating the tag. The tag must point to a commit that exists on both branches. Never commit to main after tagging — sync dev first, then promote, then tag.

# Ensure dev branch is current and has ALL commits for the release
# (including submodule updates committed on dev, not just main)
git checkout dev
git pull origin dev

# Promote dev to main for release
git checkout main
git merge dev --no-edit
git push origin main

# Verify main is up to date with dev
git log --oneline dev..main | wc -l
# Should be 0 — main must be a fast-forward of dev

# ⚠️ BEFORE TAGGING: verify both branches are at the same commit
git rev-parse dev && git rev-parse main
# Both hashes must match — if not, sync before proceeding

# After tag creation, if any commit is needed, do it on dev first:
# git checkout dev && git commit ... && git push origin dev
# git checkout main && git merge dev --no-edit && git push origin main
# Then re-tag if the tag was already created (delete remote + local, recreate)

Kangaroo Submodule Branch Sync

# Work in kangaroo submodule
cd deployment/kangaroo-electron

# Update main branch
git checkout main
git pull origin main

# Sync release branch with main
git checkout release
git merge main --no-edit
git push origin release

# Verify branches are synchronized
git log --oneline main..release | wc -l
# Should be 0 after sync

# Return to main repository
cd ../..

🚀 Release Execution

Manual Release Process

Step 1: Update Main Repository

cd /home/soushi888/Projets/Holochain/requests-and-offers

# Update changelog using the command
/update-changelog

# Update version in package.json
# Edit package.json: "version": "0.1.X"

Step 2: Build WebHapp Package

# Enter Nix shell for proper environment
nix develop

# Build the webhapp package
bun package

# Verify the webhapp was created
ls -la workdir/requests_and_offers.webhapp

Step 3: Create Main Repository Release

# Create a tag for the new version
git tag v0.1.X

# Create a basic draft release with initial notes
gh release create v0.1.X \
  --title "🚀 Requests and Offers v0.1.X" \
  --notes "### What's New

[Feature highlights from changelog]

### Installation
[WebApp installation instructions]

### Desktop Apps
🔄 Desktop applications are building... [Links will appear here when ready]

### Technical Specifications
- **Network**: Holostrap alpha network
- **Holochain Version**: 0.5.5
- **UI Framework**: SvelteKit + Svelte 5
- **Architecture**: 7-Layer Effect-TS

### Getting Started
[Quick start guide for new users]

---

⚠️ **Note**: Desktop applications are currently being built. Download links will be added automatically when the build process completes."

# Upload the webhapp (for Moss/desktop users)
gh release upload v0.1.X workdir/requests_and_offers.webhapp --clobber

# Upload the happ (for edge node operators — see documentation/guides/edge-node-setup.md)
gh release upload v0.1.X workdir/requests_and_offers.happ --clobber

Step 4: Update Kangaroo Repository

# Navigate to kangaroo submodule
cd deployment/kangaroo-electron

# Copy the fresh webhapp to kangaroo pouch
cp ../../workdir/requests_and_offers.webhapp pouch/

# Update version in package.json (if needed)
# Edit package.json: "version": "0.1.X"

# Update version in kangaroo.config.ts (already at 0.1.9)
# Edit kangaroo.config.ts: version: '0.1.X'

Step 5: Create Kangaroo GitHub Release (BEFORE triggering CI/CD)

⚠️ CRITICAL ORDERING: The GitHub release MUST exist before pushing to the release branch. CI/CD builds will attempt to upload artifacts to this release — if it doesn't exist, the upload step fails (commonly the Ubuntu build fails first).

# Create the kangaroo release FIRST — CI needs this to upload artifacts
gh release create v0.1.X \
  --title "Requests and Offers v0.1.X — Desktop Apps" \
  --notes "Desktop application builds for v0.1.X.
See main release: https://github.com/happenings-community/requests-and-offers/releases/tag/v0.1.X"

Step 5b: Trigger Kangaroo CI/CD Build

# Commit the webhapp update
git add pouch/requests_and_offers.webhapp
git commit -m "build: update webhapp for v0.1.X release"

# Push to dev first (dev is the integration branch)
git push origin dev

# Now push to release branch to trigger GitHub Actions
git checkout release
git merge main --no-edit
git push origin release

Step 6: Monitor Kangaroo Build

# Monitor GitHub Actions progress
gh run list --limit=5

# View specific run logs if needed
gh run view [RUN_ID] --log-failed

# Verify all platform builds complete:
# - macOS ARM64 (Apple Silicon)
# - macOS x64 (Intel)
# - Windows x64
# - Linux x64

Step 7: Update Release Notes from Template

Use the release notes template at documentation/templates/release-notes-template.md. The template matches the format used in v0.3.0 and v0.4.0 releases.

# 1. The only variable in the download URLs is {VERSION} — replace it globally
# 2. Fill in features/bugfixes from CHANGELOG.md
# 3. Set the network and Holochain version in Technical Specifications
# 4. Apply to GitHub release:
gh release edit v0.X.Y --notes "$(cat /tmp/release-notes.md)"

See the template's Variable Reference table for all placeholders.

Automated Deployment (Available)

Note: The automated deployment system is fully functional using bun deploy commands. This provides streamlined release management with built-in validation and rollback capabilities.

Available Automated Commands:

# Full deployment pipeline (recommended)
bun deploy                    # Execute complete deployment pipeline

# Preview and validation options
bun deploy:dry-run            # Preview deployment without executing
bun deploy:status             # Check deployment status and progress
bun deploy:validate           # Validate completed deployment
bun deploy:rollback           # Rollback failed deployment

Automated System Handles:

  • ✅ Environment validation (including submodules)
  • ✅ WebApp build and GitHub release creation
  • ✅ Kangaroo desktop app builds (all platforms)
  • ✅ Homebrew formula updates with SHA256 checksums
  • ✅ Cross-repository synchronization
  • ✅ Comprehensive validation and rollback capabilities
  • ✅ Template-based release notes generation

Manual Release (Alternative)

If you prefer manual release process:

  • Main Repository Release: Create GitHub release for main repo
    gh release create v0.1.X --title "🚀 Requests and Offers v0.1.X - Feature Name" --notes "Initial release notes"
    
  • Kangaroo Submodule Release: Create GitHub release for kangaroo submodule
    cd deployment/kangaroo-electron
    gh release create v0.1.X --title "v0.1.X - Feature Name" --notes "Desktop release notes"
    cd ../..
    
  • Upload WebHapp: Add webhapp to main repository release
    gh release upload v0.1.X /path/to/requests_and_offers.webhapp --clobber
    

Desktop Build Trigger

  • Trigger Builds: Push to kangaroo submodule release branch triggers GitHub Actions
    # Create trigger commit in kangaroo submodule if needed
    cd deployment/kangaroo-electron
    echo "$(date)" > .trigger
    git add .trigger
    git commit -m "trigger: release v0.1.X build"
    git push origin release
    cd ../..
    
  • Monitor Builds: Track build progress
    gh run list --limit 1
    gh run view [RUN_ID] --log-failed  # If builds fail
    

Build Verification

  • All Platforms Complete: Verify builds complete successfully
    • ✅ macOS ARM64 (Apple Silicon)
    • ✅ macOS x64 (Intel)
    • ✅ Windows
    • ✅ Linux (DEB + AppImage)
  • Asset Upload Confirmation: Check all expected assets are uploaded
    gh release view v0.1.X  # Should show 6+ assets (5 binaries + checksums)
    

📝 Release Notes Finalization

Main Repository Release Notes

  • Update Release Description: Add comprehensive release notes including:
    • Feature highlights with clear descriptions
    • Platform-specific download links pointing to kangaroo repo
    • Installation instructions for each platform
    • Technical specifications and network information
    • Getting started guide for new users
  • Verify Asset Links: Test all download links work correctly
  • Cross-Repository Links: Ensure links between main and kangaroo repos work

Communication & Documentation

  • Update README: Reflect any new features or installation changes
  • Documentation Updates: Update any relevant documentation
  • Community Notification: Prepare announcement for community channels

🍺 Homebrew Formula Management

Homebrew Integration Overview

The Requests and Offers application is distributed via Homebrew using a custom formula that automatically downloads the correct platform-specific binaries from GitHub releases.

Repository Structure:

  • Main Repository: https://github.com/happenings-community/requests-and-offers
  • Homebrew Repository: https://github.com/happenings-community/homebrew-requests-and-offers (submodule)
  • Submodule Path: deployment/homebrew

Formula Update Process

Prerequisites:

  • Desktop builds completed successfully
  • All GitHub release assets uploaded and verified
  • Homebrew submodule initialized and up to date

Step 1: Navigate to Homebrew Repository

cd deployment/homebrew

Step 2: Calculate SHA256 Checksums

# Download release assets to calculate checksums
wget https://github.com/happenings-community/kangaroo-electron/releases/download/v0.2.3/Requests-and-Offers-0.2.3-arm64-mac.dmg
wget https://github.com/happenings-community/kangaroo-electron/releases/download/v0.2.3/Requests-and-Offers-0.2.3-x64-mac.dmg
wget https://github.com/happenings-community/kangaroo-electron/releases/download/v0.2.3/Requests-and-Offers-0.2.3-x64-win.exe
wget https://github.com/happenings-community/kangaroo-electron/releases/download/v0.2.3/Requests-and-Offers-0.2.3-x64-linux.deb
wget https://github.com/happenings-community/kangaroo-electron/releases/download/v0.2.3/Requests-and-Offers-0.2.3.AppImage

# Calculate checksums for all binaries
sha256sum Requests-and-Offers-0.2.3-arm64-mac.dmg
sha256sum Requests-and-Offers-0.2.3-x64-mac.dmg
sha256sum Requests-and-Offers-0.2.3-x64-win.exe
sha256sum Requests-and-Offers-0.2.3-x64-linux.deb
sha256sum Requests-and-Offers-0.2.3.AppImage

# Clean up downloaded files
rm Requests-and-Offers-0.2.3-*

Step 3: Update Formula Configuration

# Edit the formula file
vim Casks/requests-and-offers.rb

Required Updates:

cask "requests-and-offers" do
  # Update version number
  version "0.2.3"

  if Hardware::CPU.arm?
    # Update SHA256 for Apple Silicon
    sha256 "NEW_ARM64_SHA256_CHECKSUM"
    url "https://github.com/happenings-community/kangaroo-electron/releases/download/v#{version}/Requests-and-Offers-#{version}-arm64-mac.dmg"
  else
    # Update SHA256 for Intel
    sha256 "NEW_X64_SHA256_CHECKSUM"
    url "https://github.com/happenings-community/kangaroo-electron/releases/download/v#{version}/Requests-and-Offers-#{version}-x64-mac.dmg"
  end

  # No other changes needed - URLs are template-based
end

Step 4: Test Formula Updates

# Test installation from local formula
brew install --build-from-source ./Casks/requests-and-offers.rb

# Verify installation works
brew list requests-and-offers
ls /usr/local/Caskroom/requests-and-offers/*/Requests\ and\ Offers.app

Step 5: Commit and Push Changes

git add Casks/requests-and-offers.rb
git commit -m "v0.2.3: Update formula with new release checksums

- Update version to 0.2.3
- Update SHA256 checksums for all binaries
- Verified installation on both ARM64 and x64 macOS"
git push origin main

# Return to main repository
cd ../..

Step 6: Update Submodule Reference

# Update submodule reference in main repository
git add deployment/homebrew
git commit -m "submodule: Update homebrew formula for v0.2.3 release"
git push origin main

Homebrew User Installation

For End Users:

# Add our tap (one-time setup)
brew tap happenings-community/homebrew-requests-and-offers

# Install the application
brew install --cask requests-and-offers

# Launch the application
open "Requests and Offers"

# Upgrade to new version
brew upgrade --cask requests-and-offers

# Uninstall (if needed)
brew uninstall --cask requests-and-offers

Verification Commands:

# Check installed version
brew info requests-and-offers

# Verify installation location
ls -la "$(brew --prefix)/Caskroom/requests-and-offers"

# Check application bundle
ls -la "/usr/local/Caskroom/requests-and-offers/*/Requests and Offers.app"

Common Homebrew Issues

Checksum Mismatch:

# Error: SHA256 mismatch
# Solution: Recalculate checksums and update formula
sha256sum downloaded-file.dmg
# Update the sha256 value in Casks/requests-and-offers.rb

Download URL Issues:

# Error: No available file with URL
# Solution: Verify GitHub release assets exist
gh release view v0.2.3  # Check available assets
# Ensure URL pattern matches actual file names

Permission Issues:

# Error: Permission denied
# Solution: Use sudo or fix homebrew permissions
sudo chown -R $(whoami) /usr/local/Caskroom/
# Or use: brew install --cask requests-and-offers --force

Architecture Detection:

# Verify which architecture formula selects
uname -m  # Should show arm64 or x86_64
# Test formula logic:
if Hardware::CPU.arm?
  echo "Apple Silicon (M1/M2/M3)"
else
  echo "Intel Mac"
end

🔄 Post-Release Verification

Download Testing

  • Test Downloads: Verify downloads work from GitHub release page
  • Installation Testing: Test installation on at least one platform
  • Network Connectivity: Verify app connects to production network
  • Basic Functionality: Confirm core features work in released version

Repository Cleanup

  • Branch Synchronization: Ensure main and release branches are in sync
  • Tag Verification: Confirm git tags are properly created
  • Asset Verification: Double-check all assets are available and working

🚨 Troubleshooting Common Issues

Authentication Issues

# GitHub CLI authentication failures
gh auth login          # Re-authenticate
gh auth status         # Check current status
gh auth logout         # Logout and re-authenticate

# Git permission issues
git config --global credential.helper "!gh auth git-credential"
git config --global --unset credential.helper  # Reset if needed

# Repository access verification
gh repo view happenings-community/requests-and-offers
gh repo view happenings-community/kangaroo-electron

Submodule Issues

# Submodule not initialized or out of sync
git submodule deinit -f deployment/kangaroo-electron
git submodule update --init --recursive

# Submodule in detached HEAD state
cd deployment/kangaroo-electron
git checkout main
cd ../..

# Submodule reference not updated
git add deployment/kangaroo-electron
git commit -m "submodule: Update reference"
git push origin main

Kangaroo Submodule Branch Sync Issues

# If kangaroo release branch has commits ahead of main
cd deployment/kangaroo-electron
git checkout main
git merge release --no-edit
git push origin main

# Reset kangaroo release branch to match main
git checkout release
git reset --hard main
git push --force-with-lease origin release
cd ../..

# Main repo: if main has drifted behind dev (should not happen in normal flow)
git checkout dev
git pull origin dev
git checkout main
git merge dev --no-edit
git push origin main

Missing GitHub Release

# Create missing release before builds
gh release create v0.2.3 --title "Release Title" --notes "Release notes"

# Check if release exists
gh release view v0.2.3

# List all releases
gh release list --limit 10

Build Failures

# Check build logs
gh run list --limit 5
gh run view [RUN_ID] --log-failed

# Monitor running builds
gh run watch [RUN_ID]

# Common fixes:
# 1. Ensure release exists on GitHub
# 2. Check webhapp file is in pouch/ directory
# 3. Verify version consistency across files
# 4. Confirm submodule reference is updated

Asset Upload Issues

Checksum Calculation Errors

# Recalculate checksums for Homebrew formula
cd deployment/homebrew

# Download assets to verify checksums
wget https://github.com/happenings-community/kangaroo-electron/releases/download/v0.2.3/Requests-and-Offers-0.2.3-arm64-mac.dmg

# Calculate correct checksums
sha256sum Requests-and-Offers-0.2.3-arm64-mac.dmg

# Update formula with correct checksums
vim Casks/requests-and-offers.rb

Missing Release Assets

# Check what assets are uploaded
gh release view v0.2.3 --json assets

# Expected assets for complete release:
# - requests_and_offers.webhapp (main repo — for Moss/desktop)
# - requests_and_offers.happ   (main repo — for edge node operators)
# - Requests-and-Offers-0.2.3-arm64-mac.dmg
# - Requests-and-Offers-0.2.3-x64-mac.dmg
# - Requests-and-Offers-0.2.3-x64-win.exe
# - Requests-and-Offers-0.2.3-x64-linux.deb
# - Requests-and-Offers-0.2.3.AppImage

# Re-trigger builds if assets missing
cd deployment/kangaroo-electron
git commit --allow-empty -m "trigger: rebuild v0.2.3"
git push origin release

Template Population Issues

# Release notes template variables not replaced
# Check template file exists
ls -la documentation/templates/release-notes-template.md

# Verify variable replacement
sed -n 's/{[^}]*}/VAR/gp' /tmp/release-notes-v0.2.3.md

# Manual template population example
cp documentation/templates/release-notes-template.md /tmp/release-notes-v0.2.3.md
sed -i 's/{VERSION}/0.2.3/g' /tmp/release-notes-v0.2.3.md
sed -i 's/{RELEASE_DATE}/2025-12-18/g' /tmp/release-notes-v0.2.3.md

Version Mismatch Issues

# Check version consistency across files
grep -r "0.2.3" --include="*.json" --include="*.ts" --include="*.yaml" .

# Common version files to check:
# - package.json (root and ui/)
# - kangaroo.config.ts
# - dnas/requests_and_offers/dna.yaml
# - deployment/homebrew/Casks/requests-and-offers.rb

# Update inconsistent versions
find . -name "*.json" -exec sed -i 's/0.2.2/0.2.3/g' {} \;
find . -name "*.ts" -exec sed -i 's/0.2.2/0.2.3/g' {} \;

Network and Connectivity Issues

# Test GitHub connectivity
gh auth status
curl -I https://api.github.com

# Test repository access
git ls-remote origin main
git fetch origin

# Submodule connectivity issues
git submodule sync
git submodule update --init --recursive

Environment Variable Issues

# Check environment variables for build
echo $VITE_MOCK_BUTTONS_ENABLED
echo $VITE_PEERS_DISPLAY_ENABLED

# Verify .env file exists and is correct
cat .env

# Test environment affects build
bun package  # Should build without dev features in production mode

📊 Release Metrics

Track these metrics for release process improvement:

  • ⏱️ Total Release Time: Target <30 minutes end-to-end
  • 🔄 Build Retry Count: Target 0 retries needed
  • ✅ Success Rate: Target 100% successful releases
  • 🐛 Issues Found: Track common failure modes for process improvement

Performance Benchmarks (v0.1.9 Achieved)

  • Total Release Time: ~2.5 hours (including issue resolution)
  • Build Retry Count: 1 retry (for macOS upload fixes)
  • Success Rate: 100% (5/5 platforms)
  • Platform Build Times:
    • macOS ARM64: 1m46s
    • macOS x64: 3m2s
    • Windows x64: 2m54s
    • Linux x64: ~4m (includes post-install scripts)

🎯 Success Criteria

A successful release includes:

  • ✅ All platform builds complete successfully (5 binaries: macOS ARM64/x64, Windows, Linux DEB/AppImage)
  • ✅ All assets uploaded and downloadable
  • ✅ Release notes complete and accurate with working links
  • ✅ Download links tested and working for all platforms
  • ✅ Branches synchronized between repositories (main + release)
  • ✅ Homebrew formula updated with correct checksums
  • ✅ Basic functionality verified in released app
  • ✅ Cross-platform installation methods available (GitHub + Homebrew)

Release Process Evolution

  • v0.1.8: 2/5 platforms (40% success) - Linux only
  • v0.1.9: 5/5 platforms (100% success) - Complete cross-platform support

Continuous Improvement Areas

  • CI/CD Reliability: Manual upload strategy more robust than electron-builder auto-publishing
  • Asset Upload Strategy: Wildcard patterns eliminate filename mismatch failures
  • Cross-Repository Coordination: Established workflow for main repo + kangaroo + homebrew synchronization
  • Documentation Quality: Enhanced troubleshooting patterns for common failure modes

Next: Use this checklist for every release to ensure consistency and reliability. Update the checklist based on lessons learned from each release.

🔧 Kangaroo CI/CD Process Details

Repository Structure

  • Main Repository: https://github.com/happenings-community/requests-and-offers
  • Kangaroo Repository: https://github.com/happenings-community/kangaroo-electron (submodule)
  • Submodule Path: deployment/kangaroo-electron

CI/CD Trigger Mechanism

  • Trigger: Any commit to release branch in kangaroo repository
  • ⚠️ PREREQUISITE: A GitHub release for the target version MUST exist before pushing to release branch. CI uploads artifacts to this release — missing release = failed uploads.
  • Required Files:
    • pouch/requests_and_offers.webhapp (the webapp package)
    • Proper version in package.json and kangaroo.config.ts
  • Build Platforms: Windows x64, macOS ARM64, macOS x64, Linux x64
  • Expected Assets: 5 files per release (4 binaries + checksums)

Critical Integration Points

  1. WebHapp Transfer: Main → Kangaroo

    # From main repo root
    cp workdir/requests_and_offers.webhapp deployment/kangaroo-electron/pouch/
    
  2. Version Synchronization:

    • Main package.json version must match Kangaroo package.json
    • Kangaroo kangaroo.config.ts version must match both
    • All three must be updated for consistent release
  3. Cross-Repository Linking:

    • Main release notes link to Kangaroo release assets
    • Kangaroo release links back to main repository
    • Both repositories should reference each other

Common Failure Points

Missing GitHub Release Before CI/CD Push

  • Symptom: Builds complete but upload step fails with release not found (Ubuntu typically fails first)
  • Fix: Create the GitHub release BEFORE pushing to the release branch: gh release create v0.X.Y --title "..." --notes "..."
  • Recovery: Create the release, then re-trigger with git commit --allow-empty -m "trigger: rebuild vX.Y.Z" && git push origin release

Missing WebHapp in Pouch

  • Symptom: CI/CD runs but produces empty/broken builds
  • Fix: Ensure fresh webhapp is copied to deployment/kangaroo-electron/pouch/

Version Mismatch

  • Symptom: Builds tagged with wrong version number
  • Fix: Synchronize all version files before triggering builds

No GitHub Actions Triggered

  • Symptom: Push to release branch but no CI/CD runs
  • Fix: Ensure proper commit with webhapp changes exists

📱 Cross-Platform Asset Verification

Expected File Structure

For each release, Kangaroo repository should generate:

Requests-and-Offers-{version}-arm64-mac.dmg    # macOS Apple Silicon
Requests-and-Offers-{version}-x64-mac.dmg     # macOS Intel
Requests-and-Offers-{version}-x64-win.exe      # Windows
Requests-and-Offers-{version}-x64-linux.deb    # Linux (Debian/Ubuntu)
Requests-and-Offers-{version}.AppImage          # Linux (Universal portable)
checksums.txt                               # SHA256 checksums for all files

Standard GitHub release download URLs:

https://github.com/happenings-community/kangaroo-electron/releases/download/v{version}/Requests-and-Offers-{version}-{platform}.{extension}

# Platform Examples:
# macOS ARM64: Requests-and-Offers-0.1.9-arm64-mac.dmg
# macOS x64:  Requests-and-Offers-0.1.9-x64-mac.dmg
# Windows:   Requests-and-Offers-0.1.9-x64-win.exe
# Linux DEB: Requests-and-Offers-0.1.9-x64-linux.deb
# Linux AppImage: Requests-and-Offers-0.1.9.AppImage

Asset Size Expectations

  • macOS DMG: ~85MB (includes bundled webhapp)
  • Windows EXE: ~90MB (includes bundled webhapp)
  • Linux DEB: ~80MB (includes bundled webhapp)
  • Linux AppImage: ~85MB (portable universal format)
  • Total Release: ~440MB across all platforms

Troubleshooting Guide

Comprehensive troubleshooting guide for common issues in the Requests and Offers project development.

🚨 Common Issues

Environment Setup Issues

Issue: Nix environment not activating properly

# Error: command not found: holochain

Solutions:

# 1. Ensure you're in the project root
cd requests-and-offers

# 2. Activate Nix environment manually
nix develop

# 3. Verify tools are available
which holochain && which hc && which rustc

# 4. If using direnv (optional)
echo "use flake" > .envrc
direnv allow

Verification:

# Check all required tools
holochain --version    # Should show Holochain version
hc --version           # Should show hc CLI version
rustc --version        # Should show Rust compiler version
bun --version          # Should show Bun version

Issue: Port conflicts when starting development

# Error: Port 8888 is already in use

Solutions:

# 1. Kill processes using the ports
lsof -ti:8888 | xargs kill -9    # Kill process on port 8888
lsof -ti:4444 | xargs kill -9    # Kill process on port 4444

# 2. Find what's using the port
lsof -i :8888                    # Show process using port 8888

# 3. Use different ports (temporary)
HC_APP_PORT=8889 ADMIN_PORT=4445 bun start

# 4. Check for running Holochain processes
ps aux | grep holochain
ps aux | grep lair-keystore

Administration & Progenitor Issues

Issue: I created a user but I'm not an administrator

Cause: In production mode, only the designated progenitor is auto-registered as admin on create_user. If someone joined the network before the progenitor and created a profile, they receive Pending status like any other user.

Diagnose:

# Check whether progenitor_pubkey is configured in your happ.yaml
grep "progenitor_pubkey" workdir/happ.yaml
# null (~) means dev mode — first user becomes admin
# a base64 key means production mode — only that key becomes admin

In the UI you can call is_progenitor() from the administration zome to check if your current agent is the progenitor. If it returns false and no admin exists yet, the progenitor has not yet called create_user.

Solutions:

  • Dev mode (progenitor_pubkey: ~): ensure your agent is the first one to call create_user after a clean sandbox reset (bun start wipes the sandbox each run).
  • Production mode: verify the correct agent pubkey is set as progenitor_pubkey. Use Kangaroo to install the hApp — it sets the key automatically. If deploying manually, read the agent key from the conductor admin API and set it in workdir/happ.yaml before bun build:happ.

For a full explanation of the two modes see The Progenitor Pattern.

Build Issues

Issue: Zome compilation failures

# Error: failed to compile Rust zomes

Solutions:

# 1. Ensure you're in Nix environment
nix develop

# 2. Clean build artifacts
rm -rf target/
rm -rf dnas/requests_and_offers/target/

# 3. Rebuild zomes
bun build:zomes

# 4. Check Rust toolchain
rustc --version
cargo --version

# 5. Update dependencies if needed
cd dnas/requests_and_offers
cargo update

Issue: Frontend build failures

# Error: TypeScript compilation errors

Solutions:

# 1. Check TypeScript configuration
cd ui
bun run check

# 2. Clear node_modules and reinstall
rm -rf node_modules
rm -f bun.lockb
bun install

# 3. Fix TypeScript errors
bun run lint --fix

# 4. Verify SvelteKit configuration
cat svelte.config.js

Testing Issues

Issue: Unit tests failing with hREA integration errors

# Error: Cannot find hREA DNA

Solutions:

# 1. Use autonomous test execution (RECOMMENDED)
nix develop --command bun test:unit

# 2. Ensure hREA DNA is downloaded
bun run download-hrea

# 3. Verify Nix environment is active
echo $IN_NIX_SHELL    # Should output "1" or "impure"

# 4. Check hREA DNA location
ls -la dnas/hrea/     # Should contain DNA files

Issue: Sweettest tests failing

# Error: Conductor startup failed

Solutions:

# 1. Ensure Nix environment
nix develop

# 2. Clean previous conductor state
rm -rf .hc/
rm -rf /tmp/holochain_*

# 3. Build zomes first
bun build:zomes

# 4. Run specific test
cd tests
bun test -- --test-name-pattern="service_types"

# 5. Check conductor logs
ls -la logs/
tail -f logs/conductor.log

Development Workflow Issues

Issue: Effect-TS service not working properly

// Error: Service not found in context

Solutions:

// 1. Ensure service is properly tagged
export const MyService = Context.GenericTag<MyService>("MyService");

// 2. Create service layer
export const MyServiceLive = Layer.effect(MyService, makeMyService);

// 3. Provide service in component/store
Effect.provide(MyServiceLive);

// 4. Verify dependency injection
const service = yield * MyService; // Should not throw

Issue: Svelte store not updating reactively

// Store state not updating UI

Solutions:

// 1. Ensure using Svelte 5 runes correctly
let entities = $state([]); // Not: let entities = [];

// 2. Update state immutably
entities = [...entities, newEntity]; // Not: entities.push(newEntity);

// 3. Check component is using runes
const { entities } = store; // Should use store.entities()

// 4. Verify store factory pattern
export const store = createEntitiesStore(); // Module level

Data Issues

Issue: Holochain entries not appearing

# Created entries not visible to other agents

Solutions:

# 1. Wait for DHT synchronization
# In tests, use wait_for_integration()

# 2. Check entry validation
# Review integrity zome validation functions

# 3. Verify link creation
# Check if entries are properly linked

# 4. Debug with Holochain Playground
# Open http://localhost:8888 when running bun start

Issue: Schema validation errors

// Error: Schema decode failed

Solutions:

// 1. Check schema matches Holochain entry structure
const EntrySchema = Schema.Struct({
  name: Schema.String,
  created_at: Schema.DateFromSelf, // Use DateFromSelf for timestamps
});

// 2. Handle unknown data properly
Schema.decodeUnknown(EntrySchema)(data).pipe(
  Effect.mapError((error) => new ValidationError({ cause: error })),
);

// 3. Add proper error context
Effect.mapError(transformErrorWithContext("ServiceType.Validation"));

// 4. Debug schema issues
console.log("Raw data:", data);
console.log("Schema:", EntrySchema);

🔧 Development Environment Debugging

Health Check Script

#!/bin/bash
# Save as: scripts/health-check.sh

echo "🔍 Development Environment Health Check"
echo "======================================"

# Check Nix environment
if [ -n "$IN_NIX_SHELL" ]; then
    echo "✅ Nix environment: Active"
else
    echo "❌ Nix environment: Not active (run 'nix develop')"
fi

# Check required tools
tools=("holochain" "hc" "lair-keystore" "rustc" "cargo" "node" "bun")
for tool in "${tools[@]}"; do
    if command -v "$tool" &> /dev/null; then
        version=$(${tool} --version 2>/dev/null | head -n1)
        echo "✅ $tool: $version"
    else
        echo "❌ $tool: Not found"
    fi
done

# Check ports
ports=(8888 4444)
for port in "${ports[@]}"; do
    if lsof -Pi :$port -sTCP:LISTEN -t >/dev/null; then
        process=$(lsof -Pi :$port -sTCP:LISTEN | tail -n1 | awk '{print $1}')
        echo "⚠️  Port $port: In use by $process"
    else
        echo "✅ Port $port: Available"
    fi
done

# Check project structure
if [ -f "package.json" ] && [ -d "dnas" ] && [ -d "ui" ]; then
    echo "✅ Project structure: Valid"
else
    echo "❌ Project structure: Invalid (not in project root?)"
fi

echo ""
echo "🚀 To start development: bun start"

Environment Reset Script

#!/bin/bash
# Save as: scripts/reset-env.sh

echo "🔄 Resetting Development Environment"
echo "===================================="

# Kill Holochain processes
echo "Stopping Holochain processes..."
pkill -f holochain
pkill -f lair-keystore

# Clean state directories
echo "Cleaning state directories..."
rm -rf .hc/
rm -rf /tmp/holochain_*
rm -rf logs/

# Clean build artifacts
echo "Cleaning build artifacts..."
rm -rf target/
rm -rf dnas/requests_and_offers/target/
cd ui && rm -rf node_modules && rm -f bun.lockb

# Reinstall dependencies
echo "Reinstalling dependencies..."
cd .. && bun install

# Rebuild zomes
echo "Rebuilding zomes..."
nix develop --command bun build:zomes

echo "✅ Environment reset complete!"
echo "🚀 Run 'bun start' to begin development"

🧪 Testing Troubleshooting

Test Debugging Strategies

// Debug Effect-TS tests
describe("ServiceType Tests", () => {
  it("should debug service operations", async () => {
    const program = Effect.gen(function* () {
      // Add logging for debugging
      yield* Effect.log("Starting service operation");

      const service = yield* ServiceTypeService;

      // Log intermediate steps
      const input = { name: "Test", description: "Test desc" };
      yield* Effect.log("Input:", input);

      const result = yield* service.createServiceType(input);
      yield* Effect.log("Result:", result);

      return result;
    });

    // Run with detailed error information
    const result = await Effect.runPromise(
      program.pipe(
        Effect.provide(TestServiceLayer),
        Effect.tapError((error) => Effect.log("Error occurred:", error)),
      ),
    );

    expect(result.name).toBe("Test");
  });
});

Backend Test Debugging

#![allow(unused)]
fn main() {
// Debug Sweettest tests
#[tokio::test(flavor = "multi_thread")]
async fn debug_service_type_creation() -> anyhow::Result<()> {
    // Enable detailed logging
    std::env::set_var("RUST_LOG", "debug");
    env_logger::init();

    let (conductor, agent, cell) = setup_conductor_test().await?;

    let input = CreateServiceTypeInput {
        name: "Debug Test".to_string(),
        description: Some("Debug description".to_string()),
        tags: vec!["debug".to_string()],
    };

    // Log input data
    println!("Input: {:?}", input);

    let result: Result<ActionHash, _> = conductor
        .call(&cell.zome("service_types_coordinator"), "create_service_type", input)
        .await;

    // Debug result
    match result {
        Ok(hash) => {
            println!("Success: {:?}", hash);
            assert!(!hash.get_raw_39().is_empty());
        }
        Err(e) => {
            println!("Error: {:?}", e);
            panic!("Service type creation failed: {:?}", e);
        }
    }

    Ok(())
}
}

🆘 Getting Help

Internal Resources

Community Support

Escalation Process

  1. Check this troubleshooting guide for common solutions
  2. Search existing GitHub issues for similar problems
  3. Ask in Discord for community help
  4. Create GitHub issue with detailed reproduction steps
  5. Tag maintainers for urgent production issues

💡 Pro Tip: Most issues can be resolved by ensuring you're in the Nix environment (nix develop) and using the autonomous test command (nix develop --command bun test:unit) for hREA-integrated tests.

🔍 Debug Mode: Set RUST_LOG=debug and VITE_LOG_LEVEL=debug for detailed logging during development.

Release Notes Template

This template matches the format used in v0.3.0 and v0.4.0 releases. Replace all {PLACEHOLDERS} before publishing.


What's New

Features

{FEATURES_LIST}

Bug Fixes

{BUGFIXES_LIST}

Known Issues

{KNOWN_ISSUES}

See CHANGELOG.md for full details.

Installation

WebApp: Download requests_and_offers.webhapp from this release

Desktop Apps:

Homebrew (macOS):

brew tap happenings-community/homebrew-requests-and-offers
brew install --cask requests-and-offers

Technical Specifications

  • Network: {NETWORK}
  • Holochain Version: {HOLOCHAIN_VERSION}
  • UI Framework: SvelteKit + Svelte 5
  • Architecture: 7-Layer Effect-TS

Full Changelog: https://github.com/happenings-community/requests-and-offers/compare/v{PREV_VERSION}...v{VERSION}


Variable Reference

VariableDescriptionExample
{VERSION}Release version number0.4.0
{PREV_VERSION}Previous release version0.3.0
{RELEASE_TITLE}Release headline with emoji🚀 Weave/Moss Integration & Markdown Support Release
{FEATURES_LIST}Feature bullet points from CHANGELOG- **Feature Name**: Description
{BUGFIXES_LIST}Bug fix bullet points from CHANGELOG- **Fix Name**: Description
{KNOWN_ISSUES}Known issues or "None"- **hREA**: Outstanding issues...
{NETWORK}Bootstrap/signal network useddev-test-bootstrap2.holochain.org
{HOLOCHAIN_VERSION}Holochain framework version0.6.0

Usage

# 1. Copy template content (everything between the --- markers above)
# 2. Replace {VERSION} globally
# 3. Fill in features/bugfixes from CHANGELOG.md
# 4. Apply to GitHub release:
gh release edit v{VERSION} --notes "$(cat /tmp/release-notes.md)"

The download URL pattern is consistent across releases — only {VERSION} changes in the links.

End-to-End Playwright Tests with Real Holochain DNA Implementation

Implementation plan for comprehensive e2e tests using Playwright with real Holochain DNA data instead of mocked data.

Completed Tasks

  • Analyzed existing UI structure and test setup
  • Reviewed current Playwright configuration
  • Examined Holochain integration and DNA setup
  • Identified existing test infrastructure

In Progress Tasks

  • Create e2e test directory structure
  • Implement Holochain test fixtures with real DNA data
  • Set up test data seeding utilities
  • Create page object models for UI components

Future Tasks

  • Implement core user journey tests
  • Add comprehensive offer/request workflow tests
  • Create organization and user management tests
  • Add service types and tags management tests
  • Implement admin functionality tests
  • Add cross-browser testing configuration
  • Create CI/CD integration for e2e tests

Implementation Plan

Architecture Overview

The e2e tests will use real Holochain DNA data by:

  1. Starting a fresh Holochain conductor for each test suite
  2. Seeding the DNA with realistic test data using a comprehensive data generator
  3. Running UI tests against the live Holochain backend
  4. Cleaning up after each test to ensure isolation

Real Data Strategy

Multi-Layered Data Seeding Approach

Level 1: Infrastructure Data

  • Service Types: Realistic categories (Web Development, Graphic Design, etc.)
  • Mediums of Exchange: Various exchange types (USD, EUR, Pay it Forward, etc.)
  • Admin Users: Network administrators for system management

Level 2: User Ecosystem Data

  • Diverse User Profiles: Different roles, skills, locations, time zones
  • Organizations: Various membership structures and purposes
  • User-Organization Relationships: Realistic coordinator/member relationships

Level 3: Marketplace Data

  • Realistic Offers: Spanning different service types and preferences
  • Realistic Requests: Matching potential with offers
  • Cross-references: Proper linking to service types and exchange mediums

Data Generation Features

  • Faker.js Integration: Generates realistic names, emails, locations, etc.
  • Skill-Based Matching: Users have relevant skills for their service offerings
  • Geographic Diversity: Users from different time zones and locations
  • Realistic Relationships: Proper linking between entities
  • Edge Case Coverage: Various data combinations for thorough testing

Test Structure

ui/tests/e2e/
├── fixtures/           # Test data and utilities
│   ├── holochain-data.ts    # DNA data seeding
│   ├── test-users.ts        # User fixtures
│   └── test-scenarios.ts    # Complex test scenarios
├── pages/              # Page Object Models
│   ├── base-page.ts         # Base page class
│   ├── offers-page.ts       # Offers management
│   ├── requests-page.ts     # Requests management
│   └── admin-page.ts        # Admin functionality
├── specs/              # Test specifications
│   ├── user-journeys/       # End-to-end user flows
│   ├── offers/              # Offer-specific tests
│   ├── requests/            # Request-specific tests
│   └── admin/               # Admin functionality tests
└── utils/              # Test utilities
    ├── holochain-setup.ts   # Holochain test setup
    ├── data-helpers.ts      # Data manipulation helpers
    └── assertions.ts        # Custom assertions

Key Components

  1. Enhanced Holochain Setup: Isolated conductor instances with test-specific configuration
  2. Realistic Data Seeding: Comprehensive data generator with faker.js integration
  3. Data Seeding Service: HolochainDataSeeder class for populating DNA with realistic data
  4. Page Object Models: Encapsulate UI interactions for maintainable tests
  5. Test Isolation: Each test suite starts with fresh conductor and clean data state

Implementation Files Created

Data Generation Layer

  • ui/tests/e2e/fixtures/realistic-data-generator.ts: Generates realistic test data using faker.js
  • ui/tests/e2e/fixtures/holochain-data-seeder.ts: Seeds Holochain DNA with generated data
  • ui/tests/e2e/utils/holochain-setup.ts: Enhanced setup with isolated conductor management

Key Features of Data Generation

  • 25 diverse users with realistic profiles, skills, and locations
  • 8 organizations with proper membership structures
  • 8 service types covering major categories (Web Dev, Design, Marketing, etc.)
  • 8 mediums of exchange including traditional and alternative currencies
  • 15 realistic requests with proper service type and medium linkage
  • 20 realistic offers matching the ecosystem needs
  • Admin user setup for service type approval and system management

Relevant Files

  • ui/tests/e2e/ - New e2e test directory (to be created)
  • ui/tests/setup/start-holochain.ts - Existing Holochain setup (to be extended)
  • ui/playwright.config.ts - Playwright configuration (to be updated)
  • ui/tests/setup/global-setup.ts - Global test setup (to be enhanced)
  • ui/tests/setup/global-teardown.ts - Global test teardown (to be enhanced)

Technical Implementation Details

Holochain Test Setup

  • Extend existing start-holochain.ts to support test-specific configurations
  • Create isolated conductor instances for each test suite
  • Implement data seeding functions for realistic test scenarios

Test Data Strategy

  • Create comprehensive test fixtures for users, offers, requests, organizations
  • Implement data relationships that mirror real-world usage
  • Ensure test data covers edge cases and error scenarios

Page Object Models

  • Abstract UI interactions into reusable page objects
  • Implement waiting strategies for Holochain async operations
  • Create helper methods for common workflows

Test Categories

  1. User Journeys: Complete workflows from user perspective
  2. Feature Tests: Specific functionality testing
  3. Integration Tests: Cross-component interactions
  4. Admin Tests: Administrative functionality
  5. Error Handling: Error scenarios and recovery

Environment Configuration

The tests will use:

  • Real Holochain conductor (not mocked)
  • Fresh DNA state for each test suite
  • Realistic test data seeded into DNA
  • UI running against live Holochain backend
  • Isolated test environments to prevent interference

Success Criteria

  • All major user journeys covered with e2e tests
  • Tests use real Holochain DNA data (no mocking)
  • Test suite runs reliably in CI/CD
  • Tests provide meaningful feedback on regressions
  • Test data setup is maintainable and extensible

Detailed Implementation Steps

Phase 1: Infrastructure Setup

1.1 Create E2E Test Directory Structure

ui/tests/e2e/
├── fixtures/
│   ├── holochain-data.ts
│   ├── test-users.ts
│   ├── test-offers.ts
│   ├── test-requests.ts
│   ├── test-organizations.ts
│   └── test-scenarios.ts
├── pages/
│   ├── base-page.ts
│   ├── offers-page.ts
│   ├── requests-page.ts
│   ├── organizations-page.ts
│   ├── users-page.ts
│   ├── service-types-page.ts
│   └── admin-page.ts
├── specs/
│   ├── user-journeys/
│   │   ├── complete-offer-request-flow.spec.ts
│   │   ├── user-registration-flow.spec.ts
│   │   └── organization-management-flow.spec.ts
│   ├── offers/
│   │   ├── create-offer.spec.ts
│   │   ├── edit-offer.spec.ts
│   │   ├── delete-offer.spec.ts
│   │   └── offer-search.spec.ts
│   ├── requests/
│   │   ├── create-request.spec.ts
│   │   ├── edit-request.spec.ts
│   │   ├── delete-request.spec.ts
│   │   └── request-search.spec.ts
│   ├── organizations/
│   │   ├── create-organization.spec.ts
│   │   ├── join-organization.spec.ts
│   │   └── manage-members.spec.ts
│   └── admin/
│       ├── user-management.spec.ts
│       ├── service-types.spec.ts
│       └── system-settings.spec.ts
└── utils/
    ├── holochain-setup.ts
    ├── data-helpers.ts
    ├── assertions.ts
    ├── wait-helpers.ts
    └── test-config.ts

1.2 Enhanced Holochain Test Setup

  • Extend ui/tests/setup/start-holochain.ts for e2e-specific needs
  • Create an isolated conductor configuration for tests
  • Implement test data seeding after conductor startup
  • Add cleanup utilities for test isolation

1.3 Update Playwright Configuration

  • Configure test directory to include ui/tests/e2e
  • Add e2e-specific test scripts to package.json
  • Set up proper timeouts for Holochain operations
  • Configure test parallelization settings

Phase 2: Test Data Infrastructure

2.1 Holochain Data Fixtures

Create comprehensive test data that covers:

  • Multiple user profiles with different roles
  • Organizations with various membership structures
  • Offers and requests with different states
  • Service types and tags
  • Medium of exchange configurations
  • Admin settings and configurations

2.2 Data Seeding Utilities

  • Functions to populate DNA with test data
  • Utilities to create realistic data relationships
  • Helper functions for specific test scenarios
  • Data cleanup and reset utilities

2.3 Test Scenarios

  • Complete user journeys from registration to transaction
  • Complex multi-user interactions
  • Error scenarios and edge cases
  • Performance testing scenarios

Phase 3: Page Object Models

3.1 Base Page Infrastructure

  • Common page interactions and utilities
  • Holochain-specific waiting strategies
  • Error handling and recovery
  • Navigation helpers

3.2 Feature-Specific Pages

  • Offers management page objects
  • Requests management page objects
  • Organization management page objects
  • User profile and settings page objects
  • Admin functionality page objects

Phase 4: Test Implementation

4.1 Core User Journeys

  • New user registration and profile setup
  • Creating and managing offers
  • Creating and managing requests
  • Joining and managing organizations
  • Complete offer-request matching flow

4.2 Feature-Specific Tests

  • CRUD operations for all entities
  • Search and filtering functionality
  • User permissions and access control
  • Data validation and error handling

4.3 Admin Functionality Tests

  • User management and moderation
  • Service type configuration
  • System settings and configuration
  • Data export and reporting

Phase 5: CI/CD Integration

5.1 Test Automation

  • Configure e2e tests to run in CI pipeline
  • Set up test reporting and artifacts
  • Implement test result notifications
  • Add performance monitoring

5.2 Test Maintenance

  • Regular test data updates
  • Test stability monitoring
  • Performance optimization
  • Documentation updates

Real Holochain Data Strategy for E2E Testing

Overview

This document outlines the comprehensive strategy for getting real Holochain data in E2E tests, moving away from mocked data to test against actual Holochain DNA operations.

Strategy Components

1. Multi-Layered Data Seeding Architecture

Level 1: Infrastructure Data

  • Service Types: 8 realistic categories (Web Development, Graphic Design, Marketing, etc.)
  • Mediums of Exchange: 8 exchange types (USD, EUR, Pay it Forward, Skill Exchange, etc.)
  • Admin Users: Network administrators for service type approval and system management

Level 2: User Ecosystem Data

  • 25 Diverse Users: Different roles (advocates/creators), skills, locations, time zones
  • 8 Organizations: Various membership structures and purposes
  • User-Organization Relationships: Realistic coordinator and member relationships

Level 3: Marketplace Data

  • 15 Realistic Requests: Properly linked to service types and mediums of exchange
  • 20 Realistic Offers: Matching ecosystem needs with proper relationships
  • Cross-references: Proper linking between all entities

2. Implementation Architecture

ui/tests/e2e/
├── fixtures/
│   ├── realistic-data-generator.ts    # Faker.js-powered data generation
│   └── holochain-data-seeder.ts       # Holochain DNA seeding service
├── utils/
│   └── holochain-setup.ts             # Enhanced conductor management
└── specs/
    └── user-journeys/
        └── complete-offer-request-flow.spec.ts  # Example E2E test

Key Features

Realistic Data Generation

  • Faker.js Integration: Generates realistic names, emails, locations, companies
  • Skill-Based Relationships: Users have relevant skills for their service offerings
  • Geographic Diversity: Users from different time zones and locations worldwide
  • Realistic Business Data: Organizations with proper email domains, URLs, descriptions
  • Edge Case Coverage: Various data combinations for thorough testing

Holochain Integration

  • Isolated Conductor Instances: Each test suite gets a fresh conductor
  • Proper DNA Seeding: Data is created through actual Holochain zome calls
  • Real Relationships: Service types, mediums of exchange properly linked to offers/requests
  • Admin Workflow: Admin users created first to approve service types
  • Dependency Management: Data seeded in proper order (admins → service types → users → offers/requests)

Test Infrastructure

  • Enhanced Setup: HolochainE2ESetup class manages conductor lifecycle
  • Data Seeding Service: HolochainDataSeeder populates DNA with realistic data
  • Test Isolation: Each test suite starts with clean state
  • Helper Functions: Easy access to seeded data for test assertions

Usage Examples

Basic Test Setup

test.describe("Real Holochain Data Tests", () => {
  let seededData: SeededData;

  test.beforeAll(async () => {
    const setup = await setupGlobalHolochain();
    seededData = setup.seededData;
  });

  test.afterAll(async () => {
    await cleanupGlobalHolochain();
  });

  test("can browse real offers", async ({ page }) => {
    await page.goto("/offers");

    // Verify real offers are displayed
    await expect(page.locator('[data-testid="offer-card"]')).toHaveCount(
      seededData.offers.length,
    );

    // Test with actual seeded data
    const firstOffer = seededData.offers[0];
    await expect(page.locator(`text=${firstOffer.data.title}`)).toBeVisible();
  });
});

Testing Data Relationships

test("service type filtering works with real data", async ({ page }) => {
  const webDevServiceType = seededData.serviceTypes.find(
    (st) => st.data.name === "Web Development",
  );

  await page.click(`text=${webDevServiceType.data.name}`);

  // Verify only offers linked to this service type are shown
  const linkedOffers = seededData.offers.filter((offer) =>
    offer.serviceTypeHashes.includes(webDevServiceType.actionHash),
  );

  await expect(page.locator('[data-testid="offer-card"]')).toHaveCount(
    linkedOffers.length,
  );
});

Benefits

1. Real Data Validation

  • Tests actual Holochain DNA operations, not mocks
  • Validates real data relationships and constraints
  • Catches integration issues between frontend and Holochain

2. Comprehensive Coverage

  • Tests complete user journeys with realistic data
  • Validates search, filtering, and relationship functionality
  • Covers admin workflows and permissions

3. Maintainable Tests

  • Centralized data generation and seeding
  • Easy to add new test scenarios
  • Clear separation between data setup and test logic

4. CI/CD Ready

  • Isolated test environments
  • Proper cleanup and teardown
  • Configurable for different environments

Configuration

Playwright Configuration

export default defineConfig({
  testDir: "./tests/e2e",
  fullyParallel: false, // Disable for Holochain
  workers: 1, // Single worker to avoid conflicts
  timeout: 60000, // Increased for Holochain operations
  // ... other config
});

Package.json Scripts

{
  "scripts": {
    "test:e2e:holochain": "cross-env UI_PORT=5173 TAURI_DEV=true playwright test tests/e2e/specs",
    "test:e2e:holochain:debug": "cross-env UI_PORT=5173 TAURI_DEV=true playwright test tests/e2e/specs --debug"
  }
}

Running the Tests

Development

# Run E2E tests with real Holochain data
bun run test:e2e:holochain

# Debug mode
bun run test:e2e:holochain:debug

# UI mode for interactive debugging
bun run test:e2e:ui

CI/CD Integration

The tests are designed to run in CI environments with:

  • Proper timeout configurations
  • Single worker execution
  • Comprehensive cleanup
  • JUnit reporting for integration

Data Seeding Details

Generated Data Volumes

  • 25 Users: Mix of advocates and creators with diverse skills
  • 8 Organizations: Different industries and sizes
  • 8 Service Types: Major service categories with tags
  • 8 Mediums of Exchange: Traditional and alternative currencies
  • 15 Requests: Realistic needs with proper linkage
  • 20 Offers: Services matching the ecosystem

Data Quality Features

  • Realistic Names: Generated using faker.js
  • Valid Emails: Proper domain structures
  • Geographic Diversity: Multiple time zones and locations
  • Skill Matching: Users have relevant skills for their offerings
  • Business Logic: Proper relationships between entities

Future Enhancements

  1. Performance Testing: Add load testing with large datasets
  2. Multi-Agent Testing: Test with multiple concurrent users
  3. Organization Workflows: Enhanced organization management testing
  4. Advanced Search: Complex search and filtering scenarios
  5. Real-time Updates: Test live data updates and synchronization

Conclusion

This strategy provides a robust foundation for E2E testing with real Holochain data, ensuring that tests validate actual system behavior rather than mocked interactions. The comprehensive data seeding approach creates realistic test scenarios that closely mirror production usage patterns.