← Back to list

model-structure
by hajimism
⭐ 0🍴 0📅 Jan 17, 2026
SKILL.md
name: model-structure description: Frontend model structure patterns. Use when creating a new model (e.g., user, product), adding list/detail pages, or asking about model architecture in front/src/model/.
Model Structure
Reference implementation: front/src/model/article
Design Philosophy
- OOUI-based: One directory per domain model under
model/ - Feature-split: Separate
list/anddetail/for different use cases - Slice pattern: Each input field is an independent Zustand slice
- Phase-based validation: Validate progressively (onChange → onDraftSubmit → onConfirmedSubmit)
- Preview separation: Pure View components (props) vs store-connected wrappers
Key Patterns
Input Slice
Each form field has its own slice with value, setter, and validation getters.
inputs/[field]/
├── slice.ts # Generate slice with createInputSlice factory
├── validation.ts # ArkType schema + InputValidation (when validation needed)
├── hook.ts # useXxxInput() - select from store, compute errors
└── index.tsx # 'use client' UI component
Slice Factory (recommended):
Use createInputSlice factory to generate slices. Reduces boilerplate.
import { createInputSlice, type InputSliceShape } from '@/model/common/lib/slice-factory'
import { titleValidation } from './validation'
// With validation
export type TitleSlice = InputSliceShape<'title', string>
export const createTitleSlice = createInputSlice('title', titleValidation)
// Without validation (enums, etc.)
export type CategorySlice = InputSliceShape<'category', ArticleCategory>
export const createCategorySlice = createInputSlice<'category', ArticleCategory>('category')
Generated slice shape: { [key], set[Key], get[Key]ErrorMessages, get[Key]IsValid }
Irregular patterns (cross-field deps, custom logic):
- Write slice manually, or
- Create base with factory, then spread-merge additional methods
Store Composition
Combine all slices into one store with meta functions.
- Spread each slice:
...createXxxSlice(initialValue)(...args) - Add
getFormIsValid()andgetFormValues()to MetaSlice - Provide via Context +
useRefpattern
Validation
- Use constraint values from
@ckda-cms/core/model/article(articleConstraints) - Use
validateWithStandardSchema()frommodel/common/lib/validation.ts - Return
InputValidationwith three phases:onChange,onDraftSubmit,onConfirmedSubmit - Returns
ValidationError[](noisOk- errors only generated when invalid)
Pattern:
import { articleConstraints, titleSchema } from '@ckda-cms/core/model/article'
import {
validateWithStandardSchema,
withValidationErrorMessages,
} from '@/model/common/lib/validation'
const c = articleConstraints.title
// withValidationErrorMessagesでメッセージを追加(otherwiseはデフォルト「不正な値です」)
const titleOnSubmitSchema = withValidationErrorMessages(titleSchema, {
minLength: 'タイトルは必須です',
maxLength: `${c.maxLength}文字以内で入力してください`,
})
export const titleValidation = (value: string): InputValidation => ({
onChange: validateWithStandardSchema(onChangeSchema, value),
onConfirmedSubmit: validateWithStandardSchema(titleOnSubmitSchema, value),
onDraftSubmit: [],
})
Query
- Use
use cachedirective withcacheTag()for data fetching - Located in
query.tsunder each feature
Actions
- Server Actions in
actions.ts - Call API via
apiClientfrommodel/common/rpc-client.ts - Use
revalidateTag()to invalidate cache after mutations
Preview
model/[entity]/preview/[field]/index.tsx: Pure View (receives value via props)model/[entity]/detail/preview/[field]/index.tsx: Connects to store and renders View
Directory Structure
model/[entity]/
├── actions.ts
├── list/
│ ├── query.ts
│ ├── search/store/ + inputs/
│ └── table/
└── detail/
├── query.ts
├── store/
├── form/inputs/
└── preview/
Read front/src/model/xxx for concrete examples.
Score
Total Score
50/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
○LICENSE
ライセンスが設定されている
0/10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
Reviews
💬
Reviews coming soon