# Embedding AC pieces: framing The host owns the preview rectangle. AC fills that rectangle. A 4:3 preview means a width-to-height ratio; it does not imply a palette, bezel, pixel density, or filter. ## Set the viewport in the host ```html
``` ```css .piece-preview { position: relative; width: 100%; aspect-ratio: 4 / 3; overflow: hidden; } .piece-preview > iframe { position: absolute; inset: 0; display: block; width: 100%; height: 100%; margin: 0; padding: 0; border: 0; } ``` The wrapper establishes a height before the iframe fills it. `display: block` avoids the baseline space below inline iframes. Remove host padding and borders when the preview should touch its container edges. In a native app, apply the same ratio to the WebView's layout bounds. An iframe is a separate viewport: style its element in the host, and configure the AC document with its URL. Host CSS cannot reach into a cross-origin frame. `object-fit` on an iframe does not resize the child canvas. Piece code should compose against `screen.width` and `screen.height`, not the surrounding app. ## Configure the runtime | Parameter | Effect | Use | | --- | --- | --- | | `nogap=true` | Sets runtime gap to zero; canvas wrapper fills its viewport and loses rounded corners/checkerboard background. | Edge-to-edge previews. | | `nolabel=true` | Suppresses the runtime corner label/HUD. | Host supplies its own controls. | | `autoreload=true` | Takes available piece updates automatically instead of showing the update badge. | Live authoring previews. | | `noauth=true` | Skips the embedded runtime's authentication startup. | Draft previews whose host handles account access; omit when the embedded piece needs authentication. | | `preview=true` | Selects the piece's `preview()` lifecycle behavior when present. | Intentional preview rendering, not an arbitrary preview identifier. | | `icon=true` | Selects icon rendering behavior. | Intentional icon generation. | | `density=…` | Changes the runtime's rendering density. | Deliberate resolution tuning, independently of aspect ratio. | These flags are read by presence in several runtime paths. Remove a flag to disable it; do not rely on `nogap=false` or `nolabel=false`. The ordinary embedded authoring baseline is `?nogap=true&nolabel=true&autoreload=true`. A blank draft runtime can start at `/wipe` with `noauth=true` added. Keep query parameters before any `#fragment`. `allow="autoplay"` permits autoplay within the iframe policy; it does not guarantee audio without a browser-required user gesture. Implementation: [boot parameters](../system/public/aesthetic.computer/boot.mjs), [BIOS framing](../system/public/aesthetic.computer/bios.mjs), [runtime CSS](../system/public/aesthetic.computer/style.css), and [piece lifecycle/label handling](../system/public/aesthetic.computer/lib/disk.mjs). ## Keep framing through source changes Changing source should not require navigating the iframe again. The native Aesel bridge waits for runtime readiness, then injects a `dropped:piece` message from inside the WebView: ```js window.acSEND({ type: "dropped:piece", content: { name: "aesel-preview", source, search: "nogap=true&nolabel=true&autoreload=true", isKidLisp: false, }, }); ``` This is an internal runtime call, not a generic cross-origin iframe messaging API. A browser host on another origin needs an explicit bridge; it cannot call `frame.contentWindow.acSEND` directly. The URL configures initial framing. The message's `search` preserves the piece layer's display flags during replacement. The worker remembers label suppression for its lifetime, and BIOS preserves view parameters when rewriting browser history. Carry intentional `preview` or `icon` flags too, but do not add them as tracking tags. A message being accepted is not proof of a successful paint; authoring bridges should wait for rendering evidence and report runtime errors. Existing integrations: - [Aesel URL construction](../apple/aesel/Sources/Session.swift), [native injection and rendering evidence](../apple/aesel/Sources/AeselPieceView.swift). - [Shared “Try” page](../system/public/aesthetic.computer/lib/try/shared-page.mjs): constructs embedded URLs with `nogap`, `nolabel`, and `noauth`. - [Aesel phone sessions](../aesel/phone/session.mjs): published-piece preview URLs. - [Whistlegraph host](../apple/whistlegraph/Resources/Web/engine.mjs), [native source bridge](../apple/whistlegraph/Sources/WhistlegraphAccount.swift). ## Diagnose an unwanted gap Measure three rectangles separately: the host wrapper, iframe viewport, and AC's `#aesthetic-computer` wrapper. In `nogap` mode, the latter should start at `0,0` and fill the child viewport. Check screenshots after first paint, a source replacement, a resize/rotation, and a density change. | Symptom | Inspect | | --- | --- | | Space outside the iframe | Host margin, padding, border, inline baseline, or a fixed height competing with `aspect-ratio`. | | Checkerboard or rounded corners inside it | `body.nogap` and the effective BIOS gap. | | Label returns after an edit | Initial URL plus source message `search`; worker label persistence. | | Correct frame, padded drawing | Piece composition and its own `screen`/reframe calls. | | Tiny seam on one edge | Wrapper/canvas CSS dimensions and fractional scaling; zero-gap BIOS uses `100%` dimensions. | BIOS must resolve an omitted `frame()` gap from `lastGap`/`startGap` **before** toggling `body.nogap`. Otherwise a density change calling `frame()` can remove the class while the effective gap remains zero. A useful regression sequence is `?nogap=true`, followed by Cmd/Ctrl+Plus or an `ac-density-change` event: the class and edge-to-edge bounds must survive both. `window.acFORCE_NOGAP = true` is an internal host hook used by packed bundles that have no normal query string. BIOS honors it on every reframe, including explicit nonzero gap requests. It is not a URL parameter, and an ordinary web embed should use `nogap=true` rather than depend on script injection. ## Iterate on Whistlegraph layout without restarting Whistlegraph lives in `apple/whistlegraph` (formerly `apple/walkieware`). The installed bundle identifier, Keychain service, and WebKit storage origin retain their legacy identifiers so updates preserve sign-in and artwork. Local storage is copied once to `whistlegraph-*` keys; saved source and history remain intact. Existing `ww…` piece codes, API routes, module imports, and the `ww.mjs` CLI remain compatibility aliases. New codes use `wg…`; the primary CLI is `slab/bin/whistlegraph.mjs`. The Ware picker defaults to **Aesthetic.Computer Piece** and also offers **Roblox Room**. Each has separate saved versions. The brain can queue a ware switch; explicit requests such as “switch to Roblox” also work locally. The room editor and private service setup are described in [`roblox/rooms/README.md`](../roblox/rooms/README.md). For a running Whistlegraph app with the live-layout bridge, send a CSS file through its existing owner-authenticated thread socket: ```sh node slab/bin/whistlegraph.mjs layout wwRuboh /tmp/whistlegraph-layout.css ``` The stylesheet replaces the previous live override and persists locally across launches. An empty file clears it. This updates the surrounding app shell, not the cross-origin AC document. It does not reload the WebView, regenerate the piece, or add a piece version. The device must be online and idle; the command returns an acknowledgement after applying the override. CSS is limited to 100 KB. Native Swift changes still require an app build/install; this is a CSS iteration path, not Swift code hot reload. Whistlegraph's native shell keeps its `Workspace` WebView at one stable SwiftUI position. `WhistlegraphScreen.swift` owns the preview card, trailing caption, version feed, and talk dock. `engine.mjs` remains the authority for versions and generation; it sends typed, throttled display snapshots and accepts native checkout/new-piece/stop commands. The engine document is the dedicated `Resources/Web/shell.html`, not an Aesel prototype. Early provisional previews are distinct from committed versions, so streaming remains visible before a version is saved. For live native layout tuning, the same CSS transport accepts bounded tokens: ```css :root { --ww-spacing: 12; --ww-page-inset: 20; --ww-history-size: 22; --ww-talk-height: 98; --ww-title-size: 30; } ``` The bridge reads these as numeric values and Swift clamps them to supported ranges before updating observable layout state. This changes native layout without recreating WebKit. Other CSS declarations affect only the web host; they do not style SwiftUI controls. An empty override restores the defaults.