feat(desktop): macOS hidden-inset chrome and floating chat title

Apply hiddenInset titleBarStyle on darwin so the system titlebar is hidden
but native traffic lights remain. Reusable web sidebars inject a new
DesktopShellKey to reserve a 36px TopBar that holds the traffic-light
inset (drag region + right border) without colliding with the bot list,
and the sidebar stays pinned open in the Electron shell so window resize
doesn't fight the layout.

Overlay a centered "Title - BotName" header above the chat content with a
bottom shadow gradient that obscures scrolling messages, and reserve top
padding so the first message stays visible when content fits the viewport.
Route the sidebar + action by path (/settings/bots) so the chat router's
/settings/* interception forwards it to the settings window cleanly while
remaining a normal navigation in web.
This commit is contained in:
Acbox
2026-04-25 12:08:33 +08:00
parent fb8614a016
commit 273f7bb911
10 changed files with 115 additions and 45 deletions
+9
View File
@@ -33,9 +33,17 @@ function loadRendererEntry(window: BrowserWindow, entry: 'index' | 'settings'):
// output is what wires the IPC bridge into the renderer.
const PRELOAD_FILE = '../preload/index.mjs'
// On macOS we hide the system titlebar but keep the native traffic lights
// (`hiddenInset`). Renderers reserve space for them via a custom TopBar.
const macTitleBarOptions: Partial<Electron.BrowserWindowConstructorOptions>
= process.platform === 'darwin'
? { titleBarStyle: 'hiddenInset', trafficLightPosition: { x: 14, y: 12 } }
: {}
function createChatWindow(): BrowserWindow {
const window = new BrowserWindow({
...CHAT_DEFAULTS,
...macTitleBarOptions,
show: false,
autoHideMenuBar: true,
title: 'Memoh',
@@ -63,6 +71,7 @@ function createChatWindow(): BrowserWindow {
function createSettingsWindow(parent: BrowserWindow | null): BrowserWindow {
const window = new BrowserWindow({
...SETTINGS_DEFAULTS,
...macTitleBarOptions,
show: false,
autoHideMenuBar: true,
title: 'Memoh · Settings',
@@ -1,9 +1,12 @@
<script setup lang="ts">
import { provide } from 'vue'
import { RouterView } from 'vue-router'
import { Toaster } from '@memohai/ui'
import 'vue-sonner/style.css'
import { useSettingsStore } from '@memohai/web/store/settings'
import { DesktopShellKey } from '@memohai/web/lib/desktop-shell'
provide(DesktopShellKey, true)
useSettingsStore()
</script>
@@ -1,10 +1,13 @@
<script setup lang="ts">
import { provide } from 'vue'
import { Toaster, SidebarInset } from '@memohai/ui'
import 'vue-sonner/style.css'
import MainLayout from '@memohai/web/layout/main-layout/index.vue'
import SettingsSidebar from '@memohai/web/components/settings-sidebar/index.vue'
import { useSettingsStore } from '@memohai/web/store/settings'
import { DesktopShellKey } from '@memohai/web/lib/desktop-shell'
provide(DesktopShellKey, true)
useSettingsStore()
</script>
+5
View File
@@ -34,6 +34,11 @@ declare module '@memohai/web/store/settings' {
export function useSettingsStore(): unknown
}
declare module '@memohai/web/lib/desktop-shell' {
import type { InjectionKey } from 'vue'
export const DesktopShellKey: InjectionKey<boolean>
}
declare module '@memohai/web/style.css'
// Fallback for every Vue SFC reachable through the @memohai/web/* wildcard