Share to Facebook (MVP) — Realtor + Church Dashboards Expected Output Spec
Status: Approved — 2026-10-09. John's words reviewing this draft: "lets get a team and build this." Build against this spec; if code needs to diverge, update the spec first (CLAUDE.md Rule #17 / the principle below), then the code.
Origin: Team Beckett (Sheri), 2026-09-23: "if I post something on our teambeckett facebook page it would automatically post to our teambeckett page? I'm thinking about my videos and daily post for listings etc." Founder chose option 3 (post once from the dashboard → website AND Facebook) over embedding a Facebook feed (not SEO-readable, loads Facebook trackers) or pulling from Facebook (poor video portability, read-access review).
0. Delivery route — direct Meta, not GoHighLevel (supersedes the original draft's §0)
The original draft of this spec (2026-09-23) routed posting through GoHighLevel Social Planner because our own Meta app was not live and the founder had not yet chosen to go through App Review. That has changed, verified today (2026-10-09):
- The Sharewise AI Meta app (id
1563118691444775) was approved 2026-10-05 withpages_show_list,pages_manage_posts,pages_read_engagement, andbusiness_management, and switched to Live 2026-10-06. - The Facebook Login for Business config
1788875648813486is the connect flow customers go through. - Direct Meta posting is THE route for this MVP — not a phase-2 composer layered on a GHL RSS feed. The GHL-routed Phase 1/Phase 2 design in the original draft is retired; do not build it.
What this means in code (verified in the repo, not re-designed here):
- The customer connects their own Facebook Page through our app. The
connect flow already exists and already requires an explicit pick — it does
not auto-select the first Page (
social-oauth.ts: "Facebook connects in TWO steps so the customer CHOOSES which Page we post to (Meta App Review requirement + product rule — we never auto-pick the first Page)";social-facebook-pick.tsholds the short-lived pre-pick state). This spec's job is to wire that existing connect flow to two more owner types (§3), not to build Page picking from scratch. - The Page token is encrypted at rest (
social-crypto.ts, AES-256-GCM) insocial_accounts, never sent to the browser, never logged. - Posting goes through the existing
publishToFacebookinsocial-publisher.ts. This spec's job is to call it from two new surfaces (§2), not to build a second Facebook posting path. - Runbook pointer:
knowledge/runbooks/meta-app-review.md.
Tech Provider verification is still "in review" at Meta (deadline 2026-12-05). This does not block building against this spec. If Meta restricts the app before or after that date, posting fails honestly, per §6.4 — "Not posted — {reason} · Reconnect." Nothing else in this spec changes on that outcome; it is a failure mode already designed for, not a new risk this feature introduces.
1. Why — and why TWO dashboards, one implementation
The agent's or church's content should live on their own website first, where it builds lasting Google presence, and go to Facebook from there in the same action — the opposite of a Facebook-first workflow where the website gets nothing lasting.
Scope widens from the original realtor-only draft to cover two dashboards
because realtor listing/content posts and church Blog/News posts already
share one table. re_content_items was made owner-agnostic in migration
20260728_church_content_items.sql (spec church-blog-mvp.md §2): every row
has exactly one of business_id (realtor) or church_id (church) via a
CHECK (num_nonnulls(business_id, church_id) = 1) constraint. The comment at
the top of church-content/posts.ts is explicit about the design intent this
spec extends:
"SHARED STORE, SEPARATE ACCESS LAYER... What is deliberately NOT shared is this file. The realtor equivalent is reached through realtor RBAC and its routes serve a live paying tenant; generalizing it to also serve churches would put a church bug in front of a realtor."
This spec follows that same discipline: one Facebook-posting implementation
(publishToFacebook, one new social_accounts owner type, one receipt
shape), reached through two separate, vertical-scoped access layers and two
separate dashboard surfaces. Nothing in realtor RBAC becomes reachable from
church admin or vice versa.
| Piece | State verified 2026-10-09 |
|---|---|
| Listings (incl. coming-soon), photos, video tour | Live on the realtor website (/listing/[id]) |
Content Studio social_post type | Exists (realtor-content-studio-mvp.md); "Mark ready" only — no public page, no channel, no posting |
| Church Blog/News posts | Exists (church-blog-mvp.md); type pinned to 'blog', published to /s/[slug]/blog equivalent church surface — no Facebook posting |
| Listing Kit Facebook caption | Generated (listing-kit.ts), copy/paste only |
| Facebook Page posting function | publishToFacebook (social-publisher.ts): text, link, 1+ photos to a Page via Page token. No native video upload. |
| Who can own a Facebook connection | social_accounts owner union is user / church / property (social-auth.ts: SocialOwner) — no business (realtor) owner today |
| Page selection | Explicit picker, never auto-pick (social-oauth.ts, social-facebook-pick.ts) — already built |
| Meta app review status | Approved 2026-10-05, Live 2026-10-06 (Sharewise AI app 1563118691444775); Tech Provider verification in review, deadline 2026-12-05 |
2. Scope
In (MVP):
2.1 Realtor dashboard (/realtor/app/*, Beckett first)
- Connect Facebook Page — Integrations (
/realtor/app/integrations): "Connect Facebook Page" → Meta login → pick which Page (list every Page the person manages; never auto-pick — already built, §0) → connected Page name- profile picture shown, with Disconnect.
- Share a listing — each listing card in Listings: "Share to Facebook". Pre-fills a post: Listing Kit Facebook caption (or a short default), link to the listing's page on HER site, first photo. She edits, then presses Post.
- Share a video — a listing with a video tour: the same "Share to Facebook," posted as a link to her listing page (whose Video tour plays the video). We do NOT upload video files to Facebook in the MVP.
- Content Studio
social_post— add a Facebook channel toggle. "Post to Facebook" publishes the text (+ optional photo, + optional link). The same editor offers "Also show on my website," which publishes it as a short item on her site's blog/updates surface — so "post once, appears on both" is true for daily posts too. - Post history — each shared item shows "Posted to Facebook · {date} · View post" (the real Facebook permalink), or the failure reason in plain words.
2.2 Church admin dashboard (/admin/[token])
- Connect Facebook Page — an Integrations "Connect Facebook Page"
card (new; no Integrations surface for this exists in church admin today),
same connect/pick/disconnect behavior as 2.1.1, scoped to the church's own
social_accountsrow (owner typechurch, which already exists). - Share a Blog/News post — on each
church-content/posts.tspost: "Share to Facebook", same composer pattern as 2.1.2 (caption, link to the post on the church's site, first image if present), Post, post history per 2.1.5.
Out (explicit non-goals for the MVP, both dashboards): automatic/scheduled posting (every post is pressed by a person); pulling posts FROM Facebook; Instagram, LinkedIn, TikTok, YouTube publishing (phase 2); native Facebook video upload; Facebook Reels/Stories; comment/like syncing; paid boosting; multi-Page cross-posting in one click; Facebook analytics beyond "clicks back to your site"; local-business (non-realtor, non-church) dashboards — no post surface there in this MVP.
3. Data shape
social_accountsgains a 4th owner type,business(the realtor account'slocal_businesses.id, reached viarealtor_memberships.account_idwhich already FKs tolocal_businesses), plus the chosen Page's id/name. Requires a migration — confirm-before-prod-DDL, founder approval. The migration is written in code now (additiveALTER TABLE ... ADD COLUMN business_id, owner-unionCHECKconstraint alongside the existinguser_id/church_id/property_idcolumns, same pattern as there_content_itemsowner-XOR migration) and applied only on John's yes (§8). Grep every caller ofSocialOwner/ownerFilter/ownerInsertFieldsinsocial-auth.tsbefore the ALTER (hard guardrail — enumerate by value). The church owner type (church) is already live insocial_accounts; no migration needed for the church side of this feature.- Post receipts:
- Realtor daily posts →
re_content_items.metadata.facebook = { page_id, post_id, permalink, posted_at, posted_by, error? }. - Realtor listing shares →
local_business_listings.metadata.facebook_posts[](same shape). - Church Blog/News shares →
re_content_items.metadata.facebook = { ... }(same shape — the column already exists on everyre_content_itemsrow regardless of owner). - No new tables.
- Realtor daily posts →
- Tokens stay encrypted (
social-crypto.ts), are never sent to the browser, and are never logged.
4. Who can do it (RBAC)
Realtor side:
- Connect / disconnect a Page:
integrations:crm:edit(owner / broker-admin). - Post:
content:item:createfor daily posts;listings:manual:editfor listing shares. - Every post records
posted_by(the signed-in member). The API derives the account fromresolveRealtorContext(?account_id=required, as on every realtor route); never from the client alone.
Church side:
- Connect / disconnect a Page: admin-dashboard team roles with existing settings/integrations edit rights (church admin has no capability-grant system as granular as realtor RBAC — gate at the same level the rest of church admin Settings is gated today).
- Post: the same role(s) that can already create/edit a Blog/News post in
church-content/posts.ts's access layer. No new role is introduced. - Every post records
posted_by(the signed-in admin user).
5. Compliance guardrails
5.1 Realtor (Ontario REALTOR® advertising — unchanged from the original draft)
- Brokerage name on every post. TRESA/RECO advertising rules require the brokerage name to be clear in a registrant's ads. The composer appends the brokerage line from the account's compliance identity and blocks posting if it's missing.
- Coming-soon listings: before posting, show the board's exclusive/coming-soon marketing reminder that already exists in the listing form, and require a tick. Never label a coming-soon post "MLS®."
- MLS® feed listings she doesn't own: "Share to Facebook" is shown only
on her own listings (manual, or feed rows where
isOwn); never on other brokerages' feed listings. - Fair-housing scrub: run the Listing Kit's existing fair-housing check on the caption; show the result as text, not colour.
- Photos: only her own uploaded or own-listing photos are attachable. MLS-watermarked images of other brokerages' listings are never attachable.
5.2 Church (new — no equivalent regulatory regime, so no equivalent gate list)
- Church name and site link on every post. The composer appends the church's name and a link back to the post on the church's own site. No brokerage-style blocking rule is needed because there is no regulator analogous to TRESA/RECO for church social posts.
- No other guardrails. There is no "coming soon" concept, no MLS feed, no fair-housing scrub applicable to church content. Do not invent gates the church vertical does not need; the realtor guardrails in §5.1 are specific to real-estate advertising law and must not be copied onto church posts.
6. Expected output (what the person sees)
6.1 Connect (both dashboards)
Should see: Integrations → "Facebook Page — Not connected · Connect".
After Meta login, a list of every Page the person manages with names +
pictures; they pick the right one; the card then reads "Connected: {Page
name} · Disconnect."
Should NOT see: a personal profile offered as a posting target; a silent
first-Page auto-pick; a "connected" state when the token or the page
permission is missing.
Success: the chosen Page is stored; a test "Can we post here?" check
(page token valid + pages_manage_posts granted) passes, shown as a plain
yes/no.
6.2 Realtor: share a listing (incl. coming-soon + video)
Should see: "Share to Facebook" on each of her own listings → a composer
with the caption, the link to teambeckett.ca/listing/{id}, the first
photo, the brokerage line, and a preview card → Post → "Posted to
Facebook · just now · View post."
Should NOT see: anything posted without her pressing Post; a native
video upload; a link to churchwiseai.com or a *.vercel.app preview host
(links always use her live site origin); "Share" on other brokerages'
listings.
Success: the post appears on her Facebook Page with the link card
pointing at her listing page, and the listing's Video tour plays there.
6.3 Realtor: daily post (Content Studio social_post)
Should see: Content Studio → New → Social post → text (+photo, +optional link) → toggles "Facebook" and "Also show on my website" → Post. If the website toggle is on, the update appears on her site's blog/updates list with its own page. Should NOT see: "Sent"/"Published" for a post that failed; a "Mark ready" state pretending to be posted; the website toggle silently creating a page when off. Success: one action → the Facebook post (receipt stored) + (if chosen) the website update page, both reachable.
6.4 Failures (honest — both dashboards)
Token expired / permission removed / Page deleted / Facebook error → the item shows "Not posted — {reason in plain words} · Reconnect" and nothing claims success. A partial success (website yes, Facebook no) is shown as exactly that. This is also the behavior if Meta's Tech Provider verification (§0) results in the app being restricted — the failure reason shown is Facebook's own error text, not a generic one.
6.5 Church: share a Blog/News post
Should see: on each existing Blog/News post in /admin/[token]:
"Share to Facebook" → a composer with the caption (seeded from the post's
title/excerpt), the link to the post on the church's own site, the first
hero image if one exists, a preview card → Post → "Posted to Facebook ·
just now · View post."
Should NOT see: anything posted without the admin pressing Post; a
realtor-only compliance gate (brokerage line, coming-soon tick, fair-housing
scrub — none apply, §5.2) appearing on a church post; a link to
churchwiseai.com or a preview host instead of the church's own site origin.
Success: the post appears on the church's Facebook Page with the link
card pointing at the Blog/News post on the church's own site.
7. Metrics (honest only)
"Clicks from Facebook to your site," counted from our own first-party
tracking via a utm_source=facebook tag on shared links. No reach,
impressions, or "engagement" numbers we can't verify. Applies identically to
both dashboards.
8. Decisions (Chief of Staff, pending John's objection)
- Website toggle default: OFF. Nothing appears publicly by surprise — applies to the realtor Content Studio "Also show on my website" toggle.
- Instagram: out of this MVP, phase 2. ShareWiseAI already requests
instagram_content_publish; not wired here. - Pricing: included, not an upsell. Share to Facebook is included in every plan that includes the Pro Website (ChurchWiseAI Website / Complete plans; the realtor website tier) — no separate charge, no add-on line.
- The
social_accounts.business_idmigration is written in code in this PR and applied only on John's yes — it is a schema change (confirm-before-prod-DDL, Rule #18/#22-adjacent) and must not run automatically just because the PR merges.
These are standing decisions unless John says otherwise when he reviews the built feature; if he objects to any one of them, that item is revisited before the affected part ships, not after.
9. Tests (write BEFORE code; Playwright on the deployed URL + unit)
// Shared (both dashboards)
test.fixme('Integrations: connecting Facebook lists ALL managed Pages and requires an explicit pick (never auto-picks the first)', () => {});
test.fixme('nothing is posted without an explicit Post click; no cron/scheduled path exists (contract test greps for callers of publishToFacebook)', () => {});
test.fixme('Facebook failure → item shows "Not posted — reason", receipt has error, no success label anywhere', () => {});
test.fixme('tokens are never returned by any dashboard API response (grep response shapes)', () => {});
// Realtor
test.fixme('Share to Facebook is shown only on own listings (manual or isOwn feed rows), never other brokerages', () => {});
test.fixme('realtor composer always includes the brokerage line; Post is blocked without it', () => {});
test.fixme('coming-soon listing share requires the exclusive-marketing reminder tick', () => {});
test.fixme('shared link uses the tenant live origin (teambeckett.ca), never churchwiseai.com or *.vercel.app, and carries utm_source=facebook', () => {});
test.fixme('daily post with website toggle ON creates a website update page; OFF creates none', () => {});
test.fixme('RBAC: a realtor member without integrations:crm:edit cannot connect/disconnect; without content:item:create cannot post', () => {});
test.fixme('social_accounts business owner rows are reachable only through realtor_memberships.account_id scoping, never cross-tenant', () => {});
// Church (new, 2 minimum per task scope)
test.fixme('church admin Share to Facebook composer never shows the realtor brokerage-line gate, coming-soon tick, or fair-housing scrub', () => {});
test.fixme('RBAC: a church admin role without Blog/News edit rights cannot connect/disconnect Facebook or post', () => {});
Acceptance checks run on the deployed preview with the founder's TEST Page
(the ChurchWiseAI Ltd Page already connected in social_accounts) — never
Team Beckett's live Page, and never a real church's live Page, until founder
go.
Verification: Playwright on the deployed preview AND on the real host
(teambeckett.ca link targets for realtor; the church's own custom domain or
churchwiseai.com-hosted slug for church admin).
10. Open questions carried forward (not blocking §8's defaults)
- Migration timing — when John approves, does the
business_idALTER ship same-PR (code + migration together, migration run manually after merge) or in a follow-up PR? Default: same PR, migration applied separately per §8.4. - Church Integrations placement — the church admin dashboard has no existing "Integrations" tab the way realtor does; this spec assumes one is added to Settings (or a new tab) rather than redesigning church admin navigation. Confirm placement against the live dashboard before building the UI shell.
End of spec. STATUS: Approved — 2026-10-09.