Church Links
Phone-style church link page with Church Center built in, for WordPress
Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/comms-church/comms-church-links/archive/refs/heads/main.zipDeveloper notes. The WordPress.org-style description is in readme.txt.
WordPress plugin from comms.church (Strategic Media). It gives a church one link page (link-in-bio, bulletin QR code) that sets itself up from the church's own site and stays current: whatever is featured in Planning Center leads the page, and latest-post links follow the newest sermon, without anyone editing them. Church Center is built in. It shows as a link list (default) or a phone home screen. The goal is a generic plugin any church can install and set up in minutes. GCC Willow Street (GCCWS) is the first real install.
Renamed from Church Home Screen on 2026-10-26, when the function (scan, link tree, watch, QR) became the product and the phone UI became one display option.
Current version: 0.2.0. It was built and tested in a Claude cloud session on 2026-10-25 against WordPress 6.8+/7.2-beta on SQLite with PHP 8.3.
Layout
comms-church-links.php Bootstrap, constants (CHS_VERSION etc.), hooks
includes/icons.php 27-icon SVG library, chs_icon_svg(), chs_glyph_color()
includes/class-chs-settings.php Option schema + defaults, sanitize(), admin page, scan review screen, welcome notice
includes/class-chs-events.php Events from Planning Center (Church Center events), an iCal feed (own RRULE parser) or The Events Calendar; 1h transient cache
includes/class-chs-frontend.php Rewrite route (/links by default), standalone render, [church_links] shortcode (iframe; [church_home_screen] still works), build() resolves settings into apps
includes/class-chs-watch.php Latest-post links: newest post of an archive on this site (live)
includes/class-chs-scanner.php "Auto-fill from your site": scan() returns findings, apply() saves only checked ones
includes/class-chs-updater.php GitHub Releases updater (admin only), modeled on the groups plugin's
templates/list.php Link-list layout (default): full HTML document, not the theme
templates/screen.php Phone layout: lock screen, app grid, dock
templates/parts/ Visit, Events and Contact panels, shared by both layouts
assets/list.css, list.js Link list: next service, panels that open in place, Church Center modal, copy buttons
assets/screen.css, screen.js Phone: lock/unlock gesture, app panels, Church Center modal, copy buttons
assets/admin.css, admin.js Settings page: sortable link repeater, media picker, QR code (SVG/PNG download)
assets/vendor/qrcode.js qrcode-generator 1.4.4 by Kazuhiko Arase (MIT)
uninstall.php, readme.txt
tests/ Playwright smoke suite + Playground blueprint (port 9405)
.github/workflows/release.yml Tag-gated build: header Version and CHS_VERSION must equal the tag
Key decisions
- All settings live in one option,
chs_settings. Everything passes throughCHS_Settings::sanitize(), including scanner applies (they go throughupdate_option). - The page renders standalone through a rewrite rule with query var
chs_screen. It doesn't load the theme, so theme CSS can't break it. Changing the path setschs_flush_rewrite. - App types:
sheet(in-page panel: visit/events/contact),cc(Church Center path),app(Church Center app store link),link,watch. Apps with no URL, and Church Center apps without a Church Center subdomain, are hidden, never shown broken. - Church Center:
- Phones get plain churchcenter.com links, which open the Church Center app through iOS Universal Links and Android App Links.
- Desktop (min-width 721px plus hover) adds
data-open-in-church-center-modal="true"and loadshttps://js.churchcenter.com/modal/v1. This is optional via thecc_modalsetting. - Store links: App Store id1357742931, Play
com.ministrycentered.churchcenter.
- Events sources (2026-10-26: the Planning Center one replaced "no API keys"):
- Planning Center (
pco): only events withvisible_in_church_centerandapproval_statusA, i.e. what the church's Church Center calendar shows. A church's iCal "subscribe" link is its whole calendar (staff meetings, home addresses), and Church Center publishes no feed of its own; its/~ccapi/endpoint 401s from a server. Fields were checked against GCC's live account:Event.visible_in_church_centeris queryable;EventInstancecarriesstarts_at,all_day_event,location,church_center_url. - One request for the visible events, then one per event for its next 10 dates (instances can't be filtered by event). GCC: 6 visible of 4,136 events → 7 requests, under 1 s. Capped at 40 events.
- Location is "Name - full address" in Planning Center; only the name is shown, like Church Center. A bare street address is dropped.
- Key: the one entered in settings, else
ccpco_app_id/ccpco_secretfrom comms-church-pco (when active), else pco-tec'scctec_*pair. The secret is never printed back into the form. Saving fetches once so the settings page can say whether the key works (chs_events_status). - iCal feed and The Events Calendar (pairs with comms-church-pco-tec) are still there.
- Planning Center (
- Scanner:
- Fetches the homepage plus up to 5 info pages with
wp_remote_getas a logged-out visitor, so page-builder (Bricks) output is read. Falls back to post content + post meta when the site can't fetch itself. - Also reads JSON-LD (Yoast/Rank Math
@graph), tel:/mailto: links, nav menus, pages, sermon CPT archives, and palettes (theme.json, Kadencekadence_global_palette, Bricksbricks_color_palette). Bricks palettes come in two shapes: older ones storehex, while variable-based ones (our design system) storeraw: var(--…)pluslight/dark. Both are read. - Address: page text runs blocks together, so a footer like "Sun 11 AM" + "ADDRESS 212 Peach Bottom Road" (GCCWS) used to match from the 11. The street now starts at the last house number when a time or an all-caps heading comes before it.
- Church name comes from JSON-LD, then from the homepage's logo/site-title link back home (text or image alt), then
og:site_name. On Bricks builds the header logo often shows a different name than the WordPress site title. - Service-time parsing is the fragile part. It handles "8:30, 10 & 11:30 AM", "9AM + 11AM", "a.m.", and per-time labels ("Sunday School 9:15; Worship 10:30"), and it ignores "Romans 8:28" and "since 1952". A name written after the time ("9:00 AM · Classic gathering") replaces a generic "Worship" but not a specific name written before the time ("Sunday School 9:15"). Page text runs lines together, so a name after a time only counts when the next time follows the same "time · name" pattern; otherwise "11 AM | Bible study 10 AM" would name the wrong service.
- Main weekend services come pre-checked; everything else starts unchecked.
- Fetches the homepage plus up to 5 info pages with
- Featured (added 2026-10-27, replacing bulletin-page watching): the point of /links is that what a church announces (stage, bulletin, social) is there when people open it. The church already marks those in Planning Center, so the page reads them: Calendar events with
featured+visible_in_church_center+ approved (approval_statusA). Sign-ups inherit Featured from Registrations, so featured registrations come through Calendar too.CHS_Events::featured():/events?where[featured]=true&where[visible_in_church_center]=true&filter=futurewithX-PCO-API-Version: 2022-07-07(wherefeaturedis documented), then the next 3 instances of each. The flags are re-checked in PHP in case a filter is ever ignored. Links go toregistration_urlwhen set, else the instance's Church Center page. Cached 15 minutes (chs_featured_cache), 10 on failure; status inchs_featured_status. Up to 6 shown, soonest first, at the top of the list layout above the next service. The "Coming up" card skips an event that's already featured.- Uses the same key as the PCO event source (own key, or borrowed from Comms.Church PCO / PCO Events Sync), independent of which event source is picked.
featured_ondefaults on, so it lights up as soon as a key exists. - Not in the phone layout yet.
- Goes live on its own, no approval queue (Addison's call). No Asana integration: GCC's comms plan lives in Asana, but what visitors need from it is a registration or event, which is in Planning Center already.
- Latest-post links (
watchtype, kept from the 0.3.0 dev build): a post type archive or the blog page on this site opens its newest published post, queried on every view; "New" for 7 days (a red badge in the phone layout). The "Only posts containing" words become a search filter. A non-archive URL just links to that page. The page-reading watcher (bulletin pages,chs_watchoption,chs_watch_dailycron) was dropped after the GCCWS staging trial: it guessed at links, while Featured is what the church already curates.CHS_Watch::init()deletes their leftovers. - First visit to Settings → Church Links auto-scans once (
chs_autoscanned) whenchs_settingsdoesn't exist yet. Activation setschs_welcomefor the admin notice. - QR code: drawn in the browser on the settings page from the live URL (so it follows path changes), downloadable as SVG or a 1200px PNG. Error correction M.
- Phone layout design: dawn wallpaper SVG with an optional steeple. Fonts are Bricolage Grotesque (display) and Atkinson Hyperlegible (body); turn Google Fonts off with the
chs_use_google_fontsfilter. Inside the phone frame at desktop sizes, full-bleed on phones (≤520px), where the fake status bar and home bar are hidden. - WordPress coding standards: tabs,
chs_/CHS_prefixes, escape on output, text domaincomms-church-links. Thechs_option names andCHS_classes kept their original names through both renames. Because the folder changed, a site with Church Home Screen installed sees Church Links as a separate plugin: delete the old one first (its uninstall clearschs_settings), then install this and let it scan. - Brought into the suite 2026-10-26 as
comms-church-home-screen, with the updater,release.ymland smoke suite; renamed tocomms-church-linksthe same day. Repo:Comms-Church/comms-church-links(public, so the updater can read releases; the old repo name redirects).
Tested
-
iCal parser: weekly BYDAY, monthly "1SU" with COUNT, EXDATE, RECURRENCE-ID overrides, CANCELLED, all-day, UTC vs TZID, the November DST change.
-
Scanner against a seeded sample church site: 16/16 correct suggestions after two fixes.
-
Full admin flow: first-visit scan → review → apply → live page. Settings save, adding apps, changing the path (old path 404s, new one 200s), sanitizing junk input.
-
Front end at 1280px desktop and 390px iPhone: swipe unlock, panels, Church Center modal attribute, the iOS store link swap.
-
Real Bricks build (testchurch.localhost, 2026-10-26): the first scan missed the church name (no JSON-LD; the header logo said otherwise), the service names (written after the times) and the brand color (variable-based Bricks palette). All three are fixed and covered by the smoke suite's blueprint fixtures.
-
Planning Center source against GCC's live account on testchurch (key from pco-tec): exactly the six Church Center events, no street addresses, every event linked to its Church Center page. The smoke suite fakes the API in Playground (
chs-test-pco.php): bad key → error on the settings page; hidden, unapproved and staff events never shown.
Not tested: the live Church Center modal script, Google Fonts, a real Planning Center iCal feed in the parser beyond one fetch, multisite.
Next steps
- Try it on GCCWS staging, then cut the first release with the suite's
cut-a-releaseskill. - Make a comms.church landing page (
page-<slug>.phpin the theme repo plus a card inpage-resources.php). - Ideas not built yet: simple tap analytics per link, more wallpapers, Spanish strings, and a WordPress.org listing once it's generic enough.
Testing locally
To re-run the first-visit scan: wp option delete chs_settings chs_autoscanned.
Smoke suite (green, or no release):
npm install
npx playwright test --project=smoke
It covers activation (deactivate + reactivate through the UI), the first-visit auto-scan review and "Use selected", saving settings, and /links rendering and unlocking with a swipe. The blueprint adds a test-only mu-plugin (?chs_test_reset=1) that puts the plugin back in its just-activated state, so the suite passes on a reused server and never depends on a retry.