graview-logo
graview-logo 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: Put a client's own logo and page icon on a Graview app — inline SVG or a path, one for each scheme so a picture on white is not a white slab in the dark, an emoji as the icon — drawn in the default shell and handed to a design's own, and let graview check refuse a mark that could act rather than trusting it.
Installed into a project by graview skills install ., and read by whichever assistant is working beside you.
The skill
A logo, and an icon
The brand's mark is drawn as it was given, never redrawn, on every face: the app bar, a design's shell, the static page, a worker view. The palette and the type are graview-brand; this is the picture beside the name.
Do this
- One mark when it can be one. Inline SVG with
currentColortakes the accent and works in both schemes from one file:
``ts logo: '<svg viewBox="0 0 24 24" ... stroke="currentColor">...</svg>', ``
- One for each scheme when it cannot. A client's real logo — a JPG or PNG on white — sits as a white slab on a dark ground. Give the dark scheme its own:
``ts logo: { light: "/graview/assets/<sha>.jpg", dark: "/graview/assets/<sha>.png" }, ``
The light one is drawn in the light scheme, and in the dark too where there is no dark one. In a document the same object takes alt: "logo": { "light": "…", "dark": "…", "alt": "TrueNorth Recon" } ({ "src", "alt" } and a plain string still mean one logo).
- Inverting is asked for, never guessed. Whether a light logo is a picture opaque on white cannot be known from it, so with no dark one nothing happens in the dark unless the brand says
darkFallback: "invert": then, in the dark scheme alone, it is drawninvert(1) hue-rotate(180deg)and screened onto the ground. Beside adarkone it is never used, andgraview checksays so (brand-logo).
- A design's shell draws it with
<AppMark brand={context.brand} />(@graview/primitives), as the default shell's bar does. Both marks are in the page, eachdata-graview-scheme-only="light"or"dark", and the theme's sheet for the scheme the page is in hides the other's — so it switches with the scheme however the page chose it, with no hook. A design that draws its own<img>readslogoFor(brand, scheme)(@graview/core):{ src, invert }. Never a CSS filter of the design's own over the brand's logo.
- On Graview Cloud, upload each with
add_image, then{"op": "set-brand", "logo": {"light": "/graview/assets/<sha>.jpg", "dark": "/graview/assets/<sha>.png"}}.set-brandsays what changed: "The logo gets one of its own for the dark scheme."nullclears it.
What a mark may be
- Inline SVG or a path on the app's own host in a document — never another origin. It is read element by element as the browser reads it: shapes, groups, gradients, filters, text and links to its own
#ids, every attribute quoted, nothing after</svg>. A script, a handler,<a>,<style>, an animation,foreignObjector anything it loads is refused (brand-mark, atbrand.logo.lightorbrand.logo.dark, with the reason), never rewritten. A declared app's mark may live at any address an image is drawn from; one that could act is refused the same. - The icon (
favicon) is a mark too, set only by a face that owns the page; an embed on somebody else's page leaves theirs alone unless the host passesfavicon: true."favicon": { "emoji": "🏡" }— exactly one standard emoji (👩🏽🌾 and 🇺🇸 count as one) — is the icon and, with no logo, the mark; anything else is refused (brand-emoji).
Served under a base, and on a home screen
- A path is on the host's root.
/graview/assets/<sha>.pngis where a document names it; a host serving each app under a base (/a/<app>/) says where it really is withassetsAt: (path) => base + pathonmount,<Embed>, the Shell orPagesApp(splashHtmltakes it too). The logo, the icon, the opening screen's image and an image a worker view draws by its path load there; inline SVG and an emoji are drawn as given. - The head for a phone's home screen is
homeScreenOf(brand, { base })from@graview/core/head:html(the icon typed by what it is, an an Apple touch icon,<link rel="manifest">, a theme color per scheme, one where the brand pins one), themanifest, and thefilesto answer under the base. A PNG mark is every size as it is (or resized); an SVG or an emoji is no PNG without a rasterizer, so it is offered as SVG alone and no Apple touch icon is written — unless the host makes PNGs and sayspng: true.graview serveandgraview previewserve them.
describe_place("home") says the app as the home draws it (its masthead): the name, the line under it, and the logo by its alt text.
Then find out whether it worked
pnpm build && npx graview check ./dist/domain/app.jsbrand-mark and brand-logo say which mark, where and why. Then look at it in both schemes — ?theme=light and ?theme=dark — on the bar and in the design's own shell: the dark one in the dark, or the light one inverted only where the brand asked. And look at the opening screen, which every picture of a place is taken after: `graview shots app.json --opening-screen
--scheme both pictures it once it settles, moving and under reduced motion. A host picturing it in its own browser loads splashPage(brand) (@graview/pages/splash) and waits on SPLASH_SETTLED`.
What the check cannot see
- Whether the logo reads at 18 pixels. Most do not.
- Whether an inverted logo still looks like the client's: its hues stay, its lightness flips, so a navy mark comes out pale blue. If that matters, give a dark one.
- Whether the dark one is the client's at all. Ask for it.