graview-host-page
graview-host-page is one of Graview's 31 authoring skills: instructions an AI coding assistant such as Claude Code or Codex reads before it changes a Graview app. What it teaches: Host a Graview app as the page itself — a service that serves it, a chat's widget, the address bar handed over, the host's own menu and news inside it — and picture its places honestly: wait for data-graview-drawn or onDrawn rather than a timer, open a page on a view proposed and not kept (draftView), and photograph places with graview preview and graview shots (--view, --manifest).
Installed into a project by graview skills install ., and read by whichever assistant is working beside you.
The skill
The host's page: when the page is the app
graview-embed puts an app on somebody else's page with mount. This is the other end of the same call: a host whose page exists to serve the app — a service that hosts apps for strangers, a chat that draws one in a widget, a desk — and so owns the address bar, the sign-in and the news, and needs to know when what it serves can be read or pictured. The host decides the frame; the app still decides what is in it.
The moves
- When the page IS the app, hand it the address bar. The default,
routing: "memory", never touches the host'slocationorhistory— right in an article. A host whose whole page is the app passesrouting: "address"(andbasePath: "/apps/a1/"when it is not served at/), and answers every address under the base with the one page:<base>/places/<as>,<base>/<plural>/<id>and the home are the routed face's pages, pushed and reloadable; the scene is<base>/places/overview#overview=1; the switch is a step Back undoes.faceAtAddress(options)is the face an address opens on, andaddressOf(place, { basePath })(@graview/core) spells a place's link. A host that keeps its own history stays on memory, hearsonNavigate(path, how)and answers its own Back withsetPath(path). A product shell of its own that shows the scene at a route mountsface: "graview"there withbar: { faces: false, name: false }andheading: false: no second name, no switch to Graview's own pages. - In a chat's widget, let the frame be sized from the embed. `height: "auto"
withonIntrinsicHeight` tells the host the height to give it;hostContext: { theme }takes the chat's theme;pagesBelowswaps the scene for the pages on a phone;remotetakes anopenRemotestore,subscriptionsthe host's Notify me (remoteSubscriptions),signIna visitor's way in. - A hosted reader sits only as who signed in. Name members through
people(andsetPeople), never asseats, which offer "sit as somebody else". Where readers cannot save a declaration, `studio: false— orstudio: { onApply }to keep what it applies: return{ ok: false, sentence, findings }` when you could not (the sentence heads its panel),offered: truewhen you decided who builds, and `place: StudioPlace`, imported, when the page is the studio and should not wait for its chunk. Where only the pages are shown, importmountfrom@graview/embed/pagesand bundle nothing else. - Put the host's own furniture inside the embed, never over it.
hostActions: [{ label, href }](oronSelectfor a press) draws the host's links in the person's menu;barActions(iconOnlyfor a mark),appMenu,schemeChoice,shareandnotificationsfill the bar, its app menu, Share and the inbox, each with aset…on the handle; a notification withneeds: truestands under Needs you, and is what the bell counts. Popovers are in the top layer: noz-indexoverride.quickAsk: falsedraws no quick ask (⌘K). Say the host's own news through `handle.notify({ kind: "toast" | "banner", sentence, tone, action })`, not a toast of your own; a History in your menu ishandle.showHistory(), the app menu's own timeline. `onReport({ message, code?, place?, record?, version })` puts a native "Report this" on the framework's own warning and error toasts (not on yours).firstLook: { seen, onDone, steps }points at the app's controls once, yourstepsnaming aFIRST_LOOK_ANCHORScontrol. - A changed app keeps the reader's place. `handle.setApp(app, store)`, not a remount, keeps the face, the page and the stop, and a
labelthat was the app's name takes the new name. To remount, passhandle.where()back asmount(…, { at }). - Start what it loads before it asks. Each face is a chunk of its own, fetched as it is first drawn:
preload(face)starts it beside the host's own requests, andhandle.drawn()waits for the face to be on the page. What the face opens as it first draws, your build names in the head:firstDrawChunks(metafile, face)from@graview/embed/first-draw, asmodulepreloadlinks.onErrorandonReadytell the host failures (class and module only) and the time to the first face drawn. - Serve the files you keep.
files: { upload, maxBytes, kinds }onmount; serve each as its bytes' type,nosniff. A page with a CSP allows the files route inobject-src, or a record's PDF is its one line — name, type, size, Open / Download — and no box. Under a base:assetsAt.
Knowing when a place has drawn
Once the place the reader is on has drawn and settled — every part drawn on it arrived or said it did not, the fonts and images loaded, each worker view drawn or stopped (what stands in its place drawn), and nothing moving for 200 ms — the embed's root wears data-graview-drawn (DRAWN_ATTRIBUTE), its value the place's path, and mount's onDrawn(place) is told where it is as where() says it (EmbedWhere: face, path, stop). A move to another page, stop or face takes the attribute off in the commit that moves, and the next place puts it back as it settles; a place that never stops moving is said drawn after ten seconds all the same. A worker view's region says data-worker-view-drawn once it has drawn what it was last shown. A React host that wants the face committed, not settled, passes onFaceDrawn to <Embed>.
Picture a place by waiting for the attribute, never a timer:
await page.waitForSelector(`[data-graview-drawn="/places/the-week"]`);An inlined opening screen says the page's own words over the brand's: splashHtml(brand, { title, kicker, fade? }).
A view proposed and not kept
A chat that proposes a worker view can try it headless and be told what it drew in words; to see it at a phone's width in dark it had to keep it first. draftView (EmbedDraftView) draws one for the seat at the keyboard without keeping it — its manifest and its source (or worker, and anything else a WorkerViewDefinition takes but draft):
mount(root, { app, remote, routing: "address", draftView: { manifest, source, author: "Proposed by Claude" } });It is drawn where its manifest attaches — the home, a kind's list at the place its title names (/places/<title>), each record's page — under one line, "Draft — <title> · Not kept", in a group named "Draft: <title>" ([data-graview-draft-view="<name>"]). A kept view of the same name is the one it stands in for: it is drawn nowhere beside the draft. A draft that is stopped says why in its place rather than standing the kind's own face there. Nothing of it is kept: it is registered under no name, so the place menu offers no Hide or Delete and keptViews is asked nothing; the store is not written; the next page, handed no draft, has none. A host that registers one itself passes draft: true to registerWorkerView. To picture several proposals, swap them on the same page with handle.setDraftView(next) (null goes back to the app's own views): the attribute comes off and returns once the new draft has settled, and onDrawn is told again. The pages alone (@graview/embed/pages) take the same draftView, onDrawn and setDraftView. A framed guest view holds the attribute back until it has drawn, or until limits.readyMs passes. Writing the view itself is graview-worker-view.
A view the host keeps, in the places
The declaration names a kept view as view:<its manifest name> in pages.order, pages.first and pages.primary; the page lists it among the lists where it is ordered once registerWorkerView has registered it under that name. The host knows its views and the declaration does not, so it hands their manifests (HostView: name, title, attach) as views to editDocument (a title resolves: "To do" is written view:to-do), compileDocument (a name it does not keep is a warning; unsaid, a note), placesOf(app, { views }) (each listed with view), describePlace(…, { views }) (a titled view's /places/<slug> described as a view's place) and runWorkerViewHeadless({ …, views }), whose check then knows a link to a titled view's place; unsaid, it says such a link as a doubt, not as going nowhere. The place menu's Hide on a kind's list reaches keptViews.lens as { op: "set-lens-hidden", title: "<kind>", hidden }: written into pages.hide, as any declared lens's is.
Pictures of places, with no product
graview preview <document.json> serves the store and Graview's own faces over a document, at the address it prints (it carries the run's key, which the page trades for a cookie; nothing but health answers without it); `graview shots <document.json> --places a,b --scheme both
--width 390,1280 photographs places to PNGs, waiting for data-graview-drawn before each. Both take an app declared in code as graview check does (./dist/domain/app.js): the store runs it, and the page is handed its module bundled with the project's esbuild. Both take a proposed view as --view <view.js> --manifest <manifest.json>, drawn as a draft where it attaches; shots takes its place by its title, as a kept view's (--places "The week"`), and lists it among the places.
Then find out whether it worked
pnpm build && npx graview check ./dist/domain/app.js # the declaration is still whole
pnpm typecheck # the host's options, no casts
graview shots app.json --view view.js --manifest manifest.json --places "The week" --scheme both --width 390Then, on the host's page: reload a deep address and land on it; press Back after the switch and come back; sign in as a member and see no "sit as" in the menu; say a notice and see it at the app's foot. Read data-graview-drawn before and after a move: off in the commit that moves, back with the new path. Open a draft and see its line, and reload without it and see none.
What the check cannot see
- Whether the picture is the right one.
data-graview-drawnsays the place settled, not that it shows what the article or the proposal says; look at each shot, in both schemes and at a phone's width. - Whether the host answers every address under its base. A route the host's server does not answer is a 404 on reload that nothing in the app can see.
- Whether a draft is worth keeping. Drawn is not good; the person decides.