add base-apps with manifests, layout system, and testing documentation

- Rename test-apps to base-apps with proper manifest.json for each app
- Add is_system_app flag to app discovery and Lua API
- Fix icon path resolution for /system/icons/ paths
- Add layout.lua and layout.rcss for reusable UI components
- Update home screen to dynamically load all apps from manifests
- Update all app RML files to use layout components
- Comprehensive testing framework documentation with JSON action format
- Add tests/ directory structure for automated UI testing
This commit is contained in:
2026-01-20 09:14:05 +01:00
parent 5de087e8e0
commit 1f91d7508e
101 changed files with 13103 additions and 966 deletions

386
docs/BASE_APPS.md Normal file
View File

@@ -0,0 +1,386 @@
# Base Apps Migration Plan
Convert system apps from embedded assets to standalone `.mosis` packages in `base-apps/`.
---
## Current State
### Apps in `src/main/assets/apps/`
| App | Files | Icon |
|-----|-------|------|
| home | home.rml, home.lua, lock.rml | home.tga |
| dialer | dialer.rml, calling.rml | phone.tga |
| contacts | contacts.rml | contacts.tga |
| messages | messages.rml | message.tga |
| settings | settings.rml | settings.tga |
| browser | browser.rml | browser.tga |
| camera | camera.rml | camera.tga |
| store | store.rml | store.tga |
| music | music.rml | music.tga |
### Shared Dependencies
All apps currently reference shared resources via relative paths:
```
../../ui/html.rcss - Base HTML element styles
../../ui/theme.rcss - Color scheme, typography
../../ui/components.rcss - Buttons, cards, lists
../../scripts/navigation.lua - Screen navigation system
../../icons/*.tga - App and UI icons
```
### Current Navigation Model
Apps are not truly separate - they're screens loaded via a central navigation system:
```lua
-- navigation.lua
local screens = {
home = "apps/home/home.rml",
dialer = "apps/dialer/dialer.rml",
-- etc.
}
mosis.loadDocument(screens[name])
```
---
## Target State
### Directory Structure
```
base-apps/
├── com.mosis.home/
│ ├── manifest.json
│ ├── main.rml
│ ├── home.lua
│ ├── lock.rml
│ ├── icon.tga
│ └── styles.rcss
├── com.mosis.dialer/
│ ├── manifest.json
│ ├── main.rml (dialer.rml)
│ ├── calling.rml
│ ├── icon.tga
│ └── styles.rcss
├── com.mosis.contacts/
├── com.mosis.messages/
├── com.mosis.settings/
├── com.mosis.browser/
├── com.mosis.camera/
├── com.mosis.store/
├── com.mosis.music/
└── package.bat
```
### Package IDs
| App | Package ID |
|-----|------------|
| Home | com.mosis.home |
| Dialer | com.mosis.dialer |
| Contacts | com.mosis.contacts |
| Messages | com.mosis.messages |
| Settings | com.mosis.settings |
| Browser | com.mosis.browser |
| Camera | com.mosis.camera |
| Store | com.mosis.store |
| Music | com.mosis.music |
---
## Key Decisions
### 1. Shared Resources Strategy
**Decision**: System asset path (`/system/`)
Apps reference shared resources via a system path that the kernel provides:
```html
<link type="text/rcss" href="/system/ui/theme.rcss"/>
<script src="/system/scripts/navigation.lua"></script>
```
The kernel maps `/system/` to `src/main/assets/` (or bundled assets on Android).
**Rationale**:
- Avoids duplicating ~50KB of styles/icons per app
- Single source of truth for theming
- Apps can still have local overrides
### 2. Navigation Model
**Decision**: Hybrid approach
- **System apps** (home, dialer, contacts, etc.) use shared navigation for seamless transitions
- **Third-party apps** launch via `mosis.apps.launch()` with isolated sandbox
System apps get `is_system_app = true` flag:
- Can access `/system/` resources
- Share navigation state
- Can load each other's screens directly
### 3. Home App Special Status
The home app is the launcher/shell:
- Always running in background
- Provides dock and app grid
- Launches other apps via `mosis.apps.launch(package_id)`
- Cannot be uninstalled
---
## Implementation Tasks
### Phase 1: Infrastructure
- [ ] **1.1** Rename `test-apps/` to `base-apps/`
- [ ] **1.2** Move `com.mosis.sandbox-test` to `base-apps/`
- [ ] **1.3** Implement `/system/` path mapping in kernel
- Desktop: Map to `assets/` directory
- Android: Map to bundled assets
- [ ] **1.4** Add `is_system_app` flag to manifest schema
- [ ] **1.5** Update designer `--test-apps` flag to `--apps` for consistency
### Phase 2: Convert Apps
For each app:
- [ ] **2.1** Create package directory in `base-apps/com.mosis.{name}/`
- [ ] **2.2** Create `manifest.json` with proper metadata
- [ ] **2.3** Copy RML files, rename entry to `main.rml`
- [ ] **2.4** Copy app-specific Lua scripts
- [ ] **2.5** Copy icon from `icons/{name}.tga` to `icon.tga`
- [ ] **2.6** Update resource paths to use `/system/` prefix
- [ ] **2.7** Create `styles.rcss` for app-specific styles (extract from inline)
### Phase 3: Update Home App
- [ ] **3.1** Update home.lua to use `mosis.apps.launch()` for launching
- [ ] **3.2** Keep shared navigation for system app transitions
- [ ] **3.3** Display all installed apps (base + third-party) in grid
- [ ] **3.4** Update dock icons to launch via package ID
### Phase 4: Cleanup
- [ ] **4.1** Remove old `src/main/assets/apps/` directory
- [ ] **4.2** Update build scripts to package base-apps
- [ ] **4.3** Update Android to include base-apps packages
- [ ] **4.4** Update documentation
---
## Manifest Examples
### System App (Dialer)
```json
{
"id": "com.mosis.dialer",
"name": "Phone",
"version": "1.0.0",
"version_code": 1,
"entry": "main.rml",
"icon": "icon.tga",
"description": "Make and receive phone calls",
"developer": {
"name": "Mosis Team",
"email": "dev@mosis.dev"
},
"permissions": [
"contacts.read"
],
"is_system_app": true,
"min_api_version": 1
}
```
### Home App (Launcher)
```json
{
"id": "com.mosis.home",
"name": "Home",
"version": "1.0.0",
"version_code": 1,
"entry": "main.rml",
"icon": "icon.tga",
"description": "Mosis Home Launcher",
"developer": {
"name": "Mosis Team",
"email": "dev@mosis.dev"
},
"permissions": [
"system.launcher"
],
"is_system_app": true,
"is_launcher": true,
"min_api_version": 1
}
```
---
## Resource Path Mapping
### Before (Relative)
```html
<link type="text/rcss" href="../../ui/theme.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<img src="../../icons/phone.tga"/>
```
### After (System Path)
```html
<link type="text/rcss" href="/system/ui/theme.rcss"/>
<script src="/system/scripts/navigation.lua"></script>
<img src="/system/icons/phone.tga"/>
```
---
## Testing Checklist
- [ ] Designer loads home from `base-apps/com.mosis.home/`
- [ ] All base apps appear in home screen grid
- [ ] Tapping app icon launches the app
- [ ] Navigation between system apps works (back button, dock)
- [ ] Third-party apps (sandbox-test) launch in isolated sandbox
- [ ] `/system/` paths resolve correctly
- [ ] Android build includes all base-apps packages
- [ ] Hot-reload still works for development
---
## Open Questions
1. **Lock screen**: Part of home app or separate `com.mosis.lockscreen`?
2. **Calling screen**: Part of dialer or system-level overlay?
3. **Notifications**: System-level or per-app handling?
4. **Settings persistence**: Shared settings service or per-app?
---
## File Changes Summary
| Action | Path |
|--------|------|
| Rename | `test-apps/``base-apps/` |
| Create | `base-apps/com.mosis.{home,dialer,...}/` |
| Create | Each app's `manifest.json` |
| Move | App RML/Lua files to package dirs |
| Update | RML paths from `../../` to `/system/` |
| Delete | `src/main/assets/apps/` (after migration) |
| Update | Designer command line args |
| Update | Android asset bundling |
---
## Reusable Layout Components
New standardized UI components for consistent app layouts. Located in `src/main/assets/ui/layout.rcss` and `src/main/assets/scripts/layout.lua`.
### Components
| Component | CSS Class | Description |
|-----------|-----------|-------------|
| Status Bar | `.system-status-bar` | Top bar with time, wifi, signal, battery |
| App Bar | `.app-bar` | Title bar with back button and actions |
| System Nav | `.system-nav-bar` | Bottom bar with back, home, recent buttons |
| App Screen | `.app-screen` | Standard app screen container |
| App Content | `.app-content` | Scrollable content area |
### Standard App Structure
```html
<body class="app-screen" onload="initLayout(document)">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
<div class="system-status-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- App Bar -->
<div class="app-bar">
<div class="app-bar-back" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
<span class="app-bar-title">App Title</span>
<div class="app-bar-actions">
<div class="app-bar-action">
<img src="../../icons/search.tga"/>
</div>
</div>
</div>
<!-- Content Area -->
<div class="app-content with-nav">
<!-- App content here -->
</div>
<!-- System Navigation Bar -->
<div class="system-nav-bar">
<div class="system-nav-btn" onclick="onBackPressed()">
<img src="../../icons/back.tga"/>
</div>
<div class="system-nav-home" onclick="onHomePressed()"></div>
<div class="system-nav-btn" onclick="onRecentPressed()">
<img src="../../icons/menu.tga"/>
</div>
</div>
</body>
```
### Required Includes
```html
<head>
<link type="text/rcss" href="../../ui/html.rcss"/>
<link type="text/rcss" href="../../ui/theme.rcss"/>
<link type="text/rcss" href="../../ui/components.rcss"/>
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
</head>
```
### CSS Modifiers
| Class | Description |
|-------|-------------|
| `.app-content.with-nav` | Adds bottom padding for system nav bar |
| `.app-content.with-dock` | Adds bottom padding for dock |
| `.app-bar.transparent` | Transparent app bar background |
| `.system-status-bar.bg-surface` | Solid surface color background |
### Lua Functions (layout.lua)
| Function | Description |
|----------|-------------|
| `initLayout(doc)` | Initialize all layout components |
| `initStatusBar(doc)` | Setup status bar time updates |
| `onBackPressed()` | Handle back button (goes back or home) |
| `onHomePressed()` | Handle home button |
| `onRecentPressed()` | Handle recent apps button |
### Updated Apps
- [x] Settings - Uses full layout with system nav bar
- [x] Contacts - Uses status bar and app bar (has own bottom tabs)
- [ ] Other apps - Still using old structure
---
*Created: 2025-01-19*
*Updated: 2025-01-20*

View File

@@ -1,26 +1,239 @@
# Automated Testing Framework
The designer-test (`designer-test/`) provides automated UI testing.
The Mosis Designer supports two testing approaches:
1. **JSON Action Playback** - Built into the designer for scripted UI testing
2. **C++ Test Framework** - External test runner in `designer-test/` for programmatic testing
## Test Architecture
---
1. **WindowController**: Finds designer window, sends mouse/keyboard events via Windows SendInput API
2. **HierarchyReader**: Parses UI hierarchy JSON to find elements by ID/class (with retry logic and exponential backoff)
3. **LogParser**: Monitors log file for navigation events
4. **TestRunner**: Orchestrates test execution, reports results
5. **UIInspector**: Dumps UI hierarchy with atomic writes (temp file + rename pattern)
## JSON Action Playback
## Key Implementation Details
The designer can record and playback UI interactions using JSON files. This is useful for:
- Automated screenshot capture
- Regression testing
- Demo recordings
- UI verification
- **Path Normalization**: RmlUi uses `|` instead of `:` in Windows paths (e.g., `D|\Dev\...`). The UIInspector normalizes paths for correct document matching.
- **Atomic File Writes**: Hierarchy files are written to `.tmp` then renamed to prevent partial reads.
- **Retry with Backoff**: HierarchyReader retries up to 10 times with exponential backoff (30ms base) and validates JSON completeness.
- **Dynamic Back Button**: `GoHome()` finds back buttons from hierarchy by class (`app-bar-nav` or `browser-nav-btn`) instead of fixed coordinates.
### Command-Line Options
## Writing Tests
```bash
# Run with action playback
mosis-designer.exe --simulator --test-apps base-apps \
--playback test_navigation.json \
--screenshot-after result.png \
--hierarchy hierarchy.json
```
| Flag | Description |
|------|-------------|
| `--playback <file>` | JSON file containing actions to replay |
| `--screenshot-after <file>` | Save screenshot after playback completes |
| `--hierarchy <file>` | Dump UI hierarchy to JSON after playback |
### Recording Actions
Press `R` in the designer to start/stop recording. Actions are saved to `recorded_actions.json`.
### Action Types
#### Tap
Single tap at coordinates.
```json
{
"type": "tap",
"x": 270,
"y": 480,
"timestamp": 1000
}
```
#### Swipe
Drag from one point to another.
```json
{
"type": "swipe",
"x1": 270,
"y1": 600,
"x2": 270,
"y2": 200,
"duration": 300,
"timestamp": 2000
}
```
#### Long Press
Press and hold at coordinates.
```json
{
"type": "long_press",
"x": 270,
"y": 480,
"duration": 800,
"timestamp": 3000
}
```
#### Button
System button press (back, home, recents).
```json
{
"type": "button",
"button": "back",
"timestamp": 4000
}
```
#### Wait
Pause execution for a duration.
```json
{
"type": "wait",
"duration": 1500,
"timestamp": 5000
}
```
#### Key
Raw keyboard input (for special keys).
```json
{
"type": "key",
"key_code": 256,
"pressed": true,
"timestamp": 6000
}
```
Common key codes:
- `256` - Escape (Back)
- `301` - F12 (Debugger)
- `294` - F5 (Reload)
### Complete Test File Example
```json
{
"name": "Navigate to Messages",
"description": "Test navigation from home to Messages app",
"screen_width": 540,
"screen_height": 960,
"initial_screen": "",
"actions": [
{
"type": "wait",
"duration": 500,
"timestamp": 0
},
{
"type": "tap",
"x": 207,
"y": 220,
"timestamp": 500
},
{
"type": "wait",
"duration": 1500,
"timestamp": 600
}
]
}
```
### Finding Tap Coordinates
Use the `--hierarchy` flag to dump UI element bounds:
```bash
mosis-designer.exe --simulator --test-apps base-apps \
--playback wait_only.json \
--hierarchy hierarchy.json
```
The hierarchy JSON contains element bounds:
```json
{
"bounds": {
"x": 171.5,
"y": 183.2,
"width": 72.0,
"height": 72.0
},
"classes": ["app-icon-image"],
"tag": "div"
}
```
Calculate tap center: `x = bounds.x + width/2`, `y = bounds.y + height/2`
### Home Screen Grid Layout
For the default 540x960 resolution with 4-column grid:
| Row | Y Range | Center Y |
|-----|---------|----------|
| 1 | 64-167 | ~115 |
| 2 | 183-286 | ~220 |
| 3 | 302-405 | ~350 |
| Column | X Range | Center X |
|--------|---------|----------|
| 1 | 20-145 | ~82 |
| 2 | 145-270 | ~207 |
| 3 | 270-395 | ~332 |
| 4 | 395-520 | ~457 |
Dock items are at Y ~910.
---
## Asset Path Requirements
When testing apps in `base-apps/`, ensure shared assets are accessible. Apps use relative paths like `../../ui/html.rcss`.
From `base-apps/com.mosis.app/`, the path `../../` resolves to `MosisService/`.
Required directories at `MosisService/` root:
```
MosisService/
├── ui/ # html.rcss, theme.rcss, components.rcss, layout.rcss
├── scripts/ # navigation.lua, layout.lua
├── icons/ # Shared icon assets
└── base-apps/ # Test apps
```
Copy from `src/main/assets/`:
```bash
cp -r src/main/assets/ui .
cp -r src/main/assets/scripts .
cp -r src/main/assets/icons .
```
---
## C++ Test Framework
The `designer-test/` project provides programmatic testing with element finding.
### Architecture
| Component | Purpose |
|-----------|---------|
| WindowController | Sends mouse/keyboard via Windows SendInput API |
| HierarchyReader | Parses UI hierarchy JSON, finds elements by ID/class |
| LogParser | Monitors log file for navigation events |
| TestRunner | Orchestrates execution, reports results |
| UIInspector | Dumps hierarchy with atomic writes |
### Key Implementation Details
- **Path Normalization**: RmlUi uses `|` instead of `:` in Windows paths
- **Atomic File Writes**: Hierarchy written to `.tmp` then renamed
- **Retry with Backoff**: HierarchyReader retries up to 10 times with exponential backoff
- **Dynamic Back Button**: Finds back buttons by class instead of fixed coordinates
### Writing C++ Tests
```cpp
// Find element by ID and click it
bool ClickById(TestContext& ctx, const std::string& id) {
ctx.hierarchy.Reload();
auto element = ctx.hierarchy.FindById(id);
@@ -28,12 +241,11 @@ bool ClickById(TestContext& ctx, const std::string& id) {
int x = element->bounds.centerX();
int y = element->bounds.centerY();
ScaleToPhysical(ctx, x, y); // Convert logical to physical coords
ScaleToPhysical(ctx, x, y);
ctx.window.SendClick(x, y);
return true;
}
// Test example
bool TestNavigateToDialer(TestContext& ctx) {
GoHome(ctx);
ctx.log.Clear();
@@ -46,9 +258,9 @@ bool TestNavigateToDialer(TestContext& ctx) {
}
```
## Test Output
### Test Output
Tests produce JSON results at `test_results.json`:
Results saved to `test_results.json`:
```json
{
"name": "Mosis Designer UI Tests",
@@ -58,3 +270,91 @@ Tests produce JSON results at `test_results.json`:
]
}
```
---
## Example Test Suite
Test files for common scenarios:
### test_home_only.json
```json
{
"name": "Home Screenshot",
"description": "Capture home screen",
"screen_width": 540,
"screen_height": 960,
"actions": [
{"type": "wait", "duration": 500, "timestamp": 0}
]
}
```
### test_settings.json
```json
{
"name": "Navigate to Settings",
"description": "Tap Settings app (Row 3, Col 1)",
"screen_width": 540,
"screen_height": 960,
"actions": [
{"type": "wait", "duration": 500, "timestamp": 0},
{"type": "tap", "x": 82, "y": 350, "timestamp": 500},
{"type": "wait", "duration": 1500, "timestamp": 600}
]
}
```
### test_browser.json
```json
{
"name": "Navigate to Browser",
"description": "Tap Browser in dock (Col 4)",
"screen_width": 540,
"screen_height": 960,
"actions": [
{"type": "wait", "duration": 500, "timestamp": 0},
{"type": "tap", "x": 445, "y": 910, "timestamp": 500},
{"type": "wait", "duration": 1500, "timestamp": 600}
]
}
```
### Running Tests
```bash
# Single test with screenshot
mosis-designer.exe --simulator --test-apps base-apps \
--playback test_settings.json \
--screenshot-after screenshot_settings.png
# Test with hierarchy dump for debugging
mosis-designer.exe --simulator --test-apps base-apps \
--playback test_home_only.json \
--screenshot-after home.png \
--hierarchy hierarchy.json
```
---
## Troubleshooting
### Tap not registering
- Check coordinates against hierarchy bounds
- Ensure tap is within the clickable element (e.g., `app-icon-image`, not `app-icon-label`)
- Use hierarchy dump to verify element positions
### Stylesheet errors
```
[ERROR] Failed to load style sheet D|/Dev/.../ui/html.rcss
```
- Copy shared assets to MosisService root (see Asset Path Requirements)
### App not loading
- Check console for `launchApp` call
- Verify manifest.json exists in app directory
- Check entry file path in manifest
### Screenshot shows wrong screen
- Increase wait duration after tap
- Verify navigation completed in console output