Workshop Kit · widget reference
Shippedgbp-card-preview
Live mock of how the operator's Google Business Profile will render in search results — built from whatever MuntinContext has so far. Captures the one GBP-specific field not collected by any other lesson (the primary category, from a 27-item restaurant taxonomy plus an "Other" escape hatch), then renders a card with name, category, today's open/closed status from hours, address, phone, description, photo-count badge, accent color from palette, and a readiness checklist that flags missing fields.
Live demo
Pick a primary category. The card preview updates immediately; the readiness checklist below shows which other fields are still needed. Open the bootcamp lessons (L4 → L7 → L8 → L10) to fill out the other fields and watch the card complete itself.
No config block — primary-category list baked in
Contract
| Behavior | Notes |
|---|---|
Reads restaurantProfile.{name,cuisine,address,phone} |
Name + address + phone shown in the card. Cuisine isn't shown directly (category overrides it for GBP rendering). |
Reads hours |
Computes today's open/closed status with a colored dot + label ("Open · 11:00–22:00" / "Closed today" / "Hours not set"). |
Reads palette[0] |
Tints the photo placeholder + the CTA pill. Defaults to teal. |
Reads shotList.length |
Shows a "N photos ready" badge. Readiness goes from missing (0) to partial (1-2) to ready (3+). |
Reads gbpDescription |
Renders below the address, line-clamped to 3 lines (matches the Google search-result truncation). |
Reads gbp.primaryCategory |
Pre-fills the select on remount. |
Writes gbp.primaryCategory |
The id of the selected category, or the operator's free-text when "Other" is chosen. |
| Live re-render | Subscribes to mtn:context-change; any other widget on the page (or the cross-device sync arriving) re-renders the card without remounting. |
Markup
No config block. The widget reads what it needs and writes only gbp.primaryCategory.
Accessibility
- Primary-category
<select>is labeled. "Other" reveals a follow-up text input with its own aria-label. - The card preview is rendered as semantic HTML (
<article>+ headings + paragraphs), so screen readers can navigate sections directly rather than parsing visual layout. - Open/closed status uses a colored dot AND a textual label ("Open · 11:00–22:00"), so high-contrast modes don't lose meaning.
- Readiness rows are
<li>s with status badges as text ("Ready" / "Missing" / "Partial") — colors reinforce, not replace, the label. - Selecting a category announces "Primary category set to …" via a polite live region.
Why a preview, not a form
The operator already filled out most of these fields elsewhere (name in L1/L5a, address + phone in L10, hours in L10, description in L11a, photos in L9a/b). Asking them to fill the same fields a second time inside L11 is the most common reason operators bounce. The preview reuses the data instead: it shows them how the same context renders in Google's specific surface, points out what's still missing (with deep links back to where it gets filled), and asks them only the question Google asks that nobody else does — which primary category.
Where it ships
- L11a — GBP from zero (preview before writing the description)m3-assemble/fresh/gbp
- L11b — GBP repair (preview after the top-3 fixes are named)m3-assemble/rebuild/gbp
The standalone GBP Grader tool grades an actual live profile by URL; this widget previews what the listing should look like before it goes live.
Source
/tools/_shared/workshop/gbp-card-preview.js — ~290 LOC. Exports { tag, contextKeys, mount, serialize }. The 27-entry category taxonomy lives at the top of the file in bilingual EN/ES tables; todayStatus() computes the open/closed pill from hours; readContext() reads MuntinContext fresh on every re-render so external commits (palette changes, new photos selected, etc.) flow through without a remount.