harmonyos-app
This skill provides guidance for HarmonyOS application development using ArkTS and the Stage model, covering declarative UI patterns, distributed capabilities, and API 12+ best practices. Use it when building apps for HarmonyOS NEXT that require strict type safety, immutable state management, and multi-device adaptation across phones, tablets, watches, and TVs.
git clone --depth 1 https://github.com/majiayu000/spellbook /tmp/harmonyos-app && cp -r /tmp/harmonyos-app/skills/harmonyos-app ~/.claude/skills/harmonyos-appSKILL.md
# HarmonyOS Application Development
## Core Principles
- **ArkTS First** — Use ArkTS with strict type safety, no `any` or dynamic types
- **Declarative UI** — Build UI with ArkUI's declarative components and state management
- **Stage Model** — Use modern Stage model (UIAbility), not legacy FA model
- **Distributed by Design** — Leverage cross-device capabilities from the start
- **Atomic Services** — Consider atomic services and cards for lightweight experiences
- **One-time Development** — Design for multi-device adaptation (phone, tablet, watch, TV)
---
## Hard Rules (Must Follow)
> These rules are mandatory. Violating them means the skill is not working correctly.
### No Dynamic Types
**ArkTS prohibits dynamic typing. Never use `any`, type assertions, or dynamic property access.**
```typescript
// ❌ FORBIDDEN: Dynamic types
let data: any = fetchData();
let obj: object = {};
obj['dynamicKey'] = value; // Dynamic property access
(someVar as SomeType).method(); // Type assertion
// ✅ REQUIRED: Strict typing
interface UserData {
id: string;
name: string;
}
let data: UserData = fetchData();
// Use Record for dynamic keys
let obj: Record<string, string> = {};
obj['key'] = value; // OK with Record type
```
### No Direct State Mutation
**Never mutate @State/@Prop variables directly in nested objects. Use immutable updates.**
```typescript
// ❌ FORBIDDEN: Direct mutation
@State user: User = { name: 'John', age: 25 };
updateAge() {
this.user.age = 26; // UI won't update!
}
// ✅ REQUIRED: Immutable update
updateAge() {
this.user = { ...this.user, age: 26 }; // Creates new object, triggers UI update
}
// For arrays
@State items: string[] = ['a', 'b'];
// ❌ FORBIDDEN
this.items.push('c'); // UI won't update
// ✅ REQUIRED
this.items = [...this.items, 'c'];
```
### Stage Model Only
**Always use Stage model (UIAbility). Never use deprecated FA model (PageAbility).**
```typescript
// ❌ FORBIDDEN: FA Model (deprecated)
// config.json with "pages" array
export default {
onCreate() { ... } // PageAbility lifecycle
}
// ✅ REQUIRED: Stage Model
// module.json5 with abilities configuration
import { UIAbility } from '@kit.AbilityKit';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// Modern Stage model lifecycle
}
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index');
}
}
```
### Component Reusability
**Extract reusable UI into @Component. No inline complex UI in build() methods.**
```typescript
// ❌ FORBIDDEN: Monolithic build method
@Entry
@Component
struct MainPage {
build() {
Column() {
// 200+ lines of inline UI...
Row() {
Image($r('app.media.avatar'))
Column() {
Text(this.user.name)
Text(this.user.email)
}
}
// More inline UI...
}
}
}
// ✅ REQUIRED: Extract components
@Component
struct UserCard {
@Prop user: User;
build() {
Row() {
Image($r('app.media.avatar'))
Column() {
Text(this.user.name)
Text(this.user.email)
}
}
}
}
@Entry
@Component
struct MainPage {
@State user: User = { name: 'John', email: 'john@example.com' };
build() {
Column() {
UserCard({ user: this.user })
}
}
}
```
---
## Quick Reference
### When to Use What
| Scenario | Pattern | Example |
|----------|---------|---------|
| Component-local state | @State | Counter, form inputs |
| Parent-to-child data | @Prop | Read-only child data |
| Two-way binding | @Link | Shared mutable state |
| Cross-component state | @Provide/@Consume | Theme, user context |
| Persistent state | PersistentStorage | User preferences |
| App-wide state | AppStorage | Global state |
| Complex state logic | @Observed/@ObjectLink | Nested object updates |
### State Decorator Selection
```
@State → Component owns the state, triggers re-render on change
@Prop → Parent passes value, child gets copy (one-way)
@Link → Parent passes reference, child can modify (two-way)
@Provide → Ancestor provides value to all descendants
@Consume → Descendant consumes value from ancestor
@StorageLink → Syncs with AppStorage, two-way binding
@StorageProp → Syncs with AppStorage, one-way binding
@Observed → Class decorator for observable objects
@ObjectLink → Links to @Observed object in parent
```
---
## Project Structure
### Recommended Architecture
```
MyApp/
├── entry/ # Main entry module
│ ├── src/main/
│ │ ├── ets/
│ │ │ ├── entryability/ # UIAbility definitions
│ │ │ │ └── EntryAbility.ets
│ │ │ ├── pages/ # Page components
│ │ │ │ ├── Index.ets
│ │ │ │ └── Detail.ets
│ │ │ ├── components/ # Reusable UI components
│ │ │ │ ├── common/ # Common components
│ │ │ │ └── business/ # Business-specific components
│ │ │ ├── viewmodel/ # ViewModels (MVVM)
│ │ │ ├── model/ # Data models
│ │ │ ├── service/ # Business logic services
│ │ │ ├── repository/ # Data access layer
│ │ │ ├── utils/ # Utility functions
│ │ │ └── constants/ # Constants and configs
│ │ ├── resources/ # Resources (strings, images)
│ │ └── module.json5 # Module configuration
│ └── build-profile.json5
├── common/ # Shared library module
│ └── src/main/ets/
├── features/ # Feature modules
│ ├── feature_home/
│ └── feature_profile/
└── build-profile.json5 # Project configuration
```
### Layer Separation
```
┌─────────────────────────────────────┐
│ UI Layer (Pages) │ ArkUI Components
├─────────────────────────────────────┤
│ ViewModel Layer │ State management, UI logic
├────────────Audit AND optimize a CLAUDE.md / AGENTS.md instruction file — score it against the five high-leverage patterns, flag anti-patterns, then apply approved fixes in place. Use when the user says 优化 CLAUDE.md / 优化 AGENTS.md / optimize my agent doc / 帮我改 claudemd, or after an audit when they want the fixes applied (not just reported).
Generate or update repository-specific AGENTS.md instruction files from real repo evidence. Use when asked to create, design, scaffold, split, or improve root or scoped AGENTS.md files for Codex/Claude/agent workflows, especially when a repo needs directory-specific rules, validation commands, generated-file boundaries, or a short agent onboarding router.
REST/GraphQL/gRPC API design best practices. Use when designing APIs, defining contracts, handling versioning. Covers OpenAPI 3.2, GraphQL Federation, gRPC streaming.
Mobile app UI design expert for iOS and Android. Use when designing app interfaces, creating design systems, ensuring accessibility, or following platform guidelines. Covers Material Design 3, Human Interface Guidelines, color theory, typography, and 2025 trends.
End-to-end app feature inventory and user-story testing workflow with a canonical tracker. Use when the user asks to audit every feature, derive expected behavior from code, test user journeys, or explicitly fix and retest documented UX or logistical defects.
Design architecture foundations before implementation. Use when asked to design or refactor architecture, choose Rust/Go crate, package, module, runtime, workflow, or service boundaries, compare mature project architecture, prevent stacked one-off PRs, audit migration debt in existing repos, or produce a spec before coding.
Evidence-driven architecture research for understanding real systems and making technical decisions. Use when doing architecture landscape studies, source-backed system archaeology, build-vs-buy or adopt/adapt/build decisions, open-source and commercial comparisons, revisiting an earlier architecture choice, or handling requests such as 架构调研, 架构选型, 竞品架构, 技术尽调, 同类方案, 开源替代, how is X built, and what should we learn from X. Do not use for small mechanical changes, market-only discovery, or detailed design after the technology direction is already fixed.
Ask Grok or Gemini via opencli (browser-session based, no API cost). Use when the user says "问 grok", "问 gemini", "ask grok", "ask gemini", "用 grok 查", "grok 怎么看", "让 gemini 分析", or wants a second opinion from Grok/Gemini without paying API tokens. Routes to opencli which drives the already-logged-in Chrome session.