Skip to content

Icons, widgets, previews, and exports

The editor, Finder artwork, and exported document should feel like the same object. They can have different layouts while reading the same saved data and theme.

Use App.svelte for the editor, required root Preview.svelte and Icon.svelte, plus optional Export.svelte for captures. The CLI discovers these exact filenames. Preview supplies a compact overview; Export includes the complete document. Without Export.svelte, PNG/PDF share/save actions are hidden and CLI export is refused.

<script lang="ts">
import { bindText, getDocument } from "@hitslop/pop/svelte";
import schema from "./schema";
const doc = getDocument(schema);
const addItem = () => doc.fields.tasks.insert({ text: "New task" });
</script>
<main class="note-editor">
<input aria-label="Title" use:bindText={doc.fields.title} />
<button onclick={addItem}>Add item</button>
</main>

Preview, Export and Icon receive snapshot, deeply readonly saved data, attachmentURL(id), and mode ("preview", "export" or "icon"). Widget receives the same data and URL function plus family, width and height. Type these props with CaptureProps<typeof schema> and CaptureMode or WidgetProps; use import type schema so the definition stays out of the capture. Shared editor/static components take data and an attachment URL function as props; the editor passes attachments.url, and captures pass attachmentURL. A dedicated export owns its static layout. For content that grows, such as a list or notes, use a thin export component with natural height and derive its content from saved state. Capture views must be synchronous and independent of browser APIs. Dedicated captures ignore the window silhouette; Quick Look displays the saved artwork. An optional export component (Export.svelte):

<script lang="ts">
import type { CaptureMode, CaptureProps } from "@hitslop/pop/svelte";
import type schema from "./schema";
let { snapshot }: CaptureProps<typeof schema> & { mode: CaptureMode } = $props();
</script>
<article class="note-export">
<h1>{snapshot.title}</h1>
<ul>{#each snapshot.tasks as task (task.$id)}<li>{task.text}</li>{/each}</ul>
</article>

Required Preview.svelte can show a bounded selection and an honest total:

<script lang="ts">
import type { CaptureMode, CaptureProps } from "@hitslop/pop/svelte";
import type schema from "./schema";
let { snapshot }: CaptureProps<typeof schema> & { mode: CaptureMode } = $props();
</script>
<article class="note-preview">
<h1>{snapshot.title}</h1>
<p>{snapshot.tasks.length} tasks</p>
<ul>{#each snapshot.tasks.slice(0, 5) as task (task.$id)}<li>{task.text}</li>{/each}</ul>
</article>

Share components between Preview and Export where useful. Avoid generating a long document just to crop its top for the preview.

Icon.svelte:

<script lang="ts">
import type { CaptureMode, CaptureProps } from "@hitslop/pop/svelte";
import type schema from "./schema";
let { snapshot }: CaptureProps<typeof schema> & { mode: CaptureMode } = $props();
</script>
<div class="note-icon">{snapshot.tasks.length}</div>

Capture components render their saved snapshot through Svelte SSR and Takumi. If an export should show a selected tab or report, save that choice in the document; transient editor state is not carried into the capture. An export or icon component that throws fails that capture without changing the live editor. A later attempt gets a fresh renderer.

Icons, previews, widgets, PNG and selectable vector PDF exports use synchronous Svelte rendering and Takumi. Mac and Linux use a restricted Rust process; the iPhone app renders locally in process. Use snapshot, ordinary flexbox, grid placement (including named grid areas), explicit font properties, and SVG attributes/currentColor. Container queries and page CSS inside inline SVG are unsupported; ignored CSS can change the result, so review native captures. Browser-dependent capture components fail the build with a role/source diagnostic, and so does your own code that uses URL, URLSearchParams, TextEncoder, TextDecoder, AbortController, Blob or structuredClone, which the capture runtime lacks. Editor runtime imports from @hitslop/pop/svelte, such as getDocument, draft, motion, notify, EditableText, bindText, speech and resizeWindow, and Svelte’s onMount are refused, including through aliases, namespace imports and re-exports, and so is attachments: read a stored image’s URL from the attachmentURL prop instead. Everything @hitslop/pop/embed exports is refused too. Static views may import the string helpers in @hitslop/pop/youtube; see YouTube and saved thumbnails. Use static representations for canvas, animation or WebGL content. A rendering failure retains previous artwork; there is no automatic fallback or public renderer override.

Use the attachmentURL(id) prop for referenced PNG/JPEG/GIF/WebP images in saved data; it throws for a missing or non-image attachment. Capture currently loads the whole saved image inventory, capped at 24 MiB, including images that this role does not display. Blob reads, imports, remote loading and document writes are unavailable. Missing or unsupported images fail capture. PNG and PDF share the same native layout, fonts and resources.

Default sans, serif, monospace, Apple color emoji and text-symbol fonts are bundled. Package additional fonts with @font-face and imported url() resources; system-font discovery and remote font loading are unavailable. Fonts need glyph coverage for the languages your document uses; vector PDF reports uncovered characters as an error. Do not use Svelte’s css="injected"; the build collects the capture component’s styles and global CSS.

Quick Checklist, Water Tracker, Grocery List, Reading Tracker, Habit Heatmap, Daily Planner, Assignment Tracker and Countdown Milestones include small, medium and large widget views. Habit Heatmap, Assignment Tracker and Countdown request approximate 30-minute refreshes for date-dependent content. Daily Planner shows the date saved in its plan; Water Tracker shows the saved intake log and does not reset it at midnight. The other views refresh with saved edits.

The same widget view serves iPhone Home Screen widgets in the iPhone app on TestFlight. On iPhone, add a pop widget and select a library pop whose embedded app includes Widget.svelte. The phone renders its widget images locally; tap the widget to open the pop. The app refreshes placed widgets after saves and library changes, and in the background when iCloud changes arrive or iOS schedules a refresh; iOS decides when that runs. See availability.

Add an optional root Widget.svelte to compose the same saved document for a widget:

<script lang="ts">
import type { CaptureProps, WidgetProps } from "@hitslop/pop/svelte";
import type schema from "./schema";
let { snapshot, family, width, height }: CaptureProps<typeof schema> & WidgetProps = $props();
</script>
<main style:width={`${width}px`} style:height={`${height}px`}>
<h1>{snapshot.title}</h1>
{#if family !== "small"}<p>A little more detail.</p>{/if}
</main>

Families are small, medium and large. Fit the provided canvas using ordinary CSS; reuse components, imported assets and theme colors. The host captures a fixed canvas at 2× through the native renderer. Only the widget component renders. WidgetKit clips its outer corners; avoid adding an outer shadow or padding just to simulate the system frame.

Create a document from the rebuilt template and open it in pop to generate its widget images. Add a pop widget from macOS’s widget gallery and choose the document under Edit Widget → Document. Only documents containing Widget.svelte with a successful capture appear. Existing documents retain their embedded app, so rebuilding a template does not add widget support to them. Clicking the image opens that document.

If a widget looks gray or loses its colors when you switch to an app, macOS may be dimming it after capture. Go to System Settings → Desktop & Dock → Widgets → Dim widgets on desktop → Never to keep widgets in full color. Automatically switches between monochrome and full color. On older macOS versions, choose Widget style → Full-color instead. This is a system-wide setting; it doesn’t change the captured image or the document’s theme. See Apple’s widget settings.

Each static role imports its own shared stylesheets (directly or through a component), in import order; captures do not inherit the editor’s stylesheets. Build and register render every declared view, including Export PNG/PDF and all Widget families, even when artwork is supplied.

In pop dev, use the Preview selector to switch between the editor, the preview, the icon and each widget family. Accepted edits survive switching, and component/CSS changes update the view live. Static views still mount inside the editor page and share its CSS cascade. These are browser layouts: review built preview/icon artwork with pop build and widget images on Mac and, for library pop, the iPhone app. Icon and widget canvases clip overflow; the preview warns when content extends beyond its canvas.

Successful saves refresh images after a one-second debounce, including edits while the editor stays open. The running app watches cached documents for closed-document edits, and catches missed changes at startup or reopen. Updating the renderer or its bundled fonts also refreshes unchanged cached documents. A failed capture retains the previous images. The browser preview uses a different renderer; inspect native images before relying on its appearance.

To request occasional recaptures while pop is running, add widget: { refreshMinutes: 30 } to pop.ts. Whole-minute intervals from 15 to 1440 are accepted and require Widget.svelte. Only documents used by placed widgets are scheduled. This requests capture attempts, not an exact visible refresh rate: macOS controls scheduling and WidgetKit controls display updates. Quitting pop leaves the last images on the desktop. refreshMinutes applies on Mac.

The native icon canvas is 512 × 512, and the host centers the artwork in it. Give the design breathing room; for example, a tile around 456 pixels wide. Prefer a recognizable silhouette and a few strong marks over pop text or a screenshot of every control. The icon may summarize current content. Use the same theme colors as the editor, so a person’s palette carries into the icon.

The artwork can be rectangular or another shape; only the canvas must be square. Size the artwork to fit within the canvas, including its shadows. For example, a square tile can use:

.note-icon {
width: 456px;
height: 456px;
display: grid;
place-items: center;
background: var(--pop-accent);
}

Icon.svelte is required when authoring. If Icon.svelte throws, capture fails rather than silently hiding the broken view.

Give export content natural height and normal document flow. Remove selection handles, menus, empty inputs, hover controls, and scroll containers. The optional Export.svelte supplies this complete static view; the editor is never captured. Dedicated exports and icons ignore the window’s shape mask.

The platform hides elements marked data-pop-export="hide" in Preview, Export and Icon, including the Preview and Icon layouts in pop dev. Mark shared editing controls with this attribute; you do not need to copy the hide rule into your CSS.

The host sets html[data-pop-capture="static"] during capture. Use it for deliberate static layout adjustments:

html[data-pop-capture="static"] .note-export {
height: auto;
overflow: visible;
}

Use the native hover toolbar, File menu, or:

Terminal window
bunx @hitslop/pop export My.pop --format png --output My.png
bunx @hitslop/pop export My.pop --format pdf --output My.pdf

Successful responses, respectively (your paths will differ):

/Users/you/Documents/My.png
/Users/you/Documents/My.pdf

An export never replaces the document it renders. For an open document, pop sends unsent text, saves and copies the document before rendering that copy independently. Closed documents also render from saved state. Both pass the saved snapshot and theme to the static view. The open editor is not used as the rendering workspace.

The editor barrier lasts through saving and copying; rendering then proceeds independently. Mac capture loads packaged fonts and saved image resources in the restricted child. The live editor’s focus, selection and scroll remain unchanged. PNG renders at 2×, up to 16,384 pixels per side and 24 megapixels. PDF export keeps searchable text and vectors on one continuous page, scaled to at most 14,400 points per side; complex layouts and long documents may need further refinement.

Completed captures publish atomically before their deadline. If the acknowledgement is lost, inspect the destination before retrying.

On macOS and Linux, building or registering captures Preview.svelte and Icon.svelte as the initial preview and icon into the template; PNGs you declare with artwork: { preview, icon } in definePop are used instead (see Finder artwork). Closing a writable document in the Mac app refreshes both images from its saved contents and theme and stores them in the document. Quick Look shows the preview when you press Space, and Finder, Mail and the share sheet show the icon, including on a Mac that received the file. A document without artwork shows pop’s document icon. Recents follows the document too. Templates keep their initial artwork. Failed captures retain the previous images. Artwork is a derived preview, not document state.

Test normal and narrow windows, empty and populated data, themed output, default local views, saved view choices, and exports from open and closed documents. Check both PNG and PDF; a good editor screenshot alone does not prove a good exported layout.