share-assets

star 0

From one source brand mark, generate the full set of identity + share assets (favicon, app icons, PWA manifest, static OG image, Twitter card, dynamic OG route, JSON-LD) and wire them into a TanStack Start app's head + public/. Use when starting a new app or when an existing app's link previews look broken on X / WhatsApp / iMessage / LinkedIn / Slack.

RonanCodes By RonanCodes schedule Updated 6/7/2026

name: share-assets description: From one source brand mark, generate the full set of identity + share assets (favicon, app icons, PWA manifest, static OG image, Twitter card, dynamic OG route, JSON-LD) and wire them into a TanStack Start app's head + public/. Use when starting a new app or when an existing app's link previews look broken on X / WhatsApp / iMessage / LinkedIn / Slack. category: development argument-hint: [--source ] [--no-dynamic-og] [--brand-color ] allowed-tools: Read Write Edit Glob Grep Bash AskUserQuestion content-pipeline: - pipeline:image - platform:agnostic - role:adapter

Share Assets

Builds out the brand-identity + social-share asset set every shipped app needs but every new repo forgets. One source SVG mark in, the full deliverable set out:

  • public/favicon.ico (multi-resolution, 16/32/48)
  • public/favicon.svg (vector, theme-aware via prefers-color-scheme)
  • public/icons/icon.svg (master)
  • public/icons/icon-192.png, icon-512.png, icon-maskable-512.png (PWA manifest)
  • public/icons/apple-touch-icon.png (180px)
  • public/manifest.webmanifest (with the icons array wired)
  • public/og-static.png (1200x630 fallback for any URL)
  • src/routes/api/og.ts (dynamic per-URL OG via Satori on Workers; delegates to /ro:og-image-dynamic if not yet wired)
  • Head meta tags in src/routes/__root.tsx: og:*, twitter:*, link rel="icon", link rel="manifest", link rel="apple-touch-icon", theme-color, JSON-LD application schema

When to use

  • New app: scaffold the whole asset set on day one. Spec must include this story (the baseline checklist enforces it).
  • Existing app: audit + fix gaps. Run with --audit-only to report what's missing without writing.

When NOT to use

  • Native mobile apps (Expo / React Native): different asset pipeline (use Expo's icon/splash conventions).
  • Pure CLI tools or libraries with no public-facing URL: nothing to share.

Why this is a baseline

Every link a customer or investor pastes into X / Reddit / LinkedIn / WhatsApp / iMessage / Slack triggers an OpenGraph fetch. The default "no preview" rendering looks broken; a wrong-aspect-ratio static image looks lazy; a per-URL dynamic image looks polished. Same with the favicon: a default Vite/Next.js placeholder favicon is the visual equivalent of a "Hello World" landing page.

Fix once, fix permanently, automate the rest.

Usage

/ro:share-assets --source brand/dataforce-mark.svg dataforce
/ro:share-assets --source ./logo.svg --brand-color "#10b981" my-app
/ro:share-assets --no-dynamic-og my-app          # skip the /api/og route, static-only
/ro:share-assets --no-hero my-app                # skip the README/landing hero illustration
/ro:share-assets --source-hero ./hero.png my-app # bring your own hero image, skip generation
/ro:share-assets --hero-prompt "calm, monoline cat" my-app  # override the auto-derived prompt
/ro:share-assets --no-mascots my-app             # skip the avatar / empty-state mascot variants
/ro:share-assets --audit-only                    # report gaps; don't write

If --source is omitted, the skill prompts (AskUserQuestion) for an existing brand mark path or offers to call /ro:generate-image to create one from a brief.

Hero + mascot variants are on by default. Every new app gets: (a) a wide hero illustration (public/brand/<app>-hero.png) for README + landing, and (b) two character-consistent mascot variants (<app>-mascot.png for avatar use, <app>-mascot-asleep.png or another mood for empty states). Character consistency comes from using the hero as the reference image in OpenAI's /v1/images/edits endpoint, plus a CHARACTER REFERENCE paragraph reused across variant prompts. See Step 3.

Step 1 — Source mark check

Verify the source SVG:

  • Square aspect ratio
  • Single-coloured OR explicitly designed for monochrome (favicon will be reduced to 2 colours)
  • No outer padding (the skill adds appropriate safe-zones for maskable icons)
  • Valid XML

If the source fails, prompt for a different path or call /ro:generate-image with a "brand mark, square, single-colour, modern" prompt.

Step 2 — Generate the favicon set

Use ImageMagick (magick on macOS / convert on Linux) or sharp (Node) to derive PNGs at 16, 32, 48, 180, 192, 512. Bundle the 16/32/48 PNGs into a multi-resolution favicon.ico:

magick -density 300 -background none source.svg \
  -define icon:auto-resize=16,32,48 public/favicon.ico

magick -density 300 -background none source.svg -resize 180x180 public/icons/apple-touch-icon.png
magick -density 300 -background none source.svg -resize 192x192 public/icons/icon-192.png
magick -density 300 -background none source.svg -resize 512x512 public/icons/icon-512.png

# Maskable: pad with 20% safe zone on all sides
magick -density 300 -background "#<brand-color>" source.svg \
  -resize 320x320 -gravity center -extent 512x512 \
  public/icons/icon-maskable-512.png

cp source.svg public/favicon.svg
cp source.svg public/icons/icon.svg

Theme-aware favicon (optional but nice): the SVG can use <style> with prefers-color-scheme: dark so the favicon adapts to the user's OS. Document this in the source.

Step 3 — Generate the hero illustration + mascot variants

Delegates to /ro:generate-mascot for the implementation. That skill is the standalone artist tool; this one wraps it inside the broader share-assets suite.

It produces three character-consistent images under public/brand/:

  • <app>-hero.png — wide hero (1536x1024), the README + landing-page anchor image.
  • <app>-mascot.png — square avatar (1024x1024, transparent), the canonical mascot for headers / app-icon foreground.
  • <app>-mascot-<mood>.png — square mood variant (1024x1024, transparent) matching the app's spine (asleep for nightly-job apps, curious for research, writing for notes, etc.).

The variants are character-consistent because /ro:generate-mascot uses OpenAI's /v1/images/edits endpoint with the hero as the reference image, plus a re-used CHARACTER REFERENCE paragraph in every variant prompt.

Pass through the flags share-assets surfaces (--no-hero--no-hero --no-variants; --no-mascots--no-variants; --source-hero--source-hero; --hero-prompt--hero-prompt). And the README + landing-page wiring is handled by /ro:generate-mascot Step 7-8.

See /ro:generate-mascot for the full instructions (prompt derivation, key resolution, generation, compression, README + landing wiring, audit mode).

Step 4 — Generate the static OG image

public/og-static.png is the fallback when /api/og isn't reachable or for very old crawlers. 1200x630, brand-coloured, with the app name and a one-line tagline.

If the user already has a designed marketing hero, derive from that. Otherwise compose a minimal layout:

magick -size 1200x630 \
  -background "#<brand-color>" \
  -fill white \
  -font Inter-Bold \
  -pointsize 92 \
  -gravity center \
  -annotate 0 "<App Name>" \
  public/og-static.png

Step 5 — Wire manifest.webmanifest

{
  "name": "<App Name>",
  "short_name": "<Short>",
  "description": "<one-line description>",
  "start_url": "/?source=pwa",
  "scope": "/",
  "display": "standalone",
  "display_override": ["standalone", "browser"],
  "background_color": "#ffffff",
  "theme_color": "#<brand-color>",
  "orientation": "portrait-primary",
  "categories": ["productivity"],
  "icons": [
    { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
    { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
    { "src": "/icons/icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
  ]
}

Step 6 — Wire head meta tags (TanStack Start)

In src/routes/__root.tsx head function:

const SITE_ORIGIN = 'https://<your-domain>'
const TITLE = '<App Name>: <one-line tagline>'
const DESCRIPTION = '<150-character description>'
const OG_IMAGE = `${SITE_ORIGIN}/api/og` // dynamic; falls back to /og-static.png if route unreachable

head: () => ({
  meta: [
    { charSet: 'utf-8' },
    { name: 'viewport', content: 'width=device-width, initial-scale=1' },
    { title: TITLE },
    { name: 'description', content: DESCRIPTION },
    { name: 'theme-color', content: '#<brand-color>' },

    // OpenGraph
    { property: 'og:type', content: 'website' },
    { property: 'og:site_name', content: '<App Name>' },
    { property: 'og:title', content: TITLE },
    { property: 'og:description', content: DESCRIPTION },
    { property: 'og:url', content: SITE_ORIGIN },
    { property: 'og:image', content: OG_IMAGE },
    { property: 'og:image:width', content: '1200' },
    { property: 'og:image:height', content: '630' },

    // Twitter / X
    { name: 'twitter:card', content: 'summary_large_image' },
    { name: 'twitter:title', content: TITLE },
    { name: 'twitter:description', content: DESCRIPTION },
    { name: 'twitter:image', content: OG_IMAGE },
  ],
  links: [
    { rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' },
    { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
    { rel: 'apple-touch-icon', href: '/icons/apple-touch-icon.png' },
    { rel: 'manifest', href: '/manifest.webmanifest' },
  ],
  scripts: [
    {
      type: 'application/ld+json',
      children: JSON.stringify({
        '@context': 'https://schema.org',
        '@type': 'WebApplication',
        name: '<App Name>',
        url: SITE_ORIGIN,
        description: DESCRIPTION,
        applicationCategory: '<Productivity / Game / Tool>',
        operatingSystem: 'Web',
      }),
    },
  ],
})

Step 7 — Dynamic OG route (if not skipped)

Delegates to the /ro:og-image-dynamic skill for the implementation. The skill scaffolds src/routes/api/og.ts using Satori + @resvg/resvg-wasm on Workers.

When this skill (/ro:share-assets) runs and the OG route doesn't exist yet, it invokes /ro:og-image-dynamic --runtime cf automatically. Pass --no-dynamic-og to skip and stick with the static og-static.png.

Step 8 — Verify

After scaffolding, the skill runs:

curl -sI https://<deployed-url>/favicon.ico  # 200 image/x-icon
curl -sI https://<deployed-url>/favicon.svg  # 200 image/svg+xml
curl -sI https://<deployed-url>/manifest.webmanifest  # 200 application/manifest+json
curl -sI https://<deployed-url>/brand/<app>-hero.png  # 200 image/png, ~400-800kb (skip if gated)
curl -sI https://<deployed-url>/og-static.png  # 200 image/png, ~30-80kb
curl -sI https://<deployed-url>/api/og  # 200 image/png if dynamic wired

And renders the head + checks:

curl -s https://<deployed-url> | grep -E 'og:image|twitter:card|rel="(icon|manifest|apple-touch)"'

Then opens https://www.opengraph.xyz/?url=https:// and https://cards-dev.twitter.com/validator (if it still exists) to visually verify the share preview.

Audit mode

--audit-only reports what's missing without writing:

✓ favicon.svg
✓ favicon.ico
✗ apple-touch-icon.png (missing)
✓ manifest.webmanifest
✗ icon-maskable-512.png (missing; manifest references it)
✗ public/brand/<app>-hero.png (missing; README + landing have no hero illustration)
✗ public/brand/<app>-mascot.png (missing; no avatar mascot)
✗ public/brand/<app>-mascot-asleep.png (missing; no empty-state mascot variant)
✗ /api/og route (no dynamic OG)
✓ og-static.png
✓ Head meta tags (og:*, twitter:*)
✗ JSON-LD application schema (missing in __root.tsx)

Returns exit code 0 if all present, 1 if any missing. Useful in CI as a launch-readiness gate.

What this skill does NOT do

  • Design the brand mark / logo (a different artefact from the hero illustration; the logo is a single-colour square SVG used for favicons. Delegate to /ro:generate-image or use an existing source).
  • Ship per-route OG titles (different feature; emit a useHead() pattern in route components, scoped to each story).
  • Replace /ro:seo-launch-ready (which handles sitemap, robots, JSON-LD basics, canonical URLs).
  • Replace /ro:pwa-install (which handles service worker + install prompt).

Per-app OpenAI key convention

For the hero step, prefer a per-app key (OPENAI_API_KEY_<APP_UPPER>) over a global one. Reasons: budgets stay scoped to the project that spent them; a billing-limit hit on one app doesn't block the others; rotation is per-app. The skill resolves keys in this order: app-specific → global OPENAI_API_KEY → fallback to /ro:generate-image (Gemini, different key entirely). Pair with /ro:env to add a new key cleanly without it landing in the chat transcript.

Related

  • [[ideal-tech-setup]] § Greenfield Spec Baseline — share-assets is item #11
  • /ro:og-image-dynamic — wires the per-URL dynamic OG route
  • /ro:seo-launch-ready — sitemap, robots, JSON-LD, canonical URLs
  • /ro:pwa-install — manifest + service worker for installability
  • /ro:app-polish — 10-point launch-readiness audit (this skill is one of the points)
Install via CLI
npx skills add https://github.com/RonanCodes/ronan-skills --skill share-assets
Repository Details
star Stars 0
call_split Forks 1
navigation Branch main
article Path SKILL.md
More from Creator