C.W.K.
Stream
Lesson 06 of 08 · published

Anchor 6 — The Viewport Context Payload Shape

~11 min · payload, schema, context, case-study

Level 0Extension Curious
0 XP0/54 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"Lesson 6 zooms into the JSON object that flows through every part of ChromeEmbed: the host-context payload. Every field justified, every field a contract the panel iframe relies on."

The Full Shape

The content script's extractContext() returns:

{
  type: 'pippa:host-context',
  payload: {
    host_kind: 'web-page',
    snapshot_at: '2026-05-16T01:23:45.000Z',
    host_id: chrome.runtime.id,
    source: location.href,
    human_label: document.title || location.hostname,
    favicon: faviconHref(),
    selection,
    viewportText,
    readableText: viewportText, // compat alias
    wordCount,
    viewport: { width, height, scrollX, scrollY, devicePixelRatio },
    scrollPosition: { y: scrollY, ofPageHeight: pageHeight },
    language: document.documentElement?.lang || navigator.language || '',
    frameUrl: location.href,
    isTopFrame: window.top === window
  }
}

The Identity Fields

  • host_kind — always 'web-page' for ChromeEmbed. The PIPPA-EMBEDS framework expects this; Adobe-embed would be 'adobe-app', Calendar would be 'gcal-event', etc. Discriminator for downstream rendering.
  • host_id — the extension's runtime id. Lets cwkPippa distinguish messages from different installed instances (multi-Chrome-profile setups).
  • source — the canonical URL the user is on. cwkPippa surfaces this as 'capturing from example.com/article'.
  • human_label — fallback-chained title. Used as the chat thread label.
  • favicon — for UI affordance.

The Content Fields

  • selection — current highlighted text (or cached selection from up to 5 minutes ago).
  • viewportText — the text inside the user's current viewport, walker-extracted and spatially sorted (Lesson 3).
  • readableText — alias for viewportText. Older cwkPippa code paths read 'readableText'; the alias means ChromeEmbed doesn't break them. Plan: deprecate when cwkPippa migrates fully to 'viewportText'.
  • wordCount — quick metric for cwkPippa UI ('1,247 words in view').

The Viewport Fields

  • viewport: { width, height, scrollX, scrollY, devicePixelRatio } — physical-pixel measurements of the user's window. Lets cwkPippa adapt UI to small viewports or hi-DPI displays.
  • scrollPosition: { y, ofPageHeight } — where the user is in the page vertically, plus total page height. cwkPippa can show 'you are 60% through this article.'

The Metadata

  • snapshot_at — ISO timestamp. Lets cwkPippa display 'context from 5 seconds ago.'
  • language — useful for cwkPippa to format responses in matching locale.
  • frameUrl, isTopFrame — for the merge logic in the SW (Lesson 2). Sub-frame pushes carry these so the SW can decide whether to override the top-frame snapshot.

Why a Schema, Not 'Just Send the DOM'

The discipline of a fixed payload shape pays back when:

  • Other embeds compose. The PIPPA-EMBEDS framework expects every embed (Chrome, Adobe, Mail, Calendar) to produce host-context with the same field names. cwkPippa renders them uniformly.
  • Versioning. If you ever need to bump payload v1 → v2, you can add a version: 1 field today and check it tomorrow.
  • Compatibility. The readableText alias is exactly the kind of migration aid a fixed schema enables. Without a schema, every consumer would have to guess.
  • Observability. When cwkPippa's logs show a context payload, you can read it like a structured record instead of guessing what fields exist.

What's NOT in the Payload

Deliberately absent:

  • Full document HTML — way too large and loses the viewport semantics.
  • Screenshot — costs storage, screenshot crops can be a future field.
  • Tab history — outside ChromeEmbed's scope; cwkPippa already has its own conversation history.
  • User identity — Pippa knows who you are from the cwkPippa session, not from extension context.

Adding fields is cheap when justified by a feature; adding them speculatively makes the schema noisy and the bridge slower.

One payload shape, every field justified, every field a contract. The schema is what makes ChromeEmbed composable with other embeds; without it, each embed reinvents 'context' and cwkPippa has to special-case all of them.
The PIPPA-EMBEDS framework angle. ChromeEmbed v0.1 is the first PIPPA embed. The host-context schema it ships is the proposed contract for future embeds (Adobe, Mail, Calendar, IDE). cwkPippa renders them uniformly; the embed's only job is to populate the schema fields from whatever its native host provides. That's the win — each new embed is a content-script-and-bus shape with one payload format, not a from-scratch UI.

Code

content-script.js — the schema, exactly as the prototype writes it·javascript
// embeds/chrome/content-script.js — extractContext shaped output
function extractContext() {
  const viewportText = collectViewportText();
  const selection = currentSelectionText();
  const pageHeight = Math.max(
    document.documentElement?.scrollHeight || 0,
    document.body?.scrollHeight || 0,
    window.innerHeight || 0
  );
  return {
    type: 'pippa:host-context',
    payload: {
      host_kind: 'web-page',
      snapshot_at: new Date().toISOString(),
      host_id: chrome.runtime.id,
      source: location.href,
      human_label: document.title || location.hostname,
      favicon: faviconHref(),
      selection,
      viewportText,
      readableText: viewportText, // back-compat alias
      wordCount: viewportText ? viewportText.split(/\s+/).filter(Boolean).length : 0,
      viewport: {
        width: Math.round(window.innerWidth || 0),
        height: Math.round(window.innerHeight || 0),
        scrollX: Math.round(window.scrollX || 0),
        scrollY: Math.round(window.scrollY || 0),
        devicePixelRatio: Number((window.devicePixelRatio || 1).toFixed(2))
      },
      scrollPosition: { y: window.scrollY || 0, ofPageHeight: pageHeight },
      language: document.documentElement?.lang || navigator.language || '',
      frameUrl: location.href,
      isTopFrame: window.top === window
    }
  };
}

Exercise

Look at the payload shape and label which fields each Track contributed to the design: host_kind (the embed framework, Track 9), source/human_label/favicon (chrome.tabs, Tracks 2 + 4), selection/viewportText (Track 3 + Track 7 Lesson 3), viewport/scrollPosition (Track 7 Lesson 3's rect math), snapshot_at (Track 5's timestamps), frameUrl/isTopFrame (Track 3's all_frames). Notice every field traces back to a concrete reason — there's no 'maybe useful' speculative field. The discipline is what makes this a contract the panel can rely on.
Hint
If you want to add a field — say documentDescription from the meta tag — pause and ask: which feature in cwkPippa would consume it? If no consumer is queued, defer. Adding fields is cheap up front but expensive forever (every later embed has to populate them too, or cwkPippa has to handle missing values). Start with the smallest schema that supports the first feature, add fields as features land.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.