# Manual Setup (/docs/advanced/manual-setup) import { Callout } from "fumadocs-ui/components/callout"; import { Tab, Tabs } from "fumadocs-ui/components/tabs"; While the `create-cosmo-widget` scaffolding tool is the easiest way to start, you can also manually configure an existing web project to work with Cosmo. ## 1. Install Dependencies Install the Widget SDK for runtime type safety and utilities. ```bash npm install @buildcosmo/widget ``` ```bash pnpm add @buildcosmo/widget ``` ```bash yarn add @buildcosmo/widget ``` If you're using Vite, you can optionally install the Cosmo Vite plugin for on-desktop dev widget testing: ```bash npm install -D @buildcosmo/vite-plugin ``` ```bash pnpm add -D @buildcosmo/vite-plugin ``` ```bash yarn add -D @buildcosmo/vite-plugin ``` ## 2. Configure Vite (Optional) If you're using Vite, add the Cosmo plugin to your `vite.config.js` or `vite.config.ts`. This plugin handles configuration validation, asset packaging, and dev server communication for on-desktop testing. ```javascript title="vite.config.js" import { defineConfig } from "vite"; import { cosmo } from "@buildcosmo/vite-plugin"; export default defineConfig({ plugins: [ // ... other plugins (e.g., react(), vue()) cosmo(), ], }); ``` Without the Vite plugin, you can still build and test your widget, but you won't have automatic on-desktop dev widget testing integration. ## 3. Add Widget Configuration Create a `widget.config.json` file in the root of your project. This file is required for Cosmo to recognize and load your widget. ```json title="widget.config.json" { "minCosmoVersion": "0.4.0", "defaultWidth": 320, "defaultHeight": 200, "minWidth": 200, "minHeight": 120, "allowResize": true, "keepAspectRatio": false, "allowLockScreen": true, "allowInternet": false } ``` See [Widget Configuration](./widget-config) for a full list of options. ## 4. Create Entry Point Cosmo expects your widget to expose a global `window.widget` function. This function is called when the widget is initialized. Modify your main entry file (e.g., `src/main.jsx`, `src/main.ts`, or `src/main.js`) to export this function. ```js title="src/main.js" function widget(preferences, widgetData) { const app = document.querySelector('#app'); app.innerHTML = `

Hello Cosmo!

`; } window.widget = widget; // Dev fallback: Simulate first load (widgetData is undefined) if (import.meta.env.DEV && !window.webkit) { widget({ theme: "Default", hideBackground: false }); } ```
```jsx title="src/main.jsx" import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import App from './App'; let root = null; function widget(preferences, widgetData) { const container = document.getElementById('root'); if (!root) { root = createRoot(container); } root.render( ); } window.widget = widget; // Dev fallback: Simulate first load (widgetData is undefined) if (import.meta.env.DEV && !window.webkit) { widget({ theme: "Default", hideBackground: false }); } ``` ```ts title="src/main.ts" import { createApp } from 'vue'; import App from './App.vue'; function widget(preferences: Record, widgetData?: Record) { const app = createApp(App, { preferences, widgetData }); app.mount('#app'); } (window as any).widget = widget; // Dev fallback: Simulate first load (widgetData is undefined) if (import.meta.env.DEV && !(window as any).webkit) { widget({ theme: "Default", hideBackground: false }); } ```
Cosmo looks for `window.widget`. If this is not defined, your widget will display a blank screen. ## 5. Run Development Server Start your development server as usual. ```bash npm run dev ``` If you're using Vite with the Cosmo plugin and Cosmo's Developer Mode is enabled, it will automatically detect your local server and prompt you to open the widget on the desktop. Without the Vite plugin, you'll need to build your widget and load it manually for testing. # Calendar (/docs/api-reference/calendar) ## Event Manager The `eventManager` module provides access to the user's calendar events and calendars. ### Usage ```typescript import { eventManager } from "@buildcosmo/widget"; ``` ## `getCalendars()` Retrieves a list of available calendars on the device. ### Usage ```typescript const calendars = await eventManager.getCalendars(); console.log("Calendars:", calendars); ``` ### Returns * `Promise`: An array of calendar objects. #### CalendarInfo Type ```typescript interface CalendarInfo { title: string; identifier: string; source: { title: string; identifier: string; }; allowsModifications: boolean; colorHex: string; type: string; // e.g., "local", "calDAV", "exchange" } ``` ## `getCalendarEvents(start, end)` Retrieves calendar events within a specified date range. ### Usage ```typescript const start = new Date().toISOString(); const end = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(); const events = await eventManager.getCalendarEvents(start, end); console.log("Events:", events); ``` ### Parameters | Name | Type | Description | | :------ | :------- | :--------------------------------- | | `start` | `string` | The start date in ISO 8601 format. | | `end` | `string` | The end date in ISO 8601 format. | ### Returns * `Promise`: An array of event objects. #### CalendarEvent Type ```typescript interface CalendarEvent { eventId: string; title: string; startDate: string; // ISO string endDate: string; // ISO string isAllDay: boolean; calendar: CalendarInfo; location?: string; notes?: string; } ``` ## `registerEventChangeObserver(callback)` Registers a callback to be notified when the calendar store changes (e.g., an event is added or modified externally). ### Usage ```typescript const unsubscribe = eventManager.registerEventChangeObserver(() => { console.log("Calendar events changed. Refreshing data..."); // Re-fetch events here }); // Later, to stop listening: unsubscribe(); ``` ### Returns * `() => void`: A function to unregister the observer. # Core (/docs/api-reference/core) ## `setWidgetData(data)` The `setWidgetData` function allows you to persist data for your widget. This data is saved by the host application and can be restored when the widget is reloaded. ### Usage ```typescript import { setWidgetData } from "@buildcosmo/widget"; // Save a simple object setWidgetData({ theme: "dark", lastUpdated: Date.now(), }); ``` ### Parameters | Name | Type | Description | | :----- | :---- | :------------------------------------------------------------------------------ | | `data` | `any` | The data to save. If an object is passed, it will be automatically stringified. | ### Behavior * The data is sent to the host application via `window.webkit.messageHandlers.saveWidgetData`. * If `data` is an object, it is `JSON.stringify`'d before sending. * This data is associated with the specific widget instance. ## `getWidgetId()` Retrieves the unique identifier for the current widget instance. ### Usage ```typescript import { getWidgetId } from "@buildcosmo/widget"; const widgetId = await getWidgetId(); console.log("Widget ID:", widgetId); ``` ### Returns * `Promise`: The unique ID of the widget. ## `getUserId()` Retrieves the unique identifier for the current user. ### Usage ```typescript import { getUserId } from "@buildcosmo/widget"; const userId = await getUserId(); console.log("User ID:", userId); ``` ### Returns * `Promise`: The unique ID of the user. ## `openUrl(url)` Opens a URL in the user's default web browser. ### Usage ```typescript import { openUrl } from "@buildcosmo/widget"; openUrl("https://example.com"); ``` ### Parameters | Name | Type | Description | | :---- | :------- | :--------------- | | `url` | `string` | The URL to open. | ## `openCosmoUrl(path)` Opens a path within the Cosmo application. ### Usage ```typescript import { openCosmoUrl } from "@buildcosmo/widget"; openCosmoUrl("/settings"); ``` ### Parameters | Name | Type | Description | | :----- | :------- | :-------------------------------- | | `path` | `string` | The internal path to navigate to. | ## `getCosmoUrl(path)` Generates a full Cosmo URL for a given path. ### Usage ```typescript import { getCosmoUrl } from "@buildcosmo/widget"; const url = await getCosmoUrl("/widget/123"); ``` ### Parameters | Name | Type | Description | | :----- | :------- | :----------------- | | `path` | `string` | The internal path. | ### Returns * `Promise`: The full URL. # Errors (/docs/api-reference/errors) ## CosmoError The `CosmoError` class is thrown when a native bridge request fails. It provides structured error information to help you debug issues. ### Structure ```typescript class CosmoError extends Error { type: string; // The category of the error (e.g., "PERMISSION_DENIED", "INTERNAL_ERROR") code: string; // A specific error code message: string; // A human-readable description } ``` ### Usage You should wrap asynchronous SDK calls in `try/catch` blocks to handle potential errors, especially when dealing with permissions or hardware resources. ```typescript import { systemResources, errors } from "@buildcosmo/widget"; try { const cpu = await systemResources.getSystemCpu(); console.log("CPU:", cpu); } catch (error) { if (error instanceof errors.CosmoError) { console.error(`SDK Error [${error.code}]: ${error.message}`); } else { console.error("Unexpected error:", error); } } ``` # System Resources (/docs/api-reference/system-resources) ## System Resources The `systemResources` module allows you to access real-time system information. ### Usage ```typescript import { systemResources } from "@buildcosmo/widget"; ``` ## `getSystemCpu()` Retrieves the current CPU usage percentage. ### Usage ```typescript const cpuUsage = await systemResources.getSystemCpu(); console.log(`CPU Usage: ${cpuUsage}%`); ``` ### Returns * `Promise`: The current CPU usage as a percentage (0-100). ## `getSystemMemory()` Retrieves the current memory usage. ### Usage ```typescript const [used, total] = await systemResources.getSystemMemory(); const usedGB = (used / (1024 * 1024 * 1024)).toFixed(2); const totalGB = (total / (1024 * 1024 * 1024)).toFixed(2); console.log(`Memory: ${usedGB}GB / ${totalGB}GB`); ``` ### Returns * `Promise<[number, number]>`: A tuple containing: 1. `used`: Used memory in bytes. 2. `total`: Total memory in bytes. ## `getSystemBattery()` Retrieves the current battery status. ### Usage ```typescript const [percentage, isCharging, remainingTime] = await systemResources.getSystemBattery(); console.log(`Battery: ${percentage}%`); console.log(`Charging: ${isCharging}`); console.log(`Time remaining: ${remainingTime} minutes`); ``` ### Returns * `Promise<[number, boolean, number]>`: A tuple containing: 1. `percentage`: Battery level as a percentage (0-100). 2. `isCharging`: `true` if the device is plugged in, `false` otherwise. 3. `remainingTime`: Estimated time remaining in minutes. Returns `-1` if unknown. # System Settings (/docs/api-reference/system-settings) ## System Settings Use the `systemSettings` module to read preference-like data that the Cosmo app exposes to widgets. These helpers proxy the native `SystemSettings` Swift API and let you keep all platform-specific logic on the Cosmo side while consuming simple async functions in JavaScript. ### Usage ```typescript import { systemSettings } from "@buildcosmo/widget"; ``` All functions return Promises that resolve after the native bridge replies. They do not require extra permissions because the data comes from the user's local settings and does not identify the user personally. ## API Reference ### Time & Date Format #### `is24HourFormat()` Returns `true` if the user prefers a 24-hour clock (no AM/PM). ```typescript const is24h = await systemSettings.is24HourFormat(); ``` #### `calendarSystem()` Returns the identifier of the calendar system used by the user (e.g., "gregorian"). ```typescript const calendar = await systemSettings.calendarSystem(); ``` #### `timeZone()` Returns information about the user's current time zone. ```typescript const tz = await systemSettings.timeZone(); console.log(tz.identifier); // e.g., "America/Los_Angeles" ``` **Returns:** `Promise` ```typescript type CosmoTimeZone = { identifier: string; // e.g., "America/Los_Angeles" abbreviation: string; // e.g., "PDT" secondsFromGMT: number; // offset in seconds }; ``` ### Locale & Region #### `locale()` Returns the user's locale identifier (e.g., "en\_US"). ```typescript const loc = await systemSettings.locale(); ``` #### `measurementSystem()` Returns the measurement system used by the locale. ```typescript const system = await systemSettings.measurementSystem(); // Returns: 'metric' | 'us' | 'uk' ``` #### `usesMetricSystem()` A convenience boolean to check if the metric system is used. ```typescript const isMetric = await systemSettings.usesMetricSystem(); ``` ### Calendar Preferences #### `firstWeekday()` Returns the index of the first day of the week (1 = Sunday, 2 = Monday, etc.). ```typescript const startDay = await systemSettings.firstWeekday(); ``` #### `minimumDaysInFirstWeek()` Returns the minimum number of days required in the first week of the year. ```typescript const minDays = await systemSettings.minimumDaysInFirstWeek(); ``` #### `weekendDays()` Returns an array of weekday indices that are considered weekend days. ```typescript const weekends = await systemSettings.weekendDays(); // e.g., [1, 7] for Sunday and Saturday ``` ## Usage Examples ### Adjusting time formats ```typescript const prefers24h = await systemSettings.is24HourFormat(); const timeOptions: Intl.DateTimeFormatOptions = { hour: "numeric", minute: "2-digit", hour12: !prefers24h, }; ``` ### Toggling measurement units ```typescript const system = await systemSettings.measurementSystem(); const distanceUnit = system === "metric" ? "km" : "mi"; const temperatureUnit = system === "us" ? "°F" : "°C"; ``` ### Highlighting weekend days ```typescript const weekendDays = await systemSettings.weekendDays(); const weekendSet = new Set(weekendDays); // Check if today is a weekend const todayIndex = new Date().getDay() + 1; // 1-based index to match API if (weekendSet.has(todayIndex)) { console.log("It's the weekend!"); } ``` ### System Appearance (Dark Mode) You can detect the system's light/dark mode using standard Web APIs. No special SDK function is required. **CSS:** ```css @media (prefers-color-scheme: dark) { body { background: #000; color: #fff; } } ``` **JavaScript:** ```typescript const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches; // Listen for changes window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => { const newColorScheme = e.matches ? "dark" : "light"; console.log(`Theme changed to ${newColorScheme}`); }); ``` # Window (/docs/api-reference/window) import { Callout } from "fumadocs-ui/components/callout"; The `window` module allows your widget to interact with its containing window. You can get and set the window's size and position on the screen. ## Usage Import the methods from `@buildcosmo/widget`: ```ts import { getWindowSize, setWindowSize, getWindowPosition, setWindowPosition, } from "@buildcosmo/widget"; ``` ## Methods ### `getWindowSize()` Returns the current size of the widget window in points. ```ts const size = await getWindowSize(); console.log(size.width, size.height); ``` ### `setWindowSize(size)` Sets the size of the widget window. ```ts setWindowSize({ width: 400, height: 300 }); ``` ### `getWindowPosition()` Returns the current position of the widget window (bottom-left corner) in screen coordinates. ```ts const pos = await getWindowPosition(); console.log(pos.x, pos.y); ``` ### `setWindowPosition(x, y, options?)` Sets the position of the widget window. You can set the position in two ways: 1. **Exact Coordinates**: Pass `x` and `y` to set the position of the window's **bottom-left corner** in screen coordinates (pixels), where (0, 0) is the bottom-left of the main screen. 2. **Proportional Coordinates**: Pass `x` and `y` with `{ proportional: true }` to set the position based on screen proportions (0-1), where `0.5, 0.5` is the center of the screen. In this mode, the position references the **center** of the window. `y=0` is the bottom edge, and `y=1` is the top edge. ```ts // Set exact position (bottom-left corner at 100, 100) setWindowPosition(100, 100); // Center the window on the screen setWindowPosition(0.5, 0.5, { proportional: true }); // Position at top-right corner (center of window at top-right of screen) setWindowPosition(1, 1, { proportional: true }); ``` The window position is clamped to the screen boundaries. If you attempt to set a position that would place the window partially or fully off-screen, it will be adjusted to stay within the screen area. # Distribution (/docs/getting-started/distribution) import { Callout } from "fumadocs-ui/components/callout"; The Cosmo Marketplace and distribution platform are currently under active development. We are building a centralized marketplace where developers can publish their widgets for Cosmo users to discover and install. This platform will handle: * **Hosting:** Secure and reliable delivery of widget assets. * **Versioning:** Automatic updates for installed widgets. * **Discovery:** Search, categories, and featured collections. * **Monetization:** (Future) Options for paid widgets. ## Current Status At this moment, widgets can only be side-loaded locally using **Developer Mode**. You can share your widget source code (e.g., via GitHub) for others to build and run locally, but there is no official mechanism for distributing pre-built widget bundles yet. Stay tuned for updates on the Cosmo Marketplace launch! Join our [Discord server](https://discord.gg/TpWuqRWWby) to be the first to know. # Getting Started (/docs/getting-started/introduction) import { Tab, Tabs } from "fumadocs-ui/components/tabs"; import { Callout } from "fumadocs-ui/components/callout"; These docs are optimized for AI. Add [`https://buildcosmo.com/llms-full.txt`](https://buildcosmo.com/llms-full.txt) to your AI IDE (like Cursor or Windsurf) for context-aware coding assistance. Cosmo allows you to build desktop widgets using standard web technologies. This guide covers the end-to-end workflow for developing a widget using the [Vite](https://vitejs.dev)-based scaffolding tool. ## Prerequisites * **Node.js 20+**: Required for the build tooling. * **Cosmo for macOS**: Must be installed and running on the same machine. * **Developer Mode**: Enable this in Cosmo via **Settings → General**. The app will ignore local development widgets if this is disabled. ## Scaffold a Project Run the create command to generate a new widget project. This initializes a Vite project configured for Cosmo. ```bash npm create cosmo-widget@latest my-widget cd my-widget npm install ``` ```bash pnpm create cosmo-widget@latest my-widget cd my-widget pnpm install ``` ```bash yarn create cosmo-widget@latest my-widget cd my-widget yarn install ``` Setting up an existing web project? See [Manual Setup](../advanced/manual-setup) to convert your project into a Cosmo widget. ## Project Structure The scaffolded project includes the following key files: | File | Purpose | | :--------------------------------- | :------------------------------------------------------------------------------------ | | `src/widget.js` | The entry point. Must export a `widget()` function. | | `widget.config.json` | Mandatory configuration (dimensions, constraints, etc.). Validated at build time. | | `widget.preferences-template.json` | Optional schema for user-configurable preferences shown in the widget's context menu. | | `thumbnail.png` | Optional preview image for the Cosmo marketplace. Recommended resolution: 512x512px+. | ## Development Workflow Start the development server to preview your widget in Cosmo. ```bash npm run dev ``` This command: 1. Starts the local Vite development server. 2. Opens your widget in the Cosmo desktop app. ### Server-Only Mode To run the Vite server without opening the widget in Cosmo (useful for UI testing in a browser): ```bash npm run dev -- -s ``` ## Build for Distribution Package your widget for release. ```bash npm run build ``` The build process: 1. Bundles your assets using Vite. 2. Validates `widget.config.json` against the schema. 3. Copies configuration files and the thumbnail to the `dist/` directory. Ensure `widget.config.json` is valid; build errors will occur if required fields are missing or out of bounds. > **Note:** Distribution through the Cosmo Marketplace is currently unavailable. Support for publishing widgets will be available soon. See [Distribution](./distribution) for more details. ## Next Steps * Configure your widget's behavior in [`widget.config.json`](./widget-config). * Define user preferences in [`widget.preferences-template.json`](./widget-preferences-template). * Explore the [Core SDK](../api-reference/core) for available APIs. ## Join our Community Connect with other developers and get support on our [Discord server](https://discord.gg/TpWuqRWWby). # Persisting Data (/docs/getting-started/persisting-data) import { Callout } from "fumadocs-ui/components/callout"; import { Tab, Tabs } from "fumadocs-ui/components/tabs"; The **Widget Data API** allows you to save state (such as user input, scroll position, or cached API responses) so that it persists across app restarts. ## Saving Data Use `setWidgetData` from the Core SDK to persist a JSON-serializable **object**. Cosmo handles the storage. ```typescript import { setWidgetData } from "@buildcosmo/widget"; setWidgetData({ lastUpdated: Date.now(), activeTab: 'settings', counter: 5 }); ``` `setWidgetData` only accepts plain objects (`Record`). Arrays and primitives are not allowed — wrap them in an object: `{ items: [...] }` or `{ value: 42 }`. Persistence is asynchronous. Avoid calling `setWidgetData` in rapid succession (e.g., on every scroll event); use debouncing. ## Restoring Data Cosmo passes the persisted data as the **second argument** to your widget's entry function. ```typescript title="src/widget.ts" export default function widget(preferences, widgetData) { // ... } ``` ```tsx title="src/main.tsx" export default function widget(preferences, widgetData) { // ... } ``` ```ts title="src/main.ts" export default function widget(preferences, widgetData) { // ... } ``` The `widgetData` parameter is typed as `Record | undefined`. It contains the exact object that was previously saved using `setWidgetData`. If no data has been saved yet, it will be `undefined`. ## Example: Persistent Counter A complete example showing how to initialize state from `widgetData` and save updates. ```typescript title="src/widget.ts" import { setWidgetData } from "@buildcosmo/widget"; export default function widget(preferences, widgetData) { let count = widgetData?.count || 0; const container = document.getElementById('root')!; const update = () => { container.innerHTML = ` `; document.getElementById('btn')?.addEventListener('click', () => { count++; setWidgetData({ count }); update(); }); }; update(); } ``` ```tsx title="src/Widget.tsx" import { useState } from 'react'; import { setWidgetData } from "@buildcosmo/widget"; export default function Widget({ widgetData }: { widgetData?: Record }) { const [count, setCount] = useState(widgetData?.count || 0); const increment = () => { const newCount = count + 1; setCount(newCount); setWidgetData({ count: newCount }); }; return ; } ``` ```vue title="src/Widget.vue" ``` ## Best Practices * **Validation**: Treat `widgetData` as untrusted input. The schema may differ if you've updated your widget code since the data was saved. * **Size Limits**: Store only configuration and lightweight state. Avoid large datasets (e.g., base64 images). * **Debouncing**: When binding to high-frequency events (input, window resize, scroll), debounce calls to `setWidgetData`. # Widget Configuration (/docs/getting-started/widget-config) import { Callout } from "fumadocs-ui/components/callout"; The `widget.config.json` file defines the metadata and behavior of your widget. Cosmo reads this file to determine initial dimensions, resizing constraints, and positioning. Changes to `widget.config.json` are not hot-reloaded. You must restart the development server (`npm run dev`) for configuration changes to take effect in the Cosmo app. ## Required Fields The build process validates these fields. Missing or invalid values will prevent the project from building. | Field | Type | Example | Description | | :---------------- | :------ | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | | `name` | string | `"Clock Widget"` | The display name of the widget shown in the marketplace and app. | | `minCosmoVersion` | string | `"0.4.0"` | The minimum version of the Cosmo app required to run this widget. | | `defaultWidth` | number | `320` | Initial width in points. | | `defaultHeight` | number | `200` | Initial height in points. | | `minWidth` | number | `200` | Minimum width allowed during resizing. | | `minHeight` | number | `120` | Minimum height allowed during resizing. | | `allowResize` | boolean | `true` | If `true`, the user can resize the widget window. | | `keepAspectRatio` | boolean | `false` | If `true`, the aspect ratio is locked during resizing. | | `allowLockScreen` | boolean | `true` | If `true`, the widget can be put on computer's lock screen. | | `allowInternet` | boolean | `false` | Whether the widget can make outgoing network requests. Set this to `false` unless internet access is necessary for your widget to function. | Internet access is allowed in development mode regardless of the `allowInternet` setting. ## Optional Fields | Field | Type | Example | Description | | :--------------------- | :-------- | :----------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `maxWidth` | number | `800` | Maximum width allowed. | | `maxHeight` | number | `600` | Maximum height allowed. | | `defaultPos` | number\[] | `[0.5, 0.5]` | Initial normalized position `[x, y]` on the screen. `[0,0]` is bottom-left; `[1,1]` is top-right. | | `backgroundBlurRadius` | number | `20` | Applies a Gaussian blur to the window background (in points). Requires a semi-transparent background color in your CSS (e.g., `rgba(255, 255, 255, 0.5)`). Set to `0` to disable. | # Preferences Template (/docs/getting-started/widget-preferences-template) import { Callout } from "fumadocs-ui/components/callout"; import { Tab, Tabs } from "fumadocs-ui/components/tabs"; The `widget.preferences-template.json` file defines user-configurable settings. Cosmo uses this schema to generate a native UI in the widget's right-click context menu, allowing users to modify widget behavior without interacting with the DOM. Changes to `widget.preferences-template.json` are not hot-reloaded. You must restart the development server (`npm run dev`) for configuration changes to take effect in the Cosmo app. ## Schema Overview Define preferences as a map where keys are identifiers and values are configuration objects. ```json title="widget.preferences-template.json" { "theme": { "label": "Theme", "value": "default", "order": 1, "controlType": "select", "options": ["default", "cosmo", "sunset"], "optionLabels": ["Default", "Cosmo", "Sunset"] }, "hideBackground": { "label": "Hide Background", "value": false, "order": 2, "controlType": "toggle" } } ``` ## Fields ### Required | Field | Type | Description | | :------------ | :-------------------------- | :----------------------------------------------------------------------------------- | | `label` | string | The display name in the context menu. | | `value` | string \| boolean \| number | The default value. Must match the type expected by `controlType`. | | `order` | number | Sort order in the menu (starting from 1). | | `controlType` | string | The UI component to render. Supported values: `"select"`, `"toggle"`, `"calendars"`. | ### Optional | Field | Type | Description | | :-------------------- | :-------- | :----------------------------------------------------------- | | `description` | string | Helper text displayed in the menu. | | `dividerAfter` | boolean | If `true`, inserts a separator after this item. | | `options` | array | **For `select`**: List of possible values. | | `optionLabels` | string\[] | **For `select`**: Display labels corresponding to `options`. | | `widthMultipliers` | number\[] | [Option modifier](#option-modifiers) for width. | | `heightMultipliers` | number\[] | [Option modifier](#option-modifiers) for height. | | `backgroundBlurRadii` | number\[] | [Option modifier](#option-modifiers) for blur. | For `select` controls: `options` are keys in the `preferences` object passed to your widget. `optionLabels` are the user-facing text shown in the right-click menu. See [Using Preferences](#using-preferences) for examples. ## Control Types ### Select Renders a dropdown menu. * **Requires:** `options`. * **Recommended:** `optionLabels`. ```json { "theme": { "label": "Theme", "value": "default", "controlType": "select", "options": ["default", "frosted"], "optionLabels": ["Default", "Frosted Glass"], "backgroundBlurRadii": [0, 30] } } ``` ### Toggle Renders a checkbox or switch. * **Requires:** `value` (boolean). ```json { "hideBackground": { "label": "Hide Background", "value": false, "controlType": "toggle" } } ``` ### Calendars Renders a calendar picker populated by the user's system calendars. * **Requires:** `value` (initialize as `{}`). * **Usage:** The value is a map of calendar IDs to booleans. ```json { "selectedCalendars": { "label": "Calendars", "value": {}, "controlType": "calendars" } } ``` ## Option Modifiers Option modifiers are arrays that map native window properties to each option in a preference. When the user changes a preference value, the corresponding modifier is applied to the widget window. For `select`, each modifier value maps to the corresponding index in `options`. For `toggle`, the order is `[true, false]`. ### widthMultipliers & heightMultipliers Multiply the widget's base width and height based on the selected preference value. The base multiplier is `1` for both. ```json { "layout": { "label": "Size", "value": "regular", "controlType": "select", "options": ["regular", "wide", "mini"], "optionLabels": ["Regular", "Wide", "Mini"], "widthMultipliers": [1, 1.5, 1], "heightMultipliers": [1, 1, 0.5] } } ``` Selecting `wide` increases width by 50%. Selecting `mini` reduces height to 50%. ### backgroundBlurRadii Sets the background blur radius based on the selected preference value. This overrides the static `backgroundBlurRadius` in `widget.config.json`. ```json { "blur": { "label": "Blur", "value": "none", "controlType": "select", "options": ["none", "heavy"], "optionLabels": ["None", "Heavy"], "backgroundBlurRadii": [0, 50] } } ``` Selecting `heavy` applies a blur radius of 50. Blur effects require the widget's CSS background to be semi-transparent. The blur is applied to the native window behind the web content. ## Using Preferences Cosmo injects preference values into your widget at runtime. They are passed as the first argument to the exported `widget()` function. The `widget()` function is called whenever preferences change, and new preferences data is passed on each call. The preferences parameter is an object where keys match the preference identifiers from your template, and values are the current preference values. ```javascript title="src/widget.js" import './style.css' export default function widget(preferences, widgetData) { // preferences is an object like: // { // "theme": "cosmo", // "hideBackground": true // } const root = ensureRoot(); root.innerHTML = ''; const container = document.createElement('div'); container.className = ` theme-${preferences.theme} ${preferences.hideBackground ? 'hide-background' : ''} `; container.textContent = 'Your widget content goes here.'; root.appendChild(container); } function ensureRoot() { let el = document.querySelector('#widget-root'); if (!el) { el = document.createElement('div'); el.id = 'widget-root'; document.body.appendChild(el); } return el; } ``` ```jsx title="src/Widget.jsx" export default function Widget({ preferences, widgetData }) { // preferences is an object like: // { // "theme": "cosmo", // "hideBackground": true // } return (
Your widget content goes here.
) } ```
```vue title="src/Widget.vue" ```
The template file is only read when a widget is first created or installed. Modifying the template file after installation won't affect existing widget instances.