Skip to content

Commit 9fe5ae9

Browse files
author
bigmacfive
committed
Add macOS titlebar presets
1 parent 534b705 commit 9fe5ae9

9 files changed

Lines changed: 301 additions & 44 deletions

File tree

README.ko.md

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ macOS, Windows, Linux를 지원합니다.
2828
- title, description, theme color, favicon, apple-touch-icon, manifest icon 수집
2929
- 깨진 응답이나 저품질 raster 아이콘을 패키징 전에 걸러냄
3030
- 대상 URL을 감싸는 로컬 Electrobun shell 생성
31-
- macOS에서 윈도우 chrome과 콘텐츠가 붙어 보이도록 통합 상단바 적용
31+
- macOS에서 자주 쓰는 윈도우 chrome 패턴을 프리셋으로 고를 수 있게 생성
3232
- macOS용 DMG 흐름 포함, 나머지 플랫폼도 빌드 가능한 출력 제공
3333
- interactive 터미널에서는 파괴적이거나 무거운 작업 전에 확인 프롬프트 표시, 자동 승인은 `--yes`
3434

@@ -61,6 +61,12 @@ bun install
6161
bun run build
6262
```
6363

64+
처음부터 더 타이트한 macOS 상단바를 쓰고 싶다면:
65+
66+
```bash
67+
appbun https://chat.openai.com --name "ChatGPT" --titlebar compact --dmg
68+
```
69+
6470
## CLI 예시
6571

6672
```bash
@@ -83,6 +89,10 @@ appbun https://chat.openai.com --theme-color '#10a37f'
8389
appbun https://www.notion.so --package-manager npm
8490
```
8591

92+
```bash
93+
appbun https://github.com --name "GitHub" --titlebar system
94+
```
95+
8696
스크립트나 CI에서 확인 프롬프트를 건너뛰려면:
8797

8898
```bash
@@ -101,6 +111,25 @@ appbun prompt http://localhost:3000 --name "My App"
101111

102112
그러면 에이전트가 현재 웹앱을 `./desktop/my-app` 아래에 `appbun@latest`로 패키징하고 빌드하게 만드는 지시문이 출력됩니다.
103113

114+
## 윈도우 chrome 프리셋
115+
116+
이제 `appbun`은 생성되는 macOS 상단바를 한 가지 스타일로 고정하지 않고, 사용자가 프리셋으로 고를 수 있게 만들었습니다.
117+
118+
| 프리셋 | 어울리는 경우 | macOS 동작 |
119+
| --- | --- | --- |
120+
| `system` | 가장 네이티브한 창이 좋을 때 | 기본 시스템 title bar, 로컬 shell header 없음 |
121+
| `unified` | 기본값, 가장 균형 잡힌 wrapper | hidden inset traffic lights + 연결된 로컬 toolbar |
122+
| `compact` | 콘텐츠가 우선인 앱 | 같은 패턴이지만 더 낮고 더 조밀함 |
123+
| `minimal` | 시각적 chrome을 덜 보이고 싶을 때 | 같은 패턴이지만 메타데이터를 줄이고 경계를 약하게 표현 |
124+
125+
Windows와 Linux에서는 현재 표준 네이티브 title bar로 폴백합니다.
126+
127+
옵션 전체를 빨리 보려면:
128+
129+
```bash
130+
appbun create --help
131+
```
132+
104133
## Showcase
105134

106135
로그인 없이 바로 동작하는 공개 웹앱을 Playwright로 캡처하고, 생성되는 shell 느낌에 맞춰 프레임한 예시입니다.
@@ -147,16 +176,17 @@ my-app/
147176

148177
### macOS
149178

150-
생성 앱은 다음을 사용합니다.
179+
생성 앱은 다음 중 하나를 사용합니다.
151180

152-
- `hiddenInset` traffic lights
153-
- `UnifiedTitleAndToolbar`
154-
- 떠 있는 가짜 헤더 대신 전체 폭 로컬 title area
181+
- `--titlebar system`일 때 기본 시스템 title bar
182+
- `--titlebar unified`, `compact`, `minimal`일 때 `hiddenInset` traffic lights
183+
- 연결형 프리셋에서 `UnifiedTitleAndToolbar` + `FullSizeContentView`
184+
- 고정된 가짜 헤더 하나가 아니라 선택한 프리셋에 맞는 로컬 title area
155185
- 설치형 배포를 위한 `build:dmg`
156186

157187
### Windows 와 Linux
158188

159-
생성된 Electrobun 프로젝트는 이미 빌드 가능합니다. 현재 `appbun`은 우선 macOS 설치 자동화에 집중하고 있고, Windows/Linux 패키징 helper는 로드맵에 있습니다.
189+
생성된 Electrobun 프로젝트는 이미 빌드 가능합니다. 현재 `appbun`이 플랫폼들에서는 표준 네이티브 title bar를 유지하고, 우선 macOS 설치 자동화에 집중하고 있습니다. Windows/Linux 패키징 helper는 로드맵에 있습니다.
160190

161191
## 로컬 개발
162192

README.md

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ What it handles for you:
2828
- fetches title, description, theme color, favicon, apple-touch icon, and manifest icons
2929
- rejects obviously broken icon responses and low-quality raster assets before packaging
3030
- generates a local Electrobun shell around the target URL
31-
- uses a unified top bar on macOS so the window chrome and content feel connected
31+
- lets you choose between common macOS window chrome presets instead of hard-coding one look
3232
- produces cross-platform build output, plus a macOS DMG flow for drag-to-Applications installs
3333
- asks before destructive or heavyweight steps in interactive terminals, with `--yes` to skip prompts
3434

@@ -61,6 +61,12 @@ bun install
6161
bun run build
6262
```
6363

64+
Need a tighter macOS chrome right away:
65+
66+
```bash
67+
appbun https://chat.openai.com --name "ChatGPT" --titlebar compact --dmg
68+
```
69+
6470
## CLI examples
6571

6672
```bash
@@ -83,6 +89,10 @@ appbun https://chat.openai.com --theme-color '#10a37f'
8389
appbun https://www.notion.so --package-manager npm
8490
```
8591

92+
```bash
93+
appbun https://github.com --name "GitHub" --titlebar system
94+
```
95+
8696
Skip confirmation prompts in scripted runs:
8797

8898
```bash
@@ -101,6 +111,25 @@ appbun prompt http://localhost:3000 --name "My App"
101111

102112
That outputs a ready-to-paste instruction block telling the agent to package the current web app into `./desktop/my-app` with `appbun@latest`, then build it.
103113

114+
## Window chrome presets
115+
116+
`appbun` now exposes the generated macOS title area as a user choice instead of locking every app to one look.
117+
118+
| Preset | Best for | macOS behavior |
119+
| --- | --- | --- |
120+
| `system` | strict native window chrome | default system title bar, no local shell header |
121+
| `unified` | default, balanced desktop wrapper | hidden inset traffic lights with a connected local toolbar |
122+
| `compact` | content-heavy apps | same pattern, but shorter and tighter |
123+
| `minimal` | distraction-free wrappers | same pattern, but lighter metadata and less visible chrome |
124+
125+
On Windows and Linux, generated apps fall back to the standard native title bar.
126+
127+
To inspect every option quickly:
128+
129+
```bash
130+
appbun create --help
131+
```
132+
104133
## Showcase
105134

106135
Public no-login web apps captured with Playwright and framed to match the generated shell:
@@ -147,16 +176,17 @@ my-app/
147176

148177
### macOS
149178

150-
Generated apps use:
179+
Generated apps can use:
151180

152-
- `hiddenInset` traffic lights
153-
- `UnifiedTitleAndToolbar`
154-
- a full-width local title area instead of a floating fake header
181+
- the default system title bar with `--titlebar system`
182+
- `hiddenInset` traffic lights with `--titlebar unified`, `compact`, or `minimal`
183+
- `UnifiedTitleAndToolbar` plus `FullSizeContentView` for the connected presets
184+
- a local title area sized to match the selected preset instead of one fixed fake header
155185
- `build:dmg` for installer-style distribution
156186

157187
### Windows and Linux
158188

159-
The generated Electrobun project is already buildable there. `appbun` currently focuses its installer automation on macOS first; Windows and Linux packaging helpers are still on the roadmap.
189+
The generated Electrobun project is already buildable there. `appbun` keeps the standard native title bar on those platforms today and focuses its installer automation on macOS first; Windows and Linux packaging helpers are still on the roadmap.
160190

161191
## Local development
162192

docs/agent-prompts/web-app-repo.ko.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Inputs you must fill in before running:
1212
- App name: [APP_NAME]
1313
- Desktop wrapper output directory inside this repo: ./desktop/[APP_SLUG]
1414
- Window size: [WIDTH]x[HEIGHT]
15+
- Titlebar preset: [system|unified|compact|minimal]
1516
- Theme color: [THEME_COLOR]
1617
1718
Rules:
@@ -27,7 +28,7 @@ Rules:
2728
Execution plan:
2829
1. 필요하면 현재 웹앱 dev server를 띄우고 [WEB_APP_URL]이 실제로 열리는지 확인한다.
2930
2. 아래 명령을 실행한다.
30-
npx -y appbun@latest [WEB_APP_URL] --name "[APP_NAME]" --out-dir ./desktop/[APP_SLUG] --width [WIDTH] --height [HEIGHT] --theme-color [THEME_COLOR] --yes
31+
npx -y appbun@latest [WEB_APP_URL] --name "[APP_NAME]" --out-dir ./desktop/[APP_SLUG] --titlebar [system|unified|compact|minimal] --width [WIDTH] --height [HEIGHT] --theme-color [THEME_COLOR] --yes
3132
3. 생성된 wrapper 디렉터리로 이동한다.
3233
cd ./desktop/[APP_SLUG]
3334
4. 의존성을 설치한다.

docs/agent-prompts/web-app-repo.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Inputs you must fill in before running:
1212
- App name: [APP_NAME]
1313
- Desktop wrapper output directory inside this repo: ./desktop/[APP_SLUG]
1414
- Window size: [WIDTH]x[HEIGHT]
15+
- Titlebar preset: [system|unified|compact|minimal]
1516
- Theme color: [THEME_COLOR]
1617
1718
Rules:
@@ -27,7 +28,7 @@ Rules:
2728
Execution plan:
2829
1. If needed, start the current web app and verify [WEB_APP_URL] loads.
2930
2. Run:
30-
npx -y appbun@latest [WEB_APP_URL] --name "[APP_NAME]" --out-dir ./desktop/[APP_SLUG] --width [WIDTH] --height [HEIGHT] --theme-color [THEME_COLOR] --yes
31+
npx -y appbun@latest [WEB_APP_URL] --name "[APP_NAME]" --out-dir ./desktop/[APP_SLUG] --titlebar [system|unified|compact|minimal] --width [WIDTH] --height [HEIGHT] --theme-color [THEME_COLOR] --yes
3132
3. Change into the generated wrapper directory:
3233
cd ./desktop/[APP_SLUG]
3334
4. Install dependencies:

src/__tests__/generator.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,63 @@ describe("generator", () => {
157157
expect(files.find((file) => file.path === "src/mainview/index.css")?.content).toContain("--shell-toolbar-height: 40px");
158158
});
159159

160+
test("system titlebar preset falls back to native chrome", () => {
161+
const config = resolveAppConfig(
162+
"https://example.com",
163+
{
164+
width: 1400,
165+
height: 900,
166+
packageManager: "bun",
167+
install: false,
168+
dmg: false,
169+
yes: false,
170+
showConfig: false,
171+
quiet: true,
172+
titlebar: "system",
173+
},
174+
{
175+
title: "Example",
176+
description: "Example app",
177+
themeColor: "#336699",
178+
sourceUrl: "https://example.com",
179+
iconCandidates: [],
180+
},
181+
);
182+
183+
const files = renderTemplateFiles(config, {});
184+
expect(files.find((file) => file.path === "src/bun/index.ts")?.content).toContain('titleBarStyle: isMac ? "default" : "default"');
185+
expect(files.find((file) => file.path === "src/mainview/index.html")?.content).not.toContain("<header");
186+
expect(files.find((file) => file.path === "src/mainview/index.css")?.content).toContain("--shell-topbar-display: none");
187+
});
188+
189+
test("compact titlebar preset lowers toolbar height", () => {
190+
const config = resolveAppConfig(
191+
"https://example.com",
192+
{
193+
width: 1400,
194+
height: 900,
195+
packageManager: "bun",
196+
install: false,
197+
dmg: false,
198+
yes: false,
199+
showConfig: false,
200+
quiet: true,
201+
titlebar: "compact",
202+
},
203+
{
204+
title: "Example",
205+
description: "Example app",
206+
themeColor: "#336699",
207+
sourceUrl: "https://example.com",
208+
iconCandidates: [],
209+
},
210+
);
211+
212+
const files = renderTemplateFiles(config, {});
213+
expect(files.find((file) => file.path === "src/bun/index.ts")?.content).toContain('titleBarStyle: isMac ? "hiddenInset" : "default"');
214+
expect(files.find((file) => file.path === "src/mainview/index.css")?.content).toContain("--shell-toolbar-height: 36px");
215+
});
216+
160217
test("writeProject creates config and icon files", async () => {
161218
const root = mkdtempSync(join(tmpdir(), "appbun-test-"));
162219
tempDirs.push(root);

src/cli.ts

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ import {
1616
writeProject,
1717
} from "./lib/generator.js";
1818
import { createFallbackSiteMetadata, fetchSiteMetadata } from "./lib/metadata.js";
19-
import type { CreateCommandOptions } from "./lib/types.js";
19+
import type { CreateCommandOptions, TitlebarStyle } from "./lib/types.js";
2020
import { clearDirectoryContents, displayPath, isDirectoryEmpty, suggestAlternativeOutputDirectory } from "./lib/utils.js";
2121

2222
const defaultOptions: CreateCommandOptions = {
2323
width: 1440,
2424
height: 900,
25+
titlebar: "unified",
2526
packageManager: "bun",
2627
install: false,
2728
dmg: false,
@@ -38,6 +39,8 @@ const program = new Command();
3839
program
3940
.name("appbun")
4041
.description("Generate an Electrobun desktop wrapper from any web app URL.")
42+
.showSuggestionAfterError()
43+
.showHelpAfterError()
4144
.version("0.5.2");
4245

4346
program
@@ -49,6 +52,12 @@ program
4952
.option("--description <description>", "package description")
5053
.option("--identifier <identifier>", "bundle identifier, for example com.example.app")
5154
.option("--theme-color <hex>", "shell accent color, for example #2563eb")
55+
.option(
56+
"--titlebar <style>",
57+
"window chrome preset: system, unified, compact, or minimal",
58+
parseTitlebar,
59+
defaultOptions.titlebar,
60+
)
5261
.option("--width <number>", "window width", parseInteger, defaultOptions.width)
5362
.option("--height <number>", "window height", parseInteger, defaultOptions.height)
5463
.option("--package-manager <pm>", "install command for the generated app: bun or npm", defaultOptions.packageManager)
@@ -57,6 +66,25 @@ program
5766
.option("-y, --yes", "accept interactive prompts automatically")
5867
.option("--show-config", "print resolved config before writing files")
5968
.option("--quiet", "reduce output")
69+
.addHelpText(
70+
"after",
71+
`
72+
73+
Titlebar presets:
74+
system Keep the default system title bar. Best for strict native chrome.
75+
unified Recommended on macOS. Hidden inset traffic lights with a connected local toolbar.
76+
compact Same macOS pattern, but tighter for content-heavy apps.
77+
minimal Same macOS pattern, with lighter metadata and less visible chrome.
78+
79+
Examples:
80+
$ appbun create https://calendar.google.com --name Calendar --out-dir ./calendar-app
81+
$ appbun https://linear.app --package-manager npm --install
82+
$ appbun create https://chat.openai.com --theme-color #10a37f --titlebar compact --width 1600 --height 1000
83+
$ appbun https://chat.openai.com --name ChatGPT --dmg
84+
$ appbun https://github.com --name GitHub --titlebar system
85+
$ appbun https://github.com --name GitHub --out-dir ./github --yes
86+
`,
87+
)
6088
.action(async (url: string, options: CreateCommandOptions) => {
6189
try {
6290
validatePackageManager(options.packageManager);
@@ -175,10 +203,25 @@ program
175203
.option("--description <description>", "package description")
176204
.option("--identifier <identifier>", "bundle identifier, for example com.example.app")
177205
.option("--theme-color <hex>", "shell accent color, for example #2563eb")
206+
.option(
207+
"--titlebar <style>",
208+
"window chrome preset: system, unified, compact, or minimal",
209+
parseTitlebar,
210+
defaultOptions.titlebar,
211+
)
178212
.option("--width <number>", "window width", parseInteger, defaultOptions.width)
179213
.option("--height <number>", "window height", parseInteger, defaultOptions.height)
180214
.option("--copy", "copy the generated prompt to the clipboard when supported")
181215
.option("--quiet", "reduce metadata logs")
216+
.addHelpText(
217+
"after",
218+
`
219+
220+
Examples:
221+
$ appbun prompt http://localhost:3000 --name "My App"
222+
$ appbun prompt https://staging.example.com --name "Staging App" --titlebar minimal --copy
223+
`,
224+
)
182225
.action(async (url: string, options: CreateCommandOptions & { copy?: boolean }) => {
183226
try {
184227
validatePackageManager(defaultOptions.packageManager);
@@ -239,13 +282,11 @@ program
239282
program.addHelpText(
240283
"after",
241284
`
242-
Examples:
243-
$ appbun create https://calendar.google.com --name Calendar --out-dir ./calendar-app
244-
$ appbun https://linear.app --package-manager npm --install
245-
$ appbun create https://chat.openai.com --theme-color #10a37f --width 1600 --height 1000
246-
$ appbun https://chat.openai.com --name ChatGPT --dmg
247-
$ appbun https://github.com --name GitHub --out-dir ./github --yes
248-
$ appbun prompt https://myapp.com --name "My App" --copy
285+
Commands:
286+
create Scaffold a desktop wrapper project from a URL.
287+
prompt Print a ready-to-paste instruction block for another coding agent.
288+
289+
Run "appbun <command> --help" to see examples and titlebar presets.
249290
`,
250291
);
251292

@@ -259,6 +300,20 @@ function parseInteger(value: string): number {
259300
return parsed;
260301
}
261302

303+
function parseTitlebar(value: string): TitlebarStyle {
304+
const normalized = value.trim().toLowerCase();
305+
if (
306+
normalized === "system" ||
307+
normalized === "unified" ||
308+
normalized === "compact" ||
309+
normalized === "minimal"
310+
) {
311+
return normalized;
312+
}
313+
314+
throw new Error(`Invalid titlebar style: ${value}. Use system, unified, compact, or minimal.`);
315+
}
316+
262317
function validatePackageManager(value: string): asserts value is "bun" | "npm" {
263318
if (value !== "bun" && value !== "npm") {
264319
throw new Error(`Unsupported package manager: ${value}`);

0 commit comments

Comments
 (0)