AgentSkillsCN

Project Guide

Boxy ——一款基于 Rust+JS 的文件共享应用的核心开发模式。

SKILL.md
--- frontmatter
description: Core development patterns for Boxy - a Rust+JS file sharing app
globs: src/**/*
alwaysApply: false

Boxy Project Guide

Tech Stack

  • Backend: Rust + Actix-web 4
  • Frontend: Vanilla JS + HTML (embedded)
  • Real-time: WebSocket broadcast
  • Tests: Playwright e2e
  • Deploy: Docker multi-stage

Project Structure

code
boxy/
├── src/main.rs              # Backend (all handlers)
├── static/
│   ├── index.html           # Main app (HTML + JS)
│   └── css/styles.css       # Extracted CSS
├── data/                    # App data (gitignored)
│   ├── boards.json          # Kanban boards + tasks
│   ├── tiles.json           # Dashboard tiles
│   └── credentials.json     # Stored credentials
├── uploads/                 # File storage (gitignored)
├── tests/ui.spec.ts         # Playwright e2e tests
├── docs/                    # Architecture docs
├── Cargo.toml               # Rust config
└── package.json             # Playwright config

Backend Patterns (src/main.rs)

Architecture

  • Single-file design - all handlers in main.rs
  • AppState holds: broadcaster, upload_dir, data_dir, max_upload_bytes
  • Settings from env: BOX_PORT, BOX_UPLOAD_DIR, BOX_DATA_DIR, BOX_MAX_UPLOAD_BYTES

Security (CRITICAL)

rust
// Always use resolve_path_safe for user-provided paths
let filepath = resolve_path_safe(&state.upload_dir, Some(&user_path))
    .ok_or_else(|| actix_web::error::ErrorForbidden("Invalid path"))?;
  • clean_relative_path() - strips .. and empty segments
  • resolve_path_safe() - canonicalizes and verifies path stays within base directory (prevents symlink attacks)
  • Never trust user-provided paths directly

Handler Pattern

rust
#[get("/api/endpoint")]
async fn handler(
    state: web::Data<AppState>,
    query: web::Query<Params>,
) -> impl Responder {
    // 1. Extract and sanitize input
    // 2. Perform operation
    // 3. Broadcast if mutation
    // 4. Return JSON response
}

WebSocket Broadcasting

rust
// Broadcast all file mutations
broadcast_update(&state.broadcaster, "upload", &path);
// Actions: upload, rename, move, delete, folder

API Endpoints

MethodEndpointPurpose
GET/api/files?path=List directory
GET/api/search?q=Recursive file search
GET/api/foldersAll folder paths
GET/api/download?path=Download/preview file
GET/api/healthHealthcheck
GET/api/data/{type}Load app data (boards, tiles, credentials)
POST/api/upload?path=Upload (multipart, supports folders)
POST/api/folderCreate folder
POST/api/renameRename item
POST/api/moveMove item
POST/api/deleteDelete item
POST/api/data/{type}Save app data with WebSocket broadcast

Kanban Board System (Frontend)

Data Structure

javascript
// Server-side storage: data/boards.json
// Board structure
{
  id: 'abc123',
  name: 'My Board',
  columns: [{ id: 'todo', name: 'Todo', order: 0 }, ...],
  tasks: [{ id, title, description, status, priority, dueDate, tags, createdAt }, ...],
  createdAt: 1705400000000
}

Key Functions

  • loadBoards() - Async, loads from server API, auto-migrates localStorage on first run
  • saveBoards() - Async, persists to server, broadcasts via WebSocket
  • saveCurrentBoard() - Sync working tasks/columns to active board
  • loadTiles() / saveTiles() - Dashboard tiles (data/tiles.json)
  • loadCredentials() / saveCredentials() - Stored keys (data/credentials.json)

Working Variables

javascript
let boards = [];           // All boards array
let currentBoardId = null; // Active board
let tasks = [];            // Current board's tasks (working copy)
let columns = [];          // Current board's columns (working copy)

Cross-Browser Sync

  • Data stored server-side in data/ directory
  • WebSocket broadcasts data_sync events on save
  • All connected browsers auto-update when data changes

Quick Commands

bash
cargo run                    # Dev server (port 8086)
cargo build --release        # Production build
npm run test:e2e            # Playwright tests
docker compose up --build   # Docker deployment

Rules

  1. Keep backend in single main.rs (no module splitting unless >1000 lines)
  2. CSS is extracted to static/css/styles.css, JS remains in index.html
  3. Always sanitize paths before filesystem access
  4. Broadcast all mutations via WebSocket (including data_sync for app data)
  5. Use env vars for config with sensible defaults
  6. Use TLDR before reading files — see tldr-first skill

Related Skills

  • tldr-first: Token-efficient exploration (TLDR commands first)
  • ui-patterns: Frontend CSS/JS patterns
  • quality-checklist: Pre-commit verification