feat(mascot): floating desktop mascot via native NSPanel + WKWebView (macOS)#1203
Conversation
The vendored tauri-cef runtime can't render transparent windowed-mode browsers (CEF clamps `BrowserSettings.background_color` alpha to 0xFF for windowed browsers; only OSR mode supports transparency, which the runtime doesn't enable). To get a true floating mascot, this bypasses Tauri's runtime on macOS and hosts the existing `<YellowMascot>` in a free-standing NSPanel with an embedded WKWebView. - `mascot_native_window.rs` (macOS-only): borderless NonactivatingPanel pinned to the bottom-right corner of the main screen, transparent + shadowless, status-bar level, can-join-all-spaces. WKWebView with the private `drawsBackground = NO` KVC trick so the page CSS shows through. Loads the Vite dev URL with `?window=mascot` so the React entry can branch on it (the panel is outside Tauri so `getCurrentWindow()` doesn't apply here). - Hover-to-flee: the panel is `ignoresMouseEvents=true` so the cursor always passes through. A Foundation NSTimer polls `NSEvent.mouseLocation()` against the panel frame at 20Hz and toggles `data-flee` on the document so the page CSS slides the mascot out of the way when the cursor is over it. - Tray menu gains a "Toggle floating mascot" entry. `mascot_window_show` / `_hide` Tauri commands delegate to the native module on macOS and return an error elsewhere (Linux/Windows would need their own native webview path). Known limitations: dev-only for now (production bundle path needs a localhost HTTP server or a `file://` loader); no Tauri IPC bridge so the mascot face is hard-coded to idle until we add a WKScriptMessageHandler.
Replace the screen-spanning corner-to-corner bounce with a small hop: the panel slides up exactly one panel-height (105pt) when the cursor enters its home rect, and slides back the moment the cursor leaves. Hover detection now compares against a fixed home rect captured at show time rather than the panel's current frame, so the return-to-home trigger fires whenever the cursor moves away from the original spot — regardless of where the panel has currently hopped to. Drops the now-unused CSS `data-flee` flee animation since the visual move is driven entirely by `setFrame:display:animate:` on the AppKit side.
Pre-push hooks reformatted the mascot files and flagged an unused helper after the home-rect refactor. No behavior change.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds a macOS-only floating mascot window: enables macOS dependency features, implements an NSPanel+WKWebView mascot host with hover-driven animations and show/hide/is_open plumbing, wires Tauri commands and a tray toggle, and adds frontend routing plus a MascotWindowApp and mascot visual props/styles. ChangesFloating Mascot Window
Sequence DiagramsequenceDiagram
actor User
participant Frontend as Frontend (React)
participant Tauri as Tauri Backend
participant Native as macOS Native (NSPanel)
participant WK as WKWebView
participant Timer as NSTimer
User->>Frontend: Click tray "Toggle floating mascot" / request show
Frontend->>Tauri: invoke mascot_window_show()
Tauri->>Native: show(app)
Native->>Native: Resolve page source (append window=mascot)
Native->>Native: Compute frame & build NSPanel
Native->>WK: Create WKWebView and load URL/file
Native->>Timer: Spawn repeating hover NSTimer
Timer->>Native: Poll cursor position
Native->>Native: Animate panel between Home/HopUp
User->>Frontend: Request hide
Frontend->>Tauri: invoke mascot_window_hide()
Tauri->>Native: hide()
Native->>Timer: Invalidate timer
Native->>Native: Order out panel & drop webview
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly Related PRs
Suggested Reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src-tauri/src/lib.rs`:
- Around line 961-970: The tray "Toggle floating mascot" MenuItem is always
created with MenuItem::with_id as mascot_item but the handler
(mascot_window_show) will fail on Windows and on macOS release until the prod
URL exists; change the code so mascot_item is only constructed and added to the
menu on platforms/builds where mascot_window_show is supported: wrap the
MenuItem::with_id call and its inclusion in the Menu::with_items array in a
cfg/conditional (e.g., cfg!(not(windows)) and the macOS-release check that
verifies the production URL exists), and ensure any handler registration
referencing mascot_item/mascot_window_show is likewise gated so no broken menu
entry is shipped.
In `@app/src-tauri/src/mascot_native_window.rs`:
- Around line 154-165: Replace the focus-dependent NSScreen::mainScreen() call
in bottom_right_frame with the primary screen obtained from
NSScreen::screens(mtm).first(), and use that screen's frame instead of the
current map/unwrap_or_else that falls back to the hard-coded 1440x900; adjust
the same change for the other occurrence at the referenced block (lines
~230–244) so both use NSScreen::screens(mtm).first() to compute x/y with
PANEL_SIZE and PANEL_MARGIN and remove the hard-coded fallback to ensure the
panel anchors to the menu-bar host screen.
In `@app/src/main.tsx`:
- Around line 33-40: The code unconditionally calls getActiveUserIdFromCore()
even in mascot mode where isMascotWindow is true (and in native WKWebView paths
that will reject), causing bootRender() to be delayed; update the bootstrap to
skip calling getActiveUserIdFromCore() when isMascotWindow is true (or when
tauriRuntimeAvailable() is false/native WKWebView), ensuring bootRender() is
invoked immediately for mascot windows; adjust both places that perform the
active-user bootstrap (the initial call near isMascotWindow and the later block
around the other bootstrap) to short-circuit and proceed to bootRender() without
awaiting getActiveUserIdFromCore() when isMascotWindow is set.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 500ca663-3094-4e06-8682-65b0cf8fb88a
⛔ Files ignored due to path filters (1)
app/src-tauri/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (6)
app/src-tauri/Cargo.tomlapp/src-tauri/src/lib.rsapp/src-tauri/src/mascot_native_window.rsapp/src/index.cssapp/src/main.tsxapp/src/mascot/MascotWindowApp.tsx
Down to ~79pt from 105 (140 → 105 → 79) so the floating mascot sits
unobtrusively in the corner. At that size the warm yellow→amber arm
inner-shadows read as a bright halo rather than a shadow, and the soft
ground shadow disappears.
- New `groundShadowOpacity` and `compactArmShading` props on
`MascotCharacter` and `YellowMascot`. The ground gradient's center
opacity becomes configurable (defaults to 0.35); compact mode swaps
the two warm-tinted arm inner-shadow `feColorMatrix`es for a single
pure-black shadow so the under-arm reads as a real shadow at small
sizes.
- `MascotWindowApp` opts in: `groundShadowOpacity={0.75}`
`compactArmShading`. Full-size views (e.g. `/human`) keep the
original hand-painted look untouched.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
app/src/features/human/Mascot/yellow/MascotCharacter.tsx (1)
601-653: 💤 Low valueConsider applying
compactArmShadingto filterf13(steady right arm) for consistency.The steady right arm filter (
f13) still uses hardcoded color matrices while the waving right arm (f4) and left arm (f5) now use the computedarmHighlightMatrix/rightArmShadowMatrix/leftArmShadowMatrix. WhencompactArmShadingis enabled, this arm will retain the warm yellow tints while the others use darker neutrals, creating visual inconsistency at small render sizes.♻️ Proposed fix to apply computed matrices to f13
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" /> - <feColorMatrix - type="matrix" - values="0 0 0 0 0.973501 0 0 0 0 0.909066 0 0 0 0 0.671677 0 0 0 1 0" - /> + <feColorMatrix type="matrix" values={armHighlightMatrix} /> <feBlend mode="normal" in2="shape" result="effect1_innerShadow" /> ... <feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" /> - <feColorMatrix - type="matrix" - values="0 0 0 0 0.796078 0 0 0 0 0.576471 0 0 0 0 0.0980392 0 0 0 0.8 0" - /> + <feColorMatrix type="matrix" values={leftArmShadowMatrix} />🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/features/human/Mascot/yellow/MascotCharacter.tsx` around lines 601 - 653, The steady right arm filter id={p('f13')} still uses hardcoded feColorMatrix values; update it to use the computed matrices when compactArmShading is enabled so shading matches the other arms. Specifically, in MascotCharacter (filter f13) replace the hardcoded highlight and shadow feColorMatrix inputs with armHighlightMatrix and the appropriate rightArmShadowMatrix/leftArmShadowMatrix logic (same approach used in filters f4 and f5), and ensure the conditional uses compactArmShading to select the computed matrix values so small-render consistency is preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@app/src/features/human/Mascot/yellow/MascotCharacter.tsx`:
- Around line 601-653: The steady right arm filter id={p('f13')} still uses
hardcoded feColorMatrix values; update it to use the computed matrices when
compactArmShading is enabled so shading matches the other arms. Specifically, in
MascotCharacter (filter f13) replace the hardcoded highlight and shadow
feColorMatrix inputs with armHighlightMatrix and the appropriate
rightArmShadowMatrix/leftArmShadowMatrix logic (same approach used in filters f4
and f5), and ensure the conditional uses compactArmShading to select the
computed matrix values so small-render consistency is preserved.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f415416d-70e9-4aa6-81af-f41ff8d888e0
📒 Files selected for processing (4)
app/src-tauri/src/mascot_native_window.rsapp/src/features/human/Mascot/YellowMascot.tsxapp/src/features/human/Mascot/yellow/MascotCharacter.tsxapp/src/mascot/MascotWindowApp.tsx
✅ Files skipped from review due to trivial changes (1)
- app/src/mascot/MascotWindowApp.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- app/src-tauri/src/mascot_native_window.rs
Replace the dev-only `resolve_page_url` with a `PageSource` enum that falls back to the packaged `index.html` when no dev URL is configured. The bundled path uses `WKWebView.loadFileURL:allowingReadAccessToURL:` with the resource directory as the read-access root so ESM imports from the Vite build resolve correctly (plain `loadRequest` on a file:// URL forbids cross-origin sub-resource loads, which Vite's chunk graph triggers immediately). We probe `resource_dir() / index.html` first and fall back to `resource_dir() / dist / index.html` so layout differences across tauri-bundler versions don't break the load. Adds the `url` crate as a direct dep (was already transitive) so we can use `Url::from_file_path` for proper percent-encoding.
- Tray "Toggle floating mascot" entry is now `cfg(target_os = "macos")` so it doesn't surface on Windows where `mascot_window_show` would always error. - `bottom_right_frame` / `slot_frame` use `NSScreen::screens(mtm)[0]` via a `primary_screen_frame` helper. `NSScreen::mainScreen` is focus-dependent (returns the screen with the active key window) and was repositioning the panel under whichever display had focus instead of pinning it to the menu-bar host. - `main.tsx` skips the `getActiveUserIdFromCore()` bootstrap in mascot mode. The native WKWebView has no Tauri IPC channel, so that call just rejected after a roundtrip and stalled first paint.
* feat(remotion): Ghosty character library with transparent MOV variants (tinyhumansai#1059) Co-authored-by: WOZCODE <contact@withwoz.com> * feat(composio/gmail): sync into memory tree (Slack-parity) (tinyhumansai#1056) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(scheduler-gate): throttle background AI on battery / busy CPU (tinyhumansai#1062) * fix(core,cef): run core in-process and stop orphaning CEF helpers on Cmd+Q (tinyhumansai#1061) * ci: add dedicated staging release workflow (tinyhumansai#1066) * fix(sentry): Rust source context + per-release deploy marker (tinyhumansai#405) (tinyhumansai#1067) * fix(welcome): re-enable OAuth buttons with focus/timeout recovery (tinyhumansai#1049) (tinyhumansai#1069) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(dependencies): update pnpm-lock.yaml and Cargo.lock for package… (tinyhumansai#1082) * fix(onboarding): personalize welcome agent greeting with user identity (tinyhumansai#1078) * fix(chat): make agent message bubbles fit content width (tinyhumansai#1083) * Feat/dmg checks (tinyhumansai#1084) * fix(linux): Add X11 platform flags to .deb package launcher (tinyhumansai#1087) Co-authored-by: unn-Known1 <unn-known1@users.noreply.github.com> * fix(sentry): auto-send React events; collapse core→tauri for desktop (tinyhumansai#1086) Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> * fix(cef): run blank reload guard on the CEF UI thread (tinyhumansai#1092) * fix(app): reload webview instead of restart_app in dev mode (tinyhumansai#1068) (tinyhumansai#1071) * fix(linux): deliver X11 ozone flags via custom .desktop template (tinyhumansai#1091) * fix(webview-accounts): retry data-dir purge so CEF handle race doesn't leak cookies (tinyhumansai#1076) (tinyhumansai#1081) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> * fix(webview/slack): media perms + deep-link isolation (tinyhumansai#1074) (tinyhumansai#1080) Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> * ci(release): split staging vs production workflows; promote staging tags (tinyhumansai#1094) * Update release-staging.yml (tinyhumansai#1097) * chore(staging): v0.53.5 * chore(staging): v0.53.6 * ci(staging): cut staging from main; add act local-debug helper (tinyhumansai#1099) * chore(staging): v0.53.7 * fix(ci): correct sentry-cli download URL and trap scope (tinyhumansai#1100) * chore(staging): v0.53.8 * feat(chat): forward thread_id to backend for KV cache locality (tinyhumansai#1095) * fix(ci): bump pinned sentry-cli to 3.4.1 (2.34.2 was never published) (tinyhumansai#1102) * chore(staging): v0.53.9 * fix(ci): drop bash trap in upload_sentry_symbols.sh; inline cleanup (tinyhumansai#1103) * chore(staging): v0.53.10 * refactor(session): flatten session_raw/, switch md to YYYY_MM_DD (tinyhumansai#1098) * Add full Composio managed-auth toolkit catalog (tinyhumansai#1093) * ci: add diff-aware 80% coverage gate (Vitest + cargo-llvm-cov) (tinyhumansai#1104) * feat(scripts): pnpm work + pnpm debug for agent-driven workflows (tinyhumansai#1105) * ci: pull pnpm into CI image, drop redundant setup steps (tinyhumansai#1107) * docs: add Cursor Cloud specific instructions to AGENTS.md (tinyhumansai#1106) Co-authored-by: Cursor Agent <cursoragent@cursor.com> * chore(staging): v0.53.11 * docs: surface 80% coverage gate and scripts/debug runners (tinyhumansai#1108) * feat(app): show Composio integrations as sorted icon grid on Skills (tinyhumansai#1109) Co-authored-by: Cursor Agent <cursoragent@cursor.com> * feat(composio): client-side trigger enable/disable toggles (tinyhumansai#1110) * feat(skills): channels grid + integrations card polish; tolerant Composio trigger decode (tinyhumansai#1112) * chore(staging): v0.53.12 * feat(home): early-bird banner + assistant→agent terminology (tinyhumansai#1113) * feat(updater): in-app auto-update with auto-download + restart prompt (tinyhumansai#677) (tinyhumansai#1114) * chore(claude): add ship-and-babysit slash command (tinyhumansai#1115) * feat(home): EarlyBirdyBanner + agent terminology + LinkedIn enrichment model pin (tinyhumansai#1118) * fix(chat): single onboarding thread in sidebar after wizard (tinyhumansai#1116) Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Steven Enamakel <senamakel@users.noreply.github.com> * fix: filter out global namespace from citation chips (tinyhumansai#1124) Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: senamakel-droid <281415773+senamakel-droid@users.noreply.github.com> * feat(nav): enable Memory tab in BottomTabBar (tinyhumansai#1125) * feat(memory): singleton ingestion + status RPC + UI pill (tinyhumansai#1126) * feat(human): mascot tab with viseme-driven lipsync (staging only) (tinyhumansai#1127) * Fix CEF zombie processes on full app close and restart (tinyhumansai#1128) Co-authored-by: senamakel-droid <281415773+senamakel-droid@users.noreply.github.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> * Update issue templates for GitHub issue types (tinyhumansai#1146) * feat(human): expand mascot expressions and tighten reply-speech state machine (tinyhumansai#1147) * feat(memory): ingestion pipeline + tree-architecture docs + ops/schemas split (tinyhumansai#1142) * feat(threads): surface live subagent work in parent thread (tinyhumansai#1122) (tinyhumansai#1159) * fix(human): keep mascot mouth animating when TTS ships no viseme data (tinyhumansai#1160) * feat(composio): consume backend markdownFormatted for LLM output (tinyhumansai#1165) * fix(subagent): lazy-register toolkit actions filtered out of fuzzy top-K (tinyhumansai#1162) * feat(memory): user-facing long-term memory window preset (tinyhumansai#1137) (tinyhumansai#1161) * fix(tauri-shell): proactively kill stale openhuman RPC on startup (tinyhumansai#1166) * chore(staging): v0.53.13 * fix(composio): per-action tool consumes backend markdownFormatted (tinyhumansai#1167) * fix(threads): persist selectedThreadId across reloads (tinyhumansai#1168) * feat(memory_tree): switch embed model to bge-m3 (1024-dim, 8K context) (tinyhumansai#1174) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(agent): drop redundant [Memory context] recall injection (tinyhumansai#1173) * chore(memory_tree): drop body-read timeouts on Ollama HTTP calls (tinyhumansai#1171) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(transcript): emit thread_id + fix orchestrator missing cost (tinyhumansai#1169) * fix(composio/gmail): phase out html2md, prefer text/plain MIME part (tinyhumansai#1170) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(tools): markdown output for internal tool results (tinyhumansai#1172) * feat(security): enforce prompt-injection guard before model and tool execution (tinyhumansai#1175) * fix(cef): popup paint dies after first frame — skip blank-page guard for popups (tinyhumansai#1079) (tinyhumansai#1182) Co-authored-by: Steven Enamakel <31011319+senamakel@users.noreply.github.com> * chore(sentry): rename OPENHUMAN_SENTRY_DSN → OPENHUMAN_CORE_SENTRY_DSN (tinyhumansai#1186) * feat(remotion): add yellow mascot character with all animation variants (tinyhumansai#1193) Co-authored-by: Neel Mistry <neelmistry@Neels-MacBook-Pro.local> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(composio): hide raw connection ID, derive friendly label (tinyhumansai#1153) (tinyhumansai#1185) Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * fix(windows): align install.ps1 MSI with per-machine scope (tinyhumansai#913) (tinyhumansai#1187) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(tauri): deterministic CEF teardown on full app close (tinyhumansai#1120) (tinyhumansai#1189) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(composio): cap Gmail HTML body before strip (crash mitigation) (tinyhumansai#1191) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(auth): stop stale chat threads after signup (tinyhumansai#1192) Co-authored-by: Cursor <cursoragent@cursor.com> * feat(sentry): staging-only "Trigger Sentry Test" button (tinyhumansai#1072) (tinyhumansai#1183) * chore(staging): v0.53.14 * chore(staging): v0.53.15 * feat(composio): format trigger slugs into human-readable labels (tinyhumansai#1129) (tinyhumansai#1179) Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * fix(ui): hide unsupported permission UI on non-macOS for Screen Intelligence (tinyhumansai#1194) Co-authored-by: Cursor <cursoragent@cursor.com> * chore(tauri-shell): retire embedded Gmail webview-account flow (tinyhumansai#1181) * feat(onboarding): replace welcome-agent bot with react-joyride walkthrough (tinyhumansai#1180) * chore(release): v0.53.16 * fix(threads): preserve selectedThreadId on cold-boot identity hydration (tinyhumansai#1196) * feat(core): version/shutdown/update RPCs + mid-thread integration refresh (tinyhumansai#1195) * fix(mascot): swap to yellow mascot via @remotion/player (tinyhumansai#1200) * feat(memory_tree): cloud-default LLM, queue priority, entity filter, Memory tab UI (tinyhumansai#1198) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Persist turn state + restore conversation history on cold-boot (tinyhumansai#1202) * feat(mascot): floating desktop mascot via native NSPanel + WKWebView (macOS) (tinyhumansai#1203) * fix(memory/tree): emit summary children as Obsidian wikilinks (tinyhumansai#1210) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(tools): coding-harness baseline primitives (tinyhumansai#1205) (tinyhumansai#1208) * docs: add Codex PR checklist for remote agents --------- Co-authored-by: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Co-authored-by: WOZCODE <contact@withwoz.com> Co-authored-by: sanil-23 <sanil@vezures.xyz> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Cyrus Gray <144336577+graycyrus@users.noreply.github.com> Co-authored-by: CodeGhost21 <164498022+CodeGhost21@users.noreply.github.com> Co-authored-by: oxoxDev <164490987+oxoxDev@users.noreply.github.com> Co-authored-by: Mega Mind <146339422+M3gA-Mind@users.noreply.github.com> Co-authored-by: Gaurang Patel <ptelgm.yt@gmail.com> Co-authored-by: unn-Known1 <unn-known1@users.noreply.github.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Steven Enamakel <senamakel@users.noreply.github.com> Co-authored-by: Steven Enamakel's Droid <enamakel.agent@tinyhumans.ai> Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: senamakel-droid <281415773+senamakel-droid@users.noreply.github.com> Co-authored-by: YellowSnnowmann <167776381+YellowSnnowmann@users.noreply.github.com> Co-authored-by: Neil <neil@maha.xyz> Co-authored-by: Neel Mistry <neelmistry@Neels-MacBook-Pro.local> Co-authored-by: obchain <167975049+obchain@users.noreply.github.com> Co-authored-by: Jwalin Shah <jshah1331@gmail.com>
Summary
<YellowMascot>Remotion player inside a nativeNSPanel+WKWebViewinstead of a Tauri webview, so the window is actually transparent.ignoresMouseEvents=true); a 20Hz Foundation timer detects when the cursor enters the home rect and animates the panel one mascot-height up the screen, returning home as soon as the cursor leaves.Problem
We wanted a "floating desktop character" that would live on top of any app, not block clicks, and politely move out of the way when the user reaches for something underneath it. Two things made this non-trivial:
tauri-cefruntime (drives every existing OpenHuman window) cannot render transparent windowed-mode browsers — CEF clampsBrowserSettings.background_coloralpha to0xFFfor windowed browsers, and the runtime doesn't enable off-screen rendering. Atransparent: trueTauriWebviewWindowends up opaque on screen.tauri-runtime-wry(WKWebView/WebView2/etc.) for a single window inside a CEF app. Every Tauri-managed window in this codebase has to go through CEF.Solution
Bypass Tauri's runtime entirely for this one window on macOS:
app/src-tauri/src/mascot_native_window.rs(macOS-only) creates a borderlessNonactivatingPanel+ embeddedWKWebViewdirectly viaobjc2-app-kit/objc2-web-kit. The panel is non-opaque, status-bar-level, can-join-all-spaces+transient (so it floats above fullscreen apps), shadowless, andignoresMouseEvents=true. TheWKWebViewuses the canonicalsetValue:NO forKey:@"drawsBackground"KVC trick so the page CSS shows through.?window=mascotappended.app/src/main.tsxwas updated to detect that URL param before touching any Tauri API (since this webview lives outside Tauri's runtime) and mount a tinyMascotWindowAppthat renders just<YellowMascot face="idle" />.mascot_window_show/mascot_window_hidedelegate to the native module on macOS and return an error elsewhere. A "Toggle floating mascot" entry was added to the existing tray menu.NSTimer(50ms tick) comparesNSEvent.mouseLocation()against the panel's home rect; entering it kicks offsetFrame:display:animate:to aHopUpslot one panel-height above home, leaving brings it back. No JS bridge needed.Trade-offs
resolve_page_urlreadsapp.config().build.dev_url; production bundle support needs either a tiny localhost HTTP server in the shell orfile://loading with ESM/CORS workarounds. Documented in the module-level doc comment.idleuntil we wire aWKScriptMessageHandlerto push real agent state in. Also means there's no in-page close button — toggle from the tray.Submission Checklist
diff-coverto evaluate; coverage gate would not measure anything meaningful here.docs/TESTING-STRATEGY.md)Impact
mascot_window_showreturns an error).objc2-web-kit, plusNSTimer+block2features onobjc2-foundation). No new network deps.drawsBackgroundprivate API is the standard wry/Electron approach; no new attack surface.Related
file://+ bundled HTML).WKScriptMessageHandlerbridge so the host can push real agent state (face changes, show/hide on context).mascot.html) so first paint isn't gated on the full app bundle.Summary by CodeRabbit