@lingxia/skill 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -0
- package/bin/install.mjs +247 -0
- package/package.json +49 -0
- package/scripts/sync.mjs +69 -0
- package/skill/SKILL.md +334 -0
- package/skill/app/apple-sdk.md +312 -0
- package/skill/app/applinks.md +289 -0
- package/skill/app/project.md +760 -0
- package/skill/cli/lxdev.md +195 -0
- package/skill/cli/reference.md +481 -0
- package/skill/examples/hello-host-js/README.md +25 -0
- package/skill/examples/hello-host-js/home/lxapp.json +12 -0
- package/skill/examples/hello-host-js/home/pages/home/index.json +4 -0
- package/skill/examples/hello-host-js/home/pages/home/index.ts +14 -0
- package/skill/examples/hello-host-js/home/pages/home/index.tsx +15 -0
- package/skill/examples/hello-host-js/lingxia.yaml +39 -0
- package/skill/examples/hello-host-rust/Cargo.toml +15 -0
- package/skill/examples/hello-host-rust/README.md +44 -0
- package/skill/examples/hello-host-rust/home/lxapp.json +13 -0
- package/skill/examples/hello-host-rust/home/pages/home/index.html +46 -0
- package/skill/examples/hello-host-rust/home/pages/home/index.json +4 -0
- package/skill/examples/hello-host-rust/lingxia.yaml +32 -0
- package/skill/examples/hello-host-rust/src/lib.rs +58 -0
- package/skill/examples/hello-lxapp/README.md +29 -0
- package/skill/examples/hello-lxapp/lxapp.config.ts +8 -0
- package/skill/examples/hello-lxapp/lxapp.json +14 -0
- package/skill/examples/hello-lxapp/package.json +14 -0
- package/skill/examples/hello-lxapp/pages/home/index.json +4 -0
- package/skill/examples/hello-lxapp/pages/home/index.ts +35 -0
- package/skill/examples/hello-lxapp/pages/home/index.tsx +34 -0
- package/skill/lxapp/bridge.md +654 -0
- package/skill/lxapp/components.md +375 -0
- package/skill/lxapp/guide.md +675 -0
- package/skill/lxapp/lx-api.md +481 -0
- package/skill/native/development.md +414 -0
- package/skill/reference/file-lifecycle.md +325 -0
- package/skill/skill-manifest.json +6 -0
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lingxia
|
|
3
|
+
description: Build apps on the LingXia cross-platform framework — standalone lxapps (page-based mini-apps with a View+Logic split), native host apps (Android/iOS/macOS/Harmony shells embedding an lxapp), and Rust native extensions. TRIGGER on `lingxia` CLI, `lxapp`, `lingxia.yaml`, `lxapp.json`, `#[lingxia::native]`, `HostAddon`, `useLxPage`, `LxAppController`, or an lxapp-flavored `Page({})`. SKIP if the project imports `@tarojs/*`, `wx.*`, `uni-app`, `@dcloudio/*`, or `@remax/*` — those share the `Page({})` shape but are different runtimes. **Always read §"Step 0" before generating any file.**
|
|
4
|
+
license: MIT
|
|
5
|
+
allowed-tools: Read, Grep, Glob, Edit, Write, Bash(lingxia:*), Bash(npm:*), Bash(npx:*), Bash(test:*), Bash(ls:*), Bash(cat:*), Bash(cargo:*)
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# LingXia App Development
|
|
9
|
+
|
|
10
|
+
LingXia is a cross-platform app framework. This skill is the entry router — it carries the decision tree, fast-path recipes, and pointers into supporting reference files. **Read sub-files only when you need them**; do not load the whole tree up front.
|
|
11
|
+
|
|
12
|
+
This skill is installed via `npx @lingxia/skill install` (the `lingxia` CLI prints a hint pointing at that command after `lingxia new`, but does not install the skill itself). When you read this you can usually assume:
|
|
13
|
+
|
|
14
|
+
- The `lingxia` CLI is on `PATH` (verify with `lingxia --version`); if not, point the human at `docs/quick-start.md` in the LingXia repo for the install steps.
|
|
15
|
+
- You are inside a LingXia project — but **confirm the shape** with the probe in Step 0 rather than guessing.
|
|
16
|
+
|
|
17
|
+
For first-time CLI + platform-toolchain setup (one-time, human-facing onramp), the repo's `docs/quick-start.md` is the source. This skill does not duplicate it.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Step 0 — Decide before scaffolding
|
|
22
|
+
|
|
23
|
+
**Always do this first.** Before generating a single file:
|
|
24
|
+
|
|
25
|
+
### 0a. Identify what you're inside
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
test -f lingxia.yaml && echo "host-app"
|
|
29
|
+
test -f lxapp.json && echo "lxapp"
|
|
30
|
+
test -f lxplugin.json && echo "lxplugin"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If none match, you're about to scaffold a new project — continue to 0b. If one matches, jump to the fast-path for that shape.
|
|
34
|
+
|
|
35
|
+
### 0b. Pick the shape (ask the user or infer)
|
|
36
|
+
|
|
37
|
+
1. **Standalone lxapp or host app?** (A vs B/C below)
|
|
38
|
+
2. **If host app:** which platforms? `android`, `ios`, `macos`, `harmony`, any combination.
|
|
39
|
+
3. **If host app:** JS Logic for the home lxapp, or native-only Rust? (B vs C)
|
|
40
|
+
4. **View framework:** React **or** Vue **or** HTML — pick **one**. The LingXia repo's `lingxia-showcase` example deliberately mixes all three; real apps do not.
|
|
41
|
+
|
|
42
|
+
Then scaffold:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Shape A — standalone lxapp
|
|
46
|
+
lingxia new my-lxapp -t lxapp -y
|
|
47
|
+
|
|
48
|
+
# Shape B/C — native host app
|
|
49
|
+
lingxia new my-app -t native-app -p macos --package-id com.example.myapp -y
|
|
50
|
+
# -p accepts: android, ios, macos, harmony, all (comma-separated)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`lingxia doctor` verifies platform toolchains.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## What you build (pick one shape)
|
|
58
|
+
|
|
59
|
+
| Shape | What it is | Pick when |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| **A. Standalone lxapp** | Page-based mini-app that runs in any LingXia host (e.g. macOS Runner). | UI/page work, no native shell. |
|
|
62
|
+
| **B. Host app + JS lxapp** | Native installable app (Android/iOS/macOS/Harmony) embedding a home lxapp whose Logic is JS. | Most product apps. |
|
|
63
|
+
| **C. Host app + native Rust logic** | Same shell, but the home lxapp's Logic is in Rust. The lxapp is HTML-only with `logic: false`. | Native-only hosts (e.g. menu-bar utilities), or when the heavy lifting belongs in Rust. |
|
|
64
|
+
|
|
65
|
+
C is just B with `features.appService: false` and Rust replacing the JS Logic. You can also mix: a JS-Logic lxapp that **calls** Rust routes via `#[lingxia::native]` — that is still B, with native Rust as an *API surface* rather than the Logic layer.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## `@lingxia/*` npm packages at a glance
|
|
70
|
+
|
|
71
|
+
Every published package and what to import from each. Don't guess imports from the package name — use this table.
|
|
72
|
+
|
|
73
|
+
| Package | What it is | Imported by | Typical import |
|
|
74
|
+
|---|---|---|---|
|
|
75
|
+
| `@lingxia/react` | React hooks + framework-wrapped native components | lxapp View (React) | `useLxPage`, `useLxStream`, `useLxChannel`, `LxInput`, `LxVideo`, … |
|
|
76
|
+
| `@lingxia/vue` | Vue composables + framework-wrapped native components | lxapp View (Vue) | same surface as React, Vue-flavored |
|
|
77
|
+
| `@lingxia/html` | DOM helpers for HTML-only views (`subscribe`, `getActions`, …) | lxapp View (HTML) | `import { getActions, subscribe } from '@lingxia/html'` |
|
|
78
|
+
| `@lingxia/elements` | Pure-JS custom elements (`<lx-video>`, `<lx-input>`, …) | rarely direct — `@lingxia/react`/`vue` re-export wrappers around these | `registerVideoComponent`, `LxVideoElement` |
|
|
79
|
+
| `@lingxia/types` | **TypeScript declarations for the Logic-side `lx.*` API + `Page({})` / `App({})` globals** | lxapp Logic (`pages/*/index.ts`) | install as dev dep; types apply globally |
|
|
80
|
+
| `@lingxia/bridge` | Bridge runtime + low-level invocation helpers | rarely direct (advanced) | only when bypassing the framework wrappers |
|
|
81
|
+
| `@lingxia/native` | Virtual module — points at the **CLI-generated** native client (`#[lingxia::native]` routes) | lxapp View | `import { native } from '@lingxia/native'` — only after a native build runs |
|
|
82
|
+
| `@lingxia/page-runtime` | Internal — shared impl behind react/vue/html | **don't import directly** | — |
|
|
83
|
+
| `@lingxia/skill` | This skill itself | install via `npx @lingxia/skill install` | not imported in code |
|
|
84
|
+
|
|
85
|
+
**Logic-side typing.** Add `@lingxia/types` to the lxapp's `devDependencies` so `pages/*/index.ts` gets full intellisense for `lx.navigateTo(...)`, `lx.chooseMedia(...)`, the `Page({ data, onLoad, … })` shape, and so on. The declarations are global — no `import` needed in your Logic files.
|
|
86
|
+
|
|
87
|
+
For the full surface of `lx.*` and which namespace covers what, see [`./lxapp/lx-api.md`](./lxapp/lx-api.md).
|
|
88
|
+
|
|
89
|
+
## Reference map (inside this skill)
|
|
90
|
+
|
|
91
|
+
| Need | File |
|
|
92
|
+
|---|---|
|
|
93
|
+
| Every CLI command, flag, env var (daily use) | [`./cli/reference.md`](./cli/reference.md) |
|
|
94
|
+
| Page authoring: `Page({})`, `useLxPage`, events | [`./lxapp/guide.md`](./lxapp/guide.md) |
|
|
95
|
+
| **Native components: `LxInput`, `LxVideo`, `LxMediaSwiper`, `LxPicker`, `LxNavigator`, `LxTextarea`** | [`./lxapp/components.md`](./lxapp/components.md) |
|
|
96
|
+
| **Logic-side `lx.*` API surface map** | [`./lxapp/lx-api.md`](./lxapp/lx-api.md) |
|
|
97
|
+
| Bridge mechanics: `setData`, stream, channel | [`./lxapp/bridge.md`](./lxapp/bridge.md) |
|
|
98
|
+
| Host project: `lingxia.yaml` reference, macOS App UI | [`./app/project.md`](./app/project.md) |
|
|
99
|
+
| Native Rust: `HostAddon`, `#[lingxia::native]`, facades, JS extensions | [`./native/development.md`](./native/development.md) |
|
|
100
|
+
| iOS/macOS SDK embedding, public startup APIs | [`./app/apple-sdk.md`](./app/apple-sdk.md) |
|
|
101
|
+
| Universal links / app links setup | [`./app/applinks.md`](./app/applinks.md) |
|
|
102
|
+
| File API lifecycle (storage classes, downloadFile, FileManager) | [`./reference/file-lifecycle.md`](./reference/file-lifecycle.md) |
|
|
103
|
+
|
|
104
|
+
## Bundled hello-world examples
|
|
105
|
+
|
|
106
|
+
Three minimal end-to-end shapes ship with this skill — one per shape, named to make the JS-vs-Rust Logic split obvious. Read them first when you need to see the exact file layout for a shape. They are **not buildable starters** (no lockfiles, generated artifacts, or platform host scaffolding); they exist so you can match the shape and write real code with `lingxia new`.
|
|
107
|
+
|
|
108
|
+
| Example | Shape | Logic in | What it shows |
|
|
109
|
+
|---|---|---|---|
|
|
110
|
+
| [`./examples/hello-lxapp/`](./examples/hello-lxapp/README.md) | A — standalone lxapp | JS | `Page({})` Logic + React `useLxPage` View + `lxapp.json` security policy |
|
|
111
|
+
| [`./examples/hello-host-js/`](./examples/hello-host-js/README.md) | B — host + JS lxapp | JS | minimal macOS `lingxia.yaml` + embedded JS-Logic lxapp |
|
|
112
|
+
| [`./examples/hello-host-rust/`](./examples/hello-host-rust/README.md) | C — host + Rust Logic | **Rust** | `features.appService: false` + `lxapp.json` `"logic": false` + HTML view calling `window.native.*` |
|
|
113
|
+
|
|
114
|
+
`hello-host-js` and `hello-host-rust` share the host shell wiring (`lingxia.yaml` `ui`, `resources.bundles`, FFI export) — the diff is entirely on the Logic side: who owns state and how the View talks to it. Read both side-by-side when picking B vs C.
|
|
115
|
+
|
|
116
|
+
For a real, buildable starter, run `lingxia new` — the CLI emits a working project that matches the version on `PATH` and is regenerated per release.
|
|
117
|
+
|
|
118
|
+
**SDK-author internals** (bridge wire protocol, logging, webview lifecycle, env-version build-time plumbing) live in the LingXia repo under `docs/internal/` and `docs/draft/`. The skill summarizes the app-author-facing surface where relevant (e.g. env-version → [`./app/project.md`](./app/project.md#environment-versions)); the internal docs themselves are not needed for building on LingXia.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Symptom router — error → file
|
|
123
|
+
|
|
124
|
+
Jump straight here when the user reports a concrete failure:
|
|
125
|
+
|
|
126
|
+
| Symptom | Where to look |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `homeAppId` doesn't match any bundle / wrong app launches | [`./app/project.md`](./app/project.md) → `resources.bundles` |
|
|
129
|
+
| `fetch()` silently fails from an lxapp | [`./lxapp/guide.md`](./lxapp/guide.md) → "Security Policy" (`trustedDomains`) |
|
|
130
|
+
| "Is `fetch` / `setTimeout` / `URL` available in Logic?" | [`./lxapp/lx-api.md`](./lxapp/lx-api.md#standard-web-apis-built-in-globals) — yes, full Rong runtime |
|
|
131
|
+
| Need to read/write files (not just `lx.downloadFile`) | [`./lxapp/lx-api.md`](./lxapp/lx-api.md#file-and-transfer) → `lx.getFileManager()` |
|
|
132
|
+
| Need a scheduled task running across pages | [`./lxapp/lx-api.md`](./lxapp/lx-api.md#appservice-only-extras) → AppService `cron` |
|
|
133
|
+
| `attachPanel` validation rejected | [`./app/project.md`](./app/project.md) → "surfaces" rules |
|
|
134
|
+
| `setData` not reflecting in View | [`./lxapp/bridge.md`](./lxapp/bridge.md) → "How replication works" |
|
|
135
|
+
| Native route returns `BRIDGE_METHOD_NOT_FOUND` | [`./native/development.md`](./native/development.md) → Host Addon registration |
|
|
136
|
+
| `#[lingxia::native]` compiles but View can't call it | [`./native/development.md`](./native/development.md) → "Generated Native Client" |
|
|
137
|
+
| Stream cancels never trigger cleanup | [`./lxapp/bridge.md`](./lxapp/bridge.md) → `finally` block / `stream.on('cancel')` |
|
|
138
|
+
| `lingxia.yaml` change ignored after rebuild | [`./cli/reference.md`](./cli/reference.md) → `lingxia clean`, then rebuild |
|
|
139
|
+
| iOS dev app can't reach Mac dev server | [`./cli/reference.md`](./cli/reference.md) → `lingxia dev` (LAN reachability) |
|
|
140
|
+
| `Lingxia.initialize(...)` not found | [`./app/apple-sdk.md`](./app/apple-sdk.md) → use `Lingxia.quickStart()` (legacy removed) |
|
|
141
|
+
| TS doesn't know about `lx.foo()` / `Page({})` in Logic | install `@lingxia/types` as a devDependency; see [`./lxapp/lx-api.md`](./lxapp/lx-api.md) |
|
|
142
|
+
| `<LxVideo>` / `<LxPicker>` attribute not recognized by TS or runtime | [`./lxapp/components.md`](./lxapp/components.md) → component attribute table |
|
|
143
|
+
| Event handler on `LxVideo` fires DOM CustomEvent, not unwrapped detail | [`./lxapp/components.md`](./lxapp/components.md) → "Callback shapes by component" |
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Fast-path recipes
|
|
148
|
+
|
|
149
|
+
These cover the 80% case. For anything beyond, jump to the linked reference file.
|
|
150
|
+
|
|
151
|
+
### Recipe 1 — A standalone lxapp page
|
|
152
|
+
|
|
153
|
+
**Logic** (`pages/home/index.ts`):
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
Page({
|
|
157
|
+
data: { count: 0 },
|
|
158
|
+
onLoad(options) { /* options = URL query params */ },
|
|
159
|
+
increment() { this.setData({ count: this.data.count + 1 }); },
|
|
160
|
+
_privateHelper(n) { return n * 2; }, // _-prefixed = NOT exposed to View
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**View — React** (`pages/home/index.tsx`):
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
import { useLxPage } from '@lingxia/react';
|
|
168
|
+
|
|
169
|
+
type PageData = { count: number };
|
|
170
|
+
type PageActions = { increment: () => void };
|
|
171
|
+
|
|
172
|
+
export default function HomePage() {
|
|
173
|
+
const { data, actions } = useLxPage<PageData, PageActions>();
|
|
174
|
+
return <button onClick={() => actions.increment()}>{data.count}</button>;
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
> **On the `?:` question.** The runtime guarantees `data` reflects Logic's initial `data` object by first paint, and `actions` is fully wired during page setup. Mark `PageData` / `PageActions` fields **required** unless your Logic genuinely populates them lazily (e.g. via an async `setData` after `onLoad`). Earlier drafts used all-`?` fields and produced unnecessary `actions.foo?.()` and `data?.x ?? default` noise — avoid that pattern.
|
|
179
|
+
|
|
180
|
+
**Page config** (`pages/home/index.json`):
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{ "navigationBarTitleText": "Home", "navigationStyle": "custom" }
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Register** in `lxapp.json`:
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
{ "pages": [{ "name": "home", "path": "pages/home/index" }] }
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Run:** `lingxia dev` (launches macOS Runner).
|
|
193
|
+
|
|
194
|
+
Vue / HTML variants, action shapes (stream, channel), native components, `trustedDomains` security policy: [`./lxapp/guide.md`](./lxapp/guide.md). Bridge mechanics: [`./lxapp/bridge.md`](./lxapp/bridge.md).
|
|
195
|
+
|
|
196
|
+
### Recipe 2 — A macOS host app window
|
|
197
|
+
|
|
198
|
+
**`lingxia.yaml`:**
|
|
199
|
+
|
|
200
|
+
```yaml
|
|
201
|
+
app:
|
|
202
|
+
projectName: myapp
|
|
203
|
+
productName: My App
|
|
204
|
+
productVersion: 1.0.0
|
|
205
|
+
platforms: [macos]
|
|
206
|
+
homeAppId: my-home
|
|
207
|
+
|
|
208
|
+
macos:
|
|
209
|
+
bundleId: com.example.myapp
|
|
210
|
+
deploymentTarget: "12.0"
|
|
211
|
+
targetName: MyApp
|
|
212
|
+
executableName: MyApp
|
|
213
|
+
|
|
214
|
+
features: { appService: true, shell: false, devtools: false }
|
|
215
|
+
|
|
216
|
+
resources:
|
|
217
|
+
bundles:
|
|
218
|
+
- { type: lxapp, appId: my-home, path: my-home }
|
|
219
|
+
|
|
220
|
+
ui:
|
|
221
|
+
launch: { initialSurface: main }
|
|
222
|
+
surfaces:
|
|
223
|
+
- id: main
|
|
224
|
+
presentation: { kind: window }
|
|
225
|
+
content: { kind: lxapp, appId: my-home }
|
|
226
|
+
activators: []
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Run:** `lingxia dev` (build, install, launch).
|
|
230
|
+
|
|
231
|
+
Menu-bar shape, sidebar + attachPanel shape, every section of `lingxia.yaml`, surfaces/activators rules, icon paths: [`./app/project.md`](./app/project.md).
|
|
232
|
+
|
|
233
|
+
### Recipe 3 — A Rust native route called from the View
|
|
234
|
+
|
|
235
|
+
**Rust** (host crate `src/lib.rs`):
|
|
236
|
+
|
|
237
|
+
```rust
|
|
238
|
+
use std::sync::Arc;
|
|
239
|
+
|
|
240
|
+
#[derive(serde::Deserialize)]
|
|
241
|
+
struct OpenInput { title: String }
|
|
242
|
+
|
|
243
|
+
// The #[lingxia::native(...)] attribute macro generates a sibling fn
|
|
244
|
+
// `pick_document_host()` that returns the host-entry registration value.
|
|
245
|
+
// You do NOT write `pick_document_host` yourself — it is macro-generated.
|
|
246
|
+
#[lingxia::native("editor.pickDocument")]
|
|
247
|
+
async fn pick_document(
|
|
248
|
+
app: Arc<lingxia::LxApp>, // optional, FIRST when present
|
|
249
|
+
input: OpenInput, // optional JSON payload
|
|
250
|
+
cancel: lingxia::host::HostCancel, // optional, LAST when present
|
|
251
|
+
) -> lingxia::Result<String> {
|
|
252
|
+
let path = lingxia::app::state_file_for(&app, &format!("{}.md", input.title))?;
|
|
253
|
+
Ok(path.to_string_lossy().into_owned())
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
struct AppHostAddon;
|
|
257
|
+
impl lingxia::HostAddon for AppHostAddon {
|
|
258
|
+
fn install_host_apis(&self) {
|
|
259
|
+
// pick_document_host() is generated by the macro on `pick_document`.
|
|
260
|
+
lingxia::host::register_host_entry(pick_document_host());
|
|
261
|
+
}
|
|
262
|
+
fn start_services(&self) {}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
|
266
|
+
#[unsafe(no_mangle)]
|
|
267
|
+
pub extern "C" fn lingxia_register_host_addon() {
|
|
268
|
+
lingxia::register_host_addon(Box::new(AppHostAddon));
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Call from the View** (after a native build regenerates the client):
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
import { native } from '@lingxia/native'; // module form, React/Vue
|
|
276
|
+
const path = await native.editor.pickDocument({ title: 'notes' });
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Streams, channels, JS AppService extensions, facade modules (`lingxia::app/task/file/media/update`), platform FFI for Android/Harmony, generated-client paths: [`./native/development.md`](./native/development.md).
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Where does this code go?
|
|
284
|
+
|
|
285
|
+
| Job | Lives in | Surface |
|
|
286
|
+
|---|---|---|
|
|
287
|
+
| UI rendering, page state | lxapp `pages/index.{tsx,vue,html}` | View |
|
|
288
|
+
| Page lifecycle, `setData`, action handlers (JS) | lxapp `pages/index.ts` | `Page({})` Logic |
|
|
289
|
+
| Cross-page business helpers callable as `lx.X(...)` | host Rust crate | `lingxia::js` extension (needs `standard` feature) |
|
|
290
|
+
| Page-scoped native UI (file/media picker, native browser) | host Rust crate | `#[lingxia::native]` route |
|
|
291
|
+
| Background services (devtool, push, ipc) | host Rust crate | `HostAddon::start_services` |
|
|
292
|
+
| Platform integrations needing predeclaration | `lingxia.yaml` | `capabilities`, `features` |
|
|
293
|
+
| Surfaces, panels, activators (macOS) | `lingxia.yaml` | `ui` |
|
|
294
|
+
| Bundled lxapp sources | folder + `resources.bundles` | `lingxia.yaml` |
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Top pitfalls (one per layer — full lists in the sub-files)
|
|
299
|
+
|
|
300
|
+
**LxApp** — see [`./lxapp/guide.md` → Common Pitfalls](./lxapp/guide.md#common-pitfalls):
|
|
301
|
+
|
|
302
|
+
- Generating `.tsx` + `.vue` + `.html` for one page. Pick **one** view framework per project.
|
|
303
|
+
- `fetch()` to a host not in `security.network.trustedDomains` fails silently.
|
|
304
|
+
|
|
305
|
+
**Host app** — see [`./app/project.md` → Common Pitfalls](./app/project.md#common-pitfalls):
|
|
306
|
+
|
|
307
|
+
- Editing generated `app.json` / `ui.json` instead of `lingxia.yaml`. They're regenerated every build.
|
|
308
|
+
- `homeAppId` not matching any `resources.bundles[].appId` — build fails or the wrong app launches.
|
|
309
|
+
|
|
310
|
+
**Native Rust** — see [`./native/development.md`](./native/development.md):
|
|
311
|
+
|
|
312
|
+
- Importing internal crates (`lingxia_logic`, `rong`) directly. Use `lingxia::*` facades.
|
|
313
|
+
- `app: Arc<LxApp>` not first, or `HostCancel` not last, in a `#[lingxia::native]` signature.
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Pre-ship checklist
|
|
318
|
+
|
|
319
|
+
**LxApp:**
|
|
320
|
+
|
|
321
|
+
- [ ] `lxapp.json` lists every page; `appId` set; `version` bumped if shipping.
|
|
322
|
+
- [ ] `security.network.trustedDomains` covers every external host (exact host names, no scheme/port/path).
|
|
323
|
+
- [ ] One view-framework file per page.
|
|
324
|
+
- [ ] Public actions typed in `PageActions`; private helpers prefixed `_`.
|
|
325
|
+
- [ ] `lingxia dev` runs cleanly.
|
|
326
|
+
|
|
327
|
+
**Host app:**
|
|
328
|
+
|
|
329
|
+
- [ ] `lingxia.yaml` validates: every required platform section present; `homeAppId` resolvable.
|
|
330
|
+
- [ ] `features.appService` matches the embedded lxapp's logic mode.
|
|
331
|
+
- [ ] All native routes return `lingxia::Result<T>` with `Serialize` outputs.
|
|
332
|
+
- [ ] `HostAddon` registers every route and extension.
|
|
333
|
+
- [ ] FFI exports for each target platform present.
|
|
334
|
+
- [ ] `lingxia doctor` passes; `lingxia dev` boots on a real/simulated device.
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# Apple SDK Host Guide
|
|
2
|
+
|
|
3
|
+
> Scope: app-facing Apple SDK APIs for host apps on iOS and macOS.
|
|
4
|
+
> If a symbol is not documented here, it is not part of the supported host-app contract.
|
|
5
|
+
|
|
6
|
+
## Integration Paths
|
|
7
|
+
|
|
8
|
+
Use one of two paths:
|
|
9
|
+
|
|
10
|
+
| If you are... | Use |
|
|
11
|
+
|---|---|
|
|
12
|
+
| Building a LingXia host app | `Lingxia.quickStart()` + `lingxia.yaml` |
|
|
13
|
+
| Embedding LingXia into an existing native app UI | `Lingxia.initializeRuntime()` + `LxAppController` + `LxAppHostView` |
|
|
14
|
+
|
|
15
|
+
Most apps should use `Lingxia.quickStart()`. Window shape, menu bar entries,
|
|
16
|
+
sidebar items, toolbar items, titlebar items, startup behavior, and the home
|
|
17
|
+
surface are configured in `lingxia.yaml`.
|
|
18
|
+
|
|
19
|
+
For `lingxia.yaml` configuration, see [App Project Configuration](../app/project.md).
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
Use `Lingxia.quickStart()` for product apps.
|
|
24
|
+
|
|
25
|
+
```swift
|
|
26
|
+
import AppKit
|
|
27
|
+
import lingxia
|
|
28
|
+
|
|
29
|
+
class AppDelegate: NSObject, NSApplicationDelegate {
|
|
30
|
+
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
31
|
+
Lingxia.enableWebViewDebugging()
|
|
32
|
+
do {
|
|
33
|
+
try Lingxia.quickStart()
|
|
34
|
+
} catch {
|
|
35
|
+
fatalError("Lingxia startup failed: \(error)")
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func applicationShouldHandleReopen(
|
|
40
|
+
_ sender: NSApplication,
|
|
41
|
+
hasVisibleWindows flag: Bool
|
|
42
|
+
) -> Bool {
|
|
43
|
+
return !Lingxia.handleAppActivation()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func applicationShouldTerminateAfterLastWindowClosed(
|
|
47
|
+
_ sender: NSApplication
|
|
48
|
+
) -> Bool {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let app = NSApplication.shared
|
|
54
|
+
let delegate = AppDelegate()
|
|
55
|
+
app.delegate = delegate
|
|
56
|
+
app.run()
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`quickStart()` loads bundled `app.json` and `ui.json`, initializes the runtime,
|
|
60
|
+
creates the host shell, installs configured activators, and opens
|
|
61
|
+
`ui.launch.initialSurface` when `openOnLaunch` is true.
|
|
62
|
+
|
|
63
|
+
`handleAppActivation()` forwards Dock/app activation to `ui.activators` entries
|
|
64
|
+
with `kind: appActivation`.
|
|
65
|
+
|
|
66
|
+
## UI Configuration
|
|
67
|
+
|
|
68
|
+
Host UI belongs in `lingxia.yaml`, not in Swift code.
|
|
69
|
+
|
|
70
|
+
Examples:
|
|
71
|
+
|
|
72
|
+
- Hide sidebar and toolbar by omitting `sidebarItem` and `toolbarItem`.
|
|
73
|
+
- Create a normal window with a `window` surface.
|
|
74
|
+
- Create a menu bar monitor app with `statusPanel`, `menuBarItem`, and `openOnLaunch: false`.
|
|
75
|
+
- Add sidebar, toolbar, or titlebar actions with `sidebarItem`, `toolbarItem`, and `titlebarItem`.
|
|
76
|
+
|
|
77
|
+
See [macOS App UI](./project.md#macos-app-ui) for the full configuration model.
|
|
78
|
+
|
|
79
|
+
## Advanced Embedding
|
|
80
|
+
|
|
81
|
+
Use this path only when an existing native app already owns its windows, scenes,
|
|
82
|
+
navigation, split views, panels, or layout, and LingXia should be mounted into
|
|
83
|
+
one native region.
|
|
84
|
+
|
|
85
|
+
```swift
|
|
86
|
+
import AppKit
|
|
87
|
+
import lingxia
|
|
88
|
+
|
|
89
|
+
@MainActor
|
|
90
|
+
func mountLingXia(in containerView: NSView) async throws {
|
|
91
|
+
try Lingxia.initializeRuntime()
|
|
92
|
+
|
|
93
|
+
let controller = LxAppController()
|
|
94
|
+
Lingxia.activate(controller: controller)
|
|
95
|
+
|
|
96
|
+
let hostView = LxAppHostView(controller: controller)
|
|
97
|
+
hostView.translatesAutoresizingMaskIntoConstraints = false
|
|
98
|
+
containerView.addSubview(hostView)
|
|
99
|
+
|
|
100
|
+
NSLayoutConstraint.activate([
|
|
101
|
+
hostView.topAnchor.constraint(equalTo: containerView.topAnchor),
|
|
102
|
+
hostView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
|
|
103
|
+
hostView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
|
|
104
|
+
hostView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
|
|
105
|
+
])
|
|
106
|
+
|
|
107
|
+
let session = try await controller.openHomeApp()
|
|
108
|
+
try await hostView.mount(session)
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Rules:
|
|
113
|
+
|
|
114
|
+
- Use one `LxAppController` per native integration flow.
|
|
115
|
+
- Use one `LxAppHostView` per embedded visual region.
|
|
116
|
+
- The host app owns native window/layout behavior.
|
|
117
|
+
- LingXia owns lxapp session lifecycle and webview attachment.
|
|
118
|
+
|
|
119
|
+
## API Reference
|
|
120
|
+
|
|
121
|
+
### `Lingxia`
|
|
122
|
+
|
|
123
|
+
```swift
|
|
124
|
+
@MainActor
|
|
125
|
+
public enum Lingxia {
|
|
126
|
+
@discardableResult
|
|
127
|
+
public static func quickStart() throws -> LxAppShell
|
|
128
|
+
|
|
129
|
+
@available(*, deprecated, message: "Configure product UI in lingxia.yaml and use Lingxia.quickStart(). Use initializeRuntime() + LxAppController + LxAppHostView for advanced embedding.")
|
|
130
|
+
@discardableResult
|
|
131
|
+
public static func quickStart(
|
|
132
|
+
configuration: LxAppShellConfiguration
|
|
133
|
+
) throws -> LxAppShell
|
|
134
|
+
|
|
135
|
+
public static func handleAppActivation() -> Bool
|
|
136
|
+
|
|
137
|
+
@discardableResult
|
|
138
|
+
public static func initializeRuntime() throws -> LxAppRuntimeInfo
|
|
139
|
+
|
|
140
|
+
public static func activate(controller: LxAppController)
|
|
141
|
+
|
|
142
|
+
public static func enableWebViewDebugging()
|
|
143
|
+
|
|
144
|
+
public static func handleAppLink(url: URL)
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`Lingxia.initialize()` has been removed. Use `quickStart()` for product apps or
|
|
149
|
+
`initializeRuntime()` for advanced embedding.
|
|
150
|
+
|
|
151
|
+
### `LxAppController`
|
|
152
|
+
|
|
153
|
+
```swift
|
|
154
|
+
@MainActor
|
|
155
|
+
public final class LxAppController {
|
|
156
|
+
public let id: LxAppControllerID
|
|
157
|
+
public private(set) var sessions: [LxAppSessionID: LxAppSession] { get }
|
|
158
|
+
public var events: AsyncStream<LxAppControllerEvent> { get }
|
|
159
|
+
|
|
160
|
+
public init()
|
|
161
|
+
|
|
162
|
+
public func setInterceptor(
|
|
163
|
+
_ kind: LxAppControllerInterceptor,
|
|
164
|
+
handler: ((LxAppInterceptContext) async -> LxAppInterceptDecision?)?
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
public func session(forAppId appId: String) -> LxAppSession?
|
|
168
|
+
|
|
169
|
+
@discardableResult
|
|
170
|
+
public func open(_ request: LxAppOpenRequest) async throws -> LxAppSession
|
|
171
|
+
|
|
172
|
+
@discardableResult
|
|
173
|
+
public func openHomeApp(path: String = "") async throws -> LxAppSession
|
|
174
|
+
|
|
175
|
+
public func navigate(_ request: LxAppNavigateRequest)
|
|
176
|
+
|
|
177
|
+
@discardableResult
|
|
178
|
+
public func close(_ sessionId: LxAppSessionID) async -> Bool
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Supporting types:
|
|
183
|
+
|
|
184
|
+
```swift
|
|
185
|
+
public struct LxAppControllerID: Hashable, Codable, Sendable
|
|
186
|
+
public struct LxAppSessionID: Hashable, Codable, Sendable
|
|
187
|
+
public struct LxAppSession: Hashable, Codable, Sendable, Identifiable
|
|
188
|
+
public struct LxAppOpenRequest: Codable, Sendable
|
|
189
|
+
public struct LxAppNavigateRequest: Codable, Sendable
|
|
190
|
+
public enum LxAppOpenPresentation: String, Codable, Sendable, CaseIterable
|
|
191
|
+
public enum LxAppAnimation: String, Codable, Sendable, CaseIterable
|
|
192
|
+
public enum LxAppControllerEvent: Codable, Sendable
|
|
193
|
+
public enum LxAppControllerInterceptor: String, Codable, Sendable
|
|
194
|
+
public struct LxAppInterceptContext: Codable, Sendable
|
|
195
|
+
public enum LxAppInterceptDecision: Codable, Sendable
|
|
196
|
+
public struct LxAppErrorPayload: Codable, Sendable, Error, Hashable
|
|
197
|
+
public enum LxAppJSONValue: Codable, Sendable, Hashable
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Controller event semantics:
|
|
201
|
+
|
|
202
|
+
- `didOpen` carries the opened `LxAppSession`.
|
|
203
|
+
- `didClose` carries the closed `LxAppSession`.
|
|
204
|
+
- `.mountInHost(id:)` mounts the opened session into the registered `LxAppHostView`.
|
|
205
|
+
|
|
206
|
+
### `LxAppHostView`
|
|
207
|
+
|
|
208
|
+
```swift
|
|
209
|
+
#if os(iOS)
|
|
210
|
+
public typealias LxAppPlatformView = UIView
|
|
211
|
+
#else
|
|
212
|
+
public typealias LxAppPlatformView = NSView
|
|
213
|
+
#endif
|
|
214
|
+
|
|
215
|
+
@MainActor
|
|
216
|
+
public final class LxAppHostView: LxAppPlatformView {
|
|
217
|
+
public let id: LxAppHostViewID
|
|
218
|
+
public let controller: LxAppController
|
|
219
|
+
public private(set) var webView: WKWebView? { get }
|
|
220
|
+
public private(set) var mountedSession: LxAppSession? { get }
|
|
221
|
+
public private(set) var appId: String? { get }
|
|
222
|
+
public private(set) var currentPath: String? { get }
|
|
223
|
+
public private(set) var canGoBack: Bool { get }
|
|
224
|
+
|
|
225
|
+
public var events: AsyncStream<LxAppHostViewEvent> { get }
|
|
226
|
+
|
|
227
|
+
public init(controller: LxAppController, frame: CGRect = .zero)
|
|
228
|
+
public func attach(_ webView: WKWebView, appId: String?, path: String?)
|
|
229
|
+
public func mount(_ session: LxAppSession) async throws
|
|
230
|
+
public func mount(sessionId: LxAppSessionID) async throws
|
|
231
|
+
public func unmount()
|
|
232
|
+
public func dispatch(_ command: LxAppHostViewCommand)
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
SwiftUI wrapper:
|
|
237
|
+
|
|
238
|
+
```swift
|
|
239
|
+
public struct LxAppHostViewRepresentable {
|
|
240
|
+
public init(hostView: LxAppHostView, onEvent: ((LxAppHostViewEvent) -> Void)? = nil)
|
|
241
|
+
public init(controller: LxAppController, onEvent: ((LxAppHostViewEvent) -> Void)? = nil)
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Supporting types:
|
|
246
|
+
|
|
247
|
+
```swift
|
|
248
|
+
public struct LxAppHostViewID: Hashable, Codable, Sendable
|
|
249
|
+
public enum LxAppHostViewEvent: Codable, Sendable
|
|
250
|
+
public enum LxAppHostViewCommand: Codable, Sendable
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Host-view event semantics:
|
|
254
|
+
|
|
255
|
+
- `didChangeTitle`, `didUpdateCanGoBack`, `didStartLoading`, `didFinishLoading`, and `didFail` come from the mounted webview.
|
|
256
|
+
- `dispatch(.triggerCapsuleAction(...))` forwards that action into the runtime for the mounted app session.
|
|
257
|
+
|
|
258
|
+
### `LxAppRuntime`
|
|
259
|
+
|
|
260
|
+
```swift
|
|
261
|
+
@MainActor
|
|
262
|
+
public final class LxAppRuntime {
|
|
263
|
+
public static let shared: LxAppRuntime
|
|
264
|
+
public private(set) var info: LxAppRuntimeInfo? { get }
|
|
265
|
+
public func initialize() throws -> LxAppRuntimeInfo
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
public struct LxAppRuntimeInfo: Codable, Sendable, Hashable {
|
|
269
|
+
public let homeAppId: String
|
|
270
|
+
public let capabilities: LxAppCapabilities
|
|
271
|
+
public let dataPath: String
|
|
272
|
+
public let cachesPath: String
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Most apps should not call `LxAppRuntime.shared.initialize()` directly. Use
|
|
277
|
+
`Lingxia.quickStart()` or `Lingxia.initializeRuntime()`.
|
|
278
|
+
|
|
279
|
+
## Legacy Shell Override
|
|
280
|
+
|
|
281
|
+
`LxAppShellConfiguration`, `LxAppShell`, `LxAppSidebarMode`, and
|
|
282
|
+
`LxAppToolbarMode` remain available for migration and internal shell work.
|
|
283
|
+
|
|
284
|
+
New host apps should not use them to configure product UI. Put product UI in
|
|
285
|
+
`lingxia.yaml` instead.
|
|
286
|
+
|
|
287
|
+
Legacy examples:
|
|
288
|
+
|
|
289
|
+
```swift
|
|
290
|
+
var config = LxAppShellConfiguration()
|
|
291
|
+
config.sidebar = .hidden
|
|
292
|
+
config.toolbar = .hidden
|
|
293
|
+
_ = try Lingxia.quickStart(configuration: config)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Equivalent product configuration:
|
|
297
|
+
|
|
298
|
+
```yaml
|
|
299
|
+
ui:
|
|
300
|
+
launch:
|
|
301
|
+
initialSurface: main
|
|
302
|
+
surfaces:
|
|
303
|
+
- id: main
|
|
304
|
+
presentation:
|
|
305
|
+
style: window
|
|
306
|
+
content:
|
|
307
|
+
kind: lxapp
|
|
308
|
+
appId: myapp
|
|
309
|
+
activators: []
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Prefer the YAML form for new apps.
|