Skip to content
Open
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
42 changes: 41 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,44 @@ yarn-error.log*
# env files (can opt-in for committing if needed)
.env*

# vercel
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem
/contracts/target
/contracts/test_snapshots

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

Expand All @@ -44,4 +82,6 @@ next-env.d.ts

target/
issue.md
pr.md
pr.md
*storybook.log
/storybook-static/
22 changes: 22 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { StorybookConfig } from "@storybook/react-vite";

const config: StorybookConfig = {
stories: [
"../src/**/*.mdx",
"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)",
],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-onboarding",
"@storybook/addon-interactions",
],
framework: {
name: "@storybook/react-vite",
options: {},
},
docs: {
autodocs: "tag",
},
};
export default config;
Comment on lines +1 to +22

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether the app relies on a "`@/`*" alias, and whether it's wired into vite/storybook config anywhere.
rg -n '"`@/`\*"' tsconfig.json
rg -n 'alias' .storybook/main.ts vite.config.* 2>/dev/null

Repository: LightForgeHub/SkillSphere-Dapp

Length of output: 196


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Files\n'
git ls-files .storybook/main.ts tsconfig.json vite.config.* .storybook/* 2>/dev/null || true

printf '\n## Storybook config\n'
if [ -f .storybook/main.ts ]; then
  cat -n .storybook/main.ts
fi

printf '\n## TS config alias\n'
if [ -f tsconfig.json ]; then
  sed -n '1,120p' tsconfig.json | cat -n
fi

printf '\n## Vite config search\n'
rg -n --hidden --glob '!*node_modules*' --glob '!dist' --glob '!build' 'resolve\s*:|alias\s*:|`@/`\*|viteFinal|storybook' .storybook vite.config.* tsconfig*.json package.json 2>/dev/null || true

Repository: LightForgeHub/SkillSphere-Dapp

Length of output: 2942


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Storybook tsconfig\n'
if [ -f .storybook/tsconfig.json ]; then
  cat -n .storybook/tsconfig.json
fi

printf '\n## Any "`@/`\" imports in repo\n'
rg -n --hidden --glob '!*node_modules*' --glob '!dist' --glob '!build' --glob '!coverage' 'from\s+["@'\'"'"'"]`@/`|import\s+["@'\'"'"'"]`@/`' . 2>/dev/null || true

printf '\n## Any Storybook Vite alias wiring\n'
rg -n --hidden --glob '!*node_modules*' --glob '!dist' --glob '!build' 'viteFinal|resolve\s*:\s*\{|alias\s*:' .storybook . 2>/dev/null || true

Repository: LightForgeHub/SkillSphere-Dapp

Length of output: 24191


🌐 Web query:

Storybook 8 react-vite tsconfig paths viteFinal alias automatic support documentation

💡 Result:

In Storybook 8 with the Vite builder, the approach for handling TypeScript path aliases (defined in tsconfig.json) depends on your version of Vite [1][2]. For projects using Vite 8 or higher, Storybook supports native resolution of TypeScript paths [1][2]. You do not need to use external plugins like vite-tsconfig-paths [1]. Instead, you should enable native support in your Vite configuration file (vite.config.ts) [1]: // vite.config.ts export default defineConfig({ resolve: { tsconfigPaths: true, }, }); If you are using an older version of Vite or need to manually configure aliases in Storybook regardless of your global Vite setup, you can use the viteFinal function in your.storybook/main.ts file [3][4]. This allows you to merge custom Vite configurations into the Storybook build process [5][4]. To manually configure paths using vite-tsconfig-paths (for older Vite versions): //.storybook/main.ts import { mergeConfig } from 'vite'; import tsconfigPaths from 'vite-tsconfig-paths'; export default { //... other config async viteFinal(config) { return mergeConfig(config, { plugins: [tsconfigPaths], }); }, }; Alternatively, to manually define specific aliases without a plugin: //.storybook/main.ts import { mergeConfig } from 'vite'; import path from 'path'; export default { //... other config async viteFinal(config) { return mergeConfig(config, { resolve: { alias: { '@': path.resolve(dirname, '../src'), }, }, }); }, }; Important Considerations: 1. Vite Configuration File: By default, Storybook's Vite builder attempts to read your project's vite.config.ts file [6][4]. However, it may sometimes override or ignore parts of it to ensure compatibility with Storybook’s internal build requirements [7][3]. 2. viteFinal: This function is the recommended way to override or extend the Vite configuration specifically for Storybook [8][4]. It receives the existing config and must return the modified version [5][4]. 3. Native Support: Always check if your project is running Vite 8+, as the native resolve.tsconfigPaths: true option is the preferred, modern approach [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## package.json Vite/Storybook deps\n'
if [ -f package.json ]; then
  rg -n '"vite"|`@storybook/`(react-vite|builder-vite|nextjs|react)' package.json
fi

printf '\n## vite config files\n'
git ls-files 'vite.config.*' '.storybook/*' 2>/dev/null || true

printf '\n## explicit Storybook Vite hooks or alias wiring\n'
rg -n --hidden --glob '!*node_modules*' --glob '!dist' --glob '!build' 'viteFinal|resolve\s*:\s*\{|alias\s*:|tsconfigPaths|tsconfigPaths' .storybook vite.config.* package.json tsconfig*.json 2>/dev/null || true

Repository: LightForgeHub/SkillSphere-Dapp

Length of output: 487


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Story files\n'
git ls-files '*.stories.*' '*.mdx' '.storybook/*' 2>/dev/null || true

printf '\n## Alias imports inside story files\n'
rg -n --hidden --glob '*.stories.*' --glob '*.mdx' 'from\s+["'\'']`@/`|import\s+["'\'']`@/`' . 2>/dev/null || true

Repository: LightForgeHub/SkillSphere-Dapp

Length of output: 474


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Button story\n'
cat -n src/components/ui/Button.stories.tsx

printf '\n## Button component imports\n'
sed -n '1,120p' src/components/ui/Button.tsx | cat -n

printf '\n## Card story\n'
cat -n src/components/ui/Card.stories.tsx

printf '\n## Card component imports\n'
sed -n '1,120p' src/components/ui/Card.tsx | cat -n

Repository: LightForgeHub/SkillSphere-Dapp

Length of output: 12057


🌐 Web query:

Storybook 8 builder-vite tsconfig paths automatically resolved vite 6 viteFinal alias docs

💡 Result:

Storybook 8 does not automatically resolve tsconfig paths for the Vite builder [1][2]. While Storybook's Vite builder attempts to merge your root vite.config.ts into its own configuration, it does not inherently apply tsconfig.json path mappings [3][4][2]. To resolve these aliases, you must explicitly configure them in your .storybook/main.ts file using the viteFinal hook [3][1][4]. The most common and effective way to achieve this is by using the vite-tsconfig-paths plugin within viteFinal [1][2][5]. Implementation Example: In your .storybook/main.ts (or .js), you can modify the Vite configuration as follows: import type { StorybookConfig } from '@storybook/your-framework'; import { mergeConfig } from 'vite'; import tsconfigPaths from 'vite-tsconfig-paths'; const config: StorybookConfig = { //... rest of your config async viteFinal(config) { return mergeConfig(config, { plugins: [tsconfigPaths], }); }, }; export default config; Alternatively, you can define aliases manually within viteFinal if you prefer not to use a plugin: async viteFinal(config) { config.resolve = config.resolve || {}; config.resolve.alias = {...(config.resolve.alias || {}), '@/src': path.resolve(dirname, '../src'), }; return config; }, Important Notes: 1. Vite Configuration: While Storybook looks for a vite.config.ts in your project root, it may not pick up all settings (like path plugins) automatically due to the specific way Storybook needs to wrap and manage the configuration [4][2]. 2. Custom Config Paths: If your Storybook configuration folder is not at the project root, you may need to explicitly specify the viteConfigPath in your main.ts to ensure your base Vite configuration is loaded correctly [3][1][4]. 3. Vite 6 Compatibility: The approach of using viteFinal to inject plugins or aliases remains the standard method for Vite 6 compatibility in Storybook 8 [3][6]. Ensure your dependencies, including vite-tsconfig-paths, are updated to versions compatible with Vite 6.

Citations:


Add Storybook Vite alias wiring

.storybook/main.ts doesn’t inherit the repo’s @/* path mapping, and Storybook 8 with Vite 6 won’t resolve tsconfig.json paths on its own. Add a viteFinal hook (or equivalent resolve.alias) here so stories can import @/... modules without build failures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.storybook/main.ts around lines 1 - 22, Storybook is missing the repo’s
`@/*` path alias, so imports in stories can fail to resolve under Vite. Update
the `config` in `.storybook/main.ts` by adding a `viteFinal` hook (or equivalent
`resolve.alias` setup) that maps `@` to the project’s source directory, so
`@/...` imports work in Storybook 8 with Vite 6.

15 changes: 15 additions & 0 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { Preview } from "@storybook/react";
import "../src/app/globals.css";

const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};

export default preview;
7 changes: 7 additions & 0 deletions .storybook/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"noEmit": false
},
"include": ["../src/**/*"]
}
35 changes: 21 additions & 14 deletions contracts/src/disputes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
//! Expert-initiated session cancellation with partial refund (#238).

use soroban_sdk::{contracttype, symbol_short, token, Address, Env, String, Vec};
use crate::DataKeyExt;

#[contracttype]
#[derive(Clone)]
pub enum DisputeKey {
AppealBondAmount,
}

use crate::{
events, DataKey, Error, SessionStatus, SkillSphereContract, MIN_SESSION_ESCROW,
Expand Down Expand Up @@ -178,21 +185,21 @@ pub struct JuryVoteRecord {
///
/// Candidates are filtered by minimum reputation, then `jury_size` jurors
/// are chosen via on-chain PRNG. Pass `jury_size = 0` to use the
/// admin-configured default (see `DataKey::JurySize`).
/// admin-configured default (see `DataKeyExt::JurySize`).
pub fn select_jury(
env: &Env,
dispute_id: u64,
candidates: Vec<Address>,
jury_size: u32,
) -> Result<(), Error> {
if env.storage().persistent().has(&DataKey::JurySession(dispute_id)) {
if env.storage().persistent().has(&DataKeyExt::JurySession(dispute_id)) {
return Err(Error::JuryAlreadyVoted);
}

let size = if jury_size == 0 {
env.storage()
.instance()
.get(&DataKey::JurySize)
.get(&DataKeyExt::JurySize)
.unwrap_or(DEFAULT_JURY_SIZE)
} else {
jury_size
Expand Down Expand Up @@ -230,7 +237,7 @@ pub fn select_jury(

env.storage()
.persistent()
.set(&DataKey::JurySession(dispute_id), &record);
.set(&DataKeyExt::JurySession(dispute_id), &record);

events::publish_event(
env,
Expand All @@ -257,7 +264,7 @@ pub fn cast_jury_vote(
let mut record: JuryVoteRecord = env
.storage()
.persistent()
.get(&DataKey::JurySession(dispute_id))
.get(&DataKeyExt::JurySession(dispute_id))
.ok_or(Error::JuryNotSelected)?;

if record.finalized {
Expand Down Expand Up @@ -294,7 +301,7 @@ pub fn cast_jury_vote(

env.storage()
.persistent()
.set(&DataKey::JurySession(dispute_id), &record);
.set(&DataKeyExt::JurySession(dispute_id), &record);

events::publish_event(
env,
Expand All @@ -315,7 +322,7 @@ pub fn finalize_jury_verdict(env: &Env, dispute_id: u64) -> Result<(u32, u32), E
let mut record: JuryVoteRecord = env
.storage()
.persistent()
.get(&DataKey::JurySession(dispute_id))
.get(&DataKeyExt::JurySession(dispute_id))
.ok_or(Error::JuryNotSelected)?;

if record.finalized {
Expand Down Expand Up @@ -343,7 +350,7 @@ pub fn finalize_jury_verdict(env: &Env, dispute_id: u64) -> Result<(u32, u32), E
record.finalized = true;
env.storage()
.persistent()
.set(&DataKey::JurySession(dispute_id), &record);
.set(&DataKeyExt::JurySession(dispute_id), &record);

events::publish_event(
env,
Expand Down Expand Up @@ -380,15 +387,15 @@ pub struct AppealRecord {
pub fn appeal_bond_amount(env: &Env) -> i128 {
env.storage()
.instance()
.get(&DataKey::AppealBondAmount)
.get(&DisputeKey::AppealBondAmount)
.unwrap_or(DEFAULT_APPEAL_BOND_AMOUNT)
}

/// Admin-only setter for the appeal bond amount.
pub fn set_appeal_bond_amount(env: &Env, amount: i128) {
env.storage()
.instance()
.set(&DataKey::AppealBondAmount, &amount);
.set(&DisputeKey::AppealBondAmount, &amount);
}

/// File an appeal against the ruling for `dispute_id`.
Expand All @@ -407,7 +414,7 @@ pub fn appeal_dispute(
if env
.storage()
.persistent()
.has(&DataKey::Appeal(dispute_id))
.has(&DataKeyExt::Appeal(dispute_id))
{
return Err(Error::AppealAlreadyFiled);
}
Expand All @@ -434,7 +441,7 @@ pub fn appeal_dispute(

env.storage()
.persistent()
.set(&DataKey::Appeal(dispute_id), &record);
.set(&DataKeyExt::Appeal(dispute_id), &record);

events::publish_event(
env,
Expand Down Expand Up @@ -463,7 +470,7 @@ pub fn resolve_appeal(
let mut record: AppealRecord = env
.storage()
.persistent()
.get(&DataKey::Appeal(dispute_id))
.get(&DataKeyExt::Appeal(dispute_id))
.ok_or(Error::AppealNotFound)?;

if record.resolved {
Expand All @@ -475,7 +482,7 @@ pub fn resolve_appeal(
record.ruling_bps_expert = expert_award_bps;
env.storage()
.persistent()
.set(&DataKey::Appeal(dispute_id), &record);
.set(&DataKeyExt::Appeal(dispute_id), &record);

// Apply the new ruling to the underlying dispute.
SkillSphereContract::resolve_dispute_internal(env, dispute_id, seeker_award_bps)?;
Expand Down
54 changes: 0 additions & 54 deletions contracts/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,13 @@ use soroban_sdk::contracterror;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Error {
InsufficientFunds = 1,
Unauthorized = 2,
SessionNotActive = 3,
SessionNotFound = 4,
InvalidAmount = 5,
NotStarted = 6,
AlreadyFinished = 7,
DisputeNotFound = 8,
UpgradeNotInitiated = 9,
TimelockNotExpired = 10,
EmptyDisputeReason = 11,
ProtocolPaused = 12,
ReputationTooLow = 13,
InvalidFeeBps = 14,
SessionExpired = 15,
InvalidCid = 16,
InvalidSplitBps = 17,
Expand All @@ -29,81 +22,34 @@ pub enum Error {
ExpertUnavailable = 23,
InvalidReferrer = 24,
ReentrantCall = 25,
DepositTooLow = 26,
AlreadyInitialized = 27,
InvalidRating = 28,
RatingSubmitted = 29,
OracleNotTrusted = 30,
InvalidOracleSig = 31,
OraclePriceUnavailable = 32,
InvalidSessionState = 33,
InsufficientBalance = 34,

// #213 / #214
StakeNotFound = 35,
NoRewardsToClaim = 36,

// #194 / #195 / #196 / #197
ContractUnset = 42,

// #198 / #199 / #200
ExpertOffline = 44,
DisputeResolved = 45,

// #202 – Soulbound Skill Badges
BadgeAlreadyMinted = 46,
HoursThresholdNotMet = 47,
SessionFrozen = 48,
SwapFailed = 49,

// #240 / #241 / #242
ExpertOnCooldown = 50,
SpendingLimitExceeded = 51,
InvalidVoucher = 56,

// #257
TimelockActive = 57,

// #250 - Dispute cooling-off period
DisputeCoolingOff = 58,

// #251 - Dispute escalation to DAO
DisputeEscalated = 59,

// #252 - Expert handoff
HandoffProposalNotFound = 60,
HandoffExpired = 61,
InvalidHandoffTarget = 62,

// #253 - NFT minting
NftContractNotSet = 63,

// #276 - Slippage protection
SlippageExceeded = 64,

// #277 - Reputation decay / #278 - Session quality score (shared)
InvalidDimensionRating = 65,

YieldPoolNotSet = 66,
InsufficientAntiSpamDeposit = 67,
CircuitBreakerActive = 68,
SessionNotExpired = 69,

// #284 - Jury Selection
JuryNotSelected = 70,
JuryAlreadyVoted = 71,
JuryVotingClosed = 72,
InsufficientCandidates = 73,

// #285 - Appeal Mechanism
AppealBondRequired = 74,
AppealAlreadyFiled = 75,
AppealNotFound = 76,

// #286 - Fee Auto-Conversion
SkillTokenNotSet = 78,
BuybackDisabled = 79,

// #287 - Surge Pricing
SurgeNotAccepted = 82,
}
11 changes: 9 additions & 2 deletions contracts/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@

use soroban_sdk::{contracttype, symbol_short, Env, IntoVal, Symbol, Val, Vec};

#[contracttype]
#[derive(Clone)]
pub enum EventKey {
EventLog(u32),
}


/// Maximum number of entries kept in the on-chain ring buffer.
pub const EVENT_LOG_CAPACITY: u32 = 1000;

Expand Down Expand Up @@ -55,7 +62,7 @@ fn append_to_ring(env: &Env, event_type: Symbol, session_id: u64) {
timestamp: env.ledger().timestamp(),
};

let key = crate::DataKey::EventLog(slot);
let key = EventKey::EventLog(slot);
env.storage().temporary().set(&key, &entry);

env.storage()
Expand Down Expand Up @@ -83,7 +90,7 @@ pub fn get_event_log(env: &Env, from_index: u32, limit: u32) -> Vec<EventLogEntr
if let Some(entry) = env
.storage()
.temporary()
.get::<crate::DataKey, EventLogEntry>(&crate::DataKey::EventLog(slot))
.get::<EventKey, EventLogEntry>(&EventKey::EventLog(slot))
{
// Confirm the slot hasn't been overwritten by a newer entry.
if entry.index == idx {
Expand Down
Loading
Loading