---
title: "API Contract"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{API Contract}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

# API Contract for `shinyseo`

This page is written so another agent can use the package without reading the source.

## Exported surface

- `social_meta(meta)` — builds and injects metadata tags into the page `<head>` at startup.
- `update_meta(session, title, description, url, image)` — reactively updates one or more metadata fields from the server at runtime.
- `write_manifest(meta, path, display, start_url, background_color)` — writes a `manifest.json` to `www/` so Shiny can serve it.

## Return value

`social_meta()` returns a `shiny::tags$head()` fragment.

You normally place it inside a Shiny UI definition, for example:

```r
shinyseo::social_meta(list(
  title = "Hello",
  description = "Short app description.",
  url = "https://example.no",
  image = "https://example.no/share.png"
))
```

## Input contract

`meta` can be one of:

- a character string containing a path to a YAML file
- a named list of metadata values

If `meta` is a character string, the package reads it with `yaml::read_yaml()`.

If the file does not exist, the function stops with:

- `Meta file not found: <path>`

## Required fields

These keys must exist in the final metadata object:

- `title`
- `description`
- `url`
- `image`

The function checks only that the names exist. It does not currently validate that the values are non-empty strings.

If any required key is missing, the function stops with:

- `Missing required meta fields: ...`

## Defaults

The package fills these defaults when the key is missing or `NULL`:

- `locale = "en_US"`
- `robots = "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1"`
- `twitter_card = "summary_large_image"`
- `bing_site_verification = Sys.getenv("SHINYSEO_BING_SITE_VERIFICATION")` when present
- `twitter_site = Sys.getenv("SHINYSEO_TWITTER_SITE")` when present
- `twitter_creator = Sys.getenv("SHINYSEO_TWITTER_CREATOR")` when present
- `schema_type = "WebApplication"`
- `operating_system = "Any"`
- `author_type = "Person"`
- `publisher_type = "Organization"`
- `in_language = locale`

Important detail:

- `schema` is enabled unless it is exactly `FALSE`
- `schema = TRUE`, `schema = NULL`, or an omitted `schema` field all result in JSON-LD output
- `schema = FALSE` suppresses JSON-LD
- The helper does not emit a `<title>` tag; set the document title in the app
  UI so it does not clash with any title Shiny already sets

## Output tags

The function writes these tags into the returned head fragment:

- `<link rel="canonical" href="...">`
- `<meta name="description" ...>`
- `<meta name="robots" ...>`
- Open Graph tags used by Facebook, LinkedIn, Slack, X, and other platforms that read Open Graph:
  - `og:type = website`
  - `og:title`
  - `og:description`
  - `og:url`
  - `og:image`
  - `og:site_name` when `site_name` is present
  - `og:locale` when `locale` is present
  - `og:image:width` when `image_width` is present
  - `og:image:height` when `image_height` is present
  - `og:image:type` when `image_type` is present
  - `og:image:alt` when `image_alt` is present
- Twitter Card tags:
  - `twitter:card`
  - `twitter:title`
  - `twitter:description`
  - `twitter:image`
  - `twitter:url`
  - `twitter:site` when `twitter_site` is present
  - `twitter:creator` when `twitter_creator` is present
  - `twitter:image:alt` when `twitter_image_alt` is present
- Bing verification:
  - `msvalidate.01` when `bing_site_verification` is present
- Google verification:
  - `google-site-verification` when `google_site_verification` is present
- Yandex verification:
  - `yandex-verification` when `yandex_site_verification` is present
- Baidu verification:
  - `baidu-site-verification` when `baidu_site_verification` is present
- Naver verification:
  - `naver-site-verification` when `naver_site_verification` is present
- Facebook domain verification:
  - `facebook-domain-verification` when `facebook_domain_verification` is present
- Pinterest domain verification:
  - `p:domain_verify` when `pinterest_domain_verification` is present
- Schema.org JSON-LD:
  - only when `schema` is not `FALSE`
  - always includes `@context`, `@type`, `name`, `description`, `url`, and `inLanguage`
  - conditionally includes `applicationCategory`, `operatingSystem`, `educationalUse`, `isAccessibleForFree`, `disclaimer`, `author`, and `publisher`

## Field map

| Key | Used for |
| --- | --- |
| `title` | Open Graph title, Twitter title, schema name |
| `description` | Meta description, Open Graph description, Twitter description, schema description |
| `url` | Canonical URL, Open Graph URL, Twitter URL, schema URL |
| `image` | Open Graph image, Twitter image |
| `locale` | Open Graph locale, schema language default |
| `robots` | Robots meta tag |
| `twitter_card` | Twitter card type |
| `twitter_site` | Twitter site handle; falls back to `SHINYSEO_TWITTER_SITE` |
| `twitter_creator` | Twitter creator handle; falls back to `SHINYSEO_TWITTER_CREATOR` |
| `twitter_image_alt` | Twitter image alt text |
| `image_alt` | Open Graph image alt text |
| `image_width` | Open Graph image width |
| `image_height` | Open Graph image height |
| `image_type` | Open Graph image MIME type |
| `bing_site_verification` | Bing Webmaster verification; falls back to `SHINYSEO_BING_SITE_VERIFICATION` |
| `google_site_verification` | Google Search Console verification |
| `yandex_site_verification` | Yandex Webmaster verification |
| `baidu_site_verification` | Baidu Webmaster verification |
| `naver_site_verification` | Naver Webmaster verification |
| `facebook_domain_verification` | Facebook domain verification |
| `pinterest_domain_verification` | Pinterest domain verification |
| `schema_type` | Schema.org `@type` |
| `application_category` | Schema.org `applicationCategory` |
| `operating_system` | Schema.org `operatingSystem` |
| `educational_use` | Schema.org `educationalUse` |
| `is_accessible_for_free` | Schema.org `isAccessibleForFree` |
| `disclaimer` | Schema.org `disclaimer` |
| `author_name` | Schema.org `author.name` |
| `author_type` | Schema.org `author.@type` |
| `publisher_name` | Schema.org `publisher.name` |
| `publisher_type` | Schema.org `publisher.@type` |
| `in_language` | Schema.org `inLanguage` |
| `schema` | Turns JSON-LD on or off |

## Favicon and web app manifest fields

These optional fields in `social_meta()` control browser icon and PWA behaviour:

| Field | Tag produced |
| --- | --- |
| `favicon` | `<link rel="icon" type="..." href="...">` |
| `favicon_type` | Overrides the MIME type inferred from `favicon`'s extension |
| `favicon_png` | `<link rel="icon" type="image/png" sizes="..." href="...">` — PNG fallback alongside an SVG `favicon` |
| `favicon_png_sizes` | Overrides the `sizes` attribute on `favicon_png` (defaults to `"32x32"`) |
| `apple_touch_icon` | `<link rel="apple-touch-icon" href="...">` |
| `manifest` | `<link rel="manifest" href="...">` |
| `theme_color` | `<meta name="theme-color" content="...">` |
| `short_name` | Used by `write_manifest()` for the manifest `short_name` field |

MIME type is inferred automatically from the file extension of `favicon` (`.ico`, `.png`, `.svg`, `.webp`, `.gif`).  Supply `favicon_type` to override.

If `favicon` is an SVG, also set `favicon_png` to a PNG fallback. Chromium-based browsers don't render SVG favicons in the address bar and fall back to a generic globe icon without one.

### `write_manifest()`

Generates `www/manifest.json` from the same metadata object.  Call it once in `global.R`:

```r
shinyseo::write_manifest(list(
  title            = "My App",
  short_name       = "MyApp",
  description      = "A Shiny web app.",
  theme_color      = "#1a73e8",
  favicon          = "/favicon.png",
  apple_touch_icon = "/apple-touch-icon.png"
))
```

Then reference the manifest from the UI:

```r
shinyseo::social_meta(list(
  ...,
  manifest = "/manifest.json"
))
```

Parameters:

- `meta` — same YAML path or list as `social_meta()`.
- `path` — directory to write into.  Defaults to `"www"`.
- `display` — PWA display mode: `"standalone"` (default), `"fullscreen"`, `"minimal-ui"`, or `"browser"`.
- `start_url` — start URL in the manifest.  Defaults to `"/"`.
- `background_color` — splash screen background.  Defaults to `theme_color` when set, otherwise `"#ffffff"`.

## `update_meta()` — reactive metadata updates

`update_meta(session, title, description, url, image)` sends a custom message to the browser that updates the live DOM without a page reload.  It is intended for multi-page or tabbed Shiny apps where the metadata should reflect the current view.

### Parameters

- `session` — the Shiny `session` object passed to `server`.
- `title` — replaces `document.title`, `og:title`, and `twitter:title`.
- `description` — replaces `meta[name="description"]`, `og:description`, and `twitter:description`.
- `url` — replaces `link[rel="canonical"]`, `og:url`, and `twitter:url`.
- `image` — replaces `og:image` and `twitter:image`.

All parameters except `session` are optional.  Only the fields you supply are changed; omitted fields keep their current values.  If no fields are supplied the function is a no-op.

### Requirements

`social_meta()` must be present in the UI for the JavaScript handler to be registered.  `update_meta()` alone has no effect.

### Example

```r
server <- function(input, output, session) {
  observeEvent(input$tabs, {
    if (input$tabs == "about") {
      shinyseo::update_meta(session,
        title       = "About – My App",
        description = "Learn more about the team behind the app."
      )
    }
  })
}
```

## Behavior notes

- The function does not mutate the input list outside its local copy.
- The helper returns plain Shiny tags, so it is safe to embed in `fluidPage()`, `navbarPage()`, or any other UI container that accepts head tags.
- The package is intentionally small and generic.
- If you need a simpler SEO-only configuration, you can still use the required four fields and leave the rest blank.
