dev-engineer

SKILL前端设计
前端设计 未标注权限 许可证不明确

Adds logic, state management, TypeScript types, and CRUD operations to UI. Works AFTER ui-first-builder creates the interface. Implements Zustand stores, form handling with React Hook Form + Zod, and prepares for backend connection. Triggers: add logic, add functionality, make it work, state management, form validation, data operations, TypeScript types.

一句话选型结论

按资料字段整理

dev-engineer是前端设计类的 Agent Skill,安装方式:GitHub 仓库,权限级别未标注,许可证不明确,没有现成安装命令,需按说明手动放置 SKILL.md。技能本质是给模型读的说明书,效果取决于模型与客户端;装完先用一个小任务验证,再放进正式流程。

分类前端设计 安装方式GitHub 仓库 权限级别未标注权限 许可证许可证不明确
累计安装 未标注 本周 未标注
GitHub 星标 75 仓库 toh-framework
下载量 未标注 技能市场下载数
权限级别 未标注权限 安装前请确认风险范围
许可证 许可证不明确 未说明

安装

GitHub 仓库
在支持 Skills 的客户端里执行,装完问一句「你有哪些技能」验证
  1. 1
    确认客户端支持

    确认你的客户端支持 Skills(Claude、Codex、Cursor 等),并升级到较新的版本。

  2. 2
    拿到技能文件

    按安装说明或仓库 README,把 SKILL.md 放到客户端的 skills 目录(一般一个技能一个文件夹)。

  3. 3
    刷新技能列表

    重新打开客户端,或刷新技能列表,让新放进来的技能被加载。

  4. 4
    验证是否生效

    问一句「你有哪些技能」,确认列表里出现它,再拿一个小任务试跑。

技能档案

前端设计
字段来自技能仓库与公开索引,中文说明由本站标注
作者 / 维护方wasintoh仓库 toh-framework · 版本 未标注
宿主客户端未标注技能可被这些客户端加载
安装方式GitHub 仓库安装包 wasintoh/toh-framework
权限级别未标注权限安装前请确认风险范围
许可证未说明许可证不明确,商用前请自行确认条款
仓库可用性available技能仓库当前是否可访问
最近核验未标注上游最后核验时间 · 数据更新 未标注
正文体量14.7 KBSKILL.md 原文大小,越大说明说明书写得越细

典型使用场景

3 类
按分类整理的常见用法,具体以 SKILL.md 为准
出页面与组件

按描述生成页面结构和组件草稿。

调整样式与布局

改间距、配色与响应式布局。

生成设计说明

把设计稿整理成开发能接的说明。

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 基础信息齐全,可以先按说明装一个试试。

先想清楚的地方

  • 没有标注权限级别,实际能做什么要装好后确认。
  • 许可证不明确,商用或二次分发前先到仓库确认条款。
  • 没有现成安装命令,要按安装说明或仓库 README 手动放置。
  • 没有做站内核验,内容以作者仓库为准。
  • 技能只是说明书,实际效果取决于模型与客户端,装完先用小任务试一遍。

更适合

  • 需要在前端设计场景里给模型补一套做法的人。
  • 愿意先读一遍 SKILL.md、确认它会让模型做什么的人。

不太适合

  • 对许可证要求很严、必须明确可商用的人。
  • 只想装完就自动变强、不打算看说明书的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
技能权限级别安装方式累计安装宿主客户端安装命令
dev-engineer当前 未标注权限 GitHub 仓库 未标注 未标注 无
前端设计 只读 GitHub 仓库 882.3 万 Codex、Claude、Cursor 有
Vercel 组合模式 联网访问 GitHub 仓库 385.9 万 Codex、Claude、Cursor 有
Vercel React native skills 前端 只读 GitHub 仓库 246.9 万 Codex、Claude、Cursor 有
adapt 搜索 浏览器控制 GitHub 仓库 202.7 万 Codex、Claude、Cursor 有
使用我的浏览器 浏览器控制 GitHub 仓库 184.8 万 Codex、Claude、Cursor 有

SKILL.md 全文

14.7 KB
模型实际读到的说明书原文,来自技能仓库

Dev Engineer

Add brains to the beauty. Connect logic to UI seamlessly.

<core_principle>

The Enhancement Promise

UI exists (from ui-first-builder) → Add state/logic → UI becomes functional

We don't create UI. We make existing UI work.
</core_principle>

<default_to_action>
NEVER ask:
- "What state management should I use?" → Use Zustand (our standard)
- "What validation library?" → Use Zod (our standard)
- "What form library?" → Use React Hook Form (our standard)

ALWAYS do:
- When scaffolding a NEW project, run npm view [package] version for each dependency
to get the latest STABLE before pinning — check live, never hardcode a version number here
(the framework must not go stale; internalize the principle, not the digits)
- Create TypeScript types FIRST
- Create Zustand store for state
- Add form validation with Zod
- Prepare CRUD operations (mock first, real later)
</default_to_action>

<typescript_patterns>

Type Definitions

Location

Always create: src/types/index.ts or src/types/[feature].ts

Pattern

// src/types/index.ts

// Entity types
export interface User {
  id: string
  name: string
  email: string
  role: &quot;admin&quot; | &quot;user&quot; | &quot;editor&quot;
  avatar?: string
  createdAt: Date
  updatedAt: Date
}

export interface Product {
  id: string
  name: string
  description: string
  price: number
  stock: number
  category: string
  images: string[]
  isActive: boolean
  createdAt: Date
  updatedAt: Date
}

// Form types (for create/update)
export type CreateProductInput = Omit&lt;Product, &quot;id&quot; | &quot;createdAt&quot; | &quot;updatedAt&quot;&gt;
export type UpdateProductInput = Partial&lt;CreateProductInput&gt;

// API response types
export interface PaginatedResponse&lt;T&gt; {
  data: T[]
  total: number
  page: number
  pageSize: number
  totalPages: number
}

// Common utility types
export type ID = string | number
export type Nullable&lt;T&gt; = T | null

Naming Conventions

  • Entity: User, Product, Order (singular, PascalCase)
  • Input: CreateUserInput, UpdateUserInput
  • Response: UserResponse, PaginatedResponse&lt;User&gt;
  • Props: UserCardProps, ProductListProps
    </typescript_patterns>

<zustand_patterns>

State Management with Zustand

Location

Create: src/stores/[feature]-store.ts

Basic Store Pattern

// src/stores/product-store.ts
import { create } from &#x27;zustand&#x27;
import { Product, CreateProductInput } from &#x27;@/types&#x27;
import { mockProducts } from &#x27;@/lib/mock-data&#x27;

interface ProductState {
  // State
  products: Product[]
  selectedProduct: Product | null
  isLoading: boolean
  error: string | null

  // Actions
  fetchProducts: () =&gt; Promise&lt;void&gt;
  addProduct: (input: CreateProductInput) =&gt; Promise&lt;void&gt;
  updateProduct: (id: string, input: Partial&lt;Product&gt;) =&gt; Promise&lt;void&gt;
  deleteProduct: (id: string) =&gt; Promise&lt;void&gt;
  selectProduct: (product: Product | null) =&gt; void
}

export const useProductStore = create&lt;ProductState&gt;((set, get) =&gt; ({
  // Initial state
  products: [],
  selectedProduct: null,
  isLoading: false,
  error: null,

  // Actions
  fetchProducts: async () =&gt; {
    set({ isLoading: true, error: null })
    try {
      // TODO: Replace with real API call
      await new Promise(resolve =&gt; setTimeout(resolve, 500)) // Simulate delay
      set({ products: mockProducts, isLoading: false })
    } catch (error) {
      set({ error: &#x27;Failed to fetch products&#x27;, isLoading: false })
    }
  },

  addProduct: async (input) =&gt; {
    set({ isLoading: true, error: null })
    try {
      // TODO: Replace with real API call
      const newProduct: Product = {
        ...input,
        id: crypto.randomUUID(),
        createdAt: new Date(),
        updatedAt: new Date(),
      }
      set(state =&gt; ({ 
        products: [...state.products, newProduct],
        isLoading: false 
      }))
    } catch (error) {
      set({ error: &#x27;Failed to add product&#x27;, isLoading: false })
    }
  },

  updateProduct: async (id, input) =&gt; {
    set({ isLoading: true, error: null })
    try {
      // TODO: Replace with real API call
      set(state =&gt; ({
        products: state.products.map(p =&gt; 
          p.id === id ? { ...p, ...input, updatedAt: new Date() } : p
        ),
        isLoading: false
      }))
    } catch (error) {
      set({ error: &#x27;Failed to update product&#x27;, isLoading: false })
    }
  },

  deleteProduct: async (id) =&gt; {
    set({ isLoading: true, error: null })
    try {
      // TODO: Replace with real API call
      set(state =&gt; ({
        products: state.products.filter(p =&gt; p.id !== id),
        isLoading: false
      }))
    } catch (error) {
      set({ error: &#x27;Failed to delete product&#x27;, isLoading: false })
    }
  },

  selectProduct: (product) =&gt; set({ selectedProduct: product }),
}))

Using Store in Components

// In component
import { useProductStore } from &#x27;@/stores/product-store&#x27;

export function ProductList() {
  const { products, isLoading, fetchProducts } = useProductStore()

  useEffect(() =&gt; {
    fetchProducts()
  }, [fetchProducts])

  if (isLoading) return &lt;LoadingSkeleton /&gt;

  return (
    &lt;div&gt;
      {products.map(product =&gt; (
        &lt;ProductCard key={product.id} product={product} /&gt;
      ))}
    &lt;/div&gt;
  )
}

</zustand_patterns>

<form_patterns>

Forms with React Hook Form + Zod

Validation messages should match the project's language setting in CLAUDE.md.

Schema Definition

// src/lib/validations/product.ts
import { z } from &#x27;zod&#x27;

export const createProductSchema = z.object({
  name: z.string()
    .min(2, &#x27;Product name must be at least 2 characters&#x27;)
    .max(100, &#x27;Product name must not exceed 100 characters&#x27;),
  description: z.string()
    .min(10, &#x27;Description must be at least 10 characters&#x27;)
    .optional(),
  price: z.number()
    .min(0, &#x27;Price cannot be negative&#x27;)
    .max(1000000, &#x27;Price cannot exceed 1,000,000&#x27;),
  stock: z.number()
    .int(&#x27;Quantity must be an integer&#x27;)
    .min(0, &#x27;Quantity cannot be negative&#x27;),
  category: z.string().min(1, &#x27;Please select a category&#x27;),
  isActive: z.boolean().default(true),
})

export type CreateProductSchema = z.infer&lt;typeof createProductSchema&gt;

export const updateProductSchema = createProductSchema.partial()

Form Component

// src/components/features/product-form.tsx
&#x27;use client&#x27;

import { useForm } from &#x27;react-hook-form&#x27;
import { zodResolver } from &#x27;@hookform/resolvers/zod&#x27;
import { createProductSchema, CreateProductSchema } from &#x27;@/lib/validations/product&#x27;
import { Button } from &#x27;@/components/ui/button&#x27;
import { Input } from &#x27;@/components/ui/input&#x27;
import { Label } from &#x27;@/components/ui/label&#x27;
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from &#x27;@/components/ui/select&#x27;
import { useProductStore } from &#x27;@/stores/product-store&#x27;

interface ProductFormProps {
  onSuccess?: () =&gt; void
}

export function ProductForm({ onSuccess }: ProductFormProps) {
  const { addProduct, isLoading } = useProductStore()

  const form = useForm&lt;CreateProductSchema&gt;({
    resolver: zodResolver(createProductSchema),
    defaultValues: {
      name: &#x27;&#x27;,
      description: &#x27;&#x27;,
      price: 0,
      stock: 0,
      category: &#x27;&#x27;,
      isActive: true,
    },
  })

  const onSubmit = async (data: CreateProductSchema) =&gt; {
    await addProduct(data)
    form.reset()
    onSuccess?.()
  }

  return (
    &lt;form onSubmit={form.handleSubmit(onSubmit)} className=&quot;space-y-4&quot;&gt;
      &lt;div className=&quot;space-y-2&quot;&gt;
        &lt;Label htmlFor=&quot;name&quot;&gt;Product Name&lt;/Label&gt;
        &lt;Input
          id=&quot;name&quot;
          {...form.register(&#x27;name&#x27;)}
          placeholder=&quot;Enter product name&quot;
        /&gt;
        {form.formState.errors.name &amp;&amp; (
          &lt;p className=&quot;text-sm text-red-500&quot;&gt;
            {form.formState.errors.name.message}
          &lt;/p&gt;
        )}
      &lt;/div&gt;

      &lt;div className=&quot;space-y-2&quot;&gt;
        &lt;Label htmlFor=&quot;price&quot;&gt;Price&lt;/Label&gt;
        &lt;Input
          id=&quot;price&quot;
          type=&quot;number&quot;
          {...form.register(&#x27;price&#x27;, { valueAsNumber: true })}
          placeholder=&quot;0&quot;
        /&gt;
        {form.formState.errors.price &amp;&amp; (
          &lt;p className=&quot;text-sm text-red-500&quot;&gt;
            {form.formState.errors.price.message}
          &lt;/p&gt;
        )}
      &lt;/div&gt;

      &lt;div className=&quot;space-y-2&quot;&gt;
        &lt;Label htmlFor=&quot;category&quot;&gt;Category&lt;/Label&gt;
        &lt;Select onValueChange={(value) =&gt; form.setValue(&#x27;category&#x27;, value)}&gt;
          &lt;SelectTrigger&gt;
            &lt;SelectValue placeholder=&quot;Select category&quot; /&gt;
          &lt;/SelectTrigger&gt;
          &lt;SelectContent&gt;
            &lt;SelectItem value=&quot;food&quot;&gt;Food&lt;/SelectItem&gt;
            &lt;SelectItem value=&quot;drink&quot;&gt;Drinks&lt;/SelectItem&gt;
            &lt;SelectItem value=&quot;dessert&quot;&gt;Desserts&lt;/SelectItem&gt;
          &lt;/SelectContent&gt;
        &lt;/Select&gt;
        {form.formState.errors.category &amp;&amp; (
          &lt;p className=&quot;text-sm text-red-500&quot;&gt;
            {form.formState.errors.category.message}
          &lt;/p&gt;
        )}
      &lt;/div&gt;

      &lt;Button type=&quot;submit&quot; disabled={isLoading} className=&quot;w-full&quot;&gt;
        {isLoading ? &#x27;Saving...&#x27; : &#x27;Save&#x27;}
      &lt;/Button&gt;
    &lt;/form&gt;
  )
}

</form_patterns>

<crud_operations>

CRUD Operation Patterns

Mock-First Approach

// src/lib/api/products.ts

import { Product, CreateProductInput, PaginatedResponse } from &#x27;@/types&#x27;
import { mockProducts } from &#x27;@/lib/mock-data&#x27;

// Simulated delay for realistic UX
const delay = (ms: number) =&gt; new Promise(resolve =&gt; setTimeout(resolve, ms))

// These functions work with mock data now
// Replace internals with real API calls later

export async function getProducts(page = 1, pageSize = 10): Promise&lt;PaginatedResponse&lt;Product&gt;&gt; {
  await delay(300)

  const start = (page - 1) * pageSize
  const end = start + pageSize
  const data = mockProducts.slice(start, end)

  return {
    data,
    total: mockProducts.length,
    page,
    pageSize,
    totalPages: Math.ceil(mockProducts.length / pageSize),
  }
}

export async function getProduct(id: string): Promise&lt;Product | null&gt; {
  await delay(200)
  return mockProducts.find(p =&gt; p.id === id) ?? null
}

export async function createProduct(input: CreateProductInput): Promise&lt;Product&gt; {
  await delay(400)

  const newProduct: Product = {
    ...input,
    id: crypto.randomUUID(),
    createdAt: new Date(),
    updatedAt: new Date(),
  }

  // In real app: POST to API
  // mockProducts.push(newProduct)

  return newProduct
}

export async function updateProduct(id: string, input: Partial&lt;Product&gt;): Promise&lt;Product&gt; {
  await delay(400)

  const product = mockProducts.find(p =&gt; p.id === id)
  if (!product) throw new Error(&#x27;Product not found&#x27;)

  const updated = { ...product, ...input, updatedAt: new Date() }

  // In real app: PUT/PATCH to API

  return updated
}

export async function deleteProduct(id: string): Promise&lt;void&gt; {
  await delay(300)

  // In real app: DELETE to API
  const index = mockProducts.findIndex(p =&gt; p.id === id)
  if (index === -1) throw new Error(&#x27;Product not found&#x27;)

  // mockProducts.splice(index, 1)
}

Transition to Real API

// When ready to connect to Supabase:

import { supabase } from &#x27;@/lib/supabase&#x27;

export async function getProducts(page = 1, pageSize = 10) {
  const from = (page - 1) * pageSize
  const to = from + pageSize - 1

  const { data, error, count } = await supabase
    .from(&#x27;products&#x27;)
    .select(&#x27;*&#x27;, { count: &#x27;exact&#x27; })
    .range(from, to)
    .order(&#x27;created_at&#x27;, { ascending: false })

  if (error) throw error

  return {
    data: data ?? [],
    total: count ?? 0,
    page,
    pageSize,
    totalPages: Math.ceil((count ?? 0) / pageSize),
  }
}

</crud_operations>

<hooks_patterns>

Custom Hooks

Data Fetching Hook

// src/hooks/use-products.ts
import { useEffect } from &#x27;react&#x27;
import { useProductStore } from &#x27;@/stores/product-store&#x27;

export function useProducts() {
  const { products, isLoading, error, fetchProducts } = useProductStore()

  useEffect(() =&gt; {
    if (products.length === 0) {
      fetchProducts()
    }
  }, [products.length, fetchProducts])

  return { products, isLoading, error, refetch: fetchProducts }
}

Debounced Search Hook

// src/hooks/use-debounced-search.ts
import { useState, useEffect } from &#x27;react&#x27;

export function useDebouncedSearch&lt;T&gt;(
  items: T[],
  searchKey: keyof T,
  delay = 300
) {
  const [query, setQuery] = useState(&#x27;&#x27;)
  const [debouncedQuery, setDebouncedQuery] = useState(&#x27;&#x27;)
  const [results, setResults] = useState&lt;T[]&gt;(items)

  useEffect(() =&gt; {
    const timer = setTimeout(() =&gt; {
      setDebouncedQuery(query)
    }, delay)
    return () =&gt; clearTimeout(timer)
  }, [query, delay])

  useEffect(() =&gt; {
    if (!debouncedQuery) {
      setResults(items)
      return
    }

    const filtered = items.filter(item =&gt; {
      const value = String(item[searchKey]).toLowerCase()
      return value.includes(debouncedQuery.toLowerCase())
    })

    setResults(filtered)
  }, [debouncedQuery, items, searchKey])

  return { query, setQuery, results }
}

Form Dialog Hook

// src/hooks/use-form-dialog.ts
import { useState } from &#x27;react&#x27;

export function useFormDialog&lt;T&gt;() {
  const [isOpen, setIsOpen] = useState(false)
  const [editingItem, setEditingItem] = useState&lt;T | null&gt;(null)

  const openCreate = () =&gt; {
    setEditingItem(null)
    setIsOpen(true)
  }

  const openEdit = (item: T) =&gt; {
    setEditingItem(item)
    setIsOpen(true)
  }

  const close = () =&gt; {
    setIsOpen(false)
    setEditingItem(null)
  }

  return {
    isOpen,
    isEditing: editingItem !== null,
    editingItem,
    openCreate,
    openEdit,
    close,
  }
}

</hooks_patterns>

<integration_checklist>

Before Completing, Verify:

  • [ ] TypeScript types defined for all entities
  • [ ] Zustand store created with CRUD actions
  • [ ] Zod schemas match form requirements
  • [ ] React Hook Form integrated with Zod resolver
  • [ ] Mock data functions have realistic delays
  • [ ] Error states handled in store
  • [ ] Loading states handled in store
  • [ ] Components connected to store correctly
  • [ ] No any types used
  • [ ] All functions have return type annotations
    </integration_checklist>

<anti_patterns>

What NOT To Do

❌ Don't

  • Use any type to escape TypeScript
  • Create complex store before simple one works
  • Skip loading/error states
  • Hardcode mock data in components
  • Mix real API calls with mock data
  • Create custom state management (use Zustand)

❌ Don't Assume

  • Backend exists (work with mock first)
  • Specific validation rules (infer from context)
  • Complex state needs (start simple)
  • User wants to see internal architecture
    </anti_patterns>

常见问题

5 问
答案由本站依据条目字段整理
dev-engineer怎么安装?

没有现成安装命令:按安装说明或仓库 README,把 SKILL.md 放进客户端的 skills 目录(一般一个技能一个文件夹)。

dev-engineer支持哪些客户端?

资料没有标注宿主客户端,但只要客户端支持 Skills(Claude、Codex、Cursor 等),一般都能加载。

dev-engineer权限高吗,安全吗?

权限级别为未标注权限。安装前先看它会读取或写入哪些内容。

dev-engineer收费吗?

本站不代收费用,也没有标注具体价格。是否收费取决于技能本身和所用的模型或客户端,正式使用前按作者说明确认。

dev-engineer用什么许可证?

许可证标注为 未说明(许可证不明确)。商用或二次分发前请到仓库确认条款原文。

热门专题

按场景挑技能
全部专题 →

技能内容以作者仓库为准;分类、权限级别与许可证状态的中文说明为中文整理,最近核验 未标注。