@even-toolkit/create-even-app 1.1.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.
Files changed (126) hide show
  1. package/index.js +159 -0
  2. package/package.json +28 -0
  3. package/templates/chat/README.md +27 -0
  4. package/templates/chat/index.html +12 -0
  5. package/templates/chat/package.json +34 -0
  6. package/templates/chat/src/App.tsx +61 -0
  7. package/templates/chat/src/app.css +54 -0
  8. package/templates/chat/src/contexts/ChatContext.tsx +99 -0
  9. package/templates/chat/src/glass/AppGlasses.tsx +70 -0
  10. package/templates/chat/src/glass/screens/home.ts +24 -0
  11. package/templates/chat/src/glass/selectors.ts +9 -0
  12. package/templates/chat/src/glass/shared.ts +8 -0
  13. package/templates/chat/src/glass/splash.ts +25 -0
  14. package/templates/chat/src/main.tsx +13 -0
  15. package/templates/chat/src/screens/ChatScreen.tsx +69 -0
  16. package/templates/chat/src/screens/Settings.tsx +88 -0
  17. package/templates/chat/src/types.ts +13 -0
  18. package/templates/chat/src/vite-env.d.ts +1 -0
  19. package/templates/chat/template.json +7 -0
  20. package/templates/chat/tsconfig.json +20 -0
  21. package/templates/chat/tsconfig.node.json +13 -0
  22. package/templates/chat/vite.config.ts +12 -0
  23. package/templates/dashboard/README.md +17 -0
  24. package/templates/dashboard/index.html +12 -0
  25. package/templates/dashboard/package.json +34 -0
  26. package/templates/dashboard/src/App.tsx +27 -0
  27. package/templates/dashboard/src/app.css +54 -0
  28. package/templates/dashboard/src/glass/AppGlasses.tsx +53 -0
  29. package/templates/dashboard/src/glass/screens/home.ts +23 -0
  30. package/templates/dashboard/src/glass/selectors.ts +9 -0
  31. package/templates/dashboard/src/glass/shared.ts +8 -0
  32. package/templates/dashboard/src/glass/splash.ts +22 -0
  33. package/templates/dashboard/src/main.tsx +13 -0
  34. package/templates/dashboard/src/screens/ChartsScreen.tsx +99 -0
  35. package/templates/dashboard/src/screens/OverviewScreen.tsx +102 -0
  36. package/templates/dashboard/src/screens/SettingsScreen.tsx +60 -0
  37. package/templates/dashboard/src/vite-env.d.ts +1 -0
  38. package/templates/dashboard/template.json +7 -0
  39. package/templates/dashboard/tsconfig.json +20 -0
  40. package/templates/dashboard/tsconfig.node.json +13 -0
  41. package/templates/dashboard/vite.config.ts +12 -0
  42. package/templates/media/README.md +27 -0
  43. package/templates/media/index.html +12 -0
  44. package/templates/media/package.json +34 -0
  45. package/templates/media/src/App.tsx +24 -0
  46. package/templates/media/src/app.css +54 -0
  47. package/templates/media/src/contexts/MediaContext.tsx +108 -0
  48. package/templates/media/src/glass/AppGlasses.tsx +59 -0
  49. package/templates/media/src/glass/screens/home.ts +24 -0
  50. package/templates/media/src/glass/selectors.ts +9 -0
  51. package/templates/media/src/glass/shared.ts +8 -0
  52. package/templates/media/src/glass/splash.ts +25 -0
  53. package/templates/media/src/layouts/shell.tsx +39 -0
  54. package/templates/media/src/main.tsx +13 -0
  55. package/templates/media/src/screens/AudioScreen.tsx +78 -0
  56. package/templates/media/src/screens/GalleryScreen.tsx +98 -0
  57. package/templates/media/src/screens/Settings.tsx +86 -0
  58. package/templates/media/src/screens/UploadScreen.tsx +95 -0
  59. package/templates/media/src/types.ts +29 -0
  60. package/templates/media/src/vite-env.d.ts +1 -0
  61. package/templates/media/template.json +7 -0
  62. package/templates/media/tsconfig.json +20 -0
  63. package/templates/media/tsconfig.node.json +13 -0
  64. package/templates/media/vite.config.ts +12 -0
  65. package/templates/minimal/README.md +27 -0
  66. package/templates/minimal/index.html +12 -0
  67. package/templates/minimal/package.json +34 -0
  68. package/templates/minimal/src/App.tsx +50 -0
  69. package/templates/minimal/src/app.css +54 -0
  70. package/templates/minimal/src/glass/AppGlasses.tsx +54 -0
  71. package/templates/minimal/src/glass/screens/home.ts +24 -0
  72. package/templates/minimal/src/glass/selectors.ts +9 -0
  73. package/templates/minimal/src/glass/shared.ts +8 -0
  74. package/templates/minimal/src/glass/splash.ts +25 -0
  75. package/templates/minimal/src/main.tsx +13 -0
  76. package/templates/minimal/src/vite-env.d.ts +1 -0
  77. package/templates/minimal/template.json +7 -0
  78. package/templates/minimal/tsconfig.json +20 -0
  79. package/templates/minimal/tsconfig.node.json +13 -0
  80. package/templates/minimal/vite.config.ts +12 -0
  81. package/templates/notes/README.md +27 -0
  82. package/templates/notes/index.html +12 -0
  83. package/templates/notes/package.json +34 -0
  84. package/templates/notes/src/App.tsx +25 -0
  85. package/templates/notes/src/app.css +54 -0
  86. package/templates/notes/src/contexts/NotesContext.tsx +140 -0
  87. package/templates/notes/src/glass/AppGlasses.tsx +58 -0
  88. package/templates/notes/src/glass/screens/home.ts +24 -0
  89. package/templates/notes/src/glass/selectors.ts +9 -0
  90. package/templates/notes/src/glass/shared.ts +8 -0
  91. package/templates/notes/src/glass/splash.ts +24 -0
  92. package/templates/notes/src/layouts/shell.tsx +36 -0
  93. package/templates/notes/src/main.tsx +13 -0
  94. package/templates/notes/src/screens/NoteDetail.tsx +104 -0
  95. package/templates/notes/src/screens/NoteForm.tsx +84 -0
  96. package/templates/notes/src/screens/NoteList.tsx +108 -0
  97. package/templates/notes/src/screens/Settings.tsx +88 -0
  98. package/templates/notes/src/types.ts +14 -0
  99. package/templates/notes/src/vite-env.d.ts +1 -0
  100. package/templates/notes/template.json +7 -0
  101. package/templates/notes/tsconfig.json +20 -0
  102. package/templates/notes/tsconfig.node.json +13 -0
  103. package/templates/notes/vite.config.ts +12 -0
  104. package/templates/tracker/README.md +27 -0
  105. package/templates/tracker/index.html +12 -0
  106. package/templates/tracker/package.json +34 -0
  107. package/templates/tracker/src/App.tsx +24 -0
  108. package/templates/tracker/src/app.css +54 -0
  109. package/templates/tracker/src/contexts/TrackerContext.tsx +193 -0
  110. package/templates/tracker/src/glass/AppGlasses.tsx +64 -0
  111. package/templates/tracker/src/glass/screens/home.ts +24 -0
  112. package/templates/tracker/src/glass/selectors.ts +9 -0
  113. package/templates/tracker/src/glass/shared.ts +8 -0
  114. package/templates/tracker/src/glass/splash.ts +24 -0
  115. package/templates/tracker/src/layouts/shell.tsx +37 -0
  116. package/templates/tracker/src/main.tsx +13 -0
  117. package/templates/tracker/src/screens/HistoryScreen.tsx +106 -0
  118. package/templates/tracker/src/screens/NewEntryScreen.tsx +135 -0
  119. package/templates/tracker/src/screens/Settings.tsx +135 -0
  120. package/templates/tracker/src/screens/TodayScreen.tsx +147 -0
  121. package/templates/tracker/src/types.ts +34 -0
  122. package/templates/tracker/src/vite-env.d.ts +1 -0
  123. package/templates/tracker/template.json +7 -0
  124. package/templates/tracker/tsconfig.json +20 -0
  125. package/templates/tracker/tsconfig.node.json +13 -0
  126. package/templates/tracker/vite.config.ts +12 -0
@@ -0,0 +1,54 @@
1
+ import { useCallback, useMemo, useRef } from 'react'
2
+ import { useNavigate, useLocation } from 'react-router'
3
+ import { useGlasses } from 'even-toolkit/useGlasses'
4
+ import { useFlashPhase } from 'even-toolkit/useFlashPhase'
5
+ import { createScreenMapper, getHomeTiles } from 'even-toolkit/glass-router'
6
+ import { appSplash } from './splash'
7
+ import { toDisplayData, onGlassAction, type AppSnapshot } from './selectors'
8
+ import type { AppActions } from './shared'
9
+
10
+ const deriveScreen = createScreenMapper([
11
+ { pattern: '/', screen: 'home' },
12
+ ], 'home')
13
+
14
+ const homeTiles = getHomeTiles(appSplash)
15
+
16
+ export function AppGlasses() {
17
+ const navigate = useNavigate()
18
+ const location = useLocation()
19
+ const flashPhase = useFlashPhase(deriveScreen(location.pathname) === 'home')
20
+
21
+ const snapshotRef = useMemo(() => ({
22
+ current: null as AppSnapshot | null,
23
+ }), [])
24
+
25
+ const snapshot: AppSnapshot = {
26
+ items: ['Welcome to {{DISPLAY_NAME}}', 'Edit App.tsx to get started'],
27
+ flashPhase,
28
+ }
29
+ snapshotRef.current = snapshot
30
+
31
+ const getSnapshot = useCallback(() => snapshotRef.current!, [snapshotRef])
32
+
33
+ const ctxRef = useRef<AppActions>({ navigate })
34
+ ctxRef.current = { navigate }
35
+
36
+ const handleGlassAction = useCallback(
37
+ (action: Parameters<typeof onGlassAction>[0], nav: Parameters<typeof onGlassAction>[1], snap: AppSnapshot) =>
38
+ onGlassAction(action, nav, snap, ctxRef.current),
39
+ [],
40
+ )
41
+
42
+ useGlasses({
43
+ getSnapshot,
44
+ toDisplayData,
45
+ onGlassAction: handleGlassAction,
46
+ deriveScreen,
47
+ appName: '{{DISPLAY_NAME_UPPER}}',
48
+ splash: appSplash,
49
+ getPageMode: (screen) => screen === 'home' ? 'home' : 'text',
50
+ homeImageTiles: homeTiles,
51
+ })
52
+
53
+ return null
54
+ }
@@ -0,0 +1,24 @@
1
+ import type { GlassScreen } from 'even-toolkit/glass-screen-router'
2
+ import { buildScrollableList } from 'even-toolkit/glass-display-builders'
3
+ import { moveHighlight } from 'even-toolkit/glass-nav'
4
+ import type { AppSnapshot, AppActions } from '../shared'
5
+
6
+ export const homeScreen: GlassScreen<AppSnapshot, AppActions> = {
7
+ display(snapshot, nav) {
8
+ return {
9
+ lines: buildScrollableList({
10
+ items: snapshot.items,
11
+ highlightedIndex: nav.highlightedIndex,
12
+ maxVisible: 5,
13
+ formatter: (item) => item,
14
+ }),
15
+ }
16
+ },
17
+
18
+ action(action, nav, snapshot) {
19
+ if (action.type === 'HIGHLIGHT_MOVE') {
20
+ return { ...nav, highlightedIndex: moveHighlight(nav.highlightedIndex, action.direction, snapshot.items.length - 1) }
21
+ }
22
+ return nav
23
+ },
24
+ }
@@ -0,0 +1,9 @@
1
+ import { createGlassScreenRouter } from 'even-toolkit/glass-screen-router'
2
+ import type { AppSnapshot, AppActions } from './shared'
3
+ import { homeScreen } from './screens/home'
4
+
5
+ export type { AppSnapshot, AppActions }
6
+
7
+ export const { toDisplayData, onGlassAction } = createGlassScreenRouter<AppSnapshot, AppActions>({
8
+ 'home': homeScreen,
9
+ }, 'home')
@@ -0,0 +1,8 @@
1
+ export interface AppSnapshot {
2
+ items: string[]
3
+ flashPhase: boolean
4
+ }
5
+
6
+ export interface AppActions {
7
+ navigate: (path: string) => void
8
+ }
@@ -0,0 +1,25 @@
1
+ import { createSplash, TILE_PRESETS } from 'even-toolkit/splash'
2
+
3
+ export function renderSplash(ctx: CanvasRenderingContext2D, w: number, h: number) {
4
+ const fg = '#e0e0e0'
5
+ const cx = w / 2
6
+ const s = Math.min(w / 200, h / 200)
7
+
8
+ // Simple text-based splash — replace with pixel-art icon for your app
9
+ ctx.fillStyle = fg
10
+ ctx.font = `bold ${14 * s}px "Courier New", monospace`
11
+ ctx.textAlign = 'center'
12
+ ctx.fillText('{{DISPLAY_NAME_UPPER}}', cx, 50 * s)
13
+ ctx.textAlign = 'left'
14
+ }
15
+
16
+ export const appSplash = createSplash({
17
+ tiles: 1,
18
+ tileLayout: 'vertical',
19
+ tilePositions: TILE_PRESETS.topCenter1,
20
+ canvasSize: { w: 200, h: 200 },
21
+ minTimeMs: 0,
22
+ maxTimeMs: 0,
23
+ menuText: '',
24
+ render: renderSplash,
25
+ })
@@ -0,0 +1,13 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import { BrowserRouter } from 'react-router'
4
+ import { App } from './App'
5
+ import './app.css'
6
+
7
+ createRoot(document.getElementById('root')!).render(
8
+ <StrictMode>
9
+ <BrowserRouter>
10
+ <App />
11
+ </BrowserRouter>
12
+ </StrictMode>,
13
+ )
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "minimal",
3
+ "displayName": "Clean Starter",
4
+ "description": "Minimal app with AppShell, NavHeader, and a welcome screen",
5
+ "tags": ["starter", "simple"],
6
+ "components": ["AppShell", "NavHeader", "Button", "Card", "ScreenHeader"]
7
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "noEmit": true,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "baseUrl": ".",
16
+ "paths": { "@/*": ["./src/*"] }
17
+ },
18
+ "include": ["src"],
19
+ "references": [{ "path": "./tsconfig.node.json" }]
20
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "types": ["node"],
8
+ "composite": true,
9
+ "noEmit": false,
10
+ "skipLibCheck": true
11
+ },
12
+ "include": ["vite.config.ts"]
13
+ }
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import tailwindcss from '@tailwindcss/vite'
4
+ import path from 'path'
5
+
6
+ export default defineConfig({
7
+ plugins: [react(), tailwindcss()],
8
+ resolve: {
9
+ alias: { '@': path.resolve(__dirname, './src') },
10
+ dedupe: ['react', 'react-dom', 'react-router', '@evenrealities/even_hub_sdk', '@jappyjan/even-better-sdk', 'upng-js'],
11
+ },
12
+ })
@@ -0,0 +1,27 @@
1
+ # {{DISPLAY_NAME}}
2
+
3
+ A G2 smart glasses app built with [even-toolkit](https://www.npmjs.com/package/even-toolkit).
4
+
5
+ ## Development
6
+
7
+ ```bash
8
+ npm install
9
+ npm run dev
10
+ ```
11
+
12
+ Open [http://localhost:5173](http://localhost:5173) in your browser.
13
+
14
+ ## Test with Simulator
15
+
16
+ ```bash
17
+ npx @evenrealities/evenhub-simulator@latest http://localhost:5173
18
+ ```
19
+
20
+ ## Build for Even Hub
21
+
22
+ ```bash
23
+ npm run build
24
+ npx @evenrealities/evenhub-cli pack app.json dist
25
+ ```
26
+
27
+ Upload the generated `.ehpk` file to the Even Hub.
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
6
+ <title>{{DISPLAY_NAME}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "{{APP_NAME}}",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite --port 5173 --host",
8
+ "build": "tsc -b && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "@evenrealities/even_hub_sdk": "^0.0.9",
13
+ "@jappyjan/even-better-sdk": "^0.0.11",
14
+ "class-variance-authority": "^0.7.1",
15
+ "clsx": "^2.1.0",
16
+ "even-toolkit": "^1.5.0",
17
+ "react": "^19.0.0",
18
+ "react-dom": "^19.0.0",
19
+ "react-is": "^19.2.4",
20
+ "react-router": "^7.0.0",
21
+ "tailwind-merge": "^3.0.0",
22
+ "upng-js": "^2.1.0"
23
+ },
24
+ "devDependencies": {
25
+ "@tailwindcss/vite": "^4.0.0",
26
+ "@types/react": "^19.0.0",
27
+ "@types/react-dom": "^19.0.0",
28
+ "@types/node": "^22.0.0",
29
+ "@vitejs/plugin-react": "^4.3.0",
30
+ "tailwindcss": "^4.0.0",
31
+ "typescript": "~5.4.0",
32
+ "vite": "^5.4.0"
33
+ }
34
+ }
@@ -0,0 +1,25 @@
1
+ import { Routes, Route } from 'react-router'
2
+ import { Shell } from './layouts/shell'
3
+ import { NotesProvider } from './contexts/NotesContext'
4
+ import { NoteList } from './screens/NoteList'
5
+ import { NoteDetail } from './screens/NoteDetail'
6
+ import { NoteForm } from './screens/NoteForm'
7
+ import { Settings } from './screens/Settings'
8
+ import { AppGlasses } from './glass/AppGlasses'
9
+
10
+ export function App() {
11
+ return (
12
+ <NotesProvider>
13
+ <Routes>
14
+ <Route element={<Shell />}>
15
+ <Route path="/" element={<NoteList />} />
16
+ <Route path="/note/:id" element={<NoteDetail />} />
17
+ <Route path="/new" element={<NoteForm />} />
18
+ <Route path="/note/:id/edit" element={<NoteForm />} />
19
+ <Route path="/settings" element={<Settings />} />
20
+ </Route>
21
+ </Routes>
22
+ <AppGlasses />
23
+ </NotesProvider>
24
+ )
25
+ }
@@ -0,0 +1,54 @@
1
+ @import "tailwindcss";
2
+ @import "even-toolkit/web/theme-light.css";
3
+ @import "even-toolkit/web/typography.css";
4
+ @import "even-toolkit/web/utilities.css";
5
+ @source "../src";
6
+ @source "../node_modules/even-toolkit/web";
7
+ @source "../node_modules/even-toolkit/dist";
8
+
9
+ @theme {
10
+ --color-bg: var(--color-bg);
11
+ --color-surface: var(--color-surface);
12
+ --color-surface-light: var(--color-surface-light);
13
+ --color-surface-lighter: var(--color-surface-lighter);
14
+ --color-border: var(--color-border);
15
+ --color-border-light: var(--color-border-light);
16
+ --color-text: var(--color-text);
17
+ --color-text-dim: var(--color-text-dim);
18
+ --color-text-muted: var(--color-text-muted);
19
+ --color-text-highlight: var(--color-text-highlight);
20
+ --color-accent: var(--color-accent);
21
+ --color-accent-alpha: var(--color-accent-alpha);
22
+ --color-accent-warning: var(--color-accent-warning);
23
+ --color-positive: var(--color-positive);
24
+ --color-positive-alpha: var(--color-positive-alpha);
25
+ --color-negative: var(--color-negative);
26
+ --color-negative-alpha: var(--color-negative-alpha);
27
+ --color-overlay: var(--color-overlay);
28
+ --color-input-bg: var(--color-input-bg);
29
+ --radius-default: var(--radius-default);
30
+ --font-display: var(--font-display);
31
+ --font-body: var(--font-body);
32
+ --font-mono: var(--font-mono);
33
+ }
34
+
35
+ html, body {
36
+ background: var(--color-bg);
37
+ color: var(--color-text);
38
+ min-height: 100dvh;
39
+ font-family: var(--font-display);
40
+ -webkit-font-smoothing: antialiased;
41
+ }
42
+
43
+ #root {
44
+ max-width: 430px;
45
+ margin: 0 auto;
46
+ }
47
+
48
+ .scrollbar-hide {
49
+ -ms-overflow-style: none;
50
+ scrollbar-width: none;
51
+ }
52
+ .scrollbar-hide::-webkit-scrollbar {
53
+ display: none;
54
+ }
@@ -0,0 +1,140 @@
1
+ import { createContext, useContext, useState, useCallback, useEffect, type ReactNode } from 'react'
2
+ import type { Note, NoteCategory, CategoryFilter } from '../types'
3
+ import { ALL_FILTER } from '../types'
4
+
5
+ interface NotesContextValue {
6
+ notes: Note[]
7
+ addNote: (title: string, content: string, category: NoteCategory) => Note
8
+ updateNote: (id: string, title: string, content: string, category: NoteCategory) => void
9
+ deleteNote: (id: string) => void
10
+ searchQuery: string
11
+ setSearchQuery: (q: string) => void
12
+ selectedCategory: CategoryFilter
13
+ setSelectedCategory: (c: CategoryFilter) => void
14
+ filteredNotes: Note[]
15
+ compactView: boolean
16
+ setCompactView: (v: boolean) => void
17
+ }
18
+
19
+ const NotesContext = createContext<NotesContextValue | null>(null)
20
+
21
+ const STORAGE_KEY = '{{APP_NAME}}-notes'
22
+ const SETTINGS_KEY = '{{APP_NAME}}-settings'
23
+
24
+ function generateId(): string {
25
+ return Date.now().toString(36) + Math.random().toString(36).slice(2, 8)
26
+ }
27
+
28
+ const SAMPLE_NOTES: Note[] = [
29
+ {
30
+ id: 'sample-1',
31
+ title: 'Meeting Notes',
32
+ content: 'Discussed Q2 roadmap with the team. Key decisions:\n\n- Launch new dashboard by end of April\n- Prioritize mobile experience improvements\n- Schedule design review for next Wednesday\n- Allocate two sprints for performance optimization',
33
+ category: 'Work',
34
+ createdAt: Date.now() - 86400000 * 2,
35
+ updatedAt: Date.now() - 86400000 * 2,
36
+ },
37
+ {
38
+ id: 'sample-2',
39
+ title: 'Recipe Ideas',
40
+ content: 'Try making sourdough bread this weekend. Need to get a starter going by Thursday at the latest.\n\nIngredients to pick up:\n- Bread flour (King Arthur)\n- Sea salt\n- Dutch oven if I can find one on sale',
41
+ category: 'Personal',
42
+ createdAt: Date.now() - 86400000,
43
+ updatedAt: Date.now() - 86400000,
44
+ },
45
+ {
46
+ id: 'sample-3',
47
+ title: 'App Feature',
48
+ content: 'What if we added voice-to-text note creation using the G2 glasses microphone? Could use the even-toolkit STT module with web-speech provider for real-time transcription.\n\nWould need:\n- Mic permission handling\n- Visual feedback on glasses display\n- Auto-punctuation',
49
+ category: 'Ideas',
50
+ createdAt: Date.now() - 3600000,
51
+ updatedAt: Date.now() - 3600000,
52
+ },
53
+ ]
54
+
55
+ function loadNotes(): Note[] {
56
+ try {
57
+ const raw = localStorage.getItem(STORAGE_KEY)
58
+ if (raw) {
59
+ const parsed = JSON.parse(raw)
60
+ if (Array.isArray(parsed) && parsed.length > 0) return parsed
61
+ }
62
+ } catch { /* ignore */ }
63
+ return SAMPLE_NOTES
64
+ }
65
+
66
+ function loadSettings(): { compactView: boolean } {
67
+ try {
68
+ const raw = localStorage.getItem(SETTINGS_KEY)
69
+ if (raw) return JSON.parse(raw)
70
+ } catch { /* ignore */ }
71
+ return { compactView: false }
72
+ }
73
+
74
+ export function NotesProvider({ children }: { children: ReactNode }) {
75
+ const [notes, setNotes] = useState<Note[]>(loadNotes)
76
+ const [searchQuery, setSearchQuery] = useState('')
77
+ const [selectedCategory, setSelectedCategory] = useState<CategoryFilter>(ALL_FILTER)
78
+ const [compactView, setCompactView] = useState(() => loadSettings().compactView)
79
+
80
+ useEffect(() => {
81
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(notes))
82
+ }, [notes])
83
+
84
+ useEffect(() => {
85
+ localStorage.setItem(SETTINGS_KEY, JSON.stringify({ compactView }))
86
+ }, [compactView])
87
+
88
+ const addNote = useCallback((title: string, content: string, category: NoteCategory): Note => {
89
+ const now = Date.now()
90
+ const note: Note = { id: generateId(), title, content, category, createdAt: now, updatedAt: now }
91
+ setNotes((prev) => [note, ...prev])
92
+ return note
93
+ }, [])
94
+
95
+ const updateNote = useCallback((id: string, title: string, content: string, category: NoteCategory) => {
96
+ setNotes((prev) =>
97
+ prev.map((n) => (n.id === id ? { ...n, title, content, category, updatedAt: Date.now() } : n)),
98
+ )
99
+ }, [])
100
+
101
+ const deleteNote = useCallback((id: string) => {
102
+ setNotes((prev) => prev.filter((n) => n.id !== id))
103
+ }, [])
104
+
105
+ const filteredNotes = notes.filter((note) => {
106
+ const matchesCategory = selectedCategory === ALL_FILTER || note.category === selectedCategory
107
+ const query = searchQuery.toLowerCase()
108
+ const matchesSearch =
109
+ !query ||
110
+ note.title.toLowerCase().includes(query) ||
111
+ note.content.toLowerCase().includes(query)
112
+ return matchesCategory && matchesSearch
113
+ })
114
+
115
+ return (
116
+ <NotesContext.Provider
117
+ value={{
118
+ notes,
119
+ addNote,
120
+ updateNote,
121
+ deleteNote,
122
+ searchQuery,
123
+ setSearchQuery,
124
+ selectedCategory,
125
+ setSelectedCategory,
126
+ filteredNotes,
127
+ compactView,
128
+ setCompactView,
129
+ }}
130
+ >
131
+ {children}
132
+ </NotesContext.Provider>
133
+ )
134
+ }
135
+
136
+ export function useNotes() {
137
+ const ctx = useContext(NotesContext)
138
+ if (!ctx) throw new Error('useNotes must be used within NotesProvider')
139
+ return ctx
140
+ }
@@ -0,0 +1,58 @@
1
+ import { useCallback, useMemo, useRef } from 'react'
2
+ import { useNavigate, useLocation } from 'react-router'
3
+ import { useGlasses } from 'even-toolkit/useGlasses'
4
+ import { useFlashPhase } from 'even-toolkit/useFlashPhase'
5
+ import { createScreenMapper, getHomeTiles } from 'even-toolkit/glass-router'
6
+ import { appSplash } from './splash'
7
+ import { toDisplayData, onGlassAction, type AppSnapshot } from './selectors'
8
+ import type { AppActions } from './shared'
9
+ import { useNotes } from '../contexts/NotesContext'
10
+
11
+ const deriveScreen = createScreenMapper([
12
+ { pattern: '/', screen: 'home' },
13
+ ], 'home')
14
+
15
+ const homeTiles = getHomeTiles(appSplash)
16
+
17
+ export function AppGlasses() {
18
+ const navigate = useNavigate()
19
+ const location = useLocation()
20
+ const { notes } = useNotes()
21
+ const flashPhase = useFlashPhase(deriveScreen(location.pathname) === 'home')
22
+
23
+ const snapshotRef = useMemo(() => ({
24
+ current: null as AppSnapshot | null,
25
+ }), [])
26
+
27
+ const snapshot: AppSnapshot = {
28
+ items: notes.length > 0
29
+ ? notes.map((n) => n.title)
30
+ : ['No notes yet'],
31
+ flashPhase,
32
+ }
33
+ snapshotRef.current = snapshot
34
+
35
+ const getSnapshot = useCallback(() => snapshotRef.current!, [snapshotRef])
36
+
37
+ const ctxRef = useRef<AppActions>({ navigate })
38
+ ctxRef.current = { navigate }
39
+
40
+ const handleGlassAction = useCallback(
41
+ (action: Parameters<typeof onGlassAction>[0], nav: Parameters<typeof onGlassAction>[1], snap: AppSnapshot) =>
42
+ onGlassAction(action, nav, snap, ctxRef.current),
43
+ [],
44
+ )
45
+
46
+ useGlasses({
47
+ getSnapshot,
48
+ toDisplayData,
49
+ onGlassAction: handleGlassAction,
50
+ deriveScreen,
51
+ appName: '{{DISPLAY_NAME_UPPER}}',
52
+ splash: appSplash,
53
+ getPageMode: (screen) => screen === 'home' ? 'home' : 'text',
54
+ homeImageTiles: homeTiles,
55
+ })
56
+
57
+ return null
58
+ }
@@ -0,0 +1,24 @@
1
+ import type { GlassScreen } from 'even-toolkit/glass-screen-router'
2
+ import { buildScrollableList } from 'even-toolkit/glass-display-builders'
3
+ import { moveHighlight } from 'even-toolkit/glass-nav'
4
+ import type { AppSnapshot, AppActions } from '../shared'
5
+
6
+ export const homeScreen: GlassScreen<AppSnapshot, AppActions> = {
7
+ display(snapshot, nav) {
8
+ return {
9
+ lines: buildScrollableList({
10
+ items: snapshot.items,
11
+ highlightedIndex: nav.highlightedIndex,
12
+ maxVisible: 5,
13
+ formatter: (item) => item,
14
+ }),
15
+ }
16
+ },
17
+
18
+ action(action, nav, snapshot) {
19
+ if (action.type === 'HIGHLIGHT_MOVE') {
20
+ return { ...nav, highlightedIndex: moveHighlight(nav.highlightedIndex, action.direction, snapshot.items.length - 1) }
21
+ }
22
+ return nav
23
+ },
24
+ }
@@ -0,0 +1,9 @@
1
+ import { createGlassScreenRouter } from 'even-toolkit/glass-screen-router'
2
+ import type { AppSnapshot, AppActions } from './shared'
3
+ import { homeScreen } from './screens/home'
4
+
5
+ export type { AppSnapshot, AppActions }
6
+
7
+ export const { toDisplayData, onGlassAction } = createGlassScreenRouter<AppSnapshot, AppActions>({
8
+ 'home': homeScreen,
9
+ }, 'home')
@@ -0,0 +1,8 @@
1
+ export interface AppSnapshot {
2
+ items: string[]
3
+ flashPhase: boolean
4
+ }
5
+
6
+ export interface AppActions {
7
+ navigate: (path: string) => void
8
+ }
@@ -0,0 +1,24 @@
1
+ import { createSplash, TILE_PRESETS } from 'even-toolkit/splash'
2
+
3
+ export function renderSplash(ctx: CanvasRenderingContext2D, w: number, h: number) {
4
+ const fg = '#e0e0e0'
5
+ const cx = w / 2
6
+ const s = Math.min(w / 200, h / 200)
7
+
8
+ ctx.fillStyle = fg
9
+ ctx.font = `bold ${14 * s}px "Courier New", monospace`
10
+ ctx.textAlign = 'center'
11
+ ctx.fillText('{{DISPLAY_NAME_UPPER}}', cx, 50 * s)
12
+ ctx.textAlign = 'left'
13
+ }
14
+
15
+ export const appSplash = createSplash({
16
+ tiles: 1,
17
+ tileLayout: 'vertical',
18
+ tilePositions: TILE_PRESETS.topCenter1,
19
+ canvasSize: { w: 200, h: 200 },
20
+ minTimeMs: 0,
21
+ maxTimeMs: 0,
22
+ menuText: '',
23
+ render: renderSplash,
24
+ })
@@ -0,0 +1,36 @@
1
+ import { DrawerShell } from 'even-toolkit/web'
2
+ import type { SideDrawerItem } from 'even-toolkit/web'
3
+
4
+ const MENU_ITEMS: SideDrawerItem[] = [
5
+ { id: '/', label: 'Notes', section: 'Notes' },
6
+ ]
7
+
8
+ const BOTTOM_ITEMS: SideDrawerItem[] = [
9
+ { id: '/settings', label: 'Settings', section: 'App' },
10
+ ]
11
+
12
+ function getPageTitle(pathname: string): string {
13
+ if (pathname === '/') return '{{DISPLAY_NAME}}'
14
+ if (pathname === '/new') return 'New Note'
15
+ if (pathname.includes('/edit')) return 'Edit Note'
16
+ if (pathname.startsWith('/note/')) return 'Note'
17
+ if (pathname === '/settings') return 'Settings'
18
+ return 'Notes'
19
+ }
20
+
21
+ function deriveActiveId(pathname: string): string {
22
+ if (pathname === '/settings') return '/settings'
23
+ return '/'
24
+ }
25
+
26
+ export function Shell() {
27
+ return (
28
+ <DrawerShell
29
+ items={MENU_ITEMS}
30
+ bottomItems={BOTTOM_ITEMS}
31
+ title="{{DISPLAY_NAME}}"
32
+ getPageTitle={getPageTitle}
33
+ deriveActiveId={deriveActiveId}
34
+ />
35
+ )
36
+ }