Skip to content

Commit e41d617

Browse files
committed
feat(skills): introduce react-frontend-standards and upgrade SkillLoader
- Add 'react-frontend-standards' skill definition in 'client/skills/' - Enforces 'Backend-Driven UI' architecture - Integrates Vercel's React performance best practices - Update 'SkillLoader' in 'server/mcp/skills.py' to support multiple skill directories (server/ and client/) - Update 'server/mcp/server.py' to initialize SkillLoader with both 'server/skills' and 'client/skills' - Update 'quanuxctl' command to use the new multi-directory loader
1 parent 7c3a8f8 commit e41d617

4 files changed

Lines changed: 122 additions & 36 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
name: react-frontend-standards
3+
description: Expert guidance on QuanuX frontend development, enforcing Backend-Driven UI architecture and high-performance React patterns.
4+
metadata:
5+
version: 1.0.0
6+
author: QuanuX
7+
priority: 10
8+
---
9+
10+
# QuanuX Frontend Standards & React Best Practices
11+
12+
You are an expert QuanuX Frontend Developer specializing in React for Desktop (Tauri) and Web.
13+
Your primary responsibility is to implement high-performance, aesthetically pleasing UIs that **strictly adhere** to the QuanuX Backend-Driven architecture.
14+
15+
## 1. Architectural Protocols (CRITICAL)
16+
17+
### [RULE 1] Backend Origin
18+
**All data presented on the frontend MUST be generated on the backend.**
19+
- **Do not** create mock data arrays in client components.
20+
- **Do not** implement business logic or data transformation in the client.
21+
- **Do not** guess data shapes. Ask to see the Pydantic model or API response from the backend.
22+
- **Action**: If you need data, request to see the relevant backend code (Pydantic models, API endpoints) first.
23+
24+
### [RULE 2] Dynamic Presentation Layer Only
25+
**The frontend is a dynamic presentation layer only.**
26+
- Its sole purpose is to receive state from the backend and render it beautifully.
27+
- Complex state machines, validation rules, and business workflows belong in the Python/Rust backend.
28+
- The frontend should be "dumb" regarding *why* something is happening, and expert at *how* it looks while happening.
29+
30+
## 2. Data Fetching Strategy
31+
32+
QuanuX uses specific protocols for data exchange.
33+
**Do not invent new fetching mechanisms.**
34+
35+
- **Reference Documentation**: See `docs/MCP_INTEGRATION.md` for specific API details.
36+
- **REST/HTTP**: Use the provided API hooks or generic Query client wrappers.
37+
- **Real-Time**: Listen to SignalR/Socket events for live updates (market data, system status).
38+
39+
## 3. React Best Practices (Performance & Quality)
40+
41+
Adhere to these Vercel-derived standards for optimal performance.
42+
43+
### A. Eliminating Waterfalls (Critical)
44+
- **Do not** chain dependent requests in `useEffect`.
45+
- **Preferred**: Parallel data fetching where possible.
46+
- **Preferred**: Let the backend aggregate data into a single response if multiple resources are always needed together.
47+
48+
### B. Re-render Optimization (Medium)
49+
- **Use `useMemo`** for expensive calculations (though expensive calcs should ideally be on backend).
50+
- **Use `useCallback`** for event handlers passed to child components.
51+
- **Ensure stable references** for dependency arrays in hooks.
52+
53+
### C. Bundle Size (Critical)
54+
- **Lazy Load** non-critical components/routes using `React.lazy` and `Suspense`.
55+
- **Import wisely**: Import specific named exports rather than entire heavy libraries (e.g., specific Lodash functions).
56+
57+
### D. Rendering Performance (Medium)
58+
- **Virtualize** long lists (e.g., trade logs, order books) using `react-window` or similar.
59+
- **Avoid Layout Thrashing**: Do not read and write DOM properties (like `offsetHeight`) in the same synchronous frame.
60+
61+
## 4. Tech Stack & Styling
62+
63+
- **Styling**: Tailwind CSS (strict). Do not write custom CSS files unless absolutely necessary for animations not covered by Tailwind config.
64+
- **Components**: Shadcn UI (Radix based). Reuse existing components from `client/shared` where possible.
65+
- **Icons**: Lucide React.

server/cli/src/quanuxctl/commands/skills.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
# specific path needs to be handled carefully depending on where quanuxctl is run from
1111
# Assuming running from root as per usual dev workflow, or we need to find root
12-
skill_loader = SkillLoader("server/skills")
12+
skill_loader = SkillLoader(["server/skills", "client/skills"])
1313

1414
@app.command("list")
1515
def list_skills():

server/mcp/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
# Initialize Skill Loader
1616
from server.mcp.skills import SkillLoader
17-
skill_loader = SkillLoader("server/skills")
17+
skill_loader = SkillLoader(["server/skills", "client/skills"])
1818

1919
@mcp.tool(name="agent.skills.list")
2020
async def list_skills() -> str:

server/mcp/skills.py

Lines changed: 55 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,57 +13,74 @@ class Skill:
1313
metadata: Optional[Dict[str, Any]] = None
1414

1515
class SkillLoader:
16-
def __init__(self, skills_dir: str):
17-
# Resolve to absolute path relative to CWD if necessary,
18-
# but store relative path for "tool authority" cleanliness
19-
self.skills_dir = Path(skills_dir)
16+
def __init__(self, skills_dirs: List[str]):
17+
"""
18+
Initialize the loader with a list of directories to search for skills.
19+
:param skills_dirs: List of directory paths (relative to CWD)
20+
"""
21+
self.skills_dirs = [Path(d) for d in skills_dirs]
2022
self.base_dir = Path(os.getcwd())
2123

22-
def _resolve_skill_path(self, skill_name: str) -> Path:
24+
def _resolve_skill_path(self, skill_name: str) -> Optional[Path]:
25+
"""Find the path to a skill's SKILL.md across all registered directories."""
2326
# Validate skill name safety (no ../, no weird chars)
2427
if not skill_name.replace("-", "").isalnum():
2528
raise ValueError("Invalid skill name")
26-
return (self.base_dir / self.skills_dir / skill_name / "SKILL.md").resolve()
29+
30+
for d in self.skills_dirs:
31+
potential_path = (self.base_dir / d / skill_name / "SKILL.md").resolve()
32+
if potential_path.exists():
33+
return potential_path
34+
return None
2735

2836
def list_skills(self) -> List[Skill]:
29-
"""Discover skills by scanning the skills directory."""
37+
"""Discover skills by scanning all skills directories."""
3038
skills = []
31-
abs_skills_dir = (self.base_dir / self.skills_dir).resolve()
3239

33-
if not abs_skills_dir.exists():
34-
return []
40+
for relative_dir in self.skills_dirs:
41+
abs_skills_dir = (self.base_dir / relative_dir).resolve()
42+
43+
if not abs_skills_dir.exists():
44+
continue
3545

36-
for item in abs_skills_dir.iterdir():
37-
if item.is_dir():
38-
skill_file = item / "SKILL.md"
39-
if skill_file.exists():
40-
try:
41-
content = skill_file.read_text(encoding="utf-8")
42-
# Parse frontmatter manually since PyYAML load_all is needed
43-
# Identify YAML block
44-
if content.startswith("---"):
45-
parts = content.split("---", 2)
46-
if len(parts) >= 3:
47-
frontmatter = yaml.safe_load(parts[1])
48-
skills.append(Skill(
49-
name=frontmatter.get("name", item.name),
50-
description=frontmatter.get("description", "No description provided."),
51-
tool_authority_path=str(skill_file.relative_to(self.base_dir)),
52-
metadata=frontmatter.get("metadata")
53-
))
54-
except Exception as e:
55-
print(f"Error loading skill {item.name}: {e}")
46+
for item in abs_skills_dir.iterdir():
47+
if item.is_dir():
48+
skill_file = item / "SKILL.md"
49+
if skill_file.exists():
50+
try:
51+
content = skill_file.read_text(encoding="utf-8")
52+
# Parse frontmatter manually since PyYAML load_all is needed
53+
# Identify YAML block
54+
if content.startswith("---"):
55+
parts = content.split("---", 2)
56+
if len(parts) >= 3:
57+
frontmatter = yaml.safe_load(parts[1])
58+
skills.append(Skill(
59+
name=frontmatter.get("name", item.name),
60+
description=frontmatter.get("description", "No description provided."),
61+
# Show path relative to base_dir (e.g. "client/skills/foo/SKILL.md")
62+
tool_authority_path=str(skill_file.relative_to(self.base_dir)),
63+
metadata=frontmatter.get("metadata")
64+
))
65+
except Exception as e:
66+
print(f"Error loading skill {item.name}: {e}")
5667
return skills
5768

5869
def read_skill(self, skill_name: str) -> str:
5970
"""Read the full content of a skill file."""
6071
try:
6172
skill_path = self._resolve_skill_path(skill_name)
62-
if not skill_path.exists():
73+
if not skill_path:
6374
return f"Error: Skill '{skill_name}' not found."
6475

65-
# Security check: Ensure path is within skills_dir
66-
if not str(skill_path).startswith(str((self.base_dir / self.skills_dir).resolve())):
76+
# Security check: Ensure path is within one of the allowed skills_dirs
77+
allowed = False
78+
for d in self.skills_dirs:
79+
if str(skill_path).startswith(str((self.base_dir / d).resolve())):
80+
allowed = True
81+
break
82+
83+
if not allowed:
6784
return "Error: Access denied."
6885

6986
return skill_path.read_text(encoding="utf-8")
@@ -74,7 +91,11 @@ async def run_script(self, skill_name: str, script_name: str, args: List[str] =
7491
"""Execute a script bundled with the skill."""
7592
try:
7693
# Resolve script path
77-
skill_dir = (self.base_dir / self.skills_dir / skill_name).resolve()
94+
skill_path = self._resolve_skill_path(skill_name)
95+
if not skill_path:
96+
return f"Error: Skill '{skill_name}' not found."
97+
98+
skill_dir = skill_path.parent
7899
script_dir = skill_dir / "scripts"
79100
script_path = (script_dir / script_name).resolve()
80101

0 commit comments

Comments
 (0)