experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(ios): bottom sheet webview + research docs

- iOS webview: change from fullscreen modal to bottom sheet presentation
- Users see list underneath while previewing
- Swipe up to expand, down to dismiss
- iOS 15+ UISheetPresentationController with medium/large detents

- Add research docs:
- docs/ios-webview-research.md - inline vs popup webview analysis
- docs/widgets-research.md - widget system implementation plan

- DEVELOPMENT.md: document Claude Code permissions (yarn:*/npm:* wildcards)

+472 -1
+21
DEVELOPMENT.md
··· 279 279 - Different security model than traditional browsers 280 280 - Be cautious with cross-origin access and custom APIs 281 281 282 + ## Claude Code Permissions 283 + 284 + All package.json scripts are pre-approved via wildcard permissions in `.claude/settings.local.json`: 285 + 286 + ```json 287 + "Bash(yarn:*)" // All yarn commands 288 + "Bash(npm:*)" // All npm commands 289 + "Bash(npx:*)" // All npx commands 290 + ``` 291 + 292 + This covers all 146+ scripts automatically, including: 293 + - Development: `yarn dev`, `yarn start`, `yarn debug` 294 + - Testing: `yarn test`, `yarn test:*` 295 + - Building: `yarn build`, `yarn build:*` 296 + - Mobile: `yarn mobile:*`, `yarn tauri:*` 297 + - Server: `yarn server:*` 298 + 299 + **No manual permission updates needed** when adding new scripts - the wildcards handle it. 300 + 301 + Other pre-approved tool patterns: `git:*`, `jj:*`, `node:*`, `cargo:*`, `electron:*` 302 + 282 303 ## Mobile Development (Tauri iOS/Android) 283 304 284 305 Mobile development uses the separate `peek-save` app in `backend/tauri-mobile/`.
+7 -1
backend/tauri-mobile/src-tauri/gen/apple/Sources/peek-save/WebviewPlugin.swift
··· 118 118 webVC.configure(url: url, itemId: itemId) 119 119 120 120 let navController = UINavigationController(rootViewController: webVC) 121 - navController.modalPresentationStyle = .fullScreen 121 + navController.modalPresentationStyle = .pageSheet 122 + 123 + if #available(iOS 15, *) { 124 + navController.sheetPresentationController?.detents = [.medium(), .large()] 125 + navController.sheetPresentationController?.prefersScrollingExpandsWhenScrolledToEdge = true 126 + navController.sheetPresentationController?.preferredCornerRadius = 12 127 + } 122 128 123 129 guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, 124 130 let window = windowScene.windows.first,
+149
docs/ios-webview-research.md
··· 1 + # iOS Webview Research: Inline vs Popup 2 + 3 + Status: **Research Complete** | Priority: Active 4 + 5 + ## Overview 6 + 7 + Currently iOS Peek opens pages in a fullscreen modal webview. This research explores more integrated UX patterns, specifically bottom sheet presentation. 8 + 9 + ## Current Implementation 10 + 11 + **File:** `backend/tauri-mobile/src-tauri/gen/apple/Sources/peek-save/WebviewPlugin.swift` 12 + 13 + - Presentation: `modalPresentationStyle = .fullScreen` 14 + - Navigation: `UINavigationController` wrapping `EmbeddedWebViewController` 15 + - Dismissal: "Done" button or back gesture 16 + - WKWebView with inline media, back/forward gestures enabled 17 + 18 + **Invocation Flow:** 19 + ``` 20 + React (App.tsx) → invoke("open_native_webview") 21 + → Rust FFI (lib.rs) → webview_plugin_open() 22 + → Swift presents UINavigationController 23 + ``` 24 + 25 + --- 26 + 27 + ## Recommended: Bottom Sheet (Phase 1) 28 + 29 + ``` 30 + ┌─────────────────────────┐ 31 + │ Main List (Pages) │ ← Still visible 32 + │ ┌─────────────────────┐ │ 33 + │ │ Save Article │ │ 34 + │ └─────────────────────┘ │ 35 + ├─────────────────────────┤ ← Drag handle 36 + │ Webview Content │ 37 + │ (swipe up to expand) │ 38 + └─────────────────────────┘ 39 + ``` 40 + 41 + ### Implementation (~20 lines Swift) 42 + 43 + ```swift 44 + // Replace .fullScreen with: 45 + navController.modalPresentationStyle = .pageSheet 46 + 47 + if #available(iOS 15, *) { 48 + navController.sheetPresentationController?.detents = [.medium, .large] 49 + navController.sheetPresentationController?.prefersScrollingExpandsWhenScrolledToEdge = true 50 + navController.sheetPresentationController?.preferredCornerRadius = 12 51 + } 52 + ``` 53 + 54 + ### Benefits 55 + - Users see list underneath (context preserved) 56 + - Swipe up to expand, down to dismiss 57 + - Keyboard auto-avoidance built-in 58 + - Natural iOS 15+ pattern (Maps, Stocks, Twitter) 59 + 60 + --- 61 + 62 + ## Phase 2: Browser Controls in Toolbar 63 + 64 + Add Safari-style controls within the sheet: 65 + 66 + ``` 67 + ┌─────────────────────────┐ 68 + │ Webview Content │ 69 + ├─────────────────────────┤ 70 + │ ◀ ▶ 🔄 ⎚ Share │ ← Toolbar 71 + └─────────────────────────┘ 72 + ``` 73 + 74 + - Move navbar controls to permanent toolbar 75 + - Show in all sheet states (medium/large) 76 + - Bind to webview navigation state 77 + 78 + **Complexity:** Medium (~1 day) 79 + 80 + --- 81 + 82 + ## Technical Considerations 83 + 84 + ### WKWebView vs SFSafariViewController 85 + 86 + | Aspect | WKWebView | SFSafariViewController | 87 + |--------|-----------|------------------------| 88 + | Customization | Full control | Limited | 89 + | Integration | Easy to embed | Fullscreen only | 90 + | Memory | ~30-50MB | Lightweight (shared) | 91 + | JavaScript | Full access | Sandboxed | 92 + | **Best for** | In-app previews | External links | 93 + 94 + **Recommendation:** Keep WKWebView for embedded views (control, visit tracking) 95 + 96 + ### Memory Management 97 + 98 + - Single WKWebView instance: ~30-50MB peak 99 + - Auto-release on dismiss via `currentWebViewController = nil` 100 + - No change needed for bottom sheet (still single instance) 101 + 102 + ### Keyboard Handling 103 + 104 + Bottom sheet auto-avoids keyboard with: 105 + ```swift 106 + sheetPresentationController?.prefersScrollingExpandsWhenScrolledToEdge = true 107 + ``` 108 + 109 + ### Safe Area / Notch 110 + 111 + Always use `safeAreaLayoutGuide`: 112 + ```swift 113 + webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor) 114 + webView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) 115 + ``` 116 + 117 + --- 118 + 119 + ## Other Patterns Considered 120 + 121 + | Pattern | Complexity | Notes | 122 + |---------|------------|-------| 123 + | Bottom sheet | Low | Recommended for Phase 1 | 124 + | Drawer panel | Medium | Good for side-by-side | 125 + | Split view (iPad) | High | Deferred | 126 + 127 + ### App Examples 128 + 129 + - **Maps**: Bottom sheet with progressive disclosure 130 + - **Slack**: Drawer for thread previews 131 + - **Twitter/X**: Sheet that expands to fullscreen 132 + - **Notes**: Inline preview → expand to webview 133 + 134 + --- 135 + 136 + ## Files 137 + 138 + | File | Purpose | 139 + |------|---------| 140 + | `backend/tauri-mobile/src-tauri/gen/apple/Sources/peek-save/WebviewPlugin.swift` | Swift webview presentation | 141 + | `backend/tauri-mobile/src/App.tsx` | React integration (lines 2435-2449) | 142 + | `backend/tauri-mobile/src-tauri/src/lib.rs` | Rust FFI bridge (lines 2539-2575) | 143 + 144 + --- 145 + 146 + ## Implementation Timeline 147 + 148 + - **Phase 1** (2-3 hrs): Bottom sheet with medium/large detents 149 + - **Phase 2** (1 day): Browser controls in toolbar
+295
docs/widgets-research.md
··· 1 + # Widgets Research & Implementation Plan 2 + 3 + Status: **Research Complete** | Priority: Future 4 + 5 + ## Overview 6 + 7 + Widgets are reusable, data-bound UI components for displaying feeds, metrics, and custom data. They leverage the existing feeds system (series/feed items) and card grid components to create composable, template-driven visualization panels. 8 + 9 + ## Core Concept 10 + 11 + Widgets are **views** on feeds/series data. The system separates: 12 + - **Data** (feeds, series, item_events) 13 + - **Visualization** (widget types, templates) 14 + - **Layout** (widget sheets, dashboards) 15 + 16 + --- 17 + 18 + ## Widget API 19 + 20 + ```javascript 21 + // Register a widget type (extensions or core) 22 + api.widgets.register({ 23 + name: 'stock-price', 24 + type: 'scalar', 25 + dataSchema: { /* JSON Schema */ }, 26 + templates: { default: '...' } 27 + }); 28 + 29 + // Create a widget instance 30 + const widget = await api.widgets.create({ 31 + type: 'stock-price', 32 + dataSource: { 33 + type: 'series', 34 + itemId: 'series_123', 35 + query: { since: '7d' }, 36 + transform: (events) => events[0].value, 37 + aggregate: 'sum' | 'avg' | 'min' | 'max' | 'streak' 38 + } 39 + }); 40 + 41 + // Widget sheets (dashboards) 42 + await api.widgets.sheets.save('my-dashboard', { widgets: [...] }); 43 + const dashboard = await api.widgets.sheets.load('my-dashboard'); 44 + 45 + // Custom data fetchers (for non-feed data) 46 + api.widgets.registerDataFetcher('weather:forecast', async (config) => { 47 + return fetch(`https://api.weather.service/forecast?lat=${config.lat}`).then(r => r.json()); 48 + }); 49 + ``` 50 + 51 + --- 52 + 53 + ## Widget Types 54 + 55 + | Category | Types | Description | 56 + |----------|-------|-------------| 57 + | **Data Display** | scalar, list, table, stats, timeline, gauge | Show values, lists, aggregates | 58 + | **Visualization** | chart, graph, media, badge | Charts, sparklines, images | 59 + | **Interactive** | button-set, form, carousel | User input, actions | 60 + | **Utility** | empty, loading, error, container | States, composition | 61 + 62 + --- 63 + 64 + ## Database Storage 65 + 66 + Widget definitions use existing datastore tables: 67 + 68 + | What | Storage | Description | 69 + |------|---------|-------------| 70 + | Widget types | `items` type=`widget-type` | Template definitions | 71 + | Widget instances | `items` type=`widget` | Configured widget + dataSource | 72 + | Widget sheets | `items` type=`widget-sheet` | Dashboard layouts | 73 + | Widget data | `item_events` | Series/feed data displayed | 74 + 75 + ```sql 76 + -- Widget type definition 77 + INSERT INTO items (type, content, metadata) VALUES ( 78 + 'widget-type', 79 + 'stock-price', 80 + '{"templates":{"default":"..."},"dataSchema":{...}}' 81 + ); 82 + 83 + -- Widget instance 84 + INSERT INTO items (type, content, metadata) VALUES ( 85 + 'widget', 86 + 'My AAPL Tracker', 87 + '{"widgetType":"stock-price","dataSource":{"type":"series","itemId":"series_123"}}' 88 + ); 89 + 90 + -- Widget sheet (dashboard) 91 + INSERT INTO items (type, content, metadata) VALUES ( 92 + 'widget-sheet', 93 + 'Trading Dashboard', 94 + '{"layout":{"columns":3},"widgetIds":["widget_1","widget_2","widget_3"]}' 95 + ); 96 + ``` 97 + 98 + --- 99 + 100 + ## Examples 101 + 102 + ### Scalar Widget (Latest Value) 103 + ```javascript 104 + { 105 + type: 'scalar', 106 + dataSource: { type: 'series', itemId: 'series_mood' }, 107 + template: 'emoji-display' 108 + } 109 + // Renders: 😊 7/10 110 + ``` 111 + 112 + ### Stats Widget (Aggregated) 113 + ```javascript 114 + { 115 + type: 'stats', 116 + dataSource: { 117 + type: 'series', 118 + itemId: 'series_pushups', 119 + query: { since: '7d' }, 120 + aggregate: 'summary' 121 + } 122 + } 123 + // Renders: Total: 245 | Avg: 35/day | 🔥 7-day streak 124 + ``` 125 + 126 + ### Feed List Widget 127 + ```javascript 128 + { 129 + type: 'list', 130 + dataSource: { 131 + type: 'feed', 132 + itemIds: ['feed_hackernews', 'feed_techcrunch'], 133 + query: { limit: 10 } 134 + } 135 + } 136 + ``` 137 + 138 + ### Dashboard Sheet 139 + ```javascript 140 + { 141 + name: 'Morning HUD', 142 + layout: { columns: 2, gap: 16 }, 143 + widgets: [ 144 + { type: 'scalar', dataSource: { type: 'custom', fetcher: 'weather' } }, 145 + { type: 'stats', dataSource: { type: 'series', itemId: 'series_sleep' } }, 146 + { type: 'list', dataSource: { type: 'feed', itemId: 'feed_calendar' } }, 147 + { type: 'timeline', dataSource: { type: 'series', itemId: 'series_weight', query: { since: '30d' } } } 148 + ] 149 + } 150 + ``` 151 + 152 + --- 153 + 154 + ## No-Code Usage Path 155 + 156 + ### Phase 1: Guided Creation 157 + ``` 158 + Cmd bar → "new widget" → prompts: 159 + 1. What to track? [number/text/choice] 160 + 2. Name it: "Daily Pushups" 161 + 3. How to display? [latest value / chart / list] 162 + → Creates series + widget automatically 163 + ``` 164 + 165 + ### Phase 2: Visual Dashboard Builder 166 + ``` 167 + Widget Sheet Editor: 168 + ┌─────────────────────────────────────┐ 169 + │ [+ Add Widget] [Layout: 2 cols ▼] │ 170 + ├─────────────────────────────────────┤ 171 + │ ┌─────────┐ ┌─────────┐ │ 172 + │ │ Pushups │ │ Weight │ ← drag │ 173 + │ │ 45 │ │ 📈 │ to │ 174 + │ └─────────┘ └─────────┘ reorder │ 175 + │ ┌───────────────────────┐ │ 176 + │ │ Sleep (7-day trend) │ ← resize │ 177 + │ └───────────────────────┘ │ 178 + └─────────────────────────────────────┘ 179 + ``` 180 + 181 + ### Phase 3: Template Gallery 182 + ``` 183 + "Add Widget" → Browse templates: 184 + 📊 Habit Tracker - daily check-ins with streaks 185 + 💰 Budget Tracker - spending categories 186 + 📰 Feed Reader - RSS/news aggregation 187 + 🏃 Fitness Dashboard - steps, calories, workouts 188 + ⏱️ Time Tracker - pomodoro / deep work hours 189 + ``` 190 + 191 + ### Phase 4: Natural Language 192 + ``` 193 + "track my daily water intake" 194 + → Creates: series + scalar widget + input form 195 + 196 + "show me a chart of my weight over 30 days" 197 + → Creates: timeline widget bound to existing weight series 198 + ``` 199 + 200 + --- 201 + 202 + ## File Structure 203 + 204 + ``` 205 + app/widgets/ 206 + ├── index.js # Entry point 207 + ├── system.js # Core registration & lifecycle 208 + ├── api.js # window.app.widgets API 209 + ├── data-source.js # Query and data fetching 210 + ├── template-engine.js # Template compilation 211 + ├── types/ 212 + │ ├── base.js # Base widget class 213 + │ ├── scalar.js # Single value 214 + │ ├── list.js # Scrollable list 215 + │ ├── table.js # Tabular data 216 + │ ├── stats.js # KPI cards 217 + │ ├── timeline.js # Event timeline 218 + │ ├── chart.js # Charts/graphs 219 + │ ├── carousel.js # Item carousel 220 + │ └── gauge.js # Progress/gauge 221 + ├── templates/ 222 + │ ├── registry.js # Template registration 223 + │ └── builtins.js # Built-in templates 224 + └── sheets/ 225 + ├── manager.js # Persistence 226 + └── definitions.js # Pre-built dashboards 227 + ``` 228 + 229 + --- 230 + 231 + ## Implementation Phases 232 + 233 + ### Phase 1: Foundation (2-3 weeks) 234 + - Widget system API (register, create, list) 235 + - `peek-widget` base component 236 + - 3 basic types: scalar, list, stats 237 + - Widget container/sheet support 238 + - Tests and docs 239 + 240 + ### Phase 2: Visualization (2-3 weeks) 241 + - Table, timeline, chart, carousel widgets 242 + - Template system with registration 243 + - Template examples library 244 + - Developer guide 245 + 246 + ### Phase 3: Integration (1-2 weeks) 247 + - Extension widget registration 248 + - Custom data fetchers 249 + - Widget sheet persistence 250 + - Observable/signal support 251 + 252 + ### Phase 4: Polish (1-2 weeks) 253 + - Performance (virtual scrolling, caching) 254 + - Accessibility (ARIA, keyboard nav) 255 + - Template gallery 256 + - Mobile responsive 257 + 258 + --- 259 + 260 + ## Integration Points 261 + 262 + ### With Feeds 263 + - Series data → scalar, stats, timeline, chart widgets 264 + - Feed items → list, table, carousel widgets 265 + - Query abstraction over `item_events` table 266 + 267 + ### With Components 268 + - `peek-grid` + `peek-grid-item` for layout 269 + - `peek-card` for widget frame 270 + - `peek-list`, `peek-button`, etc. for content 271 + 272 + ### With Extensions 273 + - Extensions register custom widget types 274 + - Extensions provide custom data fetchers 275 + - Widget types stored as items in datastore 276 + 277 + --- 278 + 279 + ## Related TODO.md Items 280 + 281 + - Widgets (primary) 282 + - Metadata QS Reflection (widgets display metrics) 283 + - Window Templates (widgets as template components) 284 + - Observability HUD (widget dashboard use case) 285 + - Daytum-style tracking (series + widgets) 286 + 287 + --- 288 + 289 + ## Open Questions 290 + 291 + 1. **Refresh strategy**: Poll vs push for real-time updates? 292 + 2. **Template syntax**: Plain literals vs lightweight template language? 293 + 3. **Widget sizing**: Fixed sizes vs fluid/responsive? 294 + 4. **Persistence**: Per-profile widget sheets or global? 295 + 5. **Migration**: How to upgrade widget definitions across versions?