Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import {
import { configDefaults, useAppStore } from '@/app/providers/store-provider'
import { ThemeProvider } from '@/app/providers/theme-provider'
import { ToolbarConfigProvider } from '@/app/providers/toolbar-config-provider'
import {
useWindowTitlePrefix,
WindowTitlePrefixProvider,
} from '@/app/providers/window-title-prefix-provider'
import {
SidebarInset,
SidebarProvider,
Expand Down Expand Up @@ -64,6 +68,7 @@ function AppContent() {
// Reads cursorAutoHideConfig directly so settings changes from the UI
// take effect immediately without re-mounting.
useCursorAutoHideEffect()
const { enabled: titlePrefixEnabled } = useWindowTitlePrefix()
const [selectedNoteId, setSelectedNoteId] = useState<string | null>(null)
// True once the persisted lastNoteId has been loaded from the store.
// Prevents the window title from flashing "Untitled" before the stored
Expand Down Expand Up @@ -221,25 +226,25 @@ function AppContent() {
// window title to avoid a flash of "Untitled".
if (!noteIdInitialized) return
const appWindow = getCurrentWindow()
const formatTitle = (title: string) =>
titlePrefixEnabled ? `Scripta - ${title}` : title
if (!selectedNoteId) {
appWindow.setTitle('Scripta - Untitled')
appWindow.setTitle(formatTitle('Untitled'))
return
}
let stale = false
getNote(selectedNoteId)
.then((note) => {
if (stale) return
appWindow.setTitle(
note ? `Scripta - ${note.title}` : 'Scripta - Untitled'
)
appWindow.setTitle(formatTitle(note ? note.title : 'Untitled'))
})
.catch(() => {
if (!stale) console.error('Failed to load note for window title')
})
return () => {
stale = true
}
}, [selectedNoteId, noteIdInitialized])
}, [selectedNoteId, noteIdInitialized, titlePrefixEnabled])

/**
* Callback invoked after a note is auto-saved.
Expand Down Expand Up @@ -433,7 +438,9 @@ function App() {
<FontSizeProvider>
<EditorFontProvider>
<ToolbarConfigProvider>
<AppContent />
<WindowTitlePrefixProvider>
<AppContent />
</WindowTitlePrefixProvider>
</ToolbarConfigProvider>
</EditorFontProvider>
</FontSizeProvider>
Expand Down
29 changes: 22 additions & 7 deletions src/app/providers/store-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import {
DEFAULT_WINDOW_STATE_RESTORE,
WINDOW_STATE_STORE_KEY,
} from '@/features/settings/lib/windowStateConfig'
import {
DEFAULT_WINDOW_TITLE_PREFIX,
WINDOW_TITLE_PREFIX_STORE_KEY,
} from '@/features/settings/lib/windowTitleConfig'

/**
* Module-scoped singleton store instances.
Expand Down Expand Up @@ -43,6 +47,8 @@ export const configDefaults = {
theme: 'system' as Theme,
/** Toolbar item order and visibility. Falls back to all-visible canonical order. */
toolbarConfig: DEFAULT_TOOLBAR_CONFIG as ToolbarItemConfig[],
/** Whether to show the "Scripta - " prefix in the window title. Falls back to `true` if not persisted. */
windowTitlePrefixEnabled: DEFAULT_WINDOW_TITLE_PREFIX,
}

/**
Expand All @@ -56,13 +62,19 @@ export const storeInitPromise = Promise.all([
configStore.init(),
editorStateStore.init(),
]).then(async () => {
const [storedSidebarOpen, storedWindowRestore, storedTheme, storedToolbar] =
await Promise.all([
configStore.get<boolean>('sidebarOpen'),
configStore.get<boolean>(WINDOW_STATE_STORE_KEY),
configStore.get<string>('theme'),
configStore.get<ToolbarItemConfig[]>(TOOLBAR_CONFIG_STORE_KEY),
])
const [
storedSidebarOpen,
storedWindowRestore,
storedTheme,
storedToolbar,
storedTitlePrefix,
] = await Promise.all([
configStore.get<boolean>('sidebarOpen'),
configStore.get<boolean>(WINDOW_STATE_STORE_KEY),
configStore.get<string>('theme'),
configStore.get<ToolbarItemConfig[]>(TOOLBAR_CONFIG_STORE_KEY),
configStore.get<boolean>(WINDOW_TITLE_PREFIX_STORE_KEY),
])
if (storedSidebarOpen != null) {
configDefaults.sidebarOpen = storedSidebarOpen
}
Expand All @@ -75,6 +87,9 @@ export const storeInitPromise = Promise.all([
}
const restoreEnabled = storedWindowRestore ?? DEFAULT_WINDOW_STATE_RESTORE
configDefaults.windowStateRestoreEnabled = restoreEnabled
if (storedTitlePrefix != null) {
configDefaults.windowTitlePrefixEnabled = storedTitlePrefix
}
if (restoreEnabled) {
try {
await restoreStateCurrent(StateFlags.POSITION | StateFlags.SIZE)
Expand Down
77 changes: 77 additions & 0 deletions src/app/providers/window-title-prefix-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {
createContext,
type ReactNode,
useCallback,
useContext,
useMemo,
useState,
} from 'react'
import { configDefaults, useAppStore } from '@/app/providers/store-provider'
import { WINDOW_TITLE_PREFIX_STORE_KEY } from '@/features/settings/lib/windowTitleConfig'

interface WindowTitlePrefixState {
enabled: boolean
setEnabled: (value: boolean) => void
}

const WindowTitlePrefixContext = createContext<
WindowTitlePrefixState | undefined
>(undefined)

/**
* Provides the window-title prefix toggle state to the component tree.
*
* Reads the initial value from the pre-fetched {@link configDefaults}
* and persists changes to `configStore` immediately. All consumers
* sharing this context see the same value in real time.
*
* @param props.children - The component subtree that needs access.
* @param props.defaultEnabled - Initial value from pre-fetched config.
*/
export function WindowTitlePrefixProvider({
children,
defaultEnabled = configDefaults.windowTitlePrefixEnabled,
}: {
children: ReactNode
defaultEnabled?: boolean
}) {
const { config: configStore } = useAppStore()
const [enabled, setEnabledState] = useState(defaultEnabled)

const setEnabled = useCallback(
(value: boolean) => {
setEnabledState(value)
configStore.set(WINDOW_TITLE_PREFIX_STORE_KEY, value).catch((err) => {
console.error('Failed to persist windowTitlePrefixEnabled:', err)
})
},
[configStore]
)

const value = useMemo(() => ({ enabled, setEnabled }), [enabled, setEnabled])

return (
<WindowTitlePrefixContext.Provider value={value}>
{children}
</WindowTitlePrefixContext.Provider>
)
}

/**
* Returns the window-title prefix toggle state.
*
* Must be called from a component inside a {@link WindowTitlePrefixProvider}.
*
* @returns An object containing:
* - `enabled` — Whether the "Scripta - " prefix is shown.
* - `setEnabled` — Toggles the flag and persists the change.
*/
export function useWindowTitlePrefix() {
const ctx = useContext(WindowTitlePrefixContext)
if (!ctx) {
throw new Error(
'useWindowTitlePrefix must be used within a WindowTitlePrefixProvider'
)
}
return ctx
}
10 changes: 10 additions & 0 deletions src/features/settings/lib/windowTitleConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** Default value: show the "Scripta - " prefix in the window title. */
export const DEFAULT_WINDOW_TITLE_PREFIX = true

/**
* Store key name for persistence via `configStore`.
*
* When `true`, the window title is formatted as `"Scripta - {note title}"`.
* When `false`, only the note title is shown (e.g. `"My Note"`).
*/
export const WINDOW_TITLE_PREFIX_STORE_KEY = 'windowTitlePrefixEnabled' as const
15 changes: 10 additions & 5 deletions src/features/settings/ui/WindowStateOption.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { Label } from '@/components/ui/label'
import { Switch } from '@/components/ui/switch'
import { useWindowState } from '../hooks/useWindowState'
import { WindowTitlePrefixOption } from './WindowTitlePrefixOption'

/**
* Settings section for configuring window position/size restoration.
* Settings section for window-related preferences.
*
* Provides an ON/OFF toggle that controls whether the window restores
* its last-saved position and size on startup. When OFF, the window
* always opens at 1200×800 centered on screen.
* Groups the following toggles under a single "Window" heading:
*
* @returns The rendered window state settings control.
* - **Restore position & size** — Whether the window restores its
* last-saved position and dimensions on startup.
* - **Show app name in title bar** — Whether the `"Scripta - "` prefix
* is displayed before the note title.
*
* @returns The rendered window settings section.
*
* @example
* ```tsx
Expand All @@ -33,6 +37,7 @@ export function WindowStateOption() {
onCheckedChange={setEnabled}
/>
</div>
<WindowTitlePrefixOption />
</div>
)
}
28 changes: 28 additions & 0 deletions src/features/settings/ui/WindowTitlePrefixOption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useWindowTitlePrefix } from '@/app/providers/window-title-prefix-provider'
import { Label } from '@/components/ui/label'
import { Switch } from '@/components/ui/switch'

/**
* Settings toggle for the "Scripta - " window title prefix.
*
* When ON, the window title displays `"Scripta - {note title}"`.
* When OFF, only the note title is shown for a minimal look.
*
* @returns The rendered window title prefix settings control.
*/
export function WindowTitlePrefixOption() {
const { enabled, setEnabled } = useWindowTitlePrefix()

return (
<div className="flex items-center justify-between px-3">
<Label htmlFor="window-title-prefix-toggle" className="text-sm">
Show app name in title bar
</Label>
<Switch
id="window-title-prefix-toggle"
checked={enabled}
onCheckedChange={setEnabled}
/>
</div>
)
}
Loading