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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/mentor-booking.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Mentor Booking Feature Checks

on:
pull_request:
paths:
- 'frontend/src/lib/mentor-booking.ts'
- 'frontend/src/lib/__tests__/mentor-booking.test.ts'
- '.github/workflows/mentor-booking.yml'
push:
branches: [main, master]
paths:
- 'frontend/src/lib/mentor-booking.ts'
- 'frontend/src/lib/__tests__/mentor-booking.test.ts'
- '.github/workflows/mentor-booking.yml'

jobs:
mentor-booking:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10.33.0
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
cache-dependency-path: frontend/pnpm-lock.yaml
- name: Install frontend dependencies
working-directory: frontend
run: pnpm install --frozen-lockfile
- name: Run mentor booking tests
working-directory: frontend
run: pnpm exec vitest run src/lib/__tests__/mentor-booking.test.ts
60 changes: 60 additions & 0 deletions docs/frontend/PLATFORM_FEATURE_OPTIMIZATIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Platform Feature Optimizations

This document covers the MVP frontend/DevOps additions for:

- Performance Profiling in the Open Source Contribution Trainer
- Optimized Merkle Tree Builder
- Consensus Algorithm Sandbox in the Web3 Learning Roadmap
- Mentor Booking optimization checks

## Open Source Contribution Performance Profiling

Core logic lives in `frontend/src/lib/contribution-performance.ts`.

The profiler accepts timestamped contribution events such as issue assignment, PR open, review request, review received, change requests, and merge. It derives:

- issue-to-merge cycle time
- review response time
- throughput, quality, focus, and overall scores
- bottlenecks and recommendations

The UI panel is rendered on `/performance-metrics` through `ContributionPerformanceProfiler`.

## Optimized Merkle Tree Builder

Core logic lives in `frontend/src/lib/merkle-tree-builder.ts`.

The builder normalizes leaves, removes duplicates, builds levels iteratively, duplicates odd leaves deterministically, and exposes proof generation plus proof verification. The `/merkle-tree` page now uses this shared logic for visualization and validation path display.

## Consensus Algorithm Sandbox

Core logic lives in `frontend/src/lib/consensus-sandbox.ts`.

The sandbox supports:

- proof of work leader selection by hash power
- proof of stake leader selection by active stake
- federated Byzantine agreement selection by quorum-slice trust overlap

The sandbox is embedded on `/roadmap` next to the learning path selector.

## Mentor Booking Optimization

Core logic lives in `frontend/src/lib/mentor-booking.ts`.

It provides deterministic slot selection based on tag match, capacity, fill ratio, and start time. It also prevents overbooking and detects overlapping slots per mentor.

CI coverage is isolated in `.github/workflows/mentor-booking.yml`, which runs mentor booking tests when relevant files change.

## Tests

Run the focused tests:

```bash
cd frontend
pnpm vitest run \
src/lib/__tests__/contribution-performance.test.ts \
src/lib/__tests__/merkle-tree-builder.test.ts \
src/lib/__tests__/consensus-sandbox.test.ts \
src/lib/__tests__/mentor-booking.test.ts
```
46 changes: 33 additions & 13 deletions frontend/src/app/merkle-tree/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

import * as d3 from 'd3';
import { Plus, Trash2, TreeDeciduous } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import {
buildOptimizedMerkleTree,
getMerkleProof,
stableHash,
} from '@/lib/merkle-tree-builder';

interface MerkleNode {
id: string;
Expand All @@ -25,6 +30,7 @@ export default function MerkleTreePage() {
const [selectedAddress, setSelectedAddress] = useState<string | null>(null);
const [validationPath, setValidationPath] = useState<string[]>([]);
const svgRef = useRef<SVGSVGElement>(null);
const merkleResult = useMemo(() => buildOptimizedMerkleTree(addresses), [addresses]);

const simpleHash = (input: string): string => {
let hash = 0;
Expand Down Expand Up @@ -138,7 +144,9 @@ export default function MerkleTreePage() {
const nodeRadius = 25;

// Convert tree to d3 hierarchy
const rootD3 = d3.hierarchy(root);
const rootD3 = d3.hierarchy(root, (node: any) =>
[node.left, node.right].filter(Boolean)
);

// Create tree layout
const treeLayout = d3.tree<MerkleNode>().size([width - 100, height - 100]);
Expand Down Expand Up @@ -173,7 +181,8 @@ export default function MerkleTreePage() {
.attr('r', nodeRadius)
.attr('fill', (d: any) => {
if (d.data.isLeaf) {
return d.data.address === selectedAddress ? '#22c55e' : '#ef4444';
const leafValue = d.data.address ?? d.data.value;
return leafValue === selectedAddress ? '#22c55e' : '#ef4444';
}
return '#3b82f6';
})
Expand All @@ -191,10 +200,14 @@ export default function MerkleTreePage() {
})
.style('cursor', 'pointer')
.on('click', (event: any, d: any) => {
if (d.data.isLeaf && d.data.address) {
setSelectedAddress(d.data.address);
const tree = buildMerkleTree(addresses);
setValidationPath(findValidationPath(tree, d.data.address));
const leafValue = d.data.address ?? d.data.value;
if (d.data.isLeaf && leafValue) {
setSelectedAddress(leafValue);
setValidationPath([
stableHash(leafValue),
...getMerkleProof(merkleResult, leafValue).map((step) => step.hash),
merkleResult.root.hash,
]);
}
});

Expand All @@ -215,15 +228,14 @@ export default function MerkleTreePage() {
.attr('fill', '#ffffff')
.attr('font-size', '9px')
.attr('font-family', 'monospace')
.text((d: any) => d.data.address);
.text((d: any) => d.data.address ?? d.data.value);
};

useEffect(() => {
if (addresses.length > 0) {
const tree = buildMerkleTree(addresses);
renderTree(tree);
renderTree(merkleResult.root as MerkleNode);
}
}, [addresses, selectedAddress, validationPath]);
}, [addresses, selectedAddress, validationPath, merkleResult]);

const addAddress = () => {
const newAddress = `0x${Math.random().toString(16).substring(2, 6)}...${Math.random().toString(16).substring(2, 6)}`;
Expand All @@ -240,8 +252,11 @@ export default function MerkleTreePage() {

const verifyAddress = (address: string) => {
setSelectedAddress(address);
const tree = buildMerkleTree(addresses);
setValidationPath(findValidationPath(tree, address));
setValidationPath([
stableHash(address),
...getMerkleProof(merkleResult, address).map((step) => step.hash),
merkleResult.root.hash,
]);
};

return (
Expand Down Expand Up @@ -278,6 +293,11 @@ export default function MerkleTreePage() {
</div>

<div className="mt-4 flex flex-wrap gap-2">
<div className="flex items-center gap-2 rounded-lg border border-white/10 bg-white/5 px-3 py-2">
<span className="text-[10px] font-bold text-gray-300 uppercase">
Root {merkleResult.root.hash} · {merkleResult.leafCount} Leaves · Depth {merkleResult.depth}
</span>
</div>
<div className="flex items-center gap-2 rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2">
<div className="h-3 w-3 rounded-full bg-red-500" />
<span className="text-[10px] font-bold text-red-500 uppercase">Leaf Node</span>
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/app/performance-metrics/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useAuth } from '@/contexts/AuthContext';
import { usePerformanceMetrics } from '@/hooks/usePerformanceMetrics';
import PerformanceMetricsDashboard from '@/components/performance-metrics/PerformanceMetricsDashboard';
import ContributionPerformanceProfiler from '@/components/performance-metrics/ContributionPerformanceProfiler';
import Link from 'next/link';
import { ArrowLeft } from 'lucide-react';
import { motion } from 'framer-motion';
Expand Down Expand Up @@ -84,6 +85,9 @@ export default function PerformanceMetricsPage() {
error={error}
isFallback={isFallback}
/>
<div className="mt-8">
<ContributionPerformanceProfiler />
</div>
</motion.div>
</main>
</div>
Expand Down
Loading