feat: custom TitleBar with window controls (no native decorations) #7

Merged
LyAhn merged 6 commits from feat/custom-titlebar into main 2026-04-06 20:02:45 +00:00
LyAhn commented 2026-04-06 13:08:58 +00:00 (Migrated from github.com)

Custom TitleBar — Frameless Window

Replaces the native OS title bar with a fully custom, in-app title bar that matches Phokus's dark aesthetic.

Changes

src-tauri/tauri.conf.json

  • Added "decorations": false — removes the native OS window frame
  • Added "shadow": true — keeps the native drop shadow on Windows/macOS

src-tauri/capabilities/default.json

  • Added window management permissions:
    • core:window:allow-minimize
    • core:window:allow-maximize / allow-unmaximize
    • core:window:allow-toggle-maximize
    • core:window:allow-close
    • core:window:allow-start-dragging
    • core:window:allow-is-maximized

src/components/TitleBar.tsx (new)

  • 36px tall bar, bg-gray-950 to match the app background
  • Phokus icon + app name on the left
  • Entire bar has data-tauri-drag-region / -webkit-app-region: drag so you can click-drag anywhere to move the window
  • Three custom window control buttons on the right (non-draggable):
    • Minimize — subtle hover
    • Maximize/Restore — toggles icon based on window state, listens to onResized event
    • Close — red hover (bg-red-500/80) for familiar affordance
  • All icons are inline SVGs (no external deps)

src/App.tsx

  • Layout changed from flex-row to flex-col with TitleBar at the top
  • Inner content wrapped in a flex flex-1 min-h-0 div to preserve existing sidebar/main layout

Visual

The title bar sits flush at the top of the frameless window. The drag region spans the full width except the control buttons, double-clicking the bar toggles maximize (native behaviour via toggleMaximize).


Note for macOS: If you want the macOS-style traffic lights in addition to (or instead of) these controls, set titleBarStyle: "overlay" in tauri.conf.json instead of decorations: false. The current implementation targets Windows/Linux where decorations: false is the standard approach.

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a custom title bar with application branding and window control buttons (minimise, maximise/restore, close functions)
  • Style & UI

    • Enhanced window appearance with shadow effects
    • Removed default window decorations in favour of custom styling
    • Reorganised app layout to integrate the new title bar seamlessly
## Custom TitleBar — Frameless Window Replaces the native OS title bar with a fully custom, in-app title bar that matches Phokus's dark aesthetic. ### Changes #### `src-tauri/tauri.conf.json` - Added `"decorations": false` — removes the native OS window frame - Added `"shadow": true` — keeps the native drop shadow on Windows/macOS #### `src-tauri/capabilities/default.json` - Added window management permissions: - `core:window:allow-minimize` - `core:window:allow-maximize` / `allow-unmaximize` - `core:window:allow-toggle-maximize` - `core:window:allow-close` - `core:window:allow-start-dragging` - `core:window:allow-is-maximized` #### `src/components/TitleBar.tsx` *(new)* - 36px tall bar, `bg-gray-950` to match the app background - Phokus icon + app name on the left - Entire bar has `data-tauri-drag-region` / `-webkit-app-region: drag` so you can click-drag anywhere to move the window - Three custom window control buttons on the right (non-draggable): - **Minimize** — subtle hover - **Maximize/Restore** — toggles icon based on window state, listens to `onResized` event - **Close** — red hover (`bg-red-500/80`) for familiar affordance - All icons are inline SVGs (no external deps) #### `src/App.tsx` - Layout changed from `flex-row` to `flex-col` with `TitleBar` at the top - Inner content wrapped in a `flex flex-1 min-h-0` div to preserve existing sidebar/main layout ### Visual The title bar sits flush at the top of the frameless window. The drag region spans the full width except the control buttons, double-clicking the bar toggles maximize (native behaviour via `toggleMaximize`). --- > **Note for macOS:** If you want the macOS-style traffic lights in addition to (or instead of) these controls, set `titleBarStyle: "overlay"` in `tauri.conf.json` instead of `decorations: false`. The current implementation targets Windows/Linux where `decorations: false` is the standard approach. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Introduced a custom title bar with application branding and window control buttons (minimise, maximise/restore, close functions) * **Style & UI** * Enhanced window appearance with shadow effects * Removed default window decorations in favour of custom styling * Reorganised app layout to integrate the new title bar seamlessly <!-- end of auto-generated comment: release notes by coderabbit.ai -->
coderabbitai[bot] commented 2026-04-06 13:09:07 +00:00 (Migrated from github.com)
ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: ba9c09e8-7ec2-4f6f-8580-61bbaf34860e

📥 Commits

Reviewing files that changed from the base of the PR and between 2807240fab and ce5c3d0033.

📒 Files selected for processing (2)
  • src-tauri/capabilities/default.json
  • src/components/TitleBar.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src-tauri/capabilities/default.json
  • src/components/TitleBar.tsx

📝 Walkthrough

Walkthrough

Adds a custom draggable TitleBar component to the React app, enables Tauri window control permissions for minimize/maximise/close queries, and updates Tauri window config to disable native decorations and enable window shadowing.

Changes

Cohort / File(s) Summary
Tauri configuration
src-tauri/capabilities/default.json, src-tauri/tauri.conf.json
Expanded the default capability permissions to include core:window:allow-minimize, core:window:allow-close, core:window:allow-toggle-maximize, and core:window:allow-is-maximized. Disabled native window decorations and enabled window shadow in Tauri config.
UI components & layout
src/App.tsx, src/components/TitleBar.tsx
Added exported TitleBar component (draggable region, minimize/maximise/restore/close handlers, maximised-state tracking) and integrated it into the app by converting the root layout to a columnar structure so the title bar sits atop the content.

Sequence Diagram

sequenceDiagram
    participant User
    participant TitleBar
    participant TauriAPI as Tauri Window API
    participant OS

    TitleBar->>TauriAPI: getCurrentWindow().isMaximized() on mount
    TauriAPI-->>TitleBar: maximized state
    TitleBar->>TitleBar: set isMaximized

    TitleBar->>TauriAPI: subscribe onResized

    User->>TitleBar: click minimise
    TitleBar->>TauriAPI: minimize()
    TauriAPI->>OS: minimise window

    User->>TitleBar: click maximise/restore
    TitleBar->>TauriAPI: toggleMaximize()
    TauriAPI->>OS: toggle maximise

    TauriAPI-->>TitleBar: onResized event
    TitleBar->>TauriAPI: isMaximized() query
    TauriAPI-->>TitleBar: updated state
    TitleBar->>TitleBar: update icon

    User->>TitleBar: click close
    TitleBar->>TauriAPI: close()
    TauriAPI->>OS: close window

    Note over TitleBar: unsubscribe on unmount

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

Poem

🐰
A bar on top, so sleek and light,
Buttons to shrink, to stretch, to flight,
I nudge the window, shadowed, free,
Chrome gone, the view belongs to me.


Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

Comment @coderabbitai help to get the list of available commands and usage tips.

<!-- This is an auto-generated comment: summarize by coderabbit.ai --> <details> <summary>ℹ️ Recent review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: Organization UI **Review profile**: CHILL **Plan**: Free **Run ID**: `ba9c09e8-7ec2-4f6f-8580-61bbaf34860e` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 2807240fab46541c80895e1f0d28d1a059c22f5c and ce5c3d00339f2bbec1cc562351df10fe25d81285. </details> <details> <summary>📒 Files selected for processing (2)</summary> * `src-tauri/capabilities/default.json` * `src/components/TitleBar.tsx` </details> <details> <summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary> * src-tauri/capabilities/default.json * src/components/TitleBar.tsx </details> </details> --- <!-- walkthrough_start --> <details> <summary>📝 Walkthrough</summary> ## Walkthrough Adds a custom draggable TitleBar component to the React app, enables Tauri window control permissions for minimize/maximise/close queries, and updates Tauri window config to disable native decorations and enable window shadowing. ## Changes |Cohort / File(s)|Summary| |---|---| |**Tauri configuration** <br> `src-tauri/capabilities/default.json`, `src-tauri/tauri.conf.json`|Expanded the `default` capability permissions to include `core:window:allow-minimize`, `core:window:allow-close`, `core:window:allow-toggle-maximize`, and `core:window:allow-is-maximized`. Disabled native window decorations and enabled window shadow in Tauri config.| |**UI components & layout** <br> `src/App.tsx`, `src/components/TitleBar.tsx`|Added exported `TitleBar` component (draggable region, minimize/maximise/restore/close handlers, maximised-state tracking) and integrated it into the app by converting the root layout to a columnar structure so the title bar sits atop the content.| ## Sequence Diagram ```mermaid sequenceDiagram participant User participant TitleBar participant TauriAPI as Tauri Window API participant OS TitleBar->>TauriAPI: getCurrentWindow().isMaximized() on mount TauriAPI-->>TitleBar: maximized state TitleBar->>TitleBar: set isMaximized TitleBar->>TauriAPI: subscribe onResized User->>TitleBar: click minimise TitleBar->>TauriAPI: minimize() TauriAPI->>OS: minimise window User->>TitleBar: click maximise/restore TitleBar->>TauriAPI: toggleMaximize() TauriAPI->>OS: toggle maximise TauriAPI-->>TitleBar: onResized event TitleBar->>TauriAPI: isMaximized() query TauriAPI-->>TitleBar: updated state TitleBar->>TitleBar: update icon User->>TitleBar: click close TitleBar->>TauriAPI: close() TauriAPI->>OS: close window Note over TitleBar: unsubscribe on unmount ``` ## Estimated code review effort 🎯 3 (Moderate) | ⏱️ ~20–30 minutes ## Poem > 🐰 > A bar on top, so sleek and light, > Buttons to shrink, to stretch, to flight, > I nudge the window, shadowed, free, > Chrome gone, the view belongs to me. </details> <!-- walkthrough_end --> <!-- tips_start --> --- > [!NOTE] > <details> > <summary>🎁 Summarized by CodeRabbit Free</summary> > > Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting <https://app.coderabbit.ai/login>. > > </details> <sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub> <!-- tips_end --> <!-- internal state start --> <!-- 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 --> <!-- internal state end -->
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2026-04-06 13:50:42 +00:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull request overview

This PR introduces a frameless Tauri window setup and replaces native window decorations with an in-app custom TitleBar that provides window controls and (intended) drag behavior.

Changes:

  • Disable native window decorations and enable shadow in tauri.conf.json.
  • Add window-management capability permissions for minimize/maximize/close/dragging APIs.
  • Add a new TitleBar React component and update the main app layout to include it.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/components/TitleBar.tsx Adds a custom top title bar with window control buttons and maximized-state tracking.
src/App.tsx Refactors layout to stack the new title bar above the existing sidebar/content layout.
src-tauri/tauri.conf.json Disables native decorations (decorations: false) and enables window shadow.
src-tauri/capabilities/default.json Grants window-control permissions needed for the new title bar controls.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

## Pull request overview This PR introduces a frameless Tauri window setup and replaces native window decorations with an in-app custom `TitleBar` that provides window controls and (intended) drag behavior. **Changes:** - Disable native window decorations and enable shadow in `tauri.conf.json`. - Add window-management capability permissions for minimize/maximize/close/dragging APIs. - Add a new `TitleBar` React component and update the main app layout to include it. ### Reviewed changes Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments. | File | Description | | ---- | ----------- | | src/components/TitleBar.tsx | Adds a custom top title bar with window control buttons and maximized-state tracking. | | src/App.tsx | Refactors layout to stack the new title bar above the existing sidebar/content layout. | | src-tauri/tauri.conf.json | Disables native decorations (`decorations: false`) and enables window shadow. | | src-tauri/capabilities/default.json | Grants window-control permissions needed for the new title bar controls. | --- 💡 <a href="/JezzWTF/phokus/new/main?filename=.github/instructions/*.instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Add Copilot custom instructions</a> for smarter, more guided reviews. <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Learn how to get started</a>.
@@ -12,6 +12,10 @@
"fs:allow-read-file",
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-04-06 13:50:42 +00:00

core:window:allow-start-dragging is added to the capability set, but the UI code in this PR doesn’t call startDragging() anywhere (and also doesn’t currently use data-tauri-drag-region). If dragging will be handled via the HTML attribute, consider removing this permission; if dragging will be handled via the API, add the corresponding call in the title bar so the extra permission is justified.


`core:window:allow-start-dragging` is added to the capability set, but the UI code in this PR doesn’t call `startDragging()` anywhere (and also doesn’t currently use `data-tauri-drag-region`). If dragging will be handled via the HTML attribute, consider removing this permission; if dragging will be handled via the API, add the corresponding call in the title bar so the extra permission is justified. ```suggestion ```
@@ -16,1 +17,4 @@
"core:window:allow-close",
"core:window:allow-toggle-maximize",
"core:window:allow-is-maximized"
]
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-04-06 13:50:42 +00:00

The capabilities add core:window:allow-maximize and core:window:allow-unmaximize, but the UI code introduced here only calls toggleMaximize() (plus minimize(), close(), isMaximized()). To keep least-privilege, consider removing permissions that aren’t required for the current implementation (unless Tauri’s permission model for toggleMaximize implicitly depends on them).

The capabilities add `core:window:allow-maximize` and `core:window:allow-unmaximize`, but the UI code introduced here only calls `toggleMaximize()` (plus `minimize()`, `close()`, `isMaximized()`). To keep least-privilege, consider removing permissions that aren’t required for the current implementation (unless Tauri’s permission model for `toggleMaximize` implicitly depends on them).
@@ -0,0 +64,4 @@
data-tauri-drag-region
className="titlebar relative z-50 flex h-9 shrink-0 items-center bg-gray-950 select-none"
style={{ WebkitAppRegion: "drag" } as React.CSSProperties}
>
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-04-06 13:50:42 +00:00

The title bar drag region is implemented only via WebkitAppRegion: "drag". In Tauri (especially on Windows/Linux with WebView2), this CSS property is not reliably honored; the recommended approach is to mark draggable areas with data-tauri-drag-region (and/or explicitly call appWindow.startDragging() on pointer-down). As-is, the window may not be draggable and the PR description’s data-tauri-drag-region behavior isn’t present.

The title bar drag region is implemented only via `WebkitAppRegion: "drag"`. In Tauri (especially on Windows/Linux with WebView2), this CSS property is not reliably honored; the recommended approach is to mark draggable areas with `data-tauri-drag-region` (and/or explicitly call `appWindow.startDragging()` on pointer-down). As-is, the window may not be draggable and the PR description’s `data-tauri-drag-region` behavior isn’t present.
Sign in to join this conversation.