{"id":"41caa3cb-7ca0-44c4-b1b9-c7526da7fc25","shortId":"7p85pF","kind":"skill","title":"harmonyos-app","tagline":"HarmonyOS application development expert. Use when building HarmonyOS apps with ArkTS, ArkUI, Stage model, and distributed capabilities. Covers HarmonyOS NEXT (API 12+) best practices.","description":"# HarmonyOS Application Development\n\n## Core Principles\n\n- **ArkTS First** — Use ArkTS with strict type safety, no `any` or dynamic types\n- **Declarative UI** — Build UI with ArkUI's declarative components and state management\n- **Stage Model** — Use modern Stage model (UIAbility), not legacy FA model\n- **Distributed by Design** — Leverage cross-device capabilities from the start\n- **Atomic Services** — Consider atomic services and cards for lightweight experiences\n- **One-time Development** — Design for multi-device adaptation (phone, tablet, watch, TV)\n\n---\n\n## Hard Rules (Must Follow)\n\n> These rules are mandatory. Violating them means the skill is not working correctly.\n\n### No Dynamic Types\n\n**ArkTS prohibits dynamic typing. Never use `any`, type assertions, or dynamic property access.**\n\n```typescript\n// ❌ FORBIDDEN: Dynamic types\nlet data: any = fetchData();\nlet obj: object = {};\nobj['dynamicKey'] = value;  // Dynamic property access\n(someVar as SomeType).method();  // Type assertion\n\n// ✅ REQUIRED: Strict typing\ninterface UserData {\n  id: string;\n  name: string;\n}\nlet data: UserData = fetchData();\n\n// Use Record for dynamic keys\nlet obj: Record<string, string> = {};\nobj['key'] = value;  // OK with Record type\n```\n\n### No Direct State Mutation\n\n**Never mutate @State/@Prop variables directly in nested objects. Use immutable updates.**\n\n```typescript\n// ❌ FORBIDDEN: Direct mutation\n@State user: User = { name: 'John', age: 25 };\n\nupdateAge() {\n  this.user.age = 26;  // UI won't update!\n}\n\n// ✅ REQUIRED: Immutable update\nupdateAge() {\n  this.user = { ...this.user, age: 26 };  // Creates new object, triggers UI update\n}\n\n// For arrays\n@State items: string[] = ['a', 'b'];\n\n// ❌ FORBIDDEN\nthis.items.push('c');  // UI won't update\n\n// ✅ REQUIRED\nthis.items = [...this.items, 'c'];\n```\n\n### Stage Model Only\n\n**Always use Stage model (UIAbility). Never use deprecated FA model (PageAbility).**\n\n```typescript\n// ❌ FORBIDDEN: FA Model (deprecated)\n// config.json with \"pages\" array\nexport default {\n  onCreate() { ... }  // PageAbility lifecycle\n}\n\n// ✅ REQUIRED: Stage Model\n// module.json5 with abilities configuration\nimport { UIAbility } from '@kit.AbilityKit';\n\nexport default class EntryAbility extends UIAbility {\n  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {\n    // Modern Stage model lifecycle\n  }\n\n  onWindowStageCreate(windowStage: window.WindowStage): void {\n    windowStage.loadContent('pages/Index');\n  }\n}\n```\n\n### Component Reusability\n\n**Extract reusable UI into @Component. No inline complex UI in build() methods.**\n\n```typescript\n// ❌ FORBIDDEN: Monolithic build method\n@Entry\n@Component\nstruct MainPage {\n  build() {\n    Column() {\n      // 200+ lines of inline UI...\n      Row() {\n        Image($r('app.media.avatar'))\n        Column() {\n          Text(this.user.name)\n          Text(this.user.email)\n        }\n      }\n      // More inline UI...\n    }\n  }\n}\n\n// ✅ REQUIRED: Extract components\n@Component\nstruct UserCard {\n  @Prop user: User;\n\n  build() {\n    Row() {\n      Image($r('app.media.avatar'))\n      Column() {\n        Text(this.user.name)\n        Text(this.user.email)\n      }\n    }\n  }\n}\n\n@Entry\n@Component\nstruct MainPage {\n  @State user: User = { name: 'John', email: 'john@example.com' };\n\n  build() {\n    Column() {\n      UserCard({ user: this.user })\n    }\n  }\n}\n```\n\n---\n\n## Quick Reference\n\n### When to Use What\n\n| Scenario | Pattern | Example |\n|----------|---------|---------|\n| Component-local state | @State | Counter, form inputs |\n| Parent-to-child data | @Prop | Read-only child data |\n| Two-way binding | @Link | Shared mutable state |\n| Cross-component state | @Provide/@Consume | Theme, user context |\n| Persistent state | PersistentStorage | User preferences |\n| App-wide state | AppStorage | Global state |\n| Complex state logic | @Observed/@ObjectLink | Nested object updates |\n\n### State Decorator Selection\n\n```\n@State        → Component owns the state, triggers re-render on change\n@Prop         → Parent passes value, child gets copy (one-way)\n@Link         → Parent passes reference, child can modify (two-way)\n@Provide      → Ancestor provides value to all descendants\n@Consume      → Descendant consumes value from ancestor\n@StorageLink  → Syncs with AppStorage, two-way binding\n@StorageProp  → Syncs with AppStorage, one-way binding\n@Observed     → Class decorator for observable objects\n@ObjectLink   → Links to @Observed object in parent\n```\n\n---\n\n## Project Structure\n\n### Recommended Architecture\n\n```\nMyApp/\n├── entry/                          # Main entry module\n│   ├── src/main/\n│   │   ├── ets/\n│   │   │   ├── entryability/       # UIAbility definitions\n│   │   │   │   └── EntryAbility.ets\n│   │   │   ├── pages/              # Page components\n│   │   │   │   ├── Index.ets\n│   │   │   │   └── Detail.ets\n│   │   │   ├── components/         # Reusable UI components\n│   │   │   │   ├── common/         # Common components\n│   │   │   │   └── business/       # Business-specific components\n│   │   │   ├── viewmodel/          # ViewModels (MVVM)\n│   │   │   ├── model/              # Data models\n│   │   │   ├── service/            # Business logic services\n│   │   │   ├── repository/         # Data access layer\n│   │   │   ├── utils/              # Utility functions\n│   │   │   └── constants/          # Constants and configs\n│   │   ├── resources/              # Resources (strings, images)\n│   │   └── module.json5            # Module configuration\n│   └── build-profile.json5\n├── common/                         # Shared library module\n│   └── src/main/ets/\n├── features/                       # Feature modules\n│   ├── feature_home/\n│   └── feature_profile/\n└── build-profile.json5             # Project configuration\n```\n\n### Layer Separation\n\n```\n┌─────────────────────────────────────┐\n│           UI Layer (Pages)          │  ArkUI Components\n├─────────────────────────────────────┤\n│         ViewModel Layer             │  State management, UI logic\n├─────────────────────────────────────┤\n│         Service Layer               │  Business logic\n├─────────────────────────────────────┤\n│        Repository Layer             │  Data access abstraction\n├─────────────────────────────────────┤\n│    Data Sources (Local/Remote)      │  Preferences, RDB, Network\n└─────────────────────────────────────┘\n```\n\n---\n\n## ArkUI Component Patterns\n\n### Basic Component Structure\n\n```typescript\nimport { router } from '@kit.ArkUI';\n\n@Component\nexport struct ProductCard {\n  // Props from parent\n  @Prop product: Product;\n  @Prop onAddToCart: (product: Product) => void;\n\n  // Local state\n  @State isExpanded: boolean = false;\n\n  // Computed values (use getters)\n  get formattedPrice(): string {\n    return `¥${this.product.price.toFixed(2)}`;\n  }\n\n  // Lifecycle\n  aboutToAppear(): void {\n    console.info('ProductCard appearing');\n  }\n\n  aboutToDisappear(): void {\n    console.info('ProductCard disappearing');\n  }\n\n  // Event handlers\n  private handleTap(): void {\n    router.pushUrl({ url: 'pages/ProductDetail', params: { id: this.product.id } });\n  }\n\n  private handleAddToCart(): void {\n    this.onAddToCart(this.product);\n  }\n\n  // UI builder\n  build() {\n    Column() {\n      Image(this.product.imageUrl)\n        .width('100%')\n        .aspectRatio(1)\n        .objectFit(ImageFit.Cover)\n\n      Text(this.product.name)\n        .fontSize(16)\n        .fontWeight(FontWeight.Medium)\n\n      Text(this.formattedPrice)\n        .fontSize(14)\n        .fontColor('#FF6B00')\n\n      Button('Add to Cart')\n        .onClick(() => this.handleAddToCart())\n    }\n    .padding(12)\n    .backgroundColor(Color.White)\n    .borderRadius(8)\n    .onClick(() => this.handleTap())\n  }\n}\n```\n\n### List with LazyForEach\n\n```typescript\nimport { BasicDataSource } from '../utils/BasicDataSource';\n\nclass ProductDataSource extends BasicDataSource<Product> {\n  private products: Product[] = [];\n\n  totalCount(): number {\n    return this.products.length;\n  }\n\n  getData(index: number): Product {\n    return this.products[index];\n  }\n\n  addData(product: Product): void {\n    this.products.push(product);\n    this.notifyDataAdd(this.products.length - 1);\n  }\n\n  updateData(index: number, product: Product): void {\n    this.products[index] = product;\n    this.notifyDataChange(index);\n  }\n}\n\n@Component\nstruct ProductList {\n  private dataSource: ProductDataSource = new ProductDataSource();\n\n  build() {\n    List() {\n      LazyForEach(this.dataSource, (product: Product, index: number) => {\n        ListItem() {\n          ProductCard({ product: product })\n        }\n      }, (product: Product) => product.id)  // Key generator\n    }\n    .lanes(2)  // Grid with 2 columns\n    .cachedCount(4)  // Cache 4 items for smooth scrolling\n  }\n}\n```\n\n### Custom Dialog\n\n```typescript\n@CustomDialog\nstruct ConfirmDialog {\n  controller: CustomDialogController;\n  title: string = 'Confirm';\n  message: string = '';\n  onConfirm: () => void = () => {};\n\n  build() {\n    Column() {\n      Text(this.title)\n        .fontSize(20)\n        .fontWeight(FontWeight.Bold)\n        .margin({ bottom: 16 })\n\n      Text(this.message)\n        .fontSize(16)\n        .margin({ bottom: 24 })\n\n      Row() {\n        Button('Cancel')\n          .onClick(() => this.controller.close())\n          .backgroundColor(Color.Gray)\n          .margin({ right: 16 })\n\n        Button('Confirm')\n          .onClick(() => {\n            this.onConfirm();\n            this.controller.close();\n          })\n      }\n    }\n    .padding(24)\n  }\n}\n\n// Usage\n@Entry\n@Component\nstruct MainPage {\n  dialogController: CustomDialogController = new CustomDialogController({\n    builder: ConfirmDialog({\n      title: 'Delete Item',\n      message: 'Are you sure you want to delete this item?',\n      onConfirm: () => this.deleteItem()\n    }),\n    autoCancel: true\n  });\n\n  private deleteItem(): void {\n    // Delete logic\n  }\n\n  build() {\n    Button('Delete')\n      .onClick(() => this.dialogController.open())\n  }\n}\n```\n\n---\n\n## State Management Patterns\n\n### ViewModel Pattern\n\n```typescript\n// viewmodel/ProductViewModel.ets\nimport { Product } from '../model/Product';\nimport { ProductRepository } from '../repository/ProductRepository';\n\n@Observed\nexport class ProductViewModel {\n  products: Product[] = [];\n  isLoading: boolean = false;\n  errorMessage: string = '';\n\n  private repository: ProductRepository = new ProductRepository();\n\n  async loadProducts(): Promise<void> {\n    this.isLoading = true;\n    this.errorMessage = '';\n\n    try {\n      this.products = await this.repository.getProducts();\n    } catch (error) {\n      this.errorMessage = `Failed to load: ${error.message}`;\n    } finally {\n      this.isLoading = false;\n    }\n  }\n\n  async addProduct(product: Product): Promise<void> {\n    const created = await this.repository.createProduct(product);\n    this.products = [...this.products, created];\n  }\n}\n\n// pages/ProductPage.ets\n@Entry\n@Component\nstruct ProductPage {\n  @State viewModel: ProductViewModel = new ProductViewModel();\n\n  aboutToAppear(): void {\n    this.viewModel.loadProducts();\n  }\n\n  build() {\n    Column() {\n      if (this.viewModel.isLoading) {\n        LoadingProgress()\n      } else if (this.viewModel.errorMessage) {\n        Text(this.viewModel.errorMessage)\n          .fontColor(Color.Red)\n      } else {\n        ForEach(this.viewModel.products, (product: Product) => {\n          ProductCard({ product: product })\n        }, (product: Product) => product.id)\n      }\n    }\n  }\n}\n```\n\n### AppStorage for Global State\n\n```typescript\n// Initialize in EntryAbility\nimport { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';\n\nexport default class EntryAbility extends UIAbility {\n  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {\n    // Initialize global state\n    AppStorage.setOrCreate('isLoggedIn', false);\n    AppStorage.setOrCreate('currentUser', null);\n    AppStorage.setOrCreate('theme', 'light');\n  }\n}\n\n// Access in components\n@Entry\n@Component\nstruct ProfilePage {\n  @StorageLink('isLoggedIn') isLoggedIn: boolean = false;\n  @StorageLink('currentUser') currentUser: User | null = null;\n  @StorageProp('theme') theme: string = 'light';  // Read-only\n\n  build() {\n    Column() {\n      if (this.isLoggedIn && this.currentUser) {\n        Text(`Welcome, ${this.currentUser.name}`)\n      } else {\n        Button('Login')\n          .onClick(() => {\n            // After login\n            this.isLoggedIn = true;\n            this.currentUser = { id: '1', name: 'John' };\n          })\n      }\n    }\n  }\n}\n```\n\n### PersistentStorage for Preferences\n\n```typescript\n// Initialize persistent storage\nPersistentStorage.persistProp('userSettings', {\n  notifications: true,\n  darkMode: false,\n  language: 'zh-CN'\n});\n\n@Entry\n@Component\nstruct SettingsPage {\n  @StorageLink('userSettings') settings: UserSettings = {\n    notifications: true,\n    darkMode: false,\n    language: 'zh-CN'\n  };\n\n  build() {\n    Column() {\n      Toggle({ type: ToggleType.Switch, isOn: this.settings.notifications })\n        .onChange((isOn: boolean) => {\n          this.settings = { ...this.settings, notifications: isOn };\n        })\n\n      Toggle({ type: ToggleType.Switch, isOn: this.settings.darkMode })\n        .onChange((isOn: boolean) => {\n          this.settings = { ...this.settings, darkMode: isOn };\n        })\n    }\n  }\n}\n```\n\n---\n\n## Navigation Patterns\n\n### Router Navigation\n\n```typescript\nimport { router } from '@kit.ArkUI';\n\n// Navigate to page\nrouter.pushUrl({\n  url: 'pages/Detail',\n  params: { productId: '123' }\n});\n\n// Navigate with result\nrouter.pushUrl({\n  url: 'pages/SelectAddress'\n}).then(() => {\n  // Navigation complete\n});\n\n// Get params in target page\n@Entry\n@Component\nstruct DetailPage {\n  @State productId: string = '';\n\n  aboutToAppear(): void {\n    const params = router.getParams() as Record<string, string>;\n    this.productId = params?.productId ?? '';\n  }\n}\n\n// Go back\nrouter.back();\n\n// Replace current page\nrouter.replaceUrl({ url: 'pages/Home' });\n\n// Clear stack and navigate\nrouter.clear();\nrouter.pushUrl({ url: 'pages/Login' });\n```\n\n### Navigation Component (Recommended for HarmonyOS NEXT)\n\n```typescript\n@Entry\n@Component\nstruct MainPage {\n  @Provide('navPathStack') navPathStack: NavPathStack = new NavPathStack();\n\n  @Builder\n  pageBuilder(name: string, params: object) {\n    if (name === 'detail') {\n      DetailPage({ params: params as DetailParams })\n    } else if (name === 'settings') {\n      SettingsPage()\n    }\n  }\n\n  build() {\n    Navigation(this.navPathStack) {\n      Column() {\n        Button('Go to Detail')\n          .onClick(() => {\n            this.navPathStack.pushPath({ name: 'detail', param: { id: '123' } });\n          })\n      }\n    }\n    .navDestination(this.pageBuilder)\n    .title('Home')\n  }\n}\n\n@Component\nstruct DetailPage {\n  @Consume('navPathStack') navPathStack: NavPathStack;\n  params: DetailParams = { id: '' };\n\n  build() {\n    NavDestination() {\n      Column() {\n        Text(`Product ID: ${this.params.id}`)\n        Button('Back')\n          .onClick(() => this.navPathStack.pop())\n      }\n    }\n    .title('Detail')\n  }\n}\n```\n\n---\n\n## Network Requests\n\n### HTTP Client\n\n```typescript\nimport { http } from '@kit.NetworkKit';\n\ninterface ApiResponse<T> {\n  code: number;\n  message: string;\n  data: T;\n}\n\nclass HttpClient {\n  private baseUrl: string = 'https://api.example.com';\n\n  async get<T>(path: string): Promise<T> {\n    const httpRequest = http.createHttp();\n\n    try {\n      const response = await httpRequest.request(\n        `${this.baseUrl}${path}`,\n        {\n          method: http.RequestMethod.GET,\n          header: {\n            'Content-Type': 'application/json',\n            'Authorization': `Bearer ${await this.getToken()}`\n          },\n          expectDataType: http.HttpDataType.OBJECT\n        }\n      );\n\n      if (response.responseCode === 200) {\n        const result = response.result as ApiResponse<T>;\n        if (result.code === 0) {\n          return result.data;\n        }\n        throw new Error(result.message);\n      }\n      throw new Error(`HTTP ${response.responseCode}`);\n    } finally {\n      httpRequest.destroy();\n    }\n  }\n\n  async post<T, R>(path: string, data: T): Promise<R> {\n    const httpRequest = http.createHttp();\n\n    try {\n      const response = await httpRequest.request(\n        `${this.baseUrl}${path}`,\n        {\n          method: http.RequestMethod.POST,\n          header: {\n            'Content-Type': 'application/json',\n            'Authorization': `Bearer ${await this.getToken()}`\n          },\n          extraData: JSON.stringify(data),\n          expectDataType: http.HttpDataType.OBJECT\n        }\n      );\n\n      const result = response.result as ApiResponse<R>;\n      return result.data;\n    } finally {\n      httpRequest.destroy();\n    }\n  }\n\n  private async getToken(): Promise<string> {\n    return AppStorage.get('authToken') ?? '';\n  }\n}\n\nexport const httpClient = new HttpClient();\n```\n\n---\n\n## Distributed Capabilities\n\n### Cross-Device Data Sync\n\n```typescript\nimport { distributedKVStore } from '@kit.ArkData';\n\nclass DistributedStore {\n  private kvManager: distributedKVStore.KVManager | null = null;\n  private kvStore: distributedKVStore.SingleKVStore | null = null;\n\n  async init(context: Context): Promise<void> {\n    const config: distributedKVStore.KVManagerConfig = {\n      context: context,\n      bundleName: 'com.example.myapp'\n    };\n\n    this.kvManager = distributedKVStore.createKVManager(config);\n\n    const options: distributedKVStore.Options = {\n      createIfMissing: true,\n      encrypt: false,\n      backup: false,\n      autoSync: true,  // Auto sync across devices\n      kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,\n      securityLevel: distributedKVStore.SecurityLevel.S1\n    };\n\n    this.kvStore = await this.kvManager.getKVStore('myStore', options);\n  }\n\n  async put(key: string, value: string): Promise<void> {\n    await this.kvStore?.put(key, value);\n  }\n\n  async get(key: string): Promise<string | null> {\n    try {\n      return await this.kvStore?.get(key) as string;\n    } catch {\n      return null;\n    }\n  }\n\n  // Subscribe to changes from other devices\n  subscribe(callback: (key: string, value: string) => void): void {\n    this.kvStore?.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL,\n      (data: distributedKVStore.ChangeNotification) => {\n        for (const entry of data.insertEntries) {\n          callback(entry.key, entry.value.value as string);\n        }\n        for (const entry of data.updateEntries) {\n          callback(entry.key, entry.value.value as string);\n        }\n      }\n    );\n  }\n}\n```\n\n### Device Discovery and Connection\n\n```typescript\nimport { distributedDeviceManager } from '@kit.DistributedServiceKit';\n\nclass DeviceManager {\n  private deviceManager: distributedDeviceManager.DeviceManager | null = null;\n\n  async init(context: Context): Promise<void> {\n    this.deviceManager = distributedDeviceManager.createDeviceManager(\n      context.applicationInfo.name\n    );\n  }\n\n  getAvailableDevices(): distributedDeviceManager.DeviceBasicInfo[] {\n    return this.deviceManager?.getAvailableDeviceListSync() ?? [];\n  }\n\n  startDiscovery(): void {\n    const filter: distributedDeviceManager.DiscoveryFilter = {\n      discoverMode: distributedDeviceManager.DiscoverMode.DISCOVER_MODE_PASSIVE\n    };\n\n    this.deviceManager?.startDiscovering(filter);\n\n    this.deviceManager?.on('discoverSuccess', (data) => {\n      console.info(`Found device: ${data.device.deviceName}`);\n    });\n  }\n\n  stopDiscovery(): void {\n    this.deviceManager?.stopDiscovering();\n  }\n}\n```\n\n---\n\n## Multi-Device Adaptation\n\n### Responsive Layout\n\n```typescript\nimport { BreakpointSystem, BreakPointType } from '../utils/BreakpointSystem';\n\n@Entry\n@Component\nstruct AdaptivePage {\n  @StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm';\n\n  build() {\n    GridRow({\n      columns: { sm: 4, md: 8, lg: 12 },\n      gutter: { x: 12, y: 12 }\n    }) {\n      GridCol({ span: { sm: 4, md: 4, lg: 3 } }) {\n        // Sidebar - full width on phone, 1/3 on tablet, 1/4 on desktop\n        Sidebar()\n      }\n\n      GridCol({ span: { sm: 4, md: 4, lg: 9 } }) {\n        // Content - full width on phone, 2/3 on tablet, 3/4 on desktop\n        MainContent()\n      }\n    }\n  }\n}\n\n// Breakpoint system\nexport class BreakpointSystem {\n  private static readonly BREAKPOINTS: Record<string, number> = {\n    'sm': 320,   // Phone\n    'md': 600,   // Foldable/Tablet\n    'lg': 840    // Desktop/TV\n  };\n\n  static register(context: UIContext): void {\n    context.getMediaQuery().matchMediaSync('(width >= 840vp)').on('change', (result) => {\n      AppStorage.setOrCreate('currentBreakpoint', result.matches ? 'lg' : 'md');\n    });\n\n    context.getMediaQuery().matchMediaSync('(width >= 600vp)').on('change', (result) => {\n      if (!result.matches) {\n        AppStorage.setOrCreate('currentBreakpoint', 'sm');\n      }\n    });\n  }\n}\n```\n\n---\n\n## Testing\n\n### Unit Testing\n\n```typescript\nimport { describe, it, expect, beforeEach } from '@ohos/hypium';\nimport { ProductViewModel } from '../viewmodel/ProductViewModel';\n\nexport default function ProductViewModelTest() {\n  describe('ProductViewModel', () => {\n    let viewModel: ProductViewModel;\n\n    beforeEach(() => {\n      viewModel = new ProductViewModel();\n    });\n\n    it('should load products successfully', async () => {\n      await viewModel.loadProducts();\n\n      expect(viewModel.products.length).assertLarger(0);\n      expect(viewModel.isLoading).assertFalse();\n      expect(viewModel.errorMessage).assertEqual('');\n    });\n\n    it('should add product to list', async () => {\n      const initialCount = viewModel.products.length;\n      const newProduct: Product = { id: 'test', name: 'Test Product', price: 99 };\n\n      await viewModel.addProduct(newProduct);\n\n      expect(viewModel.products.length).assertEqual(initialCount + 1);\n    });\n  });\n}\n```\n\n### UI Testing\n\n```typescript\nimport { describe, it, expect } from '@ohos/hypium';\nimport { Driver, ON } from '@ohos.UiTest';\n\nexport default function ProductPageUITest() {\n  describe('ProductPage UI', () => {\n    it('should display product list', async () => {\n      const driver = Driver.create();\n      await driver.delayMs(1000);\n\n      // Find and verify list exists\n      const list = await driver.findComponent(ON.type('List'));\n      expect(list).not().assertNull();\n\n      // Verify list items\n      const items = await driver.findComponents(ON.type('ListItem'));\n      expect(items.length).assertLarger(0);\n    });\n\n    it('should navigate to detail on tap', async () => {\n      const driver = Driver.create();\n\n      // Find first product card\n      const card = await driver.findComponent(ON.type('ProductCard'));\n      await card.click();\n\n      await driver.delayMs(500);\n\n      // Verify navigation to detail page\n      const detailTitle = await driver.findComponent(ON.text('Product Detail'));\n      expect(detailTitle).not().assertNull();\n    });\n  });\n}\n```\n\n---\n\n## Checklist\n\n```markdown\n## Project Setup\n- [ ] Stage model used (not FA model)\n- [ ] module.json5 properly configured\n- [ ] Permissions declared in module.json5\n- [ ] Resource files organized (strings, images)\n\n## Code Quality\n- [ ] No `any` types in codebase\n- [ ] All state decorated with proper decorators\n- [ ] No direct mutation of @State objects\n- [ ] Components extracted for reusability\n- [ ] Lifecycle methods used appropriately\n\n## UI/UX\n- [ ] LazyForEach used for long lists\n- [ ] Loading states implemented\n- [ ] Error handling with user feedback\n- [ ] Multi-device layouts with GridRow/GridCol\n- [ ] Accessibility attributes added\n\n## State Management\n- [ ] Clear state ownership (component vs global)\n- [ ] @Observed/@ObjectLink for nested objects\n- [ ] PersistentStorage for user preferences\n- [ ] AppStorage for app-wide state\n\n## Performance\n- [ ] Images optimized and cached\n- [ ] Unnecessary re-renders avoided\n- [ ] Network requests with proper error handling\n- [ ] Background tasks properly managed\n\n## Testing\n- [ ] Unit tests for ViewModels\n- [ ] UI tests for critical flows\n- [ ] Edge cases covered\n```\n\n---\n\n## See Also\n\n- [reference/arkts.md](reference/arkts.md) — ArkTS language guide and restrictions\n- [reference/arkui.md](reference/arkui.md) — ArkUI components and styling\n- [reference/stage-model.md](reference/stage-model.md) — Stage model architecture\n- [reference/distributed.md](reference/distributed.md) — Distributed capabilities guide\n- [templates/project-structure.md](templates/project-structure.md) — Project template","tags":["harmonyos","app","claude","arsenal","majiayu000","agent-skills","ai-agents","ai-coding-assistant","automation","claude-code","code-review","developer-tools"],"capabilities":["skill","source-majiayu000","skill-harmonyos-app","topic-agent-skills","topic-ai-agents","topic-ai-coding-assistant","topic-automation","topic-claude","topic-claude-code","topic-code-review","topic-developer-tools","topic-devops","topic-productivity","topic-prompt-engineering","topic-python"],"categories":["claude-arsenal"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/majiayu000/claude-arsenal/harmonyos-app","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add majiayu000/claude-arsenal","source_repo":"https://github.com/majiayu000/claude-arsenal","install_from":"skills.sh"}},"qualityScore":"0.464","qualityRationale":"deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 29 github stars · SKILL.md body (24,526 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-01T07:01:14.349Z","embedding":null,"createdAt":"2026-04-18T22:24:13.307Z","updatedAt":"2026-05-01T07:01:14.349Z","lastSeenAt":"2026-05-01T07:01:14.349Z","tsv":"'/model/product':935 '/repository/productrepository':939 '/utils/basicdatasource':759 '/utils/breakpointsystem':1647 '/viewmodel/productviewmodel':1775 '0':1376,1800,1895 '1':723,786,1107,1834 '1/3':1684 '1/4':1687 '100':721 '1000':1867 '12':25,745,1665,1668,1670 '123':1186,1287 '14':735 '16':729,862,866,879 '2':686,824,827 '2/3':1704 '20':857 '200':342,1368 '24':869,886 '25':216 '26':219,231 '3':1678 '3/4':1707 '320':1724 '4':830,832,1661,1674,1676,1694,1696 '500':1921 '600':1727 '600vp':1752 '8':749,1663 '840':1730 '840vp':1740 '9':1698 '99':1826 'abil':289 'abilityconst':1034 'abilityconstant.launchparam':305,1049 'abouttoappear':688,999,1208 'abouttodisappear':693 'abstract':638 'access':136,153,579,637,1063,2007 'across':1498 'ad':2009 'adapt':99,1639 'adaptivepag':1651 'add':739,1809 'adddata':778 'addproduct':977 'age':215,230 'also':2067 'alway':259 'ancestor':494,505 'api':24 'api.example.com':1337 'apirespons':1325,1373,1429 'app':3,12,445,2030 'app-wid':444,2029 'app.media.avatar':350,372 'appear':692 'applic':5,29 'application/json':1359,1415 'appropri':1986 'appstorag':448,509,517,1025,2027 'appstorage.get':1439 'appstorage.setorcreate':1054,1057,1060,1744,1758 'architectur':538,2085 'arkt':14,33,36,124,2070 'arkui':15,51,622,645,2077 'array':239,278 'aspectratio':722 'assert':132,159 'assertequ':1806,1832 'assertfals':1803 'assertlarg':1799,1894 'assertnul':1882,1937 'async':956,976,1338,1390,1435,1470,1511,1523,1599,1794,1813,1861,1903 'atom':80,83 'attribut':2008 'author':1360,1416 'authtoken':1440 'auto':1496 'autocancel':913 'autosync':1494 'avoid':2042 'await':964,983,1349,1362,1405,1418,1507,1518,1532,1795,1827,1865,1875,1888,1913,1917,1919,1929 'b':244 'back':1221,1310 'background':2049 'backgroundcolor':746,875 'backup':1492 'baseurl':1335 'basic':648 'basicdatasourc':757,763 'bearer':1361,1417 'beforeeach':1769,1785 'best':26 'bind':425,513,521 'boolean':675,947,1073,1152,1164 'borderradius':748 'bottom':861,868 'breakpoint':1711,1719 'breakpointsystem':1644,1715 'breakpointtyp':1645 'build':10,48,329,334,340,368,389,596,612,716,806,852,920,1002,1089,1143,1273,1302,1657 'build-profil':595,611 'builder':715,896,1254 'bundlenam':1480 'busi':562,564,574,632 'business-specif':563 'button':738,871,880,921,1098,1277,1309 'c':247,255 'cach':831,2037 'cachedcount':829 'callback':1548,1568,1578 'cancel':872 'capabl':20,76,1447,2089 'card':86,1910,1912 'card.click':1918 'cart':741 'case':2064 'catch':966,1538 'chang':472,1543,1742,1754 'checklist':1938 'child':414,420,477,487 'class':297,523,760,942,1041,1332,1458,1592,1714 'clear':1229,2012 'client':1318 'cn':1126,1142 'code':1326,1960 'codebas':1966 'color.gray':876 'color.red':1013 'color.white':747 'column':341,351,373,390,717,828,853,1003,1090,1144,1276,1304,1659 'com.example.myapp':1481 'common':559,560,599 'complet':1195 'complex':326,451 'compon':54,317,323,337,361,362,379,404,432,463,552,555,558,561,566,623,646,649,656,798,889,991,1065,1067,1128,1202,1238,1245,1292,1649,1979,2015,2078 'component-loc':403 'comput':677 'config':587,1476,1484 'config.json':275 'configur':290,594,616,1950 'confirm':847,881 'confirmdialog':842,897 'connect':1586 'consid':82 'console.info':690,695,1628 'const':981,1210,1343,1347,1369,1399,1403,1425,1442,1475,1485,1564,1574,1614,1814,1817,1862,1873,1886,1904,1911,1927 'constant':584,585 'consum':435,500,502,1295 'content':1357,1413,1699 'content-typ':1356,1412 'context':438,1472,1473,1478,1479,1601,1602,1734 'context.applicationinfo.name':1606 'context.getmediaquery':1737,1749 'control':843 'copi':479 'core':31 'correct':120 'counter':408 'cover':21,2065 'creat':232,982,988 'createifmiss':1488 'critic':2061 'cross':74,431,1449 'cross-compon':430 'cross-devic':73,1448 'current':1224 'currentbreakpoint':1653,1654,1745,1759 'currentus':1058,1076,1077 'custom':837 'customdialog':840 'customdialogcontrol':844,893,895 'darkmod':1121,1137,1167 'data':142,170,415,421,571,578,636,639,1330,1396,1422,1451,1561,1627 'data.device.devicename':1631 'data.insertentries':1567 'data.updateentries':1577 'datachang':1557 'datasourc':802 'declar':46,53,1952 'decor':460,524,1969,1972 'default':280,296,1040,1777,1850 'definit':548 'delet':899,908,918,922 'deleteitem':916 'deprec':266,274 'descend':499,501 'describ':1766,1780,1839,1853 'design':71,94 'desktop':1689,1709 'desktop/tv':1731 'detail':1262,1280,1284,1314,1900,1925,1933 'detail.ets':554 'detailpag':1204,1263,1294 'detailparam':1267,1300 'detailtitl':1928,1935 'develop':6,30,93 'devic':75,98,1450,1499,1546,1583,1630,1638,2003 'devicemanag':1593,1595 'dialog':838 'dialogcontrol':892 'direct':191,199,208,1974 'disappear':697 'discoveri':1584 'discovermod':1617 'discoversuccess':1626 'display':1858 'distribut':19,69,1446,2088 'distributeddevicemanag':1589 'distributeddevicemanager.createdevicemanager':1605 'distributeddevicemanager.devicebasicinfo':1608 'distributeddevicemanager.devicemanager':1596 'distributeddevicemanager.discovermode.discover':1618 'distributeddevicemanager.discoveryfilter':1616 'distributedkvstor':1455 'distributedkvstore.changenotification':1562 'distributedkvstore.createkvmanager':1483 'distributedkvstore.kvmanager':1462 'distributedkvstore.kvmanagerconfig':1477 'distributedkvstore.kvstoretype.single':1501 'distributedkvstore.options':1487 'distributedkvstore.securitylevel':1504 'distributedkvstore.singlekvstore':1467 'distributedkvstore.subscribetype.subscribe':1558 'distributedstor':1459 'driver':1845,1863,1905 'driver.create':1864,1906 'driver.delayms':1866,1920 'driver.findcomponent':1876,1914,1930 'driver.findcomponents':1889 'dynam':44,122,126,134,139,151,176 'dynamickey':149 'edg':2063 'els':1007,1014,1097,1268 'email':387 'encrypt':1490 'entri':336,378,540,542,888,990,1066,1127,1201,1244,1565,1575,1648 'entry.key':1569,1579 'entry.value.value':1570,1580 'entryability.ets':549 'entryabl':298,546,1032,1042 'error':967,1381,1385,1996,2047 'error.message':972 'errormessag':949 'et':545 'event':698 'exampl':402 'exist':1872 'expect':1768,1797,1801,1804,1830,1841,1879,1892,1934 'expectdatatyp':1364,1423 'experi':89 'expert':7 'export':279,295,657,941,1039,1441,1713,1776,1849 'extend':299,762,1043 'extract':319,360,1980 'extradata':1420 'fa':67,267,272,1946 'fail':969 'fals':676,948,975,1056,1074,1122,1138,1491,1493 'featur':604,605,607,609 'feedback':2000 'fetchdata':144,172 'ff6b00':737 'file':1956 'filter':1615,1623 'final':973,1388,1432 'find':1868,1907 'first':34,1908 'flow':2062 'foldable/tablet':1728 'follow':107 'fontcolor':736,1012 'fontsiz':728,734,856,865 'fontweight':730,858 'fontweight.bold':859 'fontweight.medium':731 'forbidden':138,207,245,271,332 'foreach':1015 'form':409 'formattedpric':682 'found':1629 'full':1680,1700 'function':583,1778,1851 'generat':822 'get':478,681,1196,1339,1524,1534 'getavailabledevic':1607 'getavailabledevicelistsync':1611 'getdata':771 'getter':680 'gettoken':1436 'global':449,1027,1052,2017 'go':1220,1278 'grid':825 'gridcol':1671,1691 'gridrow':1658 'gridrow/gridcol':2006 'guid':2072,2090 'gutter':1666 'handl':1997,2048 'handleaddtocart':710 'handler':699 'handletap':701 'hard':104 'harmonyo':2,4,11,22,28,1241 'harmonyos-app':1 'header':1355,1411 'home':608,1291 'http':1317,1321,1386 'http.createhttp':1345,1401 'http.httpdatatype.object':1365,1424 'http.requestmethod.get':1354 'http.requestmethod.post':1410 'httpclient':1333,1443,1445 'httprequest':1344,1400 'httprequest.destroy':1389,1433 'httprequest.request':1350,1406 'id':165,707,1106,1286,1301,1307,1820 'imag':348,370,591,718,1959,2034 'imagefit.cover':725 'immut':204,225 'implement':1995 'import':291,652,756,932,936,1033,1174,1320,1454,1588,1643,1765,1772,1838,1844 'index':772,777,788,794,797,812 'index.ets':553 'init':1471,1600 'initi':1030,1051,1114 'initialcount':1815,1833 'inlin':325,345,357 'input':410 'interfac':163,1324 'isexpand':674 'isload':946 'isloggedin':1055,1071,1072 'ison':1148,1151,1156,1160,1163,1168 'item':241,833,900,910,1885,1887 'items.length':1893 'john':214,386,1109 'john@example.com':388 'json.stringify':1421 'json5':598,614 'key':177,184,821,1513,1521,1525,1535,1549 'kit.abilitykit':294,1038 'kit.arkdata':1457 'kit.arkui':655,1177 'kit.distributedservicekit':1591 'kit.networkkit':1323 'kvmanag':1461 'kvstore':1466 'kvstoretyp':1500 'lane':823 'languag':1123,1139,2071 'launchparam':304,1048 'layer':580,617,620,625,631,635 'layout':1641,2004 'lazyforeach':754,808,1988 'legaci':66 'let':141,145,169,178,1782 'leverag':72 'lg':1664,1677,1697,1729,1747 'librari':601 'lifecycl':283,310,687,1983 'light':1062,1085 'lightweight':88 'line':343 'link':426,483,529 'list':752,807,1812,1860,1871,1874,1878,1880,1884,1992 'listitem':814,1891 'load':971,1791,1993 'loadingprogress':1006 'loadproduct':957 'local':405,671 'local/remote':641 'logic':453,575,629,633,919 'login':1099,1102 'long':1991 'main':541 'maincont':1710 'mainpag':339,381,891,1247 'manag':57,627,926,2011,2052 'mandatori':111 'margin':860,867,877 'markdown':1939 'matchmediasync':1738,1750 'md':1662,1675,1695,1726,1748 'mean':114 'messag':848,901,1328 'method':157,330,335,1353,1409,1984 'mode':1619 'model':17,59,63,68,257,262,268,273,286,309,570,572,1943,1947,2084 'modern':61,307 'modifi':489 'modul':543,593,602,606 'module.json5':287,592,1948,1954 'monolith':333 'multi':97,1637,2002 'multi-devic':96,1636,2001 'must':106 'mutabl':428 'mutat':193,195,209,1975 'mvvm':569 'myapp':539 'mystor':1509 'name':167,213,385,1108,1256,1261,1270,1283,1822 'navdestin':1288,1303 'navig':1169,1172,1178,1187,1194,1232,1237,1274,1898,1923 'navpathstack':1249,1250,1251,1253,1296,1297,1298 'nest':201,456,2021 'network':644,1315,2043 'never':128,194,264 'new':233,804,894,954,997,1252,1380,1384,1444,1787 'newproduct':1818,1829 'next':23,1242 'notif':1119,1135,1155 'null':1059,1079,1080,1463,1464,1468,1469,1529,1540,1597,1598 'number':768,773,789,813,1327,1722 'obj':146,148,179,183 'object':147,202,234,457,527,532,1259,1978,2022 'objectfit':724 'objectlink':455,528,2019 'observ':454,522,526,531,940,2018 'ohos.uitest':1848 'ohos/hypium':1771,1843 'ok':186 'on.text':1931 'on.type':1877,1890,1915 'onaddtocart':667 'onchang':1150,1162 'onclick':742,750,873,882,923,1100,1281,1311 'onconfirm':850,911 'oncreat':281,301,1045 'one':91,481,519 'one-tim':90 'one-way':480,518 'onwindowstagecr':311 'optim':2035 'option':1486,1510 'organ':1957 'own':464 'ownership':2014 'pad':744,885 'page':277,550,551,621,1180,1200,1225,1926 'pageabl':269,282 'pagebuild':1255 'pages/detail':1183 'pages/home':1228 'pages/index':316 'pages/login':1236 'pages/productdetail':705 'pages/productpage.ets':989 'pages/selectaddress':1192 'param':706,1184,1197,1211,1218,1258,1264,1265,1285,1299 'parent':412,474,484,534,662 'parent-to-child':411 'pass':475,485 'passiv':1620 'path':1340,1352,1394,1408 'pattern':401,647,927,929,1170 'perform':2033 'permiss':1951 'persist':439,1115 'persistentstorag':441,1110,2023 'persistentstorage.persistprop':1117 'phone':100,1683,1703,1725 'post':1391 'practic':27 'prefer':443,642,1112,2026 'price':1825 'principl':32 'privat':700,709,764,801,915,951,1334,1434,1460,1465,1594,1716 'product':664,665,668,669,765,766,774,779,780,783,790,791,795,810,811,816,817,818,819,933,944,945,978,979,985,1017,1018,1020,1021,1022,1023,1306,1792,1810,1819,1824,1859,1909,1932 'product.id':820,1024 'productcard':659,691,696,815,1019,1916 'productdatasourc':761,803,805 'productid':1185,1206,1219 'productlist':800 'productpag':993,1854 'productpageuitest':1852 'productrepositori':937,953,955 'productviewmodel':943,996,998,1773,1781,1784,1788 'productviewmodeltest':1779 'profil':597,610,613 'profilepag':1069 'prohibit':125 'project':535,615,1940,2093 'promis':958,980,1342,1398,1437,1474,1517,1527,1603 'prop':197,365,416,473,660,663,666 'proper':1949,1971,2046,2051 'properti':135,152 'provid':434,493,495,1248 'put':1512,1520 'qualiti':1961 'quick':394 'r':349,371,1393 'rdb':643 're':469,2040 're-rend':468,2039 'read':418,1087 'read-on':417,1086 'readon':1718 'recommend':537,1239 'record':174,180,188,1214,1720 'refer':395,486 'reference/arkts.md':2068,2069 'reference/arkui.md':2075,2076 'reference/distributed.md':2086,2087 'reference/stage-model.md':2081,2082 'regist':1733 'render':470,2041 'replac':1223 'repositori':577,634,952 'request':1316,2044 'requir':160,224,252,284,359 'resourc':588,589,1955 'respons':1348,1404,1640 'response.responsecode':1367,1387 'response.result':1371,1427 'restrict':2074 'result':1189,1370,1426,1743,1755 'result.code':1375 'result.data':1378,1431 'result.matches':1746,1757 'result.message':1382 'return':684,769,775,1377,1430,1438,1531,1539,1609 'reusabl':318,320,556,1982 'right':878 'router':653,1171,1175 'router.back':1222 'router.clear':1233 'router.getparams':1212 'router.pushurl':703,1181,1190,1234 'router.replaceurl':1226 'row':347,369,870 'rule':105,109 's1':1505 'safeti':40 'scenario':400 'scroll':836 'securitylevel':1503 'see':2066 'select':461 'separ':618 'servic':81,84,573,576,630 'set':1133,1271 'settingspag':1130,1272 'setup':1941 'share':427,600 'sidebar':1679,1690 'skill':116 'skill-harmonyos-app' 'sm':1656,1660,1673,1693,1723,1760 'smooth':835 'sometyp':156 'somevar':154 'sourc':640 'source-majiayu000' 'span':1672,1692 'specif':565 'src/main':544 'src/main/ets':603 'stack':1230 'stage':16,58,62,256,261,285,308,1942,2083 'start':79 'startdiscov':1622 'startdiscoveri':1612 'state':56,192,196,210,240,382,406,407,429,433,440,447,450,452,459,462,466,626,672,673,925,994,1028,1053,1205,1968,1977,1994,2010,2013,2032 'static':1717,1732 'stopdiscov':1635 'stopdiscoveri':1632 'storag':1116 'storagelink':506,1070,1075,1131 'storageprop':514,1081,1652 'strict':38,161 'string':166,168,181,182,242,590,683,846,849,950,1084,1207,1215,1216,1257,1329,1336,1341,1395,1514,1516,1526,1528,1537,1550,1552,1572,1582,1655,1721,1958 'struct':338,363,380,658,799,841,890,992,1068,1129,1203,1246,1293,1650 'structur':536,650 'style':2080 'subscrib':1541,1547 'success':1793 'sure':904 'sync':507,515,1452,1497 'system':1712 'tablet':101,1686,1706 'tap':1902 'target':1199 'task':2050 'templat':2094 'templates/project-structure.md':2091,2092 'test':1761,1763,1821,1823,1836,2053,2055,2059 'text':352,354,374,376,726,732,854,863,1010,1094,1305 'theme':436,1061,1082,1083 'this.baseurl':1351,1407 'this.controller.close':874,884 'this.currentuser':1093,1105 'this.currentuser.name':1096 'this.datasource':809 'this.deleteitem':912 'this.devicemanager':1604,1610,1621,1624,1634 'this.dialogcontroller.open':924 'this.errormessage':961,968 'this.formattedprice':733 'this.gettoken':1363,1419 'this.handleaddtocart':743 'this.handletap':751 'this.isloading':959,974 'this.isloggedin':1092,1103 'this.items':253,254 'this.items.push':246 'this.kvmanager':1482 'this.kvmanager.getkvstore':1508 'this.kvstore':1506,1519,1533,1555 'this.message':864 'this.navpathstack':1275 'this.navpathstack.pop':1312 'this.navpathstack.pushpath':1282 'this.notifydataadd':784 'this.notifydatachange':796 'this.onaddtocart':712 'this.onconfirm':883 'this.pagebuilder':1289 'this.params.id':1308 'this.product':713 'this.product.id':708 'this.product.imageurl':719 'this.product.name':727 'this.product.price.tofixed':685 'this.productid':1217 'this.products':776,793,963,986,987 'this.products.length':770,785 'this.products.push':782 'this.repository.createproduct':984 'this.repository.getproducts':965 'this.settings':1153,1154,1165,1166 'this.settings.darkmode':1161 'this.settings.notifications':1149 'this.title':855 'this.user':228,229,393 'this.user.age':218 'this.user.email':355,377 'this.user.name':353,375 'this.viewmodel.errormessage':1009,1011 'this.viewmodel.isloading':1005 'this.viewmodel.loadproducts':1001 'this.viewmodel.products':1016 'throw':1379,1383 'time':92 'titl':845,898,1290,1313 'toggl':1145,1157 'toggletype.switch':1147,1159 'topic-agent-skills' 'topic-ai-agents' 'topic-ai-coding-assistant' 'topic-automation' 'topic-claude' 'topic-claude-code' 'topic-code-review' 'topic-developer-tools' 'topic-devops' 'topic-productivity' 'topic-prompt-engineering' 'topic-python' 'totalcount':767 'tri':962,1346,1402,1530 'trigger':235,467 'true':914,960,1104,1120,1136,1489,1495 'tv':103 'two':423,491,511 'two-way':422,490,510 'type':39,45,123,127,131,140,158,162,189,1146,1158,1358,1414,1559,1964 'typescript':137,206,270,331,651,755,839,930,1029,1113,1173,1243,1319,1453,1587,1642,1764,1837 'ui':47,49,220,236,248,321,327,346,358,557,619,628,714,1835,1855,2058 'ui/ux':1987 'uiabil':64,263,292,300,547,1035,1044 'uicontext':1735 'unit':1762,2054 'unnecessari':2038 'updat':205,223,226,237,251,458 'updateag':217,227 'updatedata':787 'url':704,1182,1191,1227,1235 'usag':887 'use':8,35,60,129,173,203,260,265,398,679,1944,1985,1989 'user':211,212,366,367,383,384,392,437,442,1078,1999,2025 'usercard':364,391 'userdata':164,171 'userset':1118,1132,1134 'util':581,582 'valu':150,185,476,496,503,678,1515,1522,1551 'variabl':198 'verifi':1870,1883,1922 'version':1502 'viewmodel':567,568,624,928,995,1783,1786,2057 'viewmodel.addproduct':1828 'viewmodel.errormessage':1805 'viewmodel.isloading':1802 'viewmodel.loadproducts':1796 'viewmodel.products.length':1798,1816,1831 'viewmodel/productviewmodel.ets':931 'violat':112 'void':306,314,670,689,694,702,711,781,792,851,917,1000,1050,1209,1553,1554,1613,1633,1736 'vs':2016 'want':302,303,906,1036,1046,1047 'watch':102 'way':424,482,492,512,520 'welcom':1095 'wide':446,2031 'width':720,1681,1701,1739,1751 'window.windowstage':313 'windowstag':312 'windowstage.loadcontent':315 'won':221,249 'work':119 'x':1667 'y':1669 'zh':1125,1141 'zh-cn':1124,1140","prices":[{"id":"c6b0f72e-ea89-4a3b-9013-4bca21ef59a2","listingId":"41caa3cb-7ca0-44c4-b1b9-c7526da7fc25","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"majiayu000","category":"claude-arsenal","install_from":"skills.sh"},"createdAt":"2026-04-18T22:24:13.307Z"}],"sources":[{"listingId":"41caa3cb-7ca0-44c4-b1b9-c7526da7fc25","source":"github","sourceId":"majiayu000/claude-arsenal/harmonyos-app","sourceUrl":"https://github.com/majiayu000/claude-arsenal/tree/main/skills/harmonyos-app","isPrimary":false,"firstSeenAt":"2026-04-18T22:24:13.307Z","lastSeenAt":"2026-05-01T07:01:14.349Z"}],"details":{"listingId":"41caa3cb-7ca0-44c4-b1b9-c7526da7fc25","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"majiayu000","slug":"harmonyos-app","github":{"repo":"majiayu000/claude-arsenal","stars":29,"topics":["agent-skills","ai-agents","ai-coding-assistant","automation","claude","claude-code","code-review","developer-tools","devops","productivity","prompt-engineering","python","software-development","typescript","workflows"],"license":"mit","html_url":"https://github.com/majiayu000/claude-arsenal","pushed_at":"2026-04-29T04:12:22Z","description":"52 production-ready Claude Code skills and 7 specialized agents for software development, DevOps, product workflows, and automation.","skill_md_sha":"4f36d392fb705fba202b2eeedc22e195632aeb06","skill_md_path":"skills/harmonyos-app/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/majiayu000/claude-arsenal/tree/main/skills/harmonyos-app"},"layout":"multi","source":"github","category":"claude-arsenal","frontmatter":{"name":"harmonyos-app","description":"HarmonyOS application development expert. Use when building HarmonyOS apps with ArkTS, ArkUI, Stage model, and distributed capabilities. Covers HarmonyOS NEXT (API 12+) best practices."},"skills_sh_url":"https://skills.sh/majiayu000/claude-arsenal/harmonyos-app"},"updatedAt":"2026-05-01T07:01:14.349Z"}}