No new notifications.

Apps

This page is the contract between my Apps and my Homepage.

About this contract

App Coder and Homepage Builder collaborate on integrating the Apps.

App Coder and Homepage Builder collaborate on integrating the Apps.

This page describes how App Coder and Homepage Builder build, host, share, and publish the Apps.

Contract scope

The contract is generally binding for App Coder, with cleared exceptions being allowed.

For Homepage Builder, this is the living document explaining all App integrations.

The reference implementation "Maps" shows the distinction between general (required) and custom (optional) functionality.

Contract language
  • must means required for compatibility.

  • should is the normal recommendation for App Coder.

  • may means optional behavior supported by Homepage.

  • not available means an App can not depend on that yet.

Unless a section says next design pass, it describes current behavior. A should statement is current guidance; a next design pass note describes planned or proposed work.

The related API offering has its own documentation; this page only explains how Apps consume curated Homepage APIs.

The Toasts page defines parent-owned Toast presentation and lifecycle. This page defines the App-side bridge contract for requesting Toasts and receiving Toast actions.

Integrate an App

Requirements for Apps that want to be integrated in the Homepage.

Requirements for Apps that want to be integrated in the Homepage.

Build and integrate an App

Use this path when creating or integrating an App:

  1. Build or synchronize the current App source into . The local symlink exposes to Homepage Builder; it does not refresh after a source change. Published distributions are copied files.

  2. Add a valid with an explicit slug, entry, supported contexts, and bridge capabilities.

  3. Run . If generation fails, inspect and verify the App slug in the generated registry before debugging runtime behavior.

  4. Open the Homepage direct route and verify that the App works in direct mode. Standalone testing is a separate App-owned environment.

  5. Add inline or full Homepage integration only when the App needs it, and declare the supported contexts in its manifest.

  6. If hosted, install the message listener before rendering host-dependent UI. Validate the source, origin, expected App slug, and exact on instance-scoped messages. Handle , , , and ; handle and live when the App declares or uses them; handle when the App offers Toast actions; and ignore unsupported messages safely. See General bridge, Initialization handshake, and Host presentation timing for the detailed contract.

  7. Use Homepage Preferences for persistent, user-facing settings unless the App has a documented reason to keep a control inside its own UI. See Preferences for the baseline.

  8. Use or request a named Homepage API route for provider-backed work. Never ship provider credentials in the App.

  9. Request parent-owned Toasts for relevant user-facing App events with ; report named API failures and recovery with so Homepage can maintain Preferences Warnings. Apps may report analytics events through ; keep those properties safe for CDP delivery. Keep a safe App fallback for hard failures.

  10. Test direct, inline, full, reload, malformed-input, Toast, action, and unavailable-API behavior.

  11. Publish the refreshed output and verify the public App route.

The rest of this page expands each step.

Minimum integration choices

App needMinimum current choice
Standalone App onlythe standalone distribution, a working entry document, and no bridge dependency.
App embedded in Contentinline mode, opaque embed configuration, and a deliberate height strategy.
App presented as a Homepage surfacefull mode, full-surface layout handling, and an instance-scoped bridge.
Persistent user settingsHosted Preferences contribution for embedded instances; App-owned storage for direct-only settings.
Provider-backed dataA named, same-origin Homepage API route.
App-specific host behaviorA documented, opt-in extension rather than a new implicit convention.

Contract at a glance

Capabilities and extensions

These are the generally available capabilities. Each still requires the corresponding manifest and hosting context:

Table: General CapabilitiesGeneral capabilityPurpose
Distribution and manifestMakes the App discoverable and publishable.
Direct, inline, and full contextsDefines where the App can run.
Embed configurationDelivers authored App-specific input.
Host contextDelivers Homepage presentation context.
Layout contextDelivers current safe-area information to embedded instances.
Hosted PreferencesLets Homepage render persistent App settings.
Lifecycle controlsLets an embedded App expose Start/Pause and Restart when that model fits.
Smart heightLets an inline App request a host-managed height.
Curated Homepage APIsGives the App selected server-side capabilities without exposing provider credentials.
API status reportingLets a hosted App report safe API failure and recovery state for the Preferences Warnings surface.

Anything outside that set is an extension:

Table: Extension RulesExtension ruleMeaning
It is explicitThe manifest, Embed configuration, or App-specific requirement declares it.
It is scopedThe behavior identifies the App and, for embedded behavior, the exact instanceId.
It is namespacedMessages and API operations do not pretend to be general capabilities.
It is optional to other AppsAn App must not need another App's extension to implement the general contract.
It is documented beside the AppMaps route navigation belongs to the Maps reference and requirements, not to every App's baseline.

Ownership model

Table: ConcernsConcernApp Coder ownsHomepage Builder owns
App UI and runtimeRendering, interaction, state, and App-specific behavior.iframe placement and Homepage Chrome around the App.
DistributionSource, build output, assets, manifest, and App tests.Discovery, validation, route serving, and publication packaging.
Embed configurationThe meaning of App-specific keys.Forwarding host keys and preserving remaining configuration as config.
PreferencesContribution definition and applying received values.Preference rendering, persistence, disclosure, and delivery of changes.
Host contextApplying presentation and layout context.Supplying current theme, brightness, displayed accent, CSS variables, and safe area.
Provider dataRequesting a documented capability and handling its result.Owning curated server routes, provider details, and server-side credentials.
API status reportingClassifying failures and recovery, then sending safe stable reports for the active instance.Validating reports and maintaining the corresponding transient Preferences Warning.
Toasts and Toast actionsChoosing when feedback is relevant, supplying safe text and stable IDs, and handling accepted actions.Validating requests, rendering and timing Toasts, grouping them, maintaining Warnings, and delivering actions.
LifecycleReporting state and responding to accepted commands.Showing controls and sending commands for the registered instance.

Failure triage

Debug from the earliest broken boundary rather than changing the App by default:

First broken boundaryLikely ownerFirst check
Authored value is missing or altered before the bridgeContent parser or generatorInspect the authored Element, generated configuration, and parser rules.
App is missing from the registry or routeHomepage distribution discovery or generatorVerify , the App slug, generated registry, and generated assets.
A bridge message is absent or rejectedHomepage bridge or App listenerValidate source, origin, slug, instanceId, and message fields on both sides.
The App receives valid configuration or context but renders incorrectlyApp source or distributionCompare the received values with the App's rendering and state handling.
A named Homepage API route cannot resolve provider dataHomepage API route or provider boundaryCheck the API envelope, timeout, fallback, and server-side provider requirements before changing App rendering.

The boundary that produced the bad result owns the fix. An App must still present a safe fallback when a Homepage API or provider fails.

Host presentation timing

For hosted Apps, Homepage owns the timeline for presentation values that it animates. See Inbound: host context for the field reference.

  • is the accent color currently displayed by Homepage. , , and are readable foreground colors for content placed on an accent-colored surface. They are not interchangeable.

  • and are optional destination metadata. Embedded Apps must not use them for visible rendering.

  • The initial URL and live messages use the same displayed-value semantics.

  • Apply incoming hosted values directly. Do not run a second CSS transition, timer, observer, or interpolation loop toward a target value. Homepage may send frequent updates while a transition is running; the latest displayed value wins.

  • Bootstrap styling is applied without animation. The live bridge message remains authoritative after startup.

  • Semantic fields take precedence over target-like CSS variables such as .

  • Homepage owns the outer background of an embedded surface. An embedded App must not paint its standalone fallback background over it. An App-owned canvas or renderer must use the same host-provided background semantics.

  • Standalone Apps may use their own fallback colors and transitions.

What is not available

  • Apps do not receive raw Homepage API keys or provider secrets.

  • Apps cannot directly read Homepage's HTTP-only settings cookie.

  • There is no generic provider proxy.

  • There is no general bridge version handshake or capability-discovery message.

  • The manifest does not grant permission to create arbitrary API routes.

  • The current host does not yet deliver every parent-context field declared by an App manifest.

Distribution and manifest

Source and dist

App Coder may keep source, tests, requirements, documents, and logs beside the distribution. Homepage consumes only the publishable distribution:

App structure
/work/apps/<slug>/
├── app/            App Coder source
├── dist/           Homepage-ready output
│   ├── manifest.json
│   ├── index.html
│   └── runtime assets
├── test/           App tests
├── requirements/   App-specific requirements
├── documents/      App-specific design material
└── logs/           App change history

The exact App Coder workspace may differ, but the boundary does not: dist is what Homepage discovers, copies, serves, and publishes. Homepage Builder does not create the App source or repair App behavior that belongs in App Coder.

This is a hosted-capable example, not a requirement that every App use every field:

Minimum recommended manifest
{
  "slug": "example",
  "title": "Example",
  "version": "1.0.0",
  "description": "A short description of the App.",
  "entry": "index.html",
  "distribution": {
    "contexts": ["direct", "inline"],
    "preferredPresentation": "inline",
    "hostBridge": true,
    "parentContext": {
      "theme": true,
      "brightness": true,
      "accent": true,
      "accentText": true
    },
    "preferencesContributions": {
      "supported": false,
      "contexts": ["inline"]
    },
    "bridgeMessages": {
      "apiStatus": "homepage/app/api-status",
      "supportLinks": "homepage/app/support-links"
    },
    "smartHeight": true,
    "requiredAssets": [
      "index.html",
      "js/index.js",
      "css/index.css"
    ]
  },
  "supportLinks": [
    {
      "label": "Readme",
      "href": "README.md",
      "presentation": "preferences-footer"
    }
  ],
  "runtime": {
    "config": true,
    "secrets": false,
    "requiresServer": false,
    "publicOrigin": ["homepageOrigin"]
  },
  "resources": {
    "apiRoutes": []
  }
}

runtime and resources are declarations in the distribution manifest. They do not dynamically create permissions or routes.

Manifest fields

FieldCurrent contract
slugMust match the App directory name and intended public route.
entryMust be a relative file inside dist. The current generator defaults to index.html when omitted, but an App should declare it explicitly.
title, shortTitle, version, descriptionApp metadata. Missing display fields receive generator fallbacks.
distribution.contextsAuthoritatively declares the supported Homepage contexts. It must list one or more of , , and ; an undeclared context is rejected. A direct App must also declare because the direct route uses the full hosted surface.
distribution.preferredPresentationPresentation hint; it does not force a hosting mode.
distribution.hostBridgeMust be when the App declares a Homepage context. Hosted contexts require the bridge.
distribution.parentContextDeclares the host-context fields the App expects. The current general contract supports theme, brightness, and accent fields; do not declare or depend on , , or yet because Homepage does not deliver them through the generic host context.
distribution.preferencesContributionsSeeds the hosted Preferences contribution for an embedded or direct full-context instance.
distribution.bridgeMessagesDescribes the namespaced bridge messages the App uses. An API-reporting App declares ; Toast requests are globally available and need no capability declaration.
distribution.smartHeightEnables accepted homepage/app/resize requests for inline instances without authored height.
distribution.requiredAssetsDeclares relative files that must exist inside dist. Generation fails when a declared asset is missing or escapes dist.
distribution.unsupportedContextsDescriptive context exclusions.
supportLinksOptional App links rendered by Homepage. Each link may set to or ; omitted presentation defaults to . Relative links resolve under , while root-relative and absolute links are kept as authored.
runtime.configDeclares that the App has non-secret runtime configuration.
runtime.secretsMust be false for browser-published App output.
runtime.requiresServerDeclares that the App needs a server-backed runtime or API.
runtime.publicOriginDeclares the public-origin configuration the App expects.
resources.apiRoutesDocuments named App-related API resources. It does not register a route or create a proxy.
top-level idInventory metadata in the current Maps manifest; it is not a public URL. Homepage derives its registry identity from the slug.

Distribution validation

Homepage currently:

  • discovers dist/manifest.json;

  • requires valid JSON;

  • requires the manifest slug to match the App directory;

  • requires an explicit, non-empty list of supported contexts and rejects unknown or duplicate context names;

  • requires bridge support for every declared Homepage context;

  • requires when is declared;

  • rejects an absolute or escaping entry path;

  • requires the entry file to exist;

  • exposes the direct route only when is declared;

  • serves embedded assets only for their declared or context;

  • verifies every declared required asset inside dist;

  • inventories files below dist for public App assets.

An invalid manifest, missing entry, unsupported context, missing bridge declaration, or missing required asset prevents that App from becoming a valid registry entry. Do not place API keys, provider secrets, login tokens, or other credentials in dist.

Current boundary: Context support, bridge participation, and required assets are enforced. Parent-context fields, bridge-message declarations, and API/resource declarations remain descriptive; they do not grant permissions or create routes.

API registrations and capabilities

resources.apiRoutes is the registration source for an App's named Homepage API dependencies. It remains declarative: the Backend preserves the metadata in the generated App registry, while each actual Homepage route is explicitly implemented and registered by Homepage.

Credentialed host-provided routes must declare a stable capability and logical credential identifier:

Registered API resource
{
  "id": "image-generator",
  "capabilityId": "api:image-generator",
  "method": "POST",
  "path": "/api/image-generator",
  "hostProvided": true,
  "required": true,
  "requiresCredentials": true,
  "credentialId": "openai-image"
}

capabilityId is the semantic contract consumed by the App handshake. id identifies the technical resource; it must not be used as permission to create a route. required is separate from requiresCredentials: a credentialed resource may be optional in a future App. The current Image Generator pilot resolves openai-image to the global APIs Preferences group and never exposes the key itself.

Hosting and Embeds

Contexts

ContextEntry behaviorInstance behavior
directHomepage route opens the App as a direct Homepage route in a Homepage-managed full-surface iframe.Receives the full host contract, including a route-scoped instanceId, config, host context, layout, and any contributed Preferences supported by the route. It is not an authored Embed Element.
inlineHomepage serves the entry with ?embed=inline inside an iframe.Receives an instanceId, config, hosted Preferences when contributed, layout information, and optional smart-height handling.
fullHomepage serves the entry with ?embed=full inside an iframe.Receives the full hosted contract and remains surrounded by Homepage Chrome.

direct and full are not synonyms. direct describes how the App is reached as a route. From the App's perspective, the direct route currently uses the full hosting context; full also describes an authored full Embed.

Homepage has three App entry contexts: direct, inline, and full. Direct is a Homepage route; inline and full are authored Embed modes. A standalone served outside Homepage is a development or diagnostic context, not a fourth Homepage mode. Do not treat and the standalone distribution entry as interchangeable.

Local test contexts

The local URL identifies who owns the surrounding runtime:

Test contextURL shapeOwnership
Codex Homepage previewHomepage serves the route and owns any Embed or direct-App hosting.
Owner Homepage previewHomepage serves the route in the Owner preview environment.
Standalone App previewAn App-specific HTTP server URL ending in the distribution entry, usually .The App owns the page; Homepage does not send bridge messages. The port is App-specific.
diagnosticA local file URL only when the App explicitly supports it.Not a supported Homepage integration context; origin, iframe, and renderer behavior may differ.

When testing in the Codex or in-app browser environment, use port 5174. Do not infer Homepage hosting from the standalone App URL.

Authored Embed

The authored Element identifies the App and supplies App-specific input:

Embed configuration
element embed
app: maps
name: Berlin
mode: inline
height: 420
address: Berlin, Germany

An Orgs Embed uses the same required key for its Preferences heading and lifecycle Toasts:

Orgs Embed configuration
element embed
app: orgs
name: Agents
mode: inline
height: 420
fullscreen: Hover
minimap: Hover
zoom: Fit

Homepage interprets these host keys:

KeyCurrent behavior
app or slugIdentifies the registered App. app is the normal authored form.
modeExact full selects the full hosting mode; every other accepted Embed defaults to inline.
screenEnables the responsive screen presentation only for a full Embed when true.
heightControls inline host height and is not forwarded to the App's config.
name (Maps and Orgs)Required for authored Maps and Orgs Embeds, in either inline or full mode. Maps uses it for the Preferences section (), forwards it to Maps, and shows it beside the existing loading bar while an embedded map loads. For Orgs, it is the canonical organization reference (matched against the org's id, slug, or display name); Homepage trims and forwards it as and uses that same value for the Content Preferences section () and exact hosted Toast text / . A terminal loadability warning uses .
org (Orgs only)Not supported. is the sole organization reference; no fallback or compatibility handling is provided for .
Every other keyIs forwarded as App-specific after Content parsing and is interpreted by the App.

Content-to-App data path

For an authored Embed, configuration reaches the App in this order:

Content-to-App path
Content.md authored Embed
        │
        ▼
Element parser
        │  comments are removed and YAML values are parsed
        ▼
Generated presentation configuration
        │
        ▼
Homepage host-key removal
        │  app, slug, mode, screen, and height are used by Homepage
        ▼
homepage/app/config
        │
        ▼
App listener receives App-specific config

In Element YAML, an unquoted starts a comment and the rest of that line is removed. Quote URLs and other values containing when they are meant to reach the App. Quoted is preserved.

Generated presentation files are outputs of . Inspect them when debugging the path, but do not edit them by hand; fix the authored Content, generator, or App distribution that produced them.

Homepage generates the instanceId for an embedded occurrence. Treat it as opaque: do not parse its path, derive a durable identifier from its format, or use another instance's value.

Height and full Slides

For an inline App:

  • an authored numeric height wins;

  • an authored percentage becomes a viewport-relative height;

  • an authored ratio becomes an aspect ratio;

  • without an authored height, the host uses its fallback height;

  • with smartHeight: true, the App may request a finite height through homepage/app/resize;

  • accepted smart height is currently clamped to 360px through 560px.

A full App does not use inline height handling. A full App Slide contains exactly one full App Embed among its body elements. screen: true opts that full App into the responsive screen presentation.

The direct route uses the same full-context surface without ordinary Content padding: its iframe fills the available route-frame width and viewport height, while the route frame continues to reserve the Preferences area and Homepage Chrome remains available as an overlay. This direct-host rule does not change inline App embeds or the dedicated direct Maps surface.

Runtime lifecycle

Hosted startup sequence

The current hosted sequence is:

The direct route follows the full branch below: Homepage owns the route and creates the managed full-surface frame without an authored Embed Element.

Hosted App lifecycle
Homepage chooses the inline or full embed mode
        │
        ├── serves /app/<slug>/<entry>?embed=<embedMode>
        ├── provides hostContext when pre-paint context is available
        ▼
App document starts
        │
        ├── optional bootstrap applies initial host styling
        ├── App installs its message listener
        ▼
Homepage sends
        │
        ├── homepage/app/context
        ├── homepage/app/config
        ├── homepage/app/layout when relevant
        └── homepage/app/host-ready
        ▼
App accepts the initial contract
        │
        └── homepage/app/ready
        ▼
Homepage replays the latest config, context, and layout
        │
        ├── App prepares and renders
        ├── optional homepage/app/preferences
        ├── optional homepage/app/support-links
        ├── optional homepage/app/controls
        └── optional resize or App-specific messages
        ▼
Homepage sends live Preference, layout, and lifecycle changes
        │
        └── removing or reloading the iframe ends the instance

Initialization handshake

The handshake confirms that both sides have initialized the same embedded instance:

  1. After iframe load, Homepage sends the initial , , and relevant , then .

  2. The App sends only after it has accepted and for the same , , and .

  3. Homepage validates and replays the latest , , and . Replayed snapshots must be safe and idempotent; they do not create a new instance.

  4. Reload, replacement, or unmount resets the handshake. A reload must complete it again, even when the route keeps the same .

  5. Homepage waits four seconds for diagnostics only. A timeout must not block rendering, and a valid late is still accepted.

Standalone Apps ignore both handshake messages. An App must install its listener early and tolerate the initial snapshots arriving before or after its visual runtime is ready.

Pre-paint host context

For a hosted entry document, Homepage can place a base64url-encoded hostContext query parameter on the App URL. When the value is valid, the App asset route injects a small script into index.html before the first stylesheet:

  • window.HOMEPAGE_HOST_CONTEXT contains the initial style context;

  • document.documentElement.dataset.hosted is set to homepage;

  • initial theme, brightness, accent, and CSS variables can be applied before first paint.

An App that needs to avoid a flash of the wrong theme should apply this bootstrap context before rendering, without animating to it. The live homepage/app/context message remains authoritative after load. The bootstrap is a host-provided optimization, not a substitute for the bridge listener. See Host presentation timing.

The Maps distribution uses js/host-context-bootstrap.js for this purpose. A future generalized bootstrap package is not yet a separate platform contract.

Direct and embedded startup

StartupApp behavior
Direct is served by Homepage as a managed full-surface iframe without an authored Embed Element. The App receives the full bridge contract; direct-route-specific controls or storage remain App-owned.
EmbeddedAn authored Embed uses inline or full mode. The App should use config, Preferences, host context, and bridge updates rather than depending on Homepage DOM or storage.

For hosted Maps, the App waits for homepage/app/config before preparing authored map content. This is a good general pattern when an App cannot render meaningful content without authored configuration.

Repeat loads and teardown

  • Treat repeated equivalent homepage/app/config messages as idempotent.

  • Replace App-local state only when configuration or Preference values genuinely change.

  • Do not assume that an iframe reload creates a new authored occurrence; always use the current instanceId.

  • There is no general teardown message. Stop listeners, timers, observers, and network work when the document is unloaded or the App runtime is otherwise disposed.

Integrate the Homepage

Use the capabilities the embedding Homepage provides to an App.

Use the capabilities the embedding Homepage provides to an App.

General bridge

Protocol and trust

The bridge uses window.postMessage. Hosted Apps currently run at the same origin as Homepage. The App therefore uses its own current origin as the expected Homepage origin; it does not learn a trusted origin from the parent.

For messages received from Homepage, an App should:

  • require event.source === window.parent;

  • compare event.origin with ;

  • require an object message and recognized type;

  • require the expected App slug when present;

  • require the exact instanceId for instance-scoped capabilities.

For messages sent to Homepage, use with . Do not use an origin supplied by the parent, , or the first incoming message. A wildcard target is not an accepted contract. Cross-origin hosting would require a separate explicit allowlist or handshake design.

Homepage validates frame source, origin, App path, slug, and, where applicable, instance identity. Unknown or malformed messages are ignored; there is no generic error response.

Message shape and vocabulary

Every bridge message is a flat object. The shared fields are:

FieldMeaning
typeThe exact namespaced message name.
slugThe registered App slug.
instanceIdThe opaque identity of one embedded App occurrence, when the message is instance-scoped.
Other fieldsMessage-specific data kept at the top level. There is no generic object.

is the host-context message. The field in and is the embed mode: or . Do not use for the embed mode.

Use these terms consistently:

TermMeaningExamples
SnapshotHomepage sends current data to the App., host ,
UpdateHomepage sends a changed value.
CommandHomepage asks the App to do something., , , Maps ,
RequestThe App asks Homepage to do something.,
ContributionThe App registers capabilities or controls.,
ReportThe App reports an event or current state., , ,

Use state only for a current condition, event for something that happened, command for an instruction, and request for an App-to-Homepage ask.

Minimal listener pattern

This is the smallest useful hosted listener pattern. The helper functions are App-owned; the checks and message names are the current host contract.

`js [v nowrap] Generic bridge listener
const hostOrigin = window.location.origin; const appSlug = "example"; let instanceId = null;

function trustedHostMessage(event) {
return window.parent !== window
&& event.source === window.parent
&& event.origin === hostOrigin
&& event.data && typeof event.data === "object"; }

function onHostMessage(event) {
if (!trustedHostMessage(event)) return; const message = event.data; if (message.slug && message.slug !== appSlug) return;

if (message.type === "homepage/app/config") {
if (message.embedMode !== "inline" && message.embedMode !== "full") return; instanceId = message.instanceId; applyConfig(message.config || {}, message.preferences || {}); } else if (message.type === "homepage/app/context") {
applyHostContext(message); } else if (
message.type === "homepage/app/preference"
&& message.instanceId === instanceId
&& message.source === "host-preferences"
) {
applyPreference(message.preferenceId, message.value); }
}

window.addEventListener("message", onHostMessage); window.addEventListener("pagehide", () => {
window.removeEventListener("message", onHostMessage); }); `

Treat this as a pattern, not a platform helper. Add handling for layout, lifecycle, and App-specific messages only when the App declares and needs them.

Message index

The table is the compact parameter reference. The sections below provide semantics and examples. Rows whose message names include are Maps extension examples, not requirements for every App.

DirectionMessageRequired / accepted fieldsScope and constraints
Homepage → Apphomepage/app/configtype, slug, instanceId, embedMode, config, preferencesSnapshot for an embedded instance; embedMode is inline or full.
Homepage → Apphomepage/app/contexttype, slug, theme, brightness, brightnessPreference, accent, onAccent, onAccentColor, accentText, accentTarget, accentTargetText, accentRevision, cssVarsSnapshot/update of live presentation context; is the currently displayed value and target fields are optional destination metadata.
Homepage → Apphomepage/app/layouttype, slug, instanceId, embedMode, safeAreaSnapshot of embedded layout; embedMode is inline or full and safeArea contains numeric insets.
Homepage → Apphomepage/app/host-readytype, slug, instanceId, embedModeHost side of the initialization handshake; follows the initial snapshots for this instance.
Homepage → Apphomepage/app/preferencetype, slug, instanceId, preferenceId, value, sourceUpdate for an embedded instance; source is host-preferences.
Homepage → Apphomepage/app/start, homepage/app/pause, homepage/app/restarttype, slug, instanceIdCommand for an embedded instance; accepted only after matching lifecycle registration.
Homepage → Apphomepage/app/toast-actiontype, slug, instanceId, actionId, issueId optionalCommand for the exact App instance after a user invokes its Toast action.
Homepage → Apphomepage/app/maps/navigationtype, slug, instanceId, commandMaps command; command is step, location, leg, or overview.
App → Homepagehomepage/app/resizetype, slug, instanceId, heightRequest from an inline App; accepted only with smartHeight and no authored height.
App → Homepagehomepage/app/readytype, slug, instanceId, embedModeApp side of the initialization handshake; sent once after the matching config and host-ready messages are accepted.
App → Homepagehomepage/app/preferencestype, slug, instanceId, sections; version optionalPreferences contribution; a new accepted contribution replaces the previous one.
App → Homepagehomepage/app/image-generator-preferences-synctype, slug, instanceId, embedMode, source, valuesImage Generator template-selection update for declared Output values; accepted only from the expected exact instance and only when each value is declared by the hosted Output section. It updates host-owned saved values without replacing the contribution or using the manual preference-change path.
App → Homepagehomepage/app/support-linkstype, slug, instanceId, linksFooter-link contribution; each link has , , and optional ; a new accepted contribution replaces the previous links for the App instance, and an empty list removes them.
App → Homepagehomepage/app/preferences-requesttype, slug, instanceId, embedMode, action, sectionId, focusSection, collapseOtherSectionsImage Generator request to open and focus the declared Preferences section; accepted only from the expected App instance.
App → Homepagehomepage/app/controlstype, slug, instanceId, controls.startPause, controls.restart, stateLifecycle contribution; state is ready, running, or paused.
App → Homepagehomepage/app/toasttype, slug, instanceId, severity, message/title/duration/persistent/dedupeKey/issueId/issueState/action optionalParent-owned user-facing notice; warning/error requests use an active or resolved issue identity.
App → Homepagehomepage/app/tracktype, slug, instanceId, eventName, properties optionalAnalytics event request; accepted only from the expected App instance and relayed through Homepage's dataLayer to MTAM. Event names are at most 100 characters; JSON properties are at most 16 KiB.
App → Homepagehomepage/app/api-statustype, slug, instanceId, operation, state, code/message/httpStatus/details optionalNamed API failure or recovery report; accepted only from the expected App instance and transformed into a parent-owned Preferences Warning.
App → Homepagehomepage/app/maps/route-paintedtype, slug, instanceIdMaps event report; emitted after a route is visibly prepared.
App → Homepagehomepage/app/maps/route-failedtype, slug, instanceId, diagnostic optionalMaps event report; diagnostic must remain safe for the user.
App → Homepagehomepage/app/maps/navigation-statetype, slug, instanceId, navigationMode, position, locationIndex, legIndex, locationCount, legCount, canGoPrevious, canGoNextMaps state report for the prepared route position.

Outbound: CDP track event

Apps may report an event that has occurred using the flat message. This is optional for each App; it does not add an App-specific CDP client or bypass MTAM.

Code: JS
window.parent.postMessage({
  type: "homepage/app/track",
  slug: "image-generator",
  instanceId,
  eventName: "Generated Image",
  properties: {}
}, window.location.origin);

The App uses its current and . Homepage accepts the request only from the expected iframe, origin, App slug, and exact active instance. It validates the event name (non-empty, no control characters, at most 100 characters) and optional JSON-object properties (at most 16 KiB when serialized), then writes a event to . Homepage assigns from the validated App identity; that property is reserved and any App-provided value is discarded.

MTAM's CDP agent alone calls , after the initial Homepage . Events received while Analytics.js is loading are queued. Do not load or call the CDP SDK from an App. Track properties must not contain credentials, secrets, generated image bytes, private prompts, or other sensitive request data.

Inbound: embed configuration

homepage/app/config is sent to embedded instances. It carries the host identity, authored configuration, and saved Preference values:

Example message
{
  "type": "homepage/app/config",
  "slug": "maps",
  "instanceId": "/home#map-1",
  "embedMode": "inline",
  "config": {
    "address": "Berlin, Germany"
  },
  "preferences": {
    "zoom-adjustment": 0.2
  },
  "capabilities": {
    "api:image-generator": {
      "available": false,
      "state": "missing",
      "credentialId": "openai-image",
      "setupTarget": {
        "groupId": "apis",
        "credentialId": "openai-image"
      }
    }
  }
}
FieldRequiredMeaning
typeYesExact message name.
slugYesRegistered App slug.
instanceIdYes for embedded deliveryOpaque identity of this occurrence.
embedModeYesinline or full.
configYesAuthored App-specific keys after host keys have been removed.
preferencesYesSaved host Preference map; may be empty.
capabilitiesNo for Apps without registered capabilitiesNon-sensitive, server-derived capability status. state currently supports available, missing, invalid, and unavailable; available means configured, not provider-validated.
credentialAvailableLegacy fallback onlyBoolean compatibility field for the Image Generator pilot. Apps should prefer capabilities.

Homepage removes app, slug, mode, screen, and height from authored input before putting the remaining keys into config. Capability status is recomputed when the App loads and after relevant Homepage Preferences changes. Homepage never sends credentials or raw keys.

Inbound: host context

homepage/app/context is sent at initialization and when relevant Homepage presentation context changes:

Example message
{
  "type": "homepage/app/context",
  "slug": "maps",
  "theme": "system",
  "brightness": "dark",
  "brightnessPreference": "dark",
  "accent": "#246bfe",
  "onAccent": "#ffffff",
  "onAccentColor": "#ffffff",
  "accentText": "#ffffff",
  "accentTarget": "#246bfe",
  "accentTargetText": "#ffffff",
  "accentRevision": 3,
  "cssVars": {
    "--accent-color": "#246bfe",
    "--color-back": "#101216",
    "--color-surface": "#171a21"
  }
}

Current semantic fields are theme, brightness, brightnessPreference, accent, onAccent, onAccentColor, accentText, optional accentTarget and accentTargetText, accentRevision, and cssVars. and its text color are the values currently displayed by Homepage, including intermediate values while an accent transition is running. Embedded Apps must use those fields for visible styling and must not replace them with the optional target values. Homepage may send context updates for each animation frame during the transition. See Host presentation timing for the ownership rule.

The current allowlisted CSS variable set includes:

Current host CSS variables
--accent-color
--accent-color-text
--accent-color-text-surface
--accent-color-text-panel
--accent-color-light
--accent-color-dark
--accent-color-enhanced-light
--accent-color-enhanced-dark
--accent-color-standard
--accent-color-enhanced
--accent-color-standard-surface
--accent-color-enhanced-surface
--accent-color-standard-panel
--accent-color-enhanced-panel
--accent-color-standard-light-background
--accent-color-standard-light-surface
--accent-color-standard-light-panel
--accent-color-standard-dark-background
--accent-color-standard-dark-surface
--accent-color-standard-dark-panel
--accent-color-enhanced-light-background
--accent-color-enhanced-light-surface
--accent-color-enhanced-light-panel
--accent-color-enhanced-dark-background
--accent-color-enhanced-dark-surface
--accent-color-enhanced-dark-panel
--on-accent-color
--accent-color-prior
--accent-color-new
--accent-border-prior-color
--accent-border-new-color
--on-accent-color-prior
--on-accent-color-new
--nav-primary-icon-color
--nav-secondary-icon-color
--chrome-accent-shade-color
--chrome-accent-shade-strength
--color-main
--color-muted
--color-back
--color-surface
--color-panel
--color-border
--color-border-strong
--color-control
--color-control-secondary
--color-control-disabled
--color-code-content-back

The App should use semantic fields first and treat cssVars as host styling context. The current generic host message does not include , , or . Current App distributions must not declare or depend on those fields; adding one requires a separately coordinated host-and-App contract change.

Inbound: layout context

homepage/app/layout is sent to an embedded instance when the usable layout or safe area changes:

Example message
{
  "type": "homepage/app/layout",
  "slug": "maps",
  "instanceId": "/home#map-1",
  "embedMode": "inline",
  "safeArea": {
    "top": 0,
    "right": 0,
    "bottom": 72,
    "left": 0
  }
}

safeArea contains numeric CSS-pixel insets. A direct App does not receive an instance-scoped layout message.

Inbound: Preference change

homepage/app/preference is sent after a hosted Preference is committed:

Example message
{
  "type": "homepage/app/preference",
  "slug": "maps",
  "instanceId": "/home#map-1",
  "preferenceId": "zoom-adjustment",
  "value": 0.5,
  "source": "host-preferences"
}

value is currently a string, number, or boolean. The App should update its runtime when the Preference is intended to be live. The current host emits changes according to each control's commit behavior; text currently commits on blur.

Inbound: lifecycle commands

Homepage may send these commands after an App has registered lifecycle controls:

Commands
{
  "type": "homepage/app/start",
  "slug": "maps",
  "instanceId": "/home#map-1"
}
{
  "type": "homepage/app/pause",
  "slug": "maps",
  "instanceId": "/home#map-1"
}
{
  "type": "homepage/app/restart",
  "slug": "maps",
  "instanceId": "/home#map-1"
}

Commands are instance-scoped. The App must keep its reported state accurate and must not expose a control it cannot honor.

Outbound: smart height

An inline App with smartHeight true may send:

Example message
{
  "type": "homepage/app/resize",
  "slug": "maps",
  "instanceId": "/home#map-1",
  "height": 480
}

Homepage accepts the request only for an inline instance with no authored height, validates the exact instanceId, and clamps the result to the current 360px–560px range.

Outbound: hosted Preferences

An App may contribute sections and controls through homepage/app/preferences. The manifest can provide the initial contribution, and the App can send a runtime contribution when its available controls change.

Example message
{
  "type": "homepage/app/preferences",
  "slug": "maps",
  "instanceId": "/home#map-1",
  "version": "1.7.0",
  "sections": [
    {
      "id": "map-behavior",
      "label": "Map",
      "initialOpen": false,
      "preferences": [
        {
          "id": "zoom-adjustment",
          "label": "Zoom",
          "control": "range",
          "min": -3,
          "max": 3,
          "step": 0.1,
          "defaultValue": 0,
          "liveUpdate": true,
          "persistence": "homepage"
        }
      ]
    }
  ]
}

Homepage binds an accepted runtime contribution to the current frame's instance. Invalid sections or incomplete Preference records are dropped. A later accepted contribution for the same App and instance replaces the earlier contribution.

Outbound: Image Generator template-output synchronization

The Image Generator uses a separate message when a hosted template changes its Output defaults. This is not a replacement Preferences contribution and does not represent a manual Homepage control change.

Example message
{
  "type": "homepage/app/image-generator-preferences-sync",
  "slug": "image-generator",
  "instanceId": "/home#image-generator-1",
  "embedMode": "full",
  "source": "template-selection",
  "values": {
    "model": "fast",
    "format": "png",
    "background": "transparent",
    "ratio": "9:16"
  }
}

Homepage accepts this message only from the expected Image Generator frame and exact active instance. It validates the mode, source, known Output ids, allowlisted values, and the active section's Homepage-persisted declarations. is accepted only when that hosted section declares it. Accepted values update the saved instance snapshot and the visible controls together; invalid or stale messages are ignored and ordinary contributions and manual preference changes keep their existing behavior.

An App may seed footer links in the manifest's top-level array and may replace them at runtime with . Homepage accepts the runtime contribution only from the expected App frame, origin, slug, and exact .

Example message
{
  "type": "homepage/app/support-links",
  "slug": "example",
  "instanceId": "/home#example-1",
  "links": [
    {
      "label": "Guide",
      "href": "README.md",
      "presentation": "main-footer"
    },
    {
      "label": "Settings",
      "href": "/docs/apps",
      "presentation": "preferences-footer"
    }
  ]
}

renders in the main Homepage footer. , or an omitted presentation, renders in the Preferences footer. Relative links resolve to the App route; root-relative and absolute links remain unchanged. A later accepted contribution replaces all footer links for that App instance, and sending clears them.

Outbound: lifecycle registration

An embedded App registers the controls it wants Homepage to show:

Example message
{
  "type": "homepage/app/controls",
  "slug": "maps",
  "instanceId": "/presentation/road-trip#map-1",
  "controls": {
    "startPause": true,
    "restart": true
  },
  "state": "running"
}

state is ready, running, or paused. At least one of startPause and restart must be true. Homepage accepts the registration only for the exact App and embedded instance.

Outbound: API status

An App reports a named API operation when it fails or recovers. The message is flat and instance-scoped:

Example failure report
{
  "type": "homepage/app/api-status",
  "slug": "example",
  "instanceId": "/home#example-1",
  "operation": "forecast",
  "state": "failed",
  "code": "provider_unavailable",
  "message": "The forecast service is temporarily unavailable.",
  "httpStatus": 503,
  "details": [
    { "label": "Provider detail", "value": "Try again later." }
  ]
}
FieldRequiredMeaning
typeYesExact message name .
slugYesRegistered App slug.
instanceIdYesOpaque identity of the embedded App occurrence.
operationYesStable App-defined operation name, such as or .
stateYes or . A resolved report clears the matching Warning.
codeNoStable, safe App/API error code.
messageNo for recoverySafe user-facing explanation; Homepage supplies a generic fallback when a failure omits it.
httpStatusNoNumeric HTTP status when useful; do not include raw response bodies.
detailsNoA bounded list of safe diagnostics. Do not include credentials, authored secrets, request data, provider URLs, or raw upstream responses.

Apps should report for a network failure, non-2xx response, unreadable response, or API envelope with . They should report after a successful retry using the same operation name. A hosted App must wait until its active is established before sending the report.

Homepage accepts the report only from the configured App origin and exact iframe source, slug, and instanceId. A failed report creates or updates one coordinated parent Toast and one live Warning per App instance and operation. A resolved report closes the Toast and removes the Warning. Navigation away from the App does not clear its parent Toast or unresolved Warning; reloading or replacing the App iframe clears that App-instance state. The active Warning is not stored in Homepage Preferences.

Toast bridge

Request feedback and handle one user action.

Request feedback and handle one user action.

App situationMessage
Ordinary user-facing event
Named API failure or recovery
Toast action from Homepage to the exact App instance
Hard App or data failureApp-owned fallback plus the appropriate report; Toasts are supporting UI.

An App may request a parent-owned Toast after its active is established:

Toast request
{
  "type": "homepage/app/toast",
  "slug": "example",
  "instanceId": "/home#example-1",
  "severity": "warning",
  "message": "Could not save changes.",
  "issueId": "save-failed",
  "issueState": "active",
  "action": { "id": "retry", "label": "Retry" }
}
FieldRule
, , Flat bridge identity; must be the active embedded instance.
, , , or .
, Plain user-facing text; maximum 240 and 80 characters respectively. may be omitted only when resolving an issue.
, Use one: bounded duration in milliseconds (1,000–10,000), or .
Optional stable identity for equivalent events; maximum 100 characters.
, Required for warning/error Toasts. is or ; the ID is maximum 100 characters.
Optional single action. Its ID is maximum 64 characters using letters, numbers, , , or ; its label is maximum 48 characters. An optional must be an internal Homepage path/hash or an absolute / URL.

An active warning/error creates or updates the corresponding Warnings issue. A resolved report uses the same , clears the issue, and does not create a new Toast. Info/success Toasts must not include issue fields. Homepage owns placement, timing, queueing, grouping, accessibility, and text truncation; Apps send no HTML, callbacks, unvalidated URLs, placement, or styling instructions.

When an action includes , Homepage opens internal paths and hashes in the current tab and opens external / URLs in a new tab. Homepage handles that URL action locally and does not send . Without , the action uses the App-owned flow described below.

When the user invokes an action, Homepage sends:

Toast action
{
  "type": "homepage/app/toast-action",
  "slug": "example",
  "instanceId": "/home#example-1",
  "actionId": "retry",
  "issueId": "save-failed"
}

The App validates , the same-origin , slug, exact instance, expected issue, and known action ID. It ignores stale or unknown actions, performs the operation, and reports recovery separately. The command is not a callback or permission grant.

Toasts and unresolved issues survive Homepage navigation but are cleared when the App iframe is actually reloaded or replaced. Toast dismissal or expiry does not resolve an issue. Homepage may queue, group, rate-limit, or dismiss a request; an App must not depend on immediate Toast visibility. Standalone Apps must retain an App-owned fallback.

For a named API failure, the App sends only ; Homepage creates the coordinated parent Toast and Warnings issue. A matching resolved API-status report closes the Toast and clears the issue.

Preferences requests

App-owned Preferences remain App-owned. The Image Generator uses the existing sectionId: image-generation request for its output settings.

For a registered capability, an App may request the corresponding setup target with only the semantic capability ID:

Capability Preferences request
{
  "type": "homepage/app/preferences-request",
  "slug": "image-generator",
  "instanceId": "/home#image-1",
  "embedMode": "inline",
  "action": "open",
  "capabilityId": "api:image-generator"
}

Homepage validates the iframe source, same-origin target, exact App slug, instance, and mode, then resolves the capability through the registered manifest metadata. The App cannot choose an arbitrary Preferences group, section, or credential ID. The Image Generator pilot resolves this request to the global APIs group and focuses openai-image.

A recovery report uses the same operation identity:

Example recovery report
{
  "type": "homepage/app/api-status",
  "slug": "example",
  "instanceId": "/home#example-1",
  "operation": "forecast",
  "state": "resolved",
  "code": "provider_available",
  "message": "The forecast service is available again."
}

The baseline contract should omit unless a reviewed App needs safe diagnostics. When used, details are limited to at most eight entries, with labels bounded to 60 characters and values bounded to 240 characters; labels and values must be allowlisted, user-safe, and free of credentials, authored secrets, request data, provider URLs, and raw upstream responses.

If the host does not yet support this extension, the App must retain a safe in-App fallback.

Compatibility and evolution

  • The manifest version is descriptive today. There is no automatic bridge version negotiation.

  • Only names in the namespace are part of the current protocol.

  • Ignore unknown message types and fields. Do not treat an unknown field as permission or assume that an undeclared capability exists.

  • Validate the fields an App uses, tolerate optional fields being absent, and keep repeated equivalent configuration idempotent.

  • Keep App-specific messages namespaced and scoped to the exact App and instance. Do not reuse a message for a new meaning.

  • A bridge change that affects other Apps belongs in the shared App/bridge requirement, its tests, and this document. An App-specific change also updates that App's requirement and distribution documentation.

  • If a host does not support an extension, the App must retain a safe general-contract fallback.

Preferences

In Homepage-served contexts, Homepage Preferences are the normal place for persistent, user-facing App settings. The App defines the controls; Homepage renders, stores, discloses, and sends the values.

Default policy

Use Homepage Preferences when a setting is:

  • persistent across visits;

  • a user-facing choice about the App's content, view, or App-specific navigation;

  • user-facing rather than an internal implementation detail;

  • useful while the user is viewing the App in Homepage;

  • expressible as a simple supported control;

  • better kept consistent with Homepage's global Preferences experience.

Keep a setting inside the App when it is:

  • moment-to-moment selection, cursor, camera, or other interaction state;

  • part of a task flow rather than configuration;

  • a high-frequency interaction;

  • a compound form that current host controls cannot express;

  • content-specific and meaningful only inside the App canvas;

  • direct-only behavior with no hosted equivalent;

  • sensitive or permission-bearing behavior that needs a separate reviewed contract.

This is a design policy, not a technical restriction. An App may deviate when it documents why host Preferences would make the workflow less clear or less safe.

Hosted control duplication

When an App contributes a hosted Preference, the embedded App must not show a second control that changes the same value. The App may show the resulting state or provide a different, non-duplicative action.

This applies to settings, view choices, and App-specific navigation exposed through Homepage Preferences. Direct mode may still show the App-owned control. A richer embedded control is an App-specific deviation and belongs in the App extension documentation.

When to deviate

SituationRecommended treatment
Persistent display settingHomepage Preference.
Temporary selected item, cursor, or route positionApp-owned runtime state.
Large or multi-step configuration formApp-owned UI, possibly with a small persistent Preference for the default.
Primary task actionApp-owned UI unless Homepage needs to coordinate it as a lifecycle command.
App-specific navigation that should integrate with Homepage ChromeHosted Preference or App-specific navigation extension, as Maps does with navigation-list.
Direct-mode-only convenience settingApp-owned direct storage; do not expect it in hosted mode.
Setting that requires a secret or authorizationServer-side API design, not a Preference control containing the secret.

Preference structure

A contribution contains collapsible sections:

Section fieldMeaning
idStable section identifier.
labelUser-facing section label.
initialOpenInitial disclosure state.
preferencesControls in the section.

Each Preference has:

Preference fieldMeaning
idStable identifier within the App instance. Use App-specific names.
labelUser-facing label.
controlCurrent supported control type.
defaultValueValue used when no saved value exists.
valueOptional current value in a contribution.
liveUpdateMust be when declared. Current controls apply immediately; and deferred Apply/commit flows are not supported.
persistence lets Homepage store the value per ; leaves persistence to the App; survives neither remount nor reload. If omitted, the value is treated as .
min, max, stepRange constraints.
optionsSelect or radio options with value, label, optional symbol, and optional disabled.
revealsIDs disclosed by a toggle's secondary disclosure control.
itemsMaps-specific navigation items for navigation-list.

Current controls

The current Homepage renderer supports:

ControlCurrent behavior
actionOne-shot button; activation sends the value and is never stored by Homepage.
colorColor input.
navigation-listMaps-specific ordered navigation buttons.
radioSymbol-and-label button group; selection is sent immediately and is stored only when persistence is .
rangeNumeric range input.
selectSelect input.
textText input; current host commits on blur.
toggleBoolean checkbox with optional reveals disclosure.

The shared target vocabulary also mentions checkbox, date, datetime, email, number, password, textarea, time, and url. Those are not current Homepage-rendered App controls. Use the seven general controls above. is Maps-specific and requires the Maps extension.

Lifecycle and storage

  • Manifest contribution is normalized when an embedded instance is mounted.

  • A runtime contribution replaces the current contribution for the same App and instance.

  • Empty sections are removed. A navigation-list without usable items is removed.

  • Homepage stores only non-action Preferences whose persistence is , keyed by in its signed HTTP-only settings cookie.

  • is owned by the App. Homepage does not save it or reuse an old host-side value.

  • is not persisted by either side unless the App separately chooses to persist it.

  • The initial value is resolved in this order: saved value, contribution , , then an empty value.

  • Only Homepage-owned saved values are sent in ; individual changes use .

  • Current controls apply immediately. Range, color, toggle, radio, and select changes send on change; text sends on blur. There is no generic Apply or commit message.

  • An action sends a one-shot value. The App owns its result and any App-owned reset. Homepage has no generic reset command for stored App Preferences; resetting a Homepage-owned value needs a separately reviewed extension.

  • Apps do not read the Homepage settings cookie directly.

  • is a Homepage-served direct App surface and can use Homepage Preferences. Opening the App distribution outside Homepage has no Homepage-owned storage.

APIs and data

Security boundary

Homepage Apps are first-party, curated distributions created by App Coder and reviewed before integration. They are not third-party plugins.

Hosted Apps currently run in a same-origin, unsandboxed iframe. The bridge validates messages, but it is not a sandbox or permission boundary. Treat App client code as trusted Homepage code: review it before integration, keep secrets out of , and use named Homepage APIs for server-backed work.

Access matrix

Data or capabilityApp access today
Authored Embed configurationYes, through homepage/app/config, field config.
Saved hosted Preference valuesYes, through homepage/app/config.preferences.
Live Preference changesYes, through homepage/app/preference.
Theme, brightness, accent, CSS variablesYes, through host context.
Embedded safe areaYes, through layout context.
Homepage DOM or undeclared same-origin browser storageTechnically accessible under the current trusted-App model; not a supported contract.
Declared App-owned extension storesOnly through the exact reviewed extension contract; the App remains canonical owner.
Homepage settings cookieNo direct read; it is HTTP-only.
OpenAI API key stored by HomepageNo.
Provider API keys or secretsNo.
Login/session dataNo current App contract.
Curated Homepage APIOnly through the named route's documented request/response contract.

Apps must not depend on direct DOM or storage access. Same-origin access is an implementation consequence of the current trusted-App model, not a permission grant. Use explicit bridge and API contracts instead.

Curated APIs

Homepage provides a named API when repeated use, provider abstraction, normalization, server-side credentials, or security makes a capability worthwhile.

An App must not:

  • accept an arbitrary provider URL;

  • forward arbitrary request bodies to an external service;

  • infer an API route from a manifest declaration;

  • put a provider key in browser code;

  • assume that an API exists because another App uses it.

Calling a curated API

Maps is the current example of a named same-origin Homepage API. This call shows the general request shape; the Maps route catalogue appears in the Maps reference:

`js [< nowrap] Calling a Homepage API
const response = await fetch("/api/maps/address", {
method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ address: "Berlin, Germany" })
});

const result = await response.json();

if (!response.ok || !result.ok) {
throw new Error(result.error?.message || "Homepage API request failed."); }

const point = result.data; `

The current response envelope is:

API result envelope
{
  "ok": true,
  "data": {},
  "error": null
}

Failures use ok: false, data: null, and an error object containing a stable code, a user-safe message, and optional details. When an App surfaces that failure to the host, it should report only a bounded safe summary through ; provider credentials, raw upstream payloads, authored secrets, and arbitrary URLs must stay out of the bridge.

Image Generator API

The Image Generator uses the named same-origin route . The route is a single-purpose Homepage adapter; it is not a generic provider proxy. Its request uses only provider-neutral values:

Image Generator request
{
  "operation": "generate",
  "prompt": "A blue glass sculpture",
  "referenceImages": [{ "mimeType": "image/png", "base64": "..." }],
  "options": {
    "modelPolicy": "default",
    "outputFormat": "png",
    "size": "square",
    "quality": "high",
    "background": "auto"
  }
}

is or ; edit requests may include . The allowed semantic values are , , , , and . Homepage resolves the logical credential server-side, maps the model policy to an allowlisted provider model, and never sends the credential into the App iframe.

Successful responses contain exactly one normalized image at , with , , and an optional . Provider model IDs, arbitrary URLs, raw upstream bodies, and provider credentials are not accepted or returned.

App extensions

The general contract is the default. An App-specific requirement is appropriate when the App has a real need that the general capabilities cannot express clearly or safely.

When to extend

Use an extension when:

  • the behavior is meaningful to the App's domain rather than most Apps;

  • the host needs to coordinate it with Homepage Chrome, Slides, or other host-owned navigation;

  • a repeated server operation deserves a named curated API;

  • a general capability would become ambiguous or overloaded if it absorbed the behavior;

  • the App needs a richer interaction than current Preferences controls can express.

Do not add an extension merely to avoid using an existing general capability.

Extension contract

An App-specific extension should document:

  • the user need and why the general contract is insufficient;

  • whether the extension is direct-only, inline-only, full-only, or multi-context;

  • manifest declaration and support detection;

  • message or API names;

  • direction, trigger, fields, validation, and failure behavior;

  • exact slug and instanceId rules;

  • ownership between App and Homepage;

  • a compact example;

  • how the extension behaves when the host does not support it.

The App-specific requirement belongs beside the App's source and distribution. Homepage integration requirements should also be reflected in the shared App/bridge requirement before the host changes.

App-owned history extension

An App may declare an explicit app-owned Local Storage extension when the App remains the canonical owner of a domain history while Homepage must render a lightweight host surface even when the App iframe is unloaded. The Image Generator is the current example.

The manifest declares with schema version , the history key , the reference key , and the metadata key . Homepage reads only this declaration, validates the exact App slug, exact , and schema version, and does not migrate or reinterpret the App's stores.

The App emits with its exact , , and numeric . Homepage sends to the active matching instance with and an for selection/opening. Host-side actions such as preview, download, prompt copy, favorite, delete, and clear may operate on the declared stores while the App is unloaded; after a host mutation, Homepage sends to a loaded matching instance.

The App owns history entries, references, builder snapshots, and metadata. Homepage owns only the host presentation and validated coordination. When is true, the Images Preferences box remains available after the App is unloaded; it is closed by default when the App is hidden and open by default while the matching App is embedded. Unsupported hosts ignore the extension and the App continues to work in its own surface.

Extension template

Use this compact structure for a new App-specific requirement:

App extension template
Extension: <name>
Need: <user or host problem>
Scope: direct | inline | full | multiple
Declaration: <manifest or Embed declaration>
Messages/API: <names and direction>
Fields: <required fields and valid values>
Lifecycle: <trigger, readiness, updates, teardown>
Validation: <accepted, ignored, and failed inputs>
Fallback: <behavior when the host or extension is unavailable>
Ownership: <what App Coder owns; what Homepage Builder owns>
Tests: <contexts, reloads, failures, and security cases>

Maps extension

Maps extends the general contract with navigation-list hosted Preference items, route-navigation, route-painted, route-failed, route-navigation-state, route API warning adaptation, and route-readiness/lifecycle coordination.

These are intentionally Maps-specific host behaviors, not baseline requirements for another App. Homepage does not provide a generic extension framework for them yet; that work is deferred until another App has a comparable host-coordination need.

Verify and publish

Distribute the App as a standalone file or as part of the Homepage.

Distribute the App as a standalone file or as part of the Homepage.

Testing and debugging

Local integration loop

When debugging a local integration, follow the main integration workflow and use the local test contexts for the URLs. Then run this diagnostic loop:

  1. Confirm that current App source is built or synchronized into and visible through the local symlink.

  2. Run ; if it fails, inspect before trusting generated artifacts.

  3. Verify that the App slug appears in the generated registry before debugging an iframe or bridge.

  4. Run the local Codex preview on port 5174 and verify directly before adding an authored Embed.

  5. Test one inline Embed and, when supported, one full Embed. Inspect bootstrap context, live bridge messages, Preferences, reloads, transitions, and failure states.

  6. Run the App's own tests in the App workspace and Homepage Builder's host tests after changing the integration contract.

The current workspace commands are:

These are Homepage Builder commands. App Coder runs the App's own test suite with the App workspace's tooling.

Test matrix

TestVerify
Distribution and registryRefreshed is visible through , generation succeeds, and the slug is present in the generated registry.
Direct routeThe Homepage route renders as a managed full-surface App with its expected bridge messages, route-scoped instanceId, and Preferences; it does not rely on standalone-only behavior.
Standalone distributionThe App's own standalone test environment renders when the App supports one; this is not a Homepage mode.
Inline EmbedConfiguration, Preferences, height, safe area, and resize behavior.
Full EmbedFull layout, Homepage Chrome, lifecycle behavior, and screen presentation if supported.
Initialization handshake and use the exact slug, embedMode, and instanceId; replayed snapshots are idempotent; timeout, reload, and teardown behavior is safe.
Bridge conformanceHomepage host tests reject malformed, wrong-origin/source/path, wrong-slug/instance, and stale messages; reloads reprocess the current configuration; failure, recovery, and Toast reports remain instance-scoped.
ReloadInitial bootstrap, listener installation, repeated configuration, and restored Preferences.
Content parserUnquoted is removed as a comment; quoted URLs and other values containing reach the generated configuration intact.
Live PreferenceThe relevant App state changes without iframe reload, new instance, repeated initial configuration, or initial-loading presentation.
Hosted presentation transitionThe App follows Homepage's currently displayed accent and text colors throughout a transition without an early target jump or a second local transition.
Embedded surfaceThe App and any owned canvas or renderer follow Homepage's background instead of showing a standalone fallback.
Missing or malformed inputSafe App behavior without uncaught runtime errors.
Toast request validationValid info/success and active warning/error requests are accepted; invalid severity, missing message, and malformed issue fields are rejected, text and numeric limits are normalized or clamped, and wrong source/origin/slug or instance are ignored.
Toast issue lifecycleAn active warning/error creates or updates one issue; a matching report clears it without creating a new Toast; dismissal and expiry do not resolve it.
Toast actionHomepage sends only the exact and instance; the App validates source, origin, slug, instance, issue, and known action before acting.
Toast navigation and teardownNavigation preserves App Toasts and unresolved issues; an actual iframe reload or replacement clears the App-instance state.
API warning lifecycleThe App reports safe failure and recovery; Homepage creates or removes one transient Warning per instance and operation. See Outbound: API status.
API warning validationReports from the wrong source, origin, slug, or instance are ignored; equivalent reports remain one Warning.
Missing API or provider failureThe App shows its fallback and reports a safe Warning when appropriate; credentials and raw upstream detail stay private.
Theme and brightness changeApp updates from live host context.
Motion and host-only fieldsCurrent Apps use local reduced-motion fallbacks. , , and are not current bridge capabilities; do not infer them from the URL.

Debugging

  • Inspect the App iframe URL and confirm its embed context.

  • Inspect initial hostContext only as bootstrap input; verify live host-context messages separately.

  • Verify the generated registry and distribution before debugging iframe or bridge behavior.

  • For hosted transitions and backgrounds, apply the Host presentation timing rules.

  • Log message type, slug, and instanceId without logging credentials or authored sensitive data.

  • Treat unknown messages and rejected payloads as ignored host traffic, not as an implicit permission.

  • Test the direct App route before debugging an embedded failure.

  • Follow Failure triage and investigate the earliest broken boundary first.

  • Keep App-specific diagnostic detail inside the App or its reviewed extension contract.

Publishing

From dist to public route

  1. App Coder produces the distribution.

  2. Homepage Builder discovers the matching dist/manifest.json.

  3. The generator creates the App registry and asset inventory.

  4. Deployment materializes the App distribution in the published runtime.

  5. The same distribution serves direct and embedded routes.

The published asset route currently uses Cache-Control: no-store. The App entry and assets are served from the authored distribution; App source, tests, documents, and logs are not public runtime assets.

Public routes

For an App with slug maps:

UseRoute
Canonical Homepage App route/app/maps
Distribution entry/app/maps/index.html
Deployed canonical routehttps://www.<domain>/app/maps
App-focused deployment aliashttps://apps.<domain>/maps

The alias resolves through the same Frontend runtime; it is not a second App copy. Current App routes are public when the deployment is public. Gated App behavior belongs to the separate access-control contract.

Publication checklist

  • dist/manifest.json is valid and the slug matches its directory.

  • The declared entry exists inside dist.

  • Required runtime assets are present.

  • Direct mode works before embedded modes are tested.

  • Supported contexts are declared accurately.

  • No browser-shipped secret is required.

  • API routes are explicit and curated.

  • Bridge messages use the correct App slug and exact instance identity where required.

  • Public route and App listing metadata are correct.

Appendix

Supportive material.

Supportive material.

Maps reference

Maps is the current complete example of the general contract plus deliberate extensions:

  • It runs direct, inline, and full; its distribution includes a manifest, entry document, runtime assets, and a pre-paint bootstrap.

  • It waits for before preparing hosted content, uses Homepage Preferences for persistent settings, and keeps route position and task navigation in App state.

  • It consumes named same-origin address, point, route, and area APIs.

  • It reports named API status and route events, registers lifecycle controls for the active instance, and exposes Maps-only route navigation. Homepage adapts API status and route-failed/route-painted reports into the shared Preferences Warning lifecycle while Maps keeps its own detailed fallback message. See Outbound: API status for the general warning contract.

Maps API routes

MethodRouteRequest bodyCurrent purpose
POST/api/maps/address{ address: string }Resolve an address to a point.
POST/api/maps/point{ point: [latitude, longitude] }Validate and normalize coordinates.
POST/api/maps/route{ from: location, to: location, mode: string }Resolve one route leg.
POST/api/maps/area{ area: string }Resolve a named area boundary.

These Maps routes keep provider details and credentials on the server. The future API offering will be authoritative for route schemas, errors, caching, rate limits, and provider behavior.

Maps failure matrix

The Maps extension keeps provider-specific behavior inside the Maps/API contract. The general Apps contract only needs the ownership and user-visible outcome:

FailureMaps behaviorHomepage behavior
Address or point resolution failsShows a safe location error; no location is prepared and no route fit is attempted.Adds the corresponding transient Preferences Warning and keeps the host state from claiming that usable map content is ready.
Named area resolution failsShows an area error; the affected area framing is not prepared and an affected route may report route failure.Adds the corresponding transient Preferences Warning, handles the host loading state, and accepts the App's safe outcome.
Routing provider fails but direct fallback is possiblePaints a dashed direct-line fallback and shows a safe warning. This is a successful route paint, not .Adds or updates the route API Warning and stops waiting for route readiness once Maps reports the fallback paint.
Maps emits Keeps the detailed warning in Maps, reports the safe failure with the active instance, and preserves the route-specific report.Adds or updates the parent-owned route Warning and clears its route-loading overlay; it does not replace the App's domain-specific warning.
Homepage route-loading timeoutMaps may continue preparing or resolving content.After four seconds, stops blocking the embedded surface; timeout is not a success signal.

The Maps API implementation owns provider selection, required request headers, upstream timeouts, fallback policy, and provider diagnostics. Those details belong in the Maps/API documentation and the future API offering, not in the general Apps contract.

Maps example

Maps Embed configuration
{
  "app": "maps",
  "mode": "inline",
  "address": "Berlin, Germany",
  "route": {
    "from": "Hamburg",
    "travel": [
      { "mode": "train", "to": "Berlin" }
    ]
  }
}

The App receives authored values as config, saved values as preferences, and the generated instanceId through homepage/app/config. It contributes Route navigation and Map settings through homepage/app/preferences, then uses the Maps extension messages when the route is ready.

Maps-specific behavior remains in the Maps distribution README and requirements. This section explains why the App is a useful reference; it is not a replacement for Maps documentation.

Open design questions

  • Bridge versioning: Deliberately deferred. Homepage and the current Apps ship as one coordinated first-party bundle, so one unversioned bridge is sufficient for now. Revisit this before Apps can be released independently or multiple bridge versions must coexist; define versioning and capability discovery together at that point.

  • Bootstrap sharing: Maps, Models, and CX Widgets currently carry App-local pre-paint bootstrap scripts. A shared runtime package is deferred until the separate App distribution workflow can support it without adding unnecessary packaging complexity.

  • Host context coverage: , , and are not part of the current generic host-context contract. Current App distributions must not declare or depend on them. Revisit them only when a concrete shared need justifies a coordinated host-and-App change.

  • Manifest capabilities: context support, bridge participation, and required assets are enforced; parent-context fields, bridge-message declarations, and API/resource declarations remain descriptive until their contracts are mature enough to enforce.

  • Preference semantics: current , , , immediate-update, and action rules are enforced. Deferred commits and a generic reset for Homepage-owned values are deliberately not part of the contract.

  • Maps-specific host code: Maps route navigation, route loading, and navigation-list remain intentional host special cases. Generalization is deferred until another App needs comparable coordination.

  • API offering: The stable catalogue of Homepage APIs, schemas, errors, caching, and provider policy is deferred to a separate document.

  • Older design notes: Commands such as nextSlide, previousSlide, goToRoute, goToPresentation, setBrightness, setBrandTheme, and openExternal are not part of the current bridge.

Next design pass: tooling

These are follow-ups, not current App requirements:

  • Distribution freshness: Define the authoritative source-to- build or synchronization step, then check that reflects current source and contains its required runtime assets. Do not require byte-for-byte equality when the build transforms files.

  • Registry smoke test: After generation, verify that every discovered App slug appears in the generated registry at and has a matching route and asset inventory.

  • Accent parity: Add a browser regression that compares Homepage and App displayed values during initial load, reload, inline mode, and full mode, while keeping standalone mode independent.

  • Stale-artifact diagnostics: Make generation failure and stale generated-artifact state prominent enough that existing generated files cannot be mistaken for a successful current build.

Integration checklist

  • dist/manifest.json is valid; its slug, entry, supported contexts, bridge declaration, and required assets are correct.

  • declares only fields currently delivered by the general host context; unsupported fields are not treated as available capabilities.

  • The App works at its direct route before hosted behavior is tested.

  • Hosted startup installs a listener early, treats instanceId as opaque, and reads authored values from config.

  • Hosted startup completes the / handshake for the exact instance; replay, timeout, reload, and teardown are safe.

  • Bootstrap styling is optional; later updates come from live host-context messages.

  • Hosted Preferences use current controls, receive initial values, and handle live changes.

  • Each Preference uses or omits it, and chooses , , or deliberately; action controls are one-shot and never persistent.

  • Hosted presentation styling uses Homepage's displayed values and background without a competing local transition.

  • Provider access uses a named Homepage API route; no secret ships in dist.

  • Relevant hosted App events use ; Toast text is plain, bounded, user-facing, and does not contain HTML, secrets, raw provider responses, unvalidated URLs, or styling instructions.

  • Warning/error Toasts use stable and ; recovery reports resolve the same issue identity.

  • Toast actions use one stable action ID; App-owned actions validate against the exact parent, origin, slug, instance, issue, and known action, while URL actions use only the bounded internal/HTTP(S) target contract.

  • API failures and recovery use with safe bounded diagnostics; no secret, raw provider response, authored sensitive value, or arbitrary URL crosses the bridge.

  • The internal App security review checklist is complete for the submitted dist/ output.

  • Direct, inline, full, reload, malformed-input, Toast, action, and failure cases are tested.

  • App-specific extensions document scope, validation, fallback, and ownership.

  • Maps-specific messages are used only by the Maps integration.

  • The change identifies whether it belongs to App source/, Homepage bridge/generator code, or a Homepage API route.

  • App and Homepage changes are reviewed separately; any Homepage source change has explicit Owner clearance before implementation.

This checklist is intentionally short. The sections above are the contract reference when an App needs field-level detail.