···11+# YouTube Blank White Screen — Diagnosis & Fix Plan
22+33+## Summary
44+55+Opening `https://youtube.com/watch?v=Z4cS2Ivg2-M` in the Peek Electron app results in a blank white screen. After thorough investigation, the **most likely root cause** is the Ghostery adblocker (`@ghostery/adblocker-electron`) blocking critical YouTube resources. A secondary contributing factor may be `WebContentsForceDark` (if dark mode is set to "force"). There are also several minor issues around permission handling and error visibility.
66+77+---
88+99+## Architecture Context
1010+1111+The page loading pipeline:
1212+1313+1. `window-open` IPC handler in `ipc.ts` (~line 2000) detects `https://` URL, sets `useCanvas = true`
1414+2. Creates a frameless, transparent `BrowserWindow` with `webviewTag: true`
1515+3. Loads `peek://app/page/index.html?url=<encoded-youtube-url>&x=...&y=...&width=...&height=...`
1616+4. `page.js` runs `initWebview()`:
1717+ - Fetches session partition via `api.profiles.getPartition()` (returns `persist:<profileId>`)
1818+ - Sets `webview.partition = "persist:<profileId>"`
1919+ - Sets `webview.src = targetUrl` (the YouTube URL)
2020+5. The `<webview>` tag in `index.html` (line 358): `<webview id="content" allowpopups></webview>`
2121+2222+**Electron version**: 40.x (Chromium 144) — this is a January 2026 release, well within YouTube's supported Chromium range.
2323+2424+---
2525+2626+## Root Cause Analysis
2727+2828+### 1. CRITICAL: Adblocker Blocking YouTube Resources
2929+3030+**File**: `/Users/dietrich/misc/mpeek/backend/electron/adblocker.ts`
3131+3232+The app uses `@ghostery/adblocker-electron` with `ElectronBlocker.fromPrebuiltAdsAndTracking()` (line 52). This loads EasyList + EasyPrivacy filter lists, which are known to aggressively block YouTube's ad-serving infrastructure.
3333+3434+**The problem**: YouTube's modern SPA architecture loads its video player, UI components, and video streams through URLs that overlap with ad-serving patterns. The Ghostery blocker intercepts requests at the Electron session level (`session.webRequest`), which means it affects the webview's session directly. When critical YouTube JS bundles or API calls are blocked, the page renders as blank white — YouTube's error handling shows nothing rather than a broken page.
3535+3636+**Specifically blocked resources likely include**:
3737+- `googlevideo.com` streaming URLs (may match tracker patterns)
3838+- YouTube's analytics/tracking endpoints that are required for the player to initialize
3939+- `play.google.com` or `accounts.google.com` auth-related requests
4040+- Various `*.doubleclick.net` or `*.googlesyndication.com` requests that YouTube's player JS depends on before rendering
4141+4242+The blocker attaches to the profile session (line 105-106: `getProfileSession()` then `enableBlockingInSession()`), which is the same session the webview uses (set via `webview.partition = "persist:<profileId>"`).
4343+4444+**Evidence**: There are no permission handlers (`setPermissionRequestHandler`, `setPermissionCheckHandler`) configured anywhere in the codebase — the only request interception is the adblocker's `webRequest` hooks.
4545+4646+### 2. MODERATE: WebContentsForceDark May Corrupt YouTube Rendering
4747+4848+**File**: `/Users/dietrich/misc/mpeek/backend/electron/entry.ts` (lines 679-686)
4949+5050+If the user's dark mode setting is `"force"`, the app applies:
5151+```
5252+app.commandLine.appendSwitch('enable-features',
5353+ 'WebContentsForceDark:inversion_method/cielab_based/image_behavior/none'
5454+);
5555+```
5656+5757+This Chromium feature inverts colors at the compositing level. YouTube has its own dark mode, and forcing dark mode via Chromium's compositor can cause rendering conflicts — potentially resulting in white-on-white or invisible content. YouTube's custom web components and shadow DOMs may not respond well to forced color inversion.
5858+5959+### 3. MINOR: No User-Agent Override
6060+6161+There is **no** user-agent manipulation anywhere in the codebase for webview content. The default Electron user-agent string is used, which includes `Electron/40.x.x` in the UA string. YouTube does check user agents and sometimes blocks or degrades service for non-standard browsers.
6262+6363+However, Electron 40 / Chromium 144's default UA should be recent enough that YouTube wouldn't outright block it. YouTube's UA detection primarily targets very old Chromium versions or obvious bot strings.
6464+6565+**The default Electron UA looks like**:
6666+```
6767+Mozilla/5.0 (Macintosh; Intel Mac OS X ...) AppleWebKit/537.36 (KHTML, like Gecko) Peek/0.0.1 Chrome/144.0.7559.60 Electron/40.0.0 Safari/537.36
6868+```
6969+7070+The `Electron/40.0.0` token is unusual but YouTube typically doesn't block based on this.
7171+7272+### 4. MINOR: `allowServiceWorkers: false` on the `peek://` Scheme
7373+7474+**File**: `/Users/dietrich/misc/mpeek/backend/electron/protocol.ts` (line 81)
7575+7676+The custom protocol `peek://` has `allowServiceWorkers: false`. However, this only affects pages loaded via the `peek://` scheme — the webview loads `https://youtube.com` which uses its own session and the standard HTTPS scheme. Service workers for YouTube should work normally within the webview's session.
7777+7878+**This is NOT the cause** — confirmed by the fact that `allowServiceWorkers` only applies to the scheme registration, not to the webview's guest content.
7979+8080+### 5. MINOR: Silent Error Swallowing
8181+8282+**File**: `/Users/dietrich/misc/mpeek/app/page/page.js` (lines 897-899)
8383+8484+```javascript
8585+webview.addEventListener('did-fail-load', (e) => {
8686+ if (e.errorCode === -3) return; // ERR_ABORTED — normal for SPA navigations
8787+ console.error('[page] Load failed:', e.errorCode, e.errorDescription);
8888+});
8989+```
9090+9191+The error is logged to console but never shown to the user. If YouTube's main frame fails to load, the user sees a blank white screen with no feedback. The `dom-ready` handler (line 903) also sets a white background as default when no page background is detected — masking the failure further.
9292+9393+### 6. NO DRM/Widevine Configuration
9494+9595+There is **no** Widevine CDM (Content Decryption Module) configuration anywhere in the codebase. Standard Electron does NOT include Widevine by default. However:
9696+9797+- YouTube's standard video playback does not require DRM for most content
9898+- DRM is only needed for premium/purchased content and some music videos
9999+- A regular YouTube watch page should play non-DRM content without Widevine
100100+- The blank white screen is happening before video playback even begins (the entire page fails to render), so DRM is not the primary issue
101101+102102+### 7. NO Permission Handlers
103103+104104+There are no `setPermissionRequestHandler` or `setPermissionCheckHandler` calls anywhere in the Electron backend. This means:
105105+106106+- **Media autoplay**: Uses Electron's default behavior (generally allowed)
107107+- **Notifications**: Uses default (generally denied)
108108+- **Geolocation**: Uses default
109109+- **Camera/Microphone**: Uses default (denied unless explicitly allowed)
110110+111111+For basic YouTube video watching, the lack of explicit permission handlers should not cause a blank page. YouTube doesn't require camera/mic/notification permissions for regular video playback.
112112+113113+---
114114+115115+## Fix Plan
116116+117117+### Fix 1: Add YouTube to Adblocker Allowlist (PRIMARY FIX)
118118+119119+Add domain-based exception handling to the adblocker for YouTube and its required domains.
120120+121121+**Option A — Per-site allowlist in adblocker.ts**:
122122+```typescript
123123+const ADBLOCKER_ALLOWLIST = [
124124+ 'youtube.com',
125125+ 'www.youtube.com',
126126+ 'googlevideo.com',
127127+ 'ytimg.com',
128128+ 'yt3.ggpht.com',
129129+ 'accounts.google.com',
130130+];
131131+```
132132+133133+Before enabling the blocker, configure request filtering to skip these domains.
134134+135135+**Option B — Disable adblocker entirely for YouTube domains**:
136136+The `@ghostery/adblocker-electron` package supports custom filtering. Use the `isAllowlisted` callback or a custom filter rule like `@@||youtube.com^` and `@@||googlevideo.com^`.
137137+138138+**Option C — User-facing toggle per site**:
139139+Add a "disable ad blocking for this site" button to the navbar, storing the setting per-domain.
140140+141141+### Fix 2: Improve Error Visibility
142142+143143+Modify `did-fail-load` handler in `page.js` to show a visible error state:
144144+145145+```javascript
146146+webview.addEventListener('did-fail-load', (e) => {
147147+ if (e.errorCode === -3) return;
148148+ console.error('[page] Load failed:', e.errorCode, e.errorDescription);
149149+ // Show error in navbar or overlay instead of silent white screen
150150+ urlText.value = `Error ${e.errorCode}: ${e.errorDescription}`;
151151+ show({ source: 'shortcut' });
152152+});
153153+```
154154+155155+### Fix 3: Guard Against WebContentsForceDark on Complex Sites
156156+157157+Consider either:
158158+- Not applying `WebContentsForceDark` to webview content (only to peek:// internal pages)
159159+- Adding a per-site override mechanism
160160+- Using CSS `color-scheme` meta tag injection instead
161161+162162+### Fix 4: Set Standard User-Agent on Webview (Low Priority)
163163+164164+If YouTube does begin blocking, set a clean Chrome UA:
165165+166166+```javascript
167167+// In page.js initWebview():
168168+webview.useragent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36`;
169169+```
170170+171171+This strips the `Electron` and `Peek` tokens.
172172+173173+### Fix 5: Add Permission Handlers (Low Priority)
174174+175175+For media-rich sites, add explicit permission handling on the profile session:
176176+177177+```typescript
178178+profileSession.setPermissionRequestHandler((webContents, permission, callback) => {
179179+ const allowedPermissions = ['media', 'mediaKeySystem', 'midi', 'pointerLock', 'fullscreen'];
180180+ callback(allowedPermissions.includes(permission));
181181+});
182182+```
183183+184184+---
185185+186186+## Diagnostic Steps to Confirm
187187+188188+To verify the adblocker theory before implementing fixes:
189189+190190+1. **Quick test**: Temporarily disable the adblocker in settings, restart the app, and try YouTube again
191191+2. **Console logging**: Add `DEBUG=1` and watch for `[adblocker] Blocked:` log lines when loading YouTube
192192+3. **Network inspection**: Open DevTools on the webview guest (right-click the webview in the host page's DevTools, select "Inspect"), check the Network tab for blocked/failed requests
193193+4. **Isolated test**: Create a test that loads YouTube in a webview with a fresh session (no adblocker), confirm it renders
194194+195195+---
196196+197197+## Files Referenced
198198+199199+| File | Role |
200200+|------|------|
201201+| `/Users/dietrich/misc/mpeek/app/page/page.js` | Webview creation, URL loading, error handling |
202202+| `/Users/dietrich/misc/mpeek/app/page/index.html` | Page container with `<webview>` tag |
203203+| `/Users/dietrich/misc/mpeek/backend/electron/ipc.ts` | Window-open handler, canvas setup, guest webContents |
204204+| `/Users/dietrich/misc/mpeek/backend/electron/session-partition.ts` | Session/partition setup |
205205+| `/Users/dietrich/misc/mpeek/backend/electron/main.ts` | App initialization, no permission handlers |
206206+| `/Users/dietrich/misc/mpeek/backend/electron/entry.ts` | `WebContentsForceDark` command-line switch |
207207+| `/Users/dietrich/misc/mpeek/backend/electron/protocol.ts` | `peek://` scheme registration (`allowServiceWorkers: false`) |
208208+| `/Users/dietrich/misc/mpeek/backend/electron/adblocker.ts` | Ghostery adblocker — primary suspect |
209209+| `/Users/dietrich/misc/mpeek/package.json` | Electron 40.x / Chromium 144 |
+191
backend/electron/display-watcher-research.md
···11+# Display Watcher Research: Primary/Main Display Handling on macOS
22+33+Research for improving Peek's display-watcher to handle the "windows stay on laptop when external display is plugged in" problem.
44+55+---
66+77+## 1. macOS Display Concepts: "Primary" vs "Main"
88+99+macOS has **two distinct concepts** that are often confused:
1010+1111+### Primary Display (NSScreen.screens[0])
1212+- The display at coordinate origin (0,0) in the global display coordinate space.
1313+- This is the display that has the **menu bar** and **Dock** (by default).
1414+- The user sets this in **System Settings > Displays > Arrangement** by dragging the white menu bar indicator to a display.
1515+- The primary display does **NOT change automatically** when you plug in an external monitor. It stays wherever the user last set it.
1616+- `NSScreen.screens[0]` always returns this display.
1717+- In Electron: `screen.getPrimaryDisplay()` returns this display.
1818+1919+### Main Display (NSScreen.main)
2020+- The display that contains the **currently focused window** (the key window).
2121+- This changes dynamically as the user clicks on windows on different displays.
2222+- There is **no Electron API** that directly exposes `NSScreen.main`. Electron only exposes `getPrimaryDisplay()` which maps to the menu-bar display.
2323+2424+### Key Insight
2525+**Plugging in an external monitor does NOT change the primary display.** The laptop screen remains primary (has menu bar at 0,0) unless the user manually drags the white bar in System Settings > Displays. This means `screen.getPrimaryDisplay()` returns the laptop screen both before and after connecting an external monitor.
2626+2727+### What DOES change when you plug in a monitor:
2828+- A `display-added` event fires in Electron.
2929+- The coordinate space may shift (the new display gets positioned relative to the primary).
3030+- The `bounds` and `workArea` of existing displays may change (e.g., if the arrangement shifts).
3131+- `display-metrics-changed` events fire for affected displays.
3232+3333+---
3434+3535+## 2. Why Do Other Apps Move Windows to the External Display?
3636+3737+**Short answer: They mostly don't. macOS doesn't do this automatically either.**
3838+3939+### What actually happens:
4040+1. **macOS does NOT auto-move windows to newly connected displays.** When you plug in an external monitor, all existing windows stay where they are.
4141+2. **Window restore from sleep/wake**: When a MacBook is in clamshell mode (lid closed with external display), all windows are on the external. When you unplug and reopen, macOS moves them to the laptop screen. When you re-plug, macOS remembers and restores them to the external. This is **macOS window server behavior**, not individual app behavior.
4242+3. **Some apps remember per-display positions**: Apps like Safari, Xcode, and Terminal remember which display their windows were last on and restore to that display when it reappears. This is standard `NSWindow` state restoration (`restorable` property + `NSWindowRestoration` protocol).
4343+4. **The perception that "other apps move to the big screen"** likely comes from:
4444+ - Clamshell mode wake/sleep cycling (macOS handles this at the window server level)
4545+ - Apps that were previously used on that external display and have state restoration
4646+ - The user manually having moved windows there in a previous session
4747+4848+### What macOS DOES do automatically:
4949+- **Clamshell mode**: When MacBook lid closes with external display connected, all windows move to external. When external disconnects, they move to laptop screen. When external reconnects with lid closed, they move back. This is window server behavior.
5050+- **Display removal**: Windows on a removed display get relocated to the nearest remaining display (shoved into top-left typically).
5151+- **"Displays have separate Spaces"** (System Settings > Desktop & Dock): When enabled, each display has its own Space, and macOS manages window-to-Space assignment.
5252+5353+---
5454+5555+## 3. Electron Events When External Display is Plugged In
5656+5757+When an external display is connected, Electron fires these events (in approximate order):
5858+5959+1. **`display-added`** - with the new Display object
6060+ - `display.id` - unique numeric ID
6161+ - `display.bounds` - position in global coordinate space
6262+ - `display.workArea` - usable area excluding dock/menu bar
6363+ - `display.internal` - `false` for external displays, `true` for built-in
6464+ - `display.label` - e.g., "LG UltraFine" (human-readable name)
6565+6666+2. **`display-metrics-changed`** - may fire for EXISTING displays
6767+ - The laptop display's `workArea` might change (e.g., if arrangement shifts coordinate space)
6868+ - `changedMetrics` array tells you what changed: `"bounds"`, `"workArea"`, `"scaleFactor"`, `"rotation"`
6969+7070+3. Multiple events may fire in rapid succession (hence the existing 500ms debounce).
7171+7272+### The `internal` property (key for our use case)
7373+Electron's Display object has an `internal: boolean` property:
7474+- `true` = built-in laptop display
7575+- `false` = external display
7676+7777+This is **the critical signal** for detecting "user plugged in an external monitor" vs "display configuration changed for other reasons."
7878+7979+### What `getPrimaryDisplay()` returns:
8080+- Before plugging in external: laptop display (assuming user hasn't changed primary)
8181+- After plugging in external: **still the laptop display** (unchanged)
8282+- Only changes if user manually sets a different primary in System Settings
8383+8484+---
8585+8686+## 4. System Settings > Displays > Arrangement
8787+8888+### The white menu bar indicator:
8989+- Dragging it to a display makes that display the **primary display** (coordinate origin 0,0, gets the menu bar).
9090+- This is a **manual user action**, not automatic.
9191+- When changed, Electron fires `display-metrics-changed` for both the old and new primary displays (bounds change because coordinate origin moves).
9292+9393+### Display arrangement:
9494+- Users can drag display thumbnails to set relative positioning (left/right/above/below).
9595+- This affects the global coordinate space — display `bounds.x` and `bounds.y` reflect arrangement.
9696+- New displays are initially placed to the right of existing displays by default.
9797+9898+### "Mirror Displays":
9999+- When enabled, both displays show the same content.
100100+- Electron sees this as a single display (the mirrored display disappears from `getAllDisplays()`).
101101+102102+---
103103+104104+## 5. How Other Electron Apps Handle This
105105+106106+### VS Code
107107+- Does NOT auto-move windows to external displays on connect.
108108+- Restores window positions from saved state on launch.
109109+- Uses `screen.getDisplayMatching(bounds)` to validate saved positions.
110110+- If a window's saved position is off-screen, it resets to a default position on the primary display.
111111+112112+### Slack, Discord
113113+- Do NOT auto-move windows when displays change.
114114+- Standard Electron behavior: windows stay where they are.
115115+116116+### General Electron ecosystem
117117+- No mainstream Electron app automatically moves windows to a newly connected external display.
118118+- The standard approach is: save window positions, restore on relaunch, reposition if off-screen.
119119+120120+---
121121+122122+## 6. Concrete Recommendations for Peek
123123+124124+### The Core Question
125125+Should Peek move windows to a newly connected external display even when they weren't previously on that display?
126126+127127+**Recommendation: No, not by default.** This would be surprising behavior that no other app does. BUT there are several things Peek should do:
128128+129129+### Recommendation A: Improve Phase 2 Restore (already exists, verify it works)
130130+The current Phase 2 already handles restoring displaced windows to a re-appearing display. This covers the most common real scenario:
131131+1. User has windows on external display
132132+2. User unplugs external (Phase 1 saves home, Phase 1b redistributes to laptop)
133133+3. User re-plugs external (Phase 2 restores to external)
134134+135135+**Action**: Verify Phase 2 works reliably with the `internal` property as an additional matching signal. Currently matches by display ID then resolution. Could also match by `internal === false` when there was exactly one external display before.
136136+137137+### Recommendation B: Detect "primary display changed" scenario
138138+When `display-metrics-changed` fires and the primary display ID changes (compare `screen.getPrimaryDisplay().id` against saved primary ID), this means the user manually changed their primary display in System Settings. In this case:
139139+1. The menu bar and Dock have moved.
140140+2. The user likely wants their "main" workspace on the new primary.
141141+3. Consider offering to move windows, or at minimum re-run Phase 3 repositioning.
142142+143143+**Action**: Track `_previousPrimaryDisplayId` and detect when it changes.
144144+145145+### Recommendation C: Add a "Move to Display" command
146146+Rather than auto-moving, give the user explicit control:
147147+- Command palette action: "Move all windows to [display name]"
148148+- Could use the `display.label` property for a human-readable display picker.
149149+- This respects user intent while solving the "I want my windows on the big screen" problem.
150150+151151+### Recommendation D: Use `display.internal` for smarter defaults
152152+When a new external display is added and there are windows on the internal display, Peek could:
153153+- **Option 1 (Conservative)**: Do nothing extra beyond current Phase 2 restore. This is the safest.
154154+- **Option 2 (Moderate)**: If the external display is significantly larger than the internal display (e.g., >1.5x work area), and there are displaced windows from a previous session, be more aggressive about matching them to the new external display.
155155+- **Option 3 (Aggressive)**: Auto-move all windows to the largest external display. This matches the user's stated desire but is non-standard behavior.
156156+157157+**Recommended: Option 1 + Recommendation C.** Don't auto-move, but make it easy to move manually.
158158+159159+### Recommendation E: Handle the clamshell mode scenario
160160+When the laptop display disappears (`internal: true` display removed) and an external display exists, all windows should already be on the external. When the laptop display reappears (lid opened), don't move windows off the external display back to the laptop. The current Phase 3 may inadvertently do this if it sees windows as being on a "changed" display.
161161+162162+**Action**: When a display is added and it's `internal: true`, be careful not to pull windows back to it from external displays where they're happily sitting.
163163+164164+### Recommendation F: Track and persist display topology
165165+Save the last-known display configuration (display IDs, labels, internal/external, arrangement) to disk. On app launch, compare saved topology to current topology. If they match, restore windows to saved positions. If different (e.g., external display now present that wasn't before), use current Phase 2/3 logic.
166166+167167+This enables the scenario: User always docks at work with the same external monitor. Peek remembers "when this monitor is connected, windows go here."
168168+169169+---
170170+171171+## Summary of Key Technical Facts
172172+173173+| Question | Answer |
174174+|----------|--------|
175175+| Does primary display change on plug-in? | **No.** Only changes via manual System Settings action. |
176176+| Does macOS auto-move windows to new display? | **No.** Only in clamshell wake/sleep scenarios. |
177177+| What Electron events fire? | `display-added`, then `display-metrics-changed` for existing displays |
178178+| Can we detect internal vs external? | **Yes.** `display.internal` property. |
179179+| Can we get display name? | **Yes.** `display.label` property (e.g., "LG UltraFine"). |
180180+| Do other Electron apps auto-move? | **No.** None do this. |
181181+| Best approach for Peek? | Improve restore logic + add explicit "Move to Display" command. |
182182+183183+---
184184+185185+## Implementation Priority
186186+187187+1. **Verify Phase 2 restore works end-to-end** (unplug external, re-plug — windows should return). This is the highest-value fix because it covers the most common real-world scenario.
188188+2. **Add `display.internal` to matching logic** in `findMatchingNewDisplay()` for better display identification.
189189+3. **Add "Move all windows to [display]" command** to command palette.
190190+4. **Track primary display ID changes** for the rare case where the user changes it manually.
191191+5. **Persist display topology** for cross-session position memory (longer term).
+2-2
extensions/entities/background.js
···1212import { extractMicroformatEntities } from './extractors/microformats.js';
1313import { extractStructuredDataEntities, extractPageMetadata } from './extractors/structured-data.js';
1414import { processEntities } from './entity-matcher.js';
1515-import { getEntities, getObservations } from './entity-store.js';
1515+import { getEntities, getObservations, setEntityFeedback } from './entity-store.js';
1616import { registerNoun, unregisterNoun } from 'peek://ext/cmd/nouns.js';
17171818const api = window.app;
···455455export default extension;
456456457457// Export for UI
458458-export { getEntities, getObservations, extractCurrentPage };
458458+export { getEntities, getObservations, extractCurrentPage, setEntityFeedback };
···88- This file is not for notes or description - link to documents in ./notes for that
99- Checkbox states: `- [ ]` pending, `- [~]` in-progress (move to WIP.md), `- [x]` done (move to CHANGELOG.md)
10101111-## Design principles / capablities / etc
1111+## Design principles / capabilities / etc
12121313core
1414- feels like home: trust, comfort, control
···1616- sleep at night because no idea or anything you saw is ever lost
1717- create, save, classify at the speed of thought
18181919-what makes a home
1919+what makes a home / kitchen / workshop
2020- everything is right where you need it, b/c you control what is where
2121-- when you know what is where, you can make things without frustration
2222-- you generally know what’s happening - who’s around/coming/going, etc, not a lot of surprises
2121+- when you know what is where, you can make things with less frustration
2222+- you generally know what’s happening - who’s around/coming/going, etc, not too much surprise (but some!)
23232424what makes magical mind-readingness
2525-- frecency everywhere all of the time: user actions are training data for sorting
2525+- frecency everywhere all of the time
2626+- user actions are training data for sorting
2727+- the thing you were thinking of is already at the top of the list
26282727-what provides awareness
2929+what makes awareness
2830- actions tracked at the core
2931- metrics generated, rollups displayed, synthesis emergent
3232+- task context with you at the center (vs asking a companion robot - disempowering!)
30333134synthesis
3235- frecency + adaptive matching gives experience/feeling of magical mind-readingness