The manifest — pulsar.json
One file drives the app config and packaging.
{
"id": "com.example.myapp",
"name": "my-app",
"version": "1.0.0",
"main": "main.ts",
"icons": { "linux": "assets/icon.png", "macos": "assets/icon.icns", "windows": "assets/icon.ico" },
"windows": [
{
"label": "main",
"ui": "ui/main.qml",
"width": 800,
"height": 600,
"title": "My App",
"minWidth": 400,
"minHeight": 300,
"resizable": true,
"frameless": false,
"transparent": false,
"icon": "assets/icon.png"
}
],
"permissions": [],
"osPermissions": ["camera", "microphone"],
"fileAssociations": [{ "ext": "md", "mime": "text/markdown", "name": "Markdown document" }],
"protocols": [{ "scheme": "myapp" }],
"singleInstance": true
}
| Field | Meaning |
|---|---|
id |
Reverse-DNS id; used for per-app data/config/cache directories. |
name |
Display/output name (also the dist/<name> file name). |
version |
App version. |
main |
Backend entry point relative to the manifest (default main.ts). |
icons |
Per-platform icon paths (used by packaging). |
windows[] |
Initial windows: label, ui, width, height, title, minWidth, minHeight, maxWidth, maxHeight, resizable, frameless, transparent, icon. |
permissions |
Capabilities the app may use (see Permissions). Absent = all. |
osPermissions |
Runtime OS permissions to declare in the Android manifest: camera, microphone, location, notifications, storage. |
protocols |
Custom URL schemes (deep links) the app handles. |
fileAssociations |
File types the app opens: ext (without the dot), mime, name. |
singleInstance |
Forward a second launch to the running instance and exit (default false). |
ignore |
Extra paths left out of builds, in .gitignore syntax. |
Builds always skip .git, node_modules, dist, build, .cache,
.pulsar, .env* and sync.db*, and honour the app's root .gitignore.
ignore adds patterns on top of that; a ! entry re-includes a path ignored
by .gitignore, but never one of the built-in exclusions.
App derives its config from the first manifest window; explicit options
passed to new App({...}) override the manifest.
Transparent windows
"transparent": true on a window clears its background, forces frameless and
draws a rounded translucent surface. The blur itself is done by the compositor;
for Hyprland:
windowrulev2 = opacity 0.94 0.88,class:^(pulsar-host)$
windowrulev2 = blur,class:^(pulsar-host)$
windowrulev2 = rounding 18,class:^(pulsar-host)$
Check the class with hyprctl clients. Without blur transparency still works,
but the frosted look is weaker.
Native menu bar
app.setMenu([
{
label: "File",
items: [
{ id: "new", label: "New task", shortcut: "CmdOrCtrl+N" },
{ separator: true },
{ id: "hide", label: "Hide done", checked: app.store.get("hideDone") === true },
],
},
])
app.on("menu", ({ id }) => {
if (id === "new") app.store.set("composing", true)
})
The menu is built natively in the macOS global menu bar; Linux and Windows
currently ignore it. Call setMenu again to refresh checked states. An item
with items becomes a submenu; choosing a leaf emits menu with its id.
Permissions
permissions declares the OS-facing capabilities the app may use. When the
field is absent every capability is allowed; when present only the
listed ones are, and any other capability call fails with a permission denied
error.
{ "permissions": ["fs", "net", "dialog"] }
| Permission | Grants |
|---|---|
fs |
filesystem reads/writes, stat, rename, trash |
net |
net.fetch, net.request, net.websocket |
paths |
standard per-app directories |
shell |
open URLs in the default application |
notify |
native notifications |
dialog |
native file/message dialogs |
screen |
monitor list |
theme |
system color scheme |
system |
locale/language/timezone |
storage |
the app's JSON storage file |
secure |
OS keychain / secret storage |
tray |
system tray icon and menu |
clipboard |
system clipboard read/write |
share |
OS share sheet |
device |
orientation lock and keep-awake |
permissions |
runtime OS permission prompts |
media |
host-backed media playback |
Window management is core and is not gated. Custom capabilities registered by the app are never gated either.
Runtime permission prompts are separate from capabilities. List what the app
may ask for in osPermissions and request it at runtime:
import { permissions } from "pulsar"
if (await permissions.request("camera")) startScanner()
const granted = await permissions.check("microphone")
Names: camera, microphone, location, notifications, storage. On
Android they are mapped into the manifest and request shows the system
prompt; on desktop both calls resolve true.