236 lines
11 KiB
Markdown
236 lines
11 KiB
Markdown
Your First App | Documentation
|
||
|
||
|
||
|
||
-
|
||
|
||
-
|
||
|
||
|
||
|
||
-
|
||
|
||
-
|
||
|
||
-
|
||
|
||
-
|
||
|
||
|
||
|
||
|
||
Skip to contentDocumentationSearch⌘CtrlK Main Navigation PortalThemeMenuReturn to top Sidebar Navigation
|
||
## Get Started
|
||
Overview
|
||
### Quickstart
|
||
Sign inHardwareInstall Node.js & npmInstall Even Hub toolingYour First AppTemplatesArchitecture
|
||
## Build
|
||
Page LifecycleDisplay & UI SystemUI/UX Design GuidelinesDevice APIsContextual MenuNetworkingBackground & Lifecycle
|
||
## Test
|
||
SimulatorLocal TestingPrivate TestingBeta Testing
|
||
## Ship
|
||
Packaging & DeploymentApp Submission & QA Guidelines
|
||
## Reference
|
||
GlossaryCLIVersioning PolicyChangelogFAQ
|
||
## AI Tooling
|
||
Claude CodeOn this pageLast updated: 2026-07-04The end state of this page:
|
||
- A Vite + TypeScript project with the SDK wired in
|
||
- A page rendering on the simulator, with a click handler
|
||
- An app.json manifest ready for packaging
|
||
- The same app running on real Even G2 glasses via QR sideloadPrerequisites: Install Node.js & npm, then install only the global CLI and simulator from Install Even Hub tooling. Install the SDK after creating the project in Route A, or let the template dependencies install it in Route B. For the real-hardware half of step 6, also finish Hardware.
|
||
## 1. Scaffold the project
|
||
Two routes - pick one.
|
||
### Route A - Create a Vite app by hand
|
||
Create a Vite + TypeScript project, install the SDK, then generate the Even Hub manifest:bash
|
||
```
|
||
npm create vite@latest my-first-app -- --template vanilla-ts
|
||
cd my-first-app
|
||
npm install
|
||
npm install @evenrealities/even_hub_sdk@latest
|
||
evenhub init
|
||
```
|
||
evenhub init creates app.json; it does not scaffold the Vite project.
|
||
### Route B - Template repo or AI tooling
|
||
Clone a ready-to-run starter from the evenhub-templates repo:bash
|
||
```
|
||
npx degit even-realities/evenhub-templates/minimal my-first-app
|
||
cd my-first-app
|
||
npm install
|
||
```
|
||
The templates already include the SDK; npm install installs the template's dependencies.Templates has the full list - minimal, text-heavy, asr, image. If you live in Claude Code, the template skill picks and scaffolds one for you.
|
||
## 2. Project structure
|
||
A minimal Even Hub app looks like this:
|
||
```
|
||
my-first-app/
|
||
├── src/
|
||
│ └── main.ts ← your app's entry point
|
||
├── public/
|
||
│ └── icon.png ← greyscale app icon (24×24)
|
||
├── index.html ← Vite HTML entry - keep as-is
|
||
├── package.json
|
||
├── vite.config.ts ← Vite config - keep as-is for now
|
||
├── tsconfig.json
|
||
└── app.json ← Even Hub manifest (required for packaging)
|
||
```
|
||
The only Even-specific file is app.json (the manifest). Everything else is standard Vite.
|
||
## 3. Write main.ts
|
||
Open src/main.ts and replace its contents with:typescript
|
||
```
|
||
import {
|
||
waitForEvenAppBridge,
|
||
TextContainerProperty,
|
||
TextContainerUpgrade,
|
||
CreateStartUpPageContainer,
|
||
OsEventTypeList,
|
||
} from '@evenrealities/even_hub_sdk'
|
||
|
||
// Wait for the bridge to be ready before doing anything else.
|
||
// In the simulator this resolves immediately; on hardware it waits
|
||
// for the WebView to initialize the SDK bridge.
|
||
const bridge = await waitForEvenAppBridge()
|
||
|
||
// Build a single text container that fills the visible canvas (576×288).
|
||
const mainText = new TextContainerProperty({
|
||
xPosition: 0,
|
||
yPosition: 0,
|
||
width: 576,
|
||
height: 288,
|
||
borderWidth: 0,
|
||
borderColor: 5,
|
||
paddingLength: 4,
|
||
containerID: 1,
|
||
containerName: 'main',
|
||
content: 'Hello from G2!\n\nTap to count: 0\nDouble-tap to exit',
|
||
isEventCapture: 1, // ← receive click events on this container
|
||
})
|
||
|
||
// Render the page. `result` is 0 on success.
|
||
const result = await bridge.createStartUpPageContainer(
|
||
new CreateStartUpPageContainer({
|
||
containerTotalNum: 1,
|
||
textObject: [mainText],
|
||
}),
|
||
)
|
||
|
||
if (result !== 0) {
|
||
console.error('createStartUpPageContainer failed:', result)
|
||
// 1 = invalid params, 2 = oversize, 3 = out of memory
|
||
}
|
||
|
||
// Single event subscription - all OS events arrive through onEvenHubEvent.
|
||
// Inspect event.textEvent / event.listEvent / event.sysEvent to route by source.
|
||
let count = 0
|
||
|
||
bridge.onEvenHubEvent((event) => {
|
||
const textEvent = event.textEvent
|
||
if (!textEvent || textEvent.containerID !== 1) return
|
||
|
||
switch (textEvent.eventType) {
|
||
case OsEventTypeList.CLICK_EVENT:
|
||
case undefined: // SDK normalizes 0 to undefined in some cases
|
||
count += 1
|
||
bridge.textContainerUpgrade(new TextContainerUpgrade({
|
||
containerID: 1,
|
||
containerName: 'main',
|
||
content: `Hello from G2!\n\nTap to count: ${count}\nDouble-tap to exit`,
|
||
}))
|
||
break
|
||
|
||
case OsEventTypeList.DOUBLE_CLICK_EVENT:
|
||
// Mode 1 shows the system exit-confirmation dialog -
|
||
// required on the root page; silent exit (mode 0) is rejected in QA.
|
||
bridge.shutDownPageContainer(1)
|
||
break
|
||
}
|
||
})
|
||
```
|
||
Worth knowing as you read it:
|
||
- waitForEvenAppBridge() - always await this before calling any other SDK method. Calling SDK methods before the bridge is ready silently no-ops.
|
||
- isEventCapture: 1 - required if you want a container to receive input events. The default is 0 (display-only).
|
||
- onEvenHubEvent - the single subscription point for OS-to-app events. The payload's textEvent / listEvent / sysEvent fields tell you which kind fired; switch on eventType to route. See Device APIs § Handling Events.
|
||
- textContainerUpgrade - the flicker-free way to update text. Pass a new TextContainerUpgrade({...}). Reserve rebuildPageContainer for layout changes (adding/removing containers).
|
||
- shutDownPageContainer(1) - mode 1 shows the system exit-confirmation dialog and is required on the root page. Apps that exit silently with mode 0 (or a custom exit UI) get rejected in QA. See Page Lifecycle.
|
||
- Result codes - createStartUpPageContainer returns an int; everything else returns a boolean or a status string. See Page Lifecycle.
|
||
## 4. Write the manifest (app.json)
|
||
The manifest lives at the project root and is required for packaging. The minimum:json
|
||
```
|
||
{
|
||
"package_id": "com.exampleco.exampleapp",
|
||
"name": "My First App",
|
||
"version": "0.1.0",
|
||
"edition": "202601",
|
||
"min_sdk_version": "0.0.12",
|
||
"entrypoint": "index.html",
|
||
"permissions": [],
|
||
"supported_languages": ["en"]
|
||
}
|
||
```
|
||
Field notes:
|
||
- package_id - globally unique. Convention: reverse-DNS of your handle.
|
||
- edition - the platform contract version. Use "202601" until told otherwise.
|
||
- min_sdk_version - match the SDK you installed. Check with npm list @evenrealities/even_hub_sdk.
|
||
- min_app_version - omitted here on purpose. The CLI derives it from your SDK version at pack time; you only declare it to pin a stricter floor. See Auto-deriving min_app_version.
|
||
- permissions - empty for this app. To make external fetch() calls, add a network permission with a whitelist: {"name": "network", "desc": "...", "whitelist": ["https://api.example.com"]}. Microphone access uses {"name": "g2-microphone", "desc": "..."}. See Packaging § Permissions Format.
|
||
## 5. Run in the simulator
|
||
bash
|
||
```
|
||
# Terminal 1 - Vite dev server
|
||
npm run dev
|
||
|
||
# Terminal 2 - Simulator (point it at the dev URL)
|
||
evenhub-simulator http://localhost:5173
|
||
```
|
||
The simulator window opens with a 576×288 green canvas. You should see:
|
||
```
|
||
Hello from G2!
|
||
|
||
Tap to count: 0
|
||
Double-tap to exit
|
||
```
|
||
Click anywhere in the canvas - the counter increments. Double-click - the system exit-confirmation dialog appears. Edit main.ts; Vite hot-reloads; the simulator refreshes.
|
||
### Common simulator errors
|
||
|
|
||
| | Symptom | Cause | Fix
|
||
| | Blank green canvas, no text | waitForEvenAppBridge() not awaited | Check that the await is present and main.ts runs at module top-level
|
||
| | Text shows but click does nothing | Container has isEventCapture: 0, or click event type is normalized | Set isEventCapture: 1 and handle both OsEventTypeList.CLICK_EVENT and undefined inside onEvenHubEvent; don't listen for a DOM click
|
||
| | Cannot find module @evenrealities/even_hub_sdk | SDK dependency missing | Route A: run npm install @evenrealities/even_hub_sdk@latest; templates: run npm install
|
||
| | Counter increments but text disappears | textContainerUpgrade was called with a wrong containerID / containerName | Both must match the values you used in createStartUpPageContainer
|
||
## 6. Run on real hardware
|
||
Prerequisites: the phone app has the Scan QR button visible (see Enable Developer Mode), and the phone can reach your laptop's LAN IP (see Network & Firewall Setup).Find your LAN IP, then generate a QR pointing at the dev server:bash
|
||
```
|
||
# macOS / Linux
|
||
ipconfig getifaddr en0 # macOS Wi-Fi (use en1 if blank)
|
||
hostname -I | awk '{print $1}' # Linux
|
||
|
||
# Windows
|
||
ipconfig | findstr /i "IPv4"
|
||
```
|
||
Then:bash
|
||
```
|
||
evenhub qr --url "http://<YOUR-LAN-IP>:5173"
|
||
```
|
||
A QR code prints in the terminal. Tap Scan QR in the phone app and aim it at the terminal. The glasses render your app within a second. Hot-reload still works - edits to main.ts reflow on the glasses without re-scanning.Tap a temple to fire the click handler - the counter increments on the glasses.Known issue: manually triggered Link jumps in Dev Preview mode may currently fail. Fall back to re-scanning the QR. Tracked at Enable Developer Mode → Known issue.
|
||
### Common hardware errors
|
||
|
|
||
| | Symptom | Likely cause | Fix
|
||
| | Phone says "Couldn't connect" after scan | Firewall / AP isolation | See Network & Firewall Setup
|
||
| | Glasses show last app, not yours | Cached app container | Re-scan the QR (forces reload)
|
||
| | App loads but click doesn't fire | Container without isEventCapture: 1 | Same as the simulator section
|
||
| | Works in sim, blank on glasses | App tried to render before bridge ready | Confirm await waitForEvenAppBridge() is there
|
||
## 7. What you have now
|
||
A working repo with:
|
||
- A Vite + TypeScript project
|
||
- A single page rendering on the glasses
|
||
- A click handler that mutates state and updates the display
|
||
- An app.json manifest ready for packagingThat's the minimum surface area of every Even Hub app. Multi-page navigation, audio capture, IMU, networking - all of it composes from these same primitives.
|
||
## Where to go next
|
||
Branch into whichever direction you need: |
|
||
| | You want to… | Read
|
||
| | Build a multi-page UI with lists and detail views | Display & UI System
|
||
| | Handle the R1 ring, IMU, swipes, double-taps | Device APIs
|
||
| | Use the microphone or local storage | Device APIs
|
||
| | Hit an external API from your app | Networking
|
||
| | Understand what happens when the phone locks | Background & Lifecycle
|
||
| | Lay out content for the green canvas | Design Guidelines
|
||
| | Package, version, and ship | Packaging & Deployment → App Submission & QA
|
||
| | Pick the right testing mode for what you're about to do | Testing ModesPagerPrevious pageInstall Even Hub toolingNext pageTemplates |