Admin Help & FAQ
Everything admins need to keep the site running. Use the tabs to filter by category, the alphabet rail to jump by starting letter, or the search box to find a topic.
For admins presenting to their church
📊 Seed the Word Ministry Presentation
A 10-slide deck built to take in front of your pastor and elders. Each slide has a "Copy slide" button so you can paste it straight into Google Slides as a new slide. Open the deck, walk through it with the team, then send the Slides link to leadership for review.
Quick routing
"Something needs updating" — where do I go?
📅 An event
Add or edit it in Google Calendar. Site updates within 1 minute.
🖼️ A photo or video
Open the ✏️ Editor tab and pick the matching image folder or the Videos entry. Direct GitHub uploads to assets/images/ still work as a fallback.
🎧 Spotify / YouTube / link
Open the ✏️ Editor tab, pick Recommendations. Fill the form, preview in place, commit. The legacy copy-paste flow is retired.
📖 Verse of the Day
Open the ✏️ Editor tab, pick Daily verses rotation. Add an entry and commit.
📣 Telegram bot schedule / quiet hours
Open the ✏️ Editor tab, pick Telegram bot config. Structured form for all three bots — announcements, Bible, prayer.
🧯 Something is broken
Jump to the Troubleshooting section below — hard-refresh first, then diagnose by symptom.
Site map
🗺️ What's where — page by page
A quick reference for which section on the live site is backed by what data file or folder. Sections marked (HTML only) are edited directly in the page's HTML — not yet admin-form-driven.
Homepage (index.html)
- Hero + "Who is Jesus" CTA — (HTML only)
- Featured Ministry Feed carousel — rotates live data from
daily-verses.json,ministry-outreach.json,recommendations.json,instagram.json(scraper), Google Calendar, plus the FALLBACK_SLIDES / DAILY_CONTENT literals insideassets/js/showcase-carousel.js(editable from Editor → Homepage carousel fallback / daily) - Walking the Path of Light — Plant / Water / Gather waypoints — (HTML only)
About (about.html)
- "Our S.E.E.D Story" — 9 stitch tiles. Images: Editor → Seed Stitch photos. Text: (HTML only).
- "Meet our team" — 5 cards. Team photos: Editor → Team headshots. Text: (HTML only).
- "How We S.E.E.D." — 5 practice cards. (HTML only)
- "Contact us - @seedtheword" — submissions go to Formspree.
Community (community.html)
- "This Week in God's Word" — reading plan powered by
bible-spotify-map.json(Editor → Bible → Spotify chapter map). - "Our Ministry Feed" — Spotify show (hardcoded ID) + Study Saturday livestream. Weekly review: Editor → Study Saturday.
- "Community Playlist" card — collaborative Spotify playlist. Listen/add songs from the page; config under Editor → Telegram bot config → Daily Bible bot → Playlist fields.
- "Friends in Jesus" — recommendations + partners (Editor → Recommendations).
- "Learn more about us online" 4-up platform row (Telegram / Spotify / Twitch / Instagram) — (HTML only).
- Telegram quick-start cards + Instagram grid — (HTML only).
News (news.html)
- Announcements + Ministry Initiatives — Google Calendar (add/edit events there).
- Ministry Outreach cards — Editor → Ministry outreach events + photo uploads to Editor → Outreach event photos.
- Share Your Story popup — Google Form embedded.
- Ministry Highlights (3-tile grid: Worship/Prayer, Encountering the Word, See More Behind the Scenes) — poster images live in
assets/images/ministry-highlights/, clips inassets/videos/(Editor → Ministry highlights + Videos). Tile labels are hardcoded innews.html. - Calendar (full monthly view sits at the bottom of the page) — Google Calendar.
Store (store.html)
- Bundle slideshows — Editor → Bundle Essentials / Life Group / Ministry. Photos: Editor → (matching bundle folder).
Background services
- Telegram announcements bot — Editor → Telegram bot config → Announcements bot. Runs every 15 minutes, posts upcoming / reminder / live events into the Announcements topic (thread 553).
- Telegram Bible bot — Editor → Telegram bot config → Daily Bible bot. Runs 08:00 PT Mon–Sat. Daily reading + optional Russian + Prayer & Thanksgiving block → General channel. Saturday posts a dedicated Study Saturday Live teaser.
- Telegram weekly playlist digest — Editor → Telegram bot config → Daily Bible bot → Playlist fields. Runs 08:00 PT Saturday. Posts new tracks added to the community playlist into the Worship & Music topic (thread 1064).
- Telegram prayer nudge bot — currently disabled; the Bible bot carries the Prayer & Thanksgiving block daily. Re-enable under Editor → Telegram bot config → Prayer nudge bot.
- Media drop button — Editor → Media-drop upload button config.
- Instagram scraper — runs every 6h via the Instagram scrape workflow.
- Keep-alive heartbeat — hourly workflow that writes a timestamp to
.github/heartbeat/last-tick.txt. Keeps the repo "active" in GitHub's eyes so scheduled workflows (bots) don't get throttled or paused on the free tier. Zero user-visible effect; skip-ci on its commits.
📋 Overview — what the site depends on
Your website is static (GitHub Pages) but pulls live data from a few services. If something on the site looks wrong, one of these is usually why.
Calendar & Events
Google Calendar
Events shown on news.html come from the ministry's Google Calendar (seedthewordministry@gmail.com). Add, edit, or delete events in Google Calendar and they appear on the site within a minute.
Dashboard: calendar.google.com
Instagram Feed
rss.app → GitHub Action → site
The site's Instagram feed (on community.html and the homepage Showcase) is fetched from a public RSS-style feed we configured at rss.app. Every 6 hours, a GitHub Action downloads new posts + images into the repo.
Feed source: rss.app
Run/check the sync: Repo → Actions tab → Scrape Instagram Feed
Livestream Status
Twitch
The "LIVE NOW / OFFLINE" card on community.html now checks live status in real time via decapi.me, a free community Twitch proxy that requires no credentials. Re-checks every 5 minutes. If decapi ever goes down, the card silently falls back to a schedule-based heuristic (Saturday 7-10 PM Pacific).
The weekly review block at the bottom of that card ("This Saturday's Review" — Old/New Testament passages) reads from assets/data/study-saturday.json. Edit it from Editor → Study Saturday.
Dashboard: dashboard.twitch.tv
Channel: twitch.tv/seedtheword
Podcast / Audio
Spotify for Podcasters
The Spotify embed on community.html pulls episodes from our Spotify show. New episodes appear automatically when published.
Dashboard: podcasters.spotify.com
Show page: Seed the Word on Spotify
Contact Form
Formspree
Form submissions on about.html land in the ministry Gmail inbox via Formspree. First submission each cycle may need an email confirmation click.
Dashboard: formspree.io/forms
Hosting
GitHub Pages
The site itself lives in a GitHub repo. Every commit to main auto-deploys in about 30 seconds. No server to manage.
Team Media Drop
Google Drive shared folder
Team members upload photos and videos through the encouragement call-out on the News page, which links to a shared Drive folder when assets/data/media-drop.json is configured. Admins curate from there. See Managing the team media drop below for setup.
Scheduling & Outreach
Meta Business Suite
Used to schedule Instagram posts, manage the IG inbox, and plan cross-platform content. Facebook Page not required for use beyond login.
Dashboard: business.facebook.com
Task & Reminder Board
Trello — Seed the Word Admin Board
Task tracker for daily posts, events, and upcoming projects.
Board: Seed the Word Admin Board
Monthly Calendar Design
Canva
Graphic template for the monthly calendar of youth life groups, events, and gatherings.
Template: Monthly calendar template
🗓️ Ministry Operations Schedule
This is the working rhythm for admin stewardship — who does what, when, and on which platform. It ensures consistency across every channel we serve.
Weekly rhythm
What happens each day of the week
Mon–Fri · Daily
Encounter the Word chapter post, daily audio reading, prayer & thanksgiving prompt, #verse tag highlights.
Mid-week
Cross-platform outreach, announcements + IG highlights, calendar planning, youth content curation, inbox triage.
Saturday · Study Day
Theme of the week, weekly summary in Discuss Scripture, resource & stewardship accountability.
Sunday · Rest
Minimal posting, honor in-person church attendance and the rhythm of rest.
📅 Daily (Monday–Friday)
Daily Post
Encounter the Word
- Open Meta Business Suite → schedule (or publish) today's chapter post to Instagram.
- Manually schedule the same post to the Telegram admin chat.
- Use the Trello admin board to check off today's reminder and queue future daily Bible-reading posts.
Daily Audio
Audio Rotation
- Upload the daily recorded audio reading to the Telegram group.
- Potential: re-share the audio as an Instagram Reel or Story.
Daily Prompt
Prayer & Thanksgiving
Post a dedicated message inviting members to share burdens or praises.
Template: Prayer & Thanksgiving template → (group members only)
Daily Engagement
Verse Highlights
Monitor the #verse tag for ✨ Today's Chapter Highlight — share standout verses or insights to IG Stories to encourage outside seekers.
2️⃣ Mid-Week (every 2–3 days)
Outreach
Cross-Platform Posts
In the Meta Business Suite Planner, schedule reminders inviting Instagram followers to join the Telegram group chat.
Event Updates
Announcements & Highlights
- Update the 📣 Announcements Telegram topic and the Instagram Highlights covers. Tag noteworthy posts with
#pin. - Post dedicated announcements for members to share on IG or Telegram. Announcement template → (group members only)
Planning
Future Event & Calendar Management
- Update the monthly calendar for youth life groups, upcoming events, and routine gatherings. Monthly calendar template →
- Use the Trello admin board to track reminders, tasks, and events for current and future projects.
Content
Curated Youth Content
Share encouraging content from our youth groups (e.g., Edward, Sam Petrov).
Inbox
Message Triage
Check messages in Meta Business Suite and Telegram DMs. Help members with navigation or #help requests.
📚 Study Saturdays — Discipleship
Theme
Subject of the Week
Introduce the week's discussion theme and manage the vote/suggestions for the deep dive by monitoring the #theme tag.
Review
Weekly Summary
Facilitate the weekly summary in the 🎙 Discuss Scripture topic. Weekly summary template → (group members only)
Accountability
Resource, Accounting, Stewardship
- Perform accounting for the donation account.
- Update the group on resource utilization (Bibles, tools, etc.).
- Admins tie in for the week for shared accountability.
⛪️ Sundays — Fellowship Focus
Rest & Rhythm
Minimize Digital Noise
We try not to post on Sundays — this encourages in-person church attendance and honors the rhythm of rest (Genesis 2:2).
🖼️ Updating images on the website
Some parts of the site display images that admins manage directly (team photos, ministry highlights, homepage featured slides, hero backgrounds). Replacing these is done through GitHub's web interface — no coding, no terminal. Here's the full walkthrough.
Two paths
Replace vs Add — pick your path
🔁 Replacing an existing photo
Match the old filename exactly (capitalization too). On GitHub, open the file → Replace this file → upload → commit → hard-refresh in ~30–60s.
➕ Adding a brand-new photo
Pick a short descriptive filename (lowercase, dashes, no spaces). Add file → Upload files to the matching folder → commit. If the site already references that filename (via a JSON manifest), it appears automatically. If not, ping the dev to wire it in.
Step 1 — Sign in to GitHub
- Open github.com/login in your browser.
- Enter your GitHub username/email and password. (If you use two-factor, enter the code too.)
- Once signed in, go to the repo: github.com/seedtheword/seedtheword
Step 2 — Know which folder to go to
Images are organized by how they're used on the site. Pick the folder that matches what you're updating:
Folder
assets/images/team/
Team member photos. One file per person. Filename should match the name on their card (e.g., David A..jpg, Vanessa.jpg).
Appears on: About page → "Meet the Team" section
Folder
assets/images/featured/
Images used in the homepage Showcase Carousel (fallback slides). Keep file names descriptive (e.g., bible-ministry-1.jpg).
Appears on: Homepage → "Featured From Our Ministry" rotating carousel
Folder
assets/images/ministry-highlights/
Photos/videos used in the 9-tile mixed grid on the News page.
Appears on: News page → "📸 Ministry Highlights" grid
Folder
assets/images/backgrounds/
Large hero-background images. These rarely change. If you swap one, keep the same filename so all pages automatically pick up the new version.
Appears on: Hero banners on every page
Folder
assets/images/bundles/
Product photos for the Store page bundle cards.
Appears on: Store page bundle cards
Folder
assets/images/seed-stitch/
The nine panels of the S-E-E-D ministry stitch logo. Don't rename these — they're numbered 1 through 9 in a specific order.
Appears on: About page → S.E.E.D. interactive grid
Folder
assets/images/instagram/
⚠️ Don't touch this folder. It's automatically managed by the Instagram sync every 6 hours. Files here are overwritten each run.
Step 3 — Replace an existing image (keeping the same filename)
Easiest case: you want to swap out a photo but keep everything on the page pointing to the same file.
- In the repo, navigate to the folder (e.g.,
assets/images/team/). - Click the file you want to replace (e.g.,
Vanessa.jpg). - Near the top right, click the pencil icon (🖊 Edit this file) — if the pencil is missing for image files, look for a button labeled "..." or "Replace".
- On the file page, there should be an option "Replace this file". Click it.
- Drag your new image into the upload box. The new file must have the same filename as the old one or the site won't find it.
- At the bottom of the page, add a short commit message like "Update Vanessa's photo" and click Commit changes.
- Wait about 30–60 seconds, then hard-refresh the live site (
Ctrl+Shift+R/Cmd+Shift+R) to see the new image.
vanessa.jpg and Vanessa.jpg are different files on the web server. When you replace, use the exact same name and capitalization as the original.
Step 4 — Add a brand-new image to a folder
Use this when you want to add (not replace) a photo — for example, adding a new team member or a new ministry-highlights photo.
- Navigate to the correct folder in the repo (see Step 2).
- Near the top right, click "Add file" → "Upload files".
- Drag your image(s) into the upload area. Multiple files at once is fine.
- Before you drag: make sure the filename is descriptive and uses only letters, numbers, and dashes. Avoid spaces, emojis, and unusual characters. Example good names:
pack-ship-april-2026.jpg,easter-worship.jpg,youth-outreach-may.jpg. - Add a commit message like "Add April pack & ship photos" at the bottom.
- Click Commit changes.
news.html to point at it — that's a code change. Ping the dev on duty.
Step 5 — Update a specific spot on the site
Location
Homepage Showcase Carousel (fallback slides)
- Replace an image in
assets/images/featured/with the same filename. - If you want different slide captions/titles, ping the dev. Captions live in the JavaScript and need a small code edit.
Location
Ministry Highlights grid (news page)
- Replace an image in
assets/images/ministry-highlights/with the same filename. - Grid titles are in
news.html. If you want to rename a tile, ping the dev.
Location
Team photos (about page)
- Navigate to
assets/images/team/. - Replace a photo with the same filename, or add a new one.
- If adding a new member (new filename), the
about.htmlteam section needs a code edit to include them. Ping the dev.
Step 6 — How to tell if your change went live
- After clicking Commit changes, you'll see a small progress indicator at the top of the page. Wait for it to finish.
- Go to the repo's Actions tab. There'll be a new workflow run named "pages build and deployment" — wait for its green check (usually 30–60 seconds).
- Open the live website and hard-refresh (
Ctrl+Shift+Ron Windows/Linux,Cmd+Shift+Ron Mac). Normal refresh may serve a cached old version. - If the change still isn't visible after 2 minutes + hard refresh, check the Actions tab for a red X — that means the build failed. Screenshot it and ping the dev.
🎧 Managing "What We're Listening To" & Partner Ministries
Both sections on the Community page read from a simple list in one JavaScript file. Adding or removing items is a small, guided edit — no images to upload, no styling to touch.
File to edit
assets/data/recommendations.json
Two arrays live here: "listening" (for "What We're Listening To") and "partners" (for Partner Ministries). Add or remove entries in those arrays — the builder tool below generates the JSON for you, so there's no JavaScript syntax to get wrong.
Interactive tool
🪄 Recommendations builder
Fill out the form. The live preview below uses the same Spotify/YouTube embed the site does — if it plays here, it'll play on the site. When it looks right, copy the JSON block and paste it into assets/data/recommendations.json. No JavaScript syntax to get wrong.
Live preview (this is exactly how it'll look on the site)
// Fill out the form above to generate a JSON block.
Paste this JSON block inside the "listening" array in assets/data/recommendations.json. Put it between [ and ], with a comma after the previous block's closing }. The last item in the array should not have a trailing comma.
Add a Spotify episode to "What We're Listening To"
4 steps
Add a Spotify episode
Copy the URL
Open the Spotify episode or show and copy the full URL from the address bar.
Use the builder
Scroll up to the Recommendations builder, pick the Spotify tab, paste the URL, fill in title/source/note.
Copy + paste JSON
Click Copy JSON, then paste it into the "listening" array in recommendations.json.
Commit
The card appears on the Community page within 60 seconds.
4 steps
Add a YouTube channel
Open the channel page
Copy the URL from the address bar — it should end in /channel/UC… or /@handle.
Use the builder
Pick the YouTube channel tab, paste the URL, and fill in the channel name and creator.
Copy + paste JSON
Click Copy JSON, then paste it into the "listening" array in recommendations.json.
Commit
The card appears on the Community page within 60 seconds.
4 steps
Add an Instagram profile
Open the profile
Copy the profile URL — https://instagram.com/<handle>/. Bare handles also work.
Use the builder
Pick the Instagram tab, paste the URL, and fill in the display name plus the optional avatar path.
Copy + paste JSON
Click Copy JSON, then paste it into the "listening" array in recommendations.json.
Commit
The card appears on the Community page within 60 seconds.
4 steps
Add a Twitch channel
Open the channel
Copy the channel URL — https://twitch.tv/<channel>. Bare slugs also work.
Use the builder
Pick the Twitch tab, paste the URL, and fill in the display name.
Copy + paste JSON
Click Copy JSON, then paste it into the "listening" array in recommendations.json.
Commit
The card appears on the Community page within 60 seconds.
- Open the Spotify episode or show in your browser. Copy the full URL from the address bar.
- Scroll up to the Recommendations builder tool above. Make sure the Spotify tab is selected.
- Paste the URL into the Spotify URL field. The ID and type (episode/show) are extracted automatically. Fill in the title, source, and note fields. Check the live preview — if it plays, it'll play on the site.
- Click 📋 Copy JSON.
- Open
assets/data/recommendations.jsonon GitHub and click the pencil to edit. - Paste the JSON block inside the
"listening"array. Put a comma after the previous block's closing}. The last item in the array should not have a trailing comma. - Commit. The card appears on the Community page within 60 seconds.
Add a YouTube video
Same workflow, but use kind: 'youtube' and use the video's ID (the string after v= in a YouTube URL).
{
kind: 'youtube',
id: 'dQw4w9WgXcQ',
title: 'Video title',
source: 'Channel name',
note: "Why this stood out.",
},
Add a plain link (article, sermon on another site)
{
kind: 'link',
url: 'https://example.com/full-url',
title: 'Article or sermon title',
source: 'Author or ministry',
note: "Why it matters.",
image: 'assets/images/featured/some-image.jpg', // optional thumbnail
},
Remove an item
Delete its full block (the curly braces { ... } and the comma after) from the array. Commit. Card disappears.
Reorder items
Cut and paste the blocks into a new order. The order in the array is the order on the page.
Add a Partner Ministry
- If you have a partner logo, upload it first to
assets/images/partners/(create the folder if it doesn't exist — github.com's web UI lets you do this while uploading a file). - Scroll up to the Recommendations builder tool and pick the 🤝 Partner Ministry tab. Fill in the name, website URL, optional logo path, and optional description.
- Click 📋 Copy JSON.
- Open
assets/data/recommendations.jsonon GitHub and click the pencil to edit. - Paste the JSON block inside the
"partners"array, with a comma after the previous block's closing}. The last item in the array should not have a trailing comma. - Commit. The partner card appears on the Community page within 60 seconds.
} (except optionally the last one). If you commit a broken file, the whole section will just disappear until it's fixed. When in doubt, ping the dev and copy-paste the example above exactly.
🖼️ Managing bundle slideshow images (store.html)
Each of the three bundle cards on the Store page has a rotating slideshow at the top of the card. The photos are pulled from three dedicated folders in the repo, and each folder has a tiny manifest file (images.json) that tells the site which photos to show and what caption to put on them. This is designed so you can keep the store feeling fresh without touching any HTML or CSS.
Folders to edit
assets/images/bundles/
Three subfolders, one per bundle card:
assets/images/bundles/essentials/— Essentials cardassets/images/bundles/lifegroup/— Life Group + Friends Starter cardassets/images/bundles/ministry/— Jesus' Favorite Disciple / Ministry Needs card
Add a new photo to a bundle slideshow
Step by step
Add a photo to a bundle card
Name it with a number prefix
e.g. 03-group-study.jpg. Lowercase, dashes, no spaces or apostrophes.
Upload to the right folder
Pick essentials/, lifegroup/, or ministry/ under assets/images/bundles/, drag the file in, commit.
Edit images.json
Add a new { "file": "...", "caption": "..." } block to the images array in that folder.
Commit
Hard-refresh the store page. The new slide joins the rotation right away.
- Resize and compress your photo first. Aim for 1600×900 px (16:9) and under 300 KB. A free tool like squoosh.app handles this in one step.
- Rename the file with a numeric prefix so the order is obvious — for example
03-group-study.jpg. Use lowercase and dashes. No spaces, no apostrophes, no weird characters. - In the GitHub repo, navigate into the right bundle folder (e.g.
assets/images/bundles/lifegroup/) and click Add file → Upload files. Drag your photo in. Commit. - In that same folder, open
images.jsonand click the pencil icon to edit. Add a new block inside the"images"array:{ "file": "03-group-study.jpg", "caption": "Friday night life group at Riverside" } - Important: put a comma after the previous block's closing
}so the JSON stays valid. The last block in the array should not have a trailing comma. - Commit with a message like "Add group study photo to life group bundle." Hard-refresh the store page — the new slide rotates in automatically.
Replace a photo
Upload the replacement with the same filename, overwrite the existing one when GitHub asks, and commit. The caption in images.json stays the same. That's it.
Remove a photo
- Delete the image file from the bundle folder (click the file in GitHub → trash icon → commit).
- Open
images.jsonin the same folder and delete the matching block (including its surrounding curly braces and the comma). - Commit. The slide is gone.
Reorder the slides
The order of blocks in "images" is the order they appear. Cut and paste them into whatever order you want, commit, done. Renaming the file prefixes (01-, 02-, etc.) is a courtesy for future admins but not required for the rotation to work.
Change a caption
Open images.json, edit the "caption" field on the right block, keep the double quotes around the new text, commit. Captions can be empty ("caption": "") — the caption strip just won't show on that slide.
- Every string needs double quotes. Don't use single quotes or smart quotes.
- Every block (pair of
{}) except the last one needs a comma after it. - Never put a comma after the last block in the array.
- If the JSON is broken, the slideshow on that card just hides itself and logs a warning in the browser console. The rest of the page keeps working.
Test everything at once
- Go to store.html and hard-refresh (
Ctrl+Shift+R/Cmd+Shift+R). - You should see each bundle card rotating through its photos every ~5.5 seconds, with dots at the bottom of each slideshow.
- Hover over a card — the rotation pauses. Click a dot — it jumps to that slide.
- If a slideshow doesn't appear at all, open the console (F12) and look for a "could not load manifest" warning — usually it's a JSON typo.
📤 Team media drop (how the team shares photos/videos)
We don't have a separate upload page. Instead, we use an encouragement call-out inside the "Ministry Outreach & Highlights" section on news.html — the "Your story belongs here too" card. When assets/data/media-drop.json is configured, a 📤 Share Photos & Videos button appears next to "Submit a Testimony" and "Share on Telegram", and it opens our Google Form. Every submission lands in our ministry Google account with the uploader's name, caption, and the files themselves — plus a row in a Responses spreadsheet. Admins review from there and push the good stuff to the site.
The pipeline
Phone → Form → Drive → Site
Capture
Team member takes photos/videos at an outreach or gathering.
Share via form
Taps "Share Photos & Videos" on news.html → fills a short Google Form with name, caption, files.
Lands in Drive
Files auto-upload to our team Google Drive folder. Metadata logged in a linked Sheet.
Admin reviews
Scans new submissions in Drive or the Sheet. Picks what's site-worthy.
Publish
Commits good media to the repo's matching folder (outreach, highlights, team). Site updates in ~60s. Rest stays in Drive as an archive.
Config file
assets/data/media-drop.json
Controls whether the 📤 Share Photos & Videos button shows up on the news page, and where it points. Two options supported: formUrl (Google Form — recommended) or uploadUrl (direct Drive folder — less accountable). The button stays hidden on the site until one of these is set.
First-time setup — Google Form (recommended)
This is the cleanest path. Every upload arrives with who sent it, what caption they gave it, when, plus a row in a Responses spreadsheet. All files live in our own Google Drive.
- Sign in to forms.google.com with the ministry Google account (
seedthewordministry@gmail.com). - Click Blank form. Title it "Share Photos & Videos — Seed the Word".
- Add these questions (use the + button to add each one):
- Your name — Short answer, required
- Your email — Short answer, required, enable Response validation → Email
- Where was this taken? — Short answer, optional (e.g. "UW Seattle outreach" or "Saturday study")
- Tell us about it — Paragraph, optional
- Upload photos & videos — File upload, required. Allow any file type. Set max file size to 10 MB (or higher if your storage allows) and max number of files to 10.
- Click the Settings tab:
- Leave "Collect email addresses" unchecked (you already ask in a question, and this setting would force a Google login for verified email).
- Under Responses, enable "Restrict to users in..." → OFF so non-ministry-account users can submit.
- Turn ON "Allow response editing" so uploaders can fix typos.
- Click the Responses tab → Link to Sheets → create a new spreadsheet. This is your review queue.
- Click the Send button (top right) → the link icon tab → check "Shorten URL" → Copy.
- In the repo, open
assets/data/media-drop.jsonand paste that URL into the"formUrl"field, replacingREPLACE_WITH_YOUR_FORM_ID. - Commit. Hard-refresh news.html — the 📤 Share Photos & Videos button now appears on the "Your story belongs here too" call-out.
Where uploaded files live
Google Drive folder (auto-created by Forms)
Google Forms automatically creates a folder in our Drive named "Share Photos & Videos — Seed the Word (File responses)" (or similar) and drops every upload inside it. You can find the folder link inside the Form's Responses tab. Paste it into "adminReviewUrl" so you have a quick jump link bookmarked here.
Admin review flow
- Open the Form's Responses tab (or the linked Google Sheet) to see a table of every submission — uploader name, email, caption, links to files.
- Click a file link to preview it in Drive.
- For media you want to publish, download, resize/compress, and commit to the right folder in the repo (outreach, ministry-highlights, team, etc.). Flowcharts earlier in this page walk through each destination.
- Leave the rest in Drive as an archive. Nothing has to be deleted.
Alternative: direct Drive folder (less recommended)
If you'd rather skip the Google sign-in step and let anyone with the link drop files straight into a shared folder (no name, no caption, no accountability), you can use a plain Drive folder instead of a Form:
- Create a Drive folder. Right-click → Share → "Anyone with the link" → Editor.
- Paste the share URL into
"uploadUrl"inmedia-drop.json(leave"formUrl"blank or as the placeholder). - Commit.
Turn the upload button off or swap targets
- Switch to a different Form or folder: paste the new URL into
"formUrl"or"uploadUrl", commit. The call-out button updates without any HTML changes. - Turn the upload button off temporarily: set
"enabled": falseinmedia-drop.json. The button hides; "Submit a Testimony" and "Share on Telegram" stay visible. - Leave it unconfigured: if both URLs still contain
REPLACE_WITH_..., the button stays hidden automatically — nothing looks broken on the site.
🙌 Managing Ministry Outreach cards (news.html)
The "Ministry Outreach & Highlights" section on the News page leads with up to 3 outreach cards, each with its own rotating slideshow of photos and videos. Each card represents one real outreach (a date, a location, a short write-up, an optional testimony). New events are added by dropping a new folder of media in and appending one entry to a master index — no HTML edits needed.
Master index (what the page shows)
assets/data/ministry-outreach.json
Contains an ordered events array. Only the top 3 are displayed on the News page. Keep the most-recent event first. When there are fewer than 3 real events, an automatic "Want to join the next one?" invitation card fills the empty slot.
Media folders (one per outreach)
assets/images/ministry-outreach/<folder>/
Each outreach gets its own subfolder. Pick a short kebab-case name that hints at the event (e.g. slavic-awakening-bellevue-may-2026). Inside, you drop the photos/videos and an images.json file listing them in play order.
Add a new outreach card
Step by step
Add a new outreach to the News page
Name the folder
Kebab-case with a date, e.g. bellevue-park-july-2026.
Upload media
Under assets/images/ministry-outreach/<folder>/ — photos 01.jpg, 02.jpg… and videos under 10 MB.
Write images.json
List media in play order inside the same folder (see the template below).
Register the event
Open ministry-outreach.json and add a new entry at the top with title, date, location, body. Leave testimony blank.
Commit
The card shows on the News page and the homepage's Latest Outreach right away. Add a testimony later when you have one.
- In the repo, open
assets/images/ministry-outreach/. Click Add file → Create new file and type a folder path likebellevue-park-july-2026/images.json(GitHub creates the folder for you as you type the slash). - Upload your photos and videos into that new folder. Prep guidelines:
- Photos: 1600×1200 or similar 4:3, under ~300 KB each. Rename to
01.jpg,02.jpg,03.jpg… for predictable ordering. - Videos: MP4, vertical or horizontal both fine. Keep under ~10 MB each; compress on freeconvert.com if needed. Name them
video-01.mp4,video-02.mp4.
- Photos: 1600×1200 or similar 4:3, under ~300 KB each. Rename to
- Open the
images.jsonfile you just created and paste this template (adjust filenames to match what you uploaded):
Order is play order. Photos hold for ~5.5 seconds; videos play their full length before advancing. Captions are optional — leave{ "media": [ { "file": "01.jpg", "type": "photo", "caption": "" }, { "file": "02.jpg", "type": "photo", "caption": "" }, { "file": "video-01.mp4", "type": "video", "caption": "Moment from the outreach" }, { "file": "03.jpg", "type": "photo", "caption": "" } ] }""to hide the caption strip. - Now open
assets/data/ministry-outreach.jsonand add a new entry at the top of theeventsarray (most-recent first):
Put a comma after the closing{ "folder": "bellevue-park-july-2026", "title": "Summer Park Evangelism", "date": "July 12, 2026", "location": "Downtown Park, Bellevue", "body": "A short paragraph about the outreach — who we partnered with, what you handed out, how the day felt.", "testimony": "" },}if there are other entries below it. - Commit both changes with a message like "Add July 12 Bellevue park outreach card." Hard-refresh
news.htmland the new card appears at the front of the outreach row.
Add a testimony to an existing outreach
Open assets/data/ministry-outreach.json, find the right event, and fill in the "testimony" field with a 1-3 sentence first-person quote. Keep the double quotes around it. Example:
"testimony": "Aliosha handed me a pocket New Testament and said he'd pray for my studies. I hadn't opened a Bible in 10 years. Two weeks later, I'm still reading."
Commit. The quote appears in a gold-bordered block on the card.
Change the order of outreach cards
The order of the events array is the display order. Cut and paste the blocks to reorder. Only the top 3 appear on the page — the rest sit archived in the file for later.
Remove an outreach from the page
Two options:
- Temporary hide — move the event's block further down in the
eventsarray (below position 3). - Permanent delete — delete the event's block from
ministry-outreach.json, then delete its folder fromassets/images/ministry-outreach/.
🏠 Managing the Homepage
The homepage is intentionally tight: Hero → Featured From Our Ministry (Showcase Carousel) → Walking the Path of Light → Footer. The Showcase Carousel does all the rotating-content heavy lifting, and the daily verse, tips, and featured highlights are integrated into it.
Daily verse rotation (used by the Showcase Carousel)
assets/data/daily-verses.json
A list of verses that rotates by day of year. Verse #N is picked using dayOfYear % verses.length, so the order matters only for "which verse lands on which day" — it doesn't cycle manually. Add more verses to expand the pool.
Add a new verse to the rotation
- Open
assets/data/daily-verses.jsonin the repo and click the pencil icon to edit. - Inside the
"verses"array, add a new block at the end:{ "text": "Full verse text here.", "ref": "Book 1:1", "version": "KJV" } - Put a comma after the previous block's closing
}so the JSON stays valid. The last block should NOT have a trailing comma. - Commit. The new verse joins the rotation. It may not land on today's screen — that depends on the day-of-year math.
What the Showcase Carousel rotates
- Featured ministry highlights — editorial slides defined in
assets/js/showcase-carousel.js(Study Saturday Live, Pack & Ship Nights, Zander's Bible, etc.) - Daily Bible content — one slide per day auto-picked from
DAILY_CONTENTinshowcase-carousel.js(verses, tips, facts, encouragement). The rotation cycles through categories day-of-year so each day feels different. - Latest Instagram posts — top 3 by likes from the last 60 days, pulled live from
assets/data/instagram.json.
Edit the showcase slides or daily content
Both lists live at the top of assets/js/showcase-carousel.js — FALLBACK_SLIDES and DAILY_CONTENT. This is a JS file, so follow JSON-ish syntax carefully: single quotes, commas after every item, no trailing comma on the last one. If in doubt, ping the dev on duty.
🌱 Walking the Path of Light (homepage ministry map)
Ministry framing
Seed → Water & Grow → Harvest → Repeat
Seed
Evangelism & new believers. Grace first — action before theology. Tuesday meetings are where this begins.
Water & Grow
Discipleship & deepening. Amen Prayer, Philippians 4:6–8, Saturday studies using the 5 pillars framework.
Harvest
Placement & maturation. Commission members into a local church body. Mentorship anchored in 1 Timothy 1:5.
Repeat
For the next person He brings us.
The homepage's second section, anchored in Psalm 119:105 ("Thy word is a lamp unto my feet, and a light unto my path"), draws the ministry's journey as a glowing path of waypoints on a dark canvas. Each waypoint card has a short paragraph, a tag line, and a call-to-action link. Changes here require an HTML edit in index.html (search for the-path). Keep paragraphs short — the cards are meant to be scannable. If the team's lifecycle framing evolves, update all three phase cards together so the loop still makes sense.
🔧 How to do common tasks
Add a new event to the calendar
- Sign in to calendar.google.com with
seedthewordministry@gmail.com. - Click anywhere in the calendar on the target date.
- Enter a title, pick a start/end time, add a description.
- Save. The event shows on the website within 1 minute.
Publish a new podcast episode
- Go to podcasters.spotify.com and sign in.
- Click New Episode → upload the audio file (MP3 or WAV).
- Add a short title and 1-2 sentence description.
- Click Publish. The episode appears in the site's Spotify embed automatically (may take a minute).
🎙️ Set up the Bible Audio pipeline
The Bible Audio pipeline captures volunteer-recorded chapter readings posted to the Today's Chapter Telegram topic, runs them through a conservative ffmpeg cleanup, and emails the team a per-chapter checklist for the manual Spotify upload above. Three stages: an Apps Script poller pulls voice memos from Telegram into Drive, a nightly GitHub Actions workflow cleans them with ffmpeg, and a second Apps Script trigger emails the team at 07:00 PT the next morning. The pipeline plugs into the existing bible.todayChapterTopic block in assets/data/telegram-bot.json — same chat, same topic id (13).
Until you finish all six steps below, the pipeline is a no-op: the committed config has bible.audio.enabled set to false, the Apps Script entry points bail at the top, and the cleanup workflow exits 0 immediately.
- Create the Google Cloud service account. Go to Cloud Console → IAM & Admin → Service Accounts while signed in as
seedthewordministry@gmail.com. Pick the existing project (or create a new one named stw-ministry-automation). Click Create Service Account. Name itstw-bible-audio-cleanup. Skip the optional grant-access steps. - Enable the Drive API in the same project: Cloud Console → APIs & Services → Drive API → Enable.
- Generate a JSON key. On the service account's row, Actions menu → Manage keys → Add key → Create new key → JSON. The browser downloads
stw-bible-audio-cleanup-XXXXXXXX.json. Treat this file like a password — don't email it, don't commit it. - Share both Drive folders with the service account's email. Create two folders in your Drive (any names — e.g. STW Bible Recordings - Raw and STW Bible Recordings - Cleaned). The service account has an email like
stw-bible-audio-cleanup@<project>.iam.gserviceaccount.com. Open each folder → Share → paste that email → set role to Editor → uncheck Notify people (service accounts have no inbox) → Send. Copy each folder's ID from its URL (the long string after/folders/) — you'll paste them in step 6. - Paste the JSON key into a GitHub Secret. Open the repo's Settings → Secrets and variables → Actions → New repository secret. Name:
GDRIVE_SERVICE_ACCOUNT_JSON. Value: open the downloaded JSON file in a text editor, copy the entire contents (curly brace to curly brace), paste, Add secret. Then delete the local JSON copy from your downloads folder and empty the trash. - Stand up the Apps Script project. Sign in to script.google.com as
seedthewordministry@gmail.com. Click + New project, rename it to STW Bible Audio. Open the repo filedocs/apps-script/bible-audio.gs, copy its full contents, paste overCode.gs, save. Open Project Settings (⚙️) → Script Properties → Add script property: nameTELEGRAM_BIBLE_BOT_TOKEN, value the @seedtheword bot's token (same one used by the daily Bible bot). Save. RunregisterBibleAudioTriggersonce from the function dropdown — it installs 30 half-hourly poller triggers (07:00–21:30 PT) plus one daily 07:00 PT notifier trigger. Authorize when Google prompts (UrlFetch + Drive + Mail scopes).
Then in the editor: open the admin editor → Bible → Audio config, paste both Drive folder IDs from step 4, flip enabled to true, save & commit.
How it runs: after the morning post on Mon–Fri, the topic auto-renames to today's chapter ("Today's Chapter is Luke 7"); volunteers post voice memos into the topic; cleaned MP3s land in your inbox at 07:00 PT the next morning with a 5-step Path A checklist that mirrors the podcast publishing walkthrough above.
Key rotation (every 12 months, or any time the JSON key is suspected leaked): in Cloud Console → Manage keys on the service account, Add key → Create new key → JSON. Update the GDRIVE_SERVICE_ACCOUNT_JSON secret with the new value. Trigger Bible Audio Cleanup manually (Actions → workflow_dispatch → Run workflow) to confirm the new key works. Then back in Cloud Console delete the old key — that's the actual revocation step.
Change the livestream schedule
The "LIVE NOW / OFFLINE" logic is hardcoded in assets/js/twitch-integration.js. If the schedule changes (e.g., moves from Saturday 7 PM), the site needs a code edit. Ping the dev on duty.
Trigger a fresh Instagram sync
- Go to the repo's Actions tab.
- Pick Scrape Instagram Feed from the left sidebar.
- Click Run workflow → choose branch
main→ Run workflow. - Wait ~1 minute. The site updates within a minute of completion.
⚙️ Set up the order email handler (Apps Script Web App)
The bundle-builder review form on bundle-builder.html can either submit to a free Formspree inbox (default) or to a Google Apps Script Web App you own. The Apps Script path adds three things Formspree's free tier can't: an automatic gifter receipt, a permanent Google Sheet ledger of every order, and an optional heads-up email to the giftee. Setup takes about 10 minutes the first time, no installs, no payments.
Heads-up: MailApp.sendEmail sends as the script owner via Google's session — there is no SMTP, no Gmail App Password, no 2FA prerequisite for this script to function. (2FA on the account is generally a good idea, but unrelated.)
- Sign in to script.google.com as
seedthewordministry@gmail.com. - Click + New project. Rename it to STW Order Handler (top left, click the title).
- In another tab, open sheets.new. Rename the spreadsheet to STW Order Ledger. Rename the first tab from
Sheet1toOrders(right-click the tab → Rename). (Already done — the existing Sheet ID is baked intoorder-handler.gs.) - (Skipped — Sheet ID is already in the source.)
- Open the repo file
docs/apps-script/order-handler.gsin another tab. Copy its full contents. - Back in the Apps Script editor's
Code.gs, select all (Ctrl/Cmd+A) and paste over. - (Skipped — no placeholder to replace;
LEDGER_SHEET_IDis already populated.) - Click Save (disk icon, top toolbar). Then click Deploy → New deployment. Set Type = Web app. Set Description = v1. Set Execute as = Me (seedthewordministry@gmail.com). Set Who has access = Anyone. Click Deploy.
- Authorize when prompted — Google will ask for permission to access Sheets and send Gmail. Click Allow. (You may see an "unverified app" warning — click Advanced → Go to STW Order Handler (unsafe). This is expected for personal scripts.)
- Copy the Web app URL at the end of the dialog (starts with
https://script.google.com/macros/s/…/exec). - Open the admin editor → pick Site config (cross-cutting) → paste the URL into orderHandlerUrl → Save & commit.
Verify it works: hard-refresh bundle-builder.html in another tab. Place one test Essentials order to your own email, ticking the giftee opt-in box and entering a second email you can read. You should get three emails arrive (gifter receipt, team notification, giftee heads-up) and a new row in the Orders tab of the Sheet. For Ministry Calling you'll get two emails (the giftee path is intentionally never used for outreach orders).
If something looks off: open the Apps Script editor → Executions tab in the left sidebar — every doPost call logs there with full input/output. Most issues are a typo'd LEDGER_SHEET_ID or the deployment access set to Only myself instead of Anyone.
Updating the script later: when docs/apps-script/order-handler.gs changes in the repo, repeat steps 5–6 (paste new contents over Code.gs), then click Deploy → Manage deployments → ✏️ pencil → New version → Deploy. The Web app URL stays the same.
📖 Wire the Share-Your-Story Google Form to the team email
The "Share your story" modal on news.html iframes the same Google Form configured in assets/data/media-drop.json. To get the styled team-notification email and a row in the Stories tab of the STW Order Ledger every time someone submits, we install an On form submit trigger on onStoryFormSubmit. One-time setup, ~5 minutes.
Why a programmatic trigger install? The STW Order Handler is a standalone Apps Script project (created at script.google.com directly), not bound to a spreadsheet. The trigger UI's Event source dropdown only offers Time-driven and From calendar in standalone projects — From spreadsheet is hidden. We get around that by calling ScriptApp.newTrigger(...).forSpreadsheet(...).onFormSubmit().create() from inside the script itself, which Google honors regardless. The helper functions installStoryTrigger() and removeStoryTriggers() live at the bottom of order-handler.gs for exactly this purpose.
- Open the Form (Share Photos & Videos — Seed the Word) in the editor (click the pencil icon if you only have the public link, or open it from forms.google.com while signed in as the ministry account).
- Click the Responses tab → green Sheets icon → Select existing spreadsheet → pick STW Order Ledger (the same sheet your orders write to:
17j5TDDTZ-58MuZ7VO7c1ohPkyHw2LZ2GCWYMFb-CJ50). Click Select. Google will add a new tab to the spreadsheet automatically — leave it; our trigger writes to its own Stories tab and ignores Google's auto-tab. - Open the Apps Script editor (script.google.com → STW Order Handler). Make sure the latest
order-handler.gsfrom the repo is pasted intoCode.gsand saved (the trigger-install helpers were added in the same commit that fixedonStoryFormSubmit). - In the function dropdown at the top of the editor (just left of the ▶ Run button), pick
installStoryTrigger. - Click ▶ Run. Authorize the new Forms scope when Google prompts (you'll see "STW Order Handler wants to access… Manage your forms in Google Drive" — click Allow).
- Open the ⏰ Triggers tab (clock icon, left rail). Confirm there's now an entry for
onStoryFormSubmitwith event type On form submit. Done.
Verify it works: open the live news.html, click 📤 Share photos, video & testimony, fill in the form with your own name/email and any caption, and submit. Within ~30 seconds you should see a styled email at seedthewordministry@gmail.com with subject [STW Story] {your name}, and a new row in the Stories tab of the STW Order Ledger.
If no email arrives: open the Apps Script editor → Executions tab. Each form submission should log an onStoryFormSubmit entry with the trigger payload. Common failure modes:
- "Form trigger fired without namedValues" — the form's responses aren't linked to the spreadsheet that the trigger is bound to. Repeat step 2.
- "Form trigger missing name/email" — the form's question labels don't contain "name" or "email". Open the Form editor and confirm the questions are titled Your name and Your email.
- No execution logged at all — the trigger isn't installed. Repeat steps 4–5 (or run
installStoryTriggeragain — it's idempotent and removes any prior triggers before re-creating).
To remove the trigger (e.g. switching forms, debugging duplicate emails): pick removeStoryTriggers from the function dropdown and click ▶ Run. It deletes every existing onStoryFormSubmit trigger.
If you'd rather skip the form entirely and have the modal POST directly to the same Apps Script Web App (the same path the contact form uses): change the Share-Your-Story modal in news.html from an iframe to a native form, and have it POST { type: 'story', name, email, story, mediaUrl, consentToPublish } to the URL in orderHandlerUrl. The handler already has a handleStory branch wired up for that path; you just lose the file-upload affordance Google Forms gives you for free.
✅ Verify the bundle customization tiers
The store-page journey now organizes every customization into three tiers: ⭐ Recommended (gold-bordered pills with a star prefix), Standard (normal pills), and 🛎 Special-order (collapsed talk-to-team card). Picking any special-order item changes the order flow: the CTA reads "Continue to share your special-order request →", the team email subject becomes [STW Special Order], and the Sheet's column O is marked yes. After any catalog edit (assets/js/bundle-item-catalog.js) or Apps Script redeploy, walk this checklist to confirm everything still lines up:
- P1 — tiers render correctly. Hard-refresh
store.html, click Essentials → Start customizing → For myself. In step 2's "On the Bible" section, confirm Engraving, Custom painting, and Page-edge spray painting are gold-bordered ★ pills listed first. Confirm a 🛎 Special-order options card exists below — collapsed by default — containing items like Foil stamping and Edge gilding. Repeat for Life Group step 4 and Ministry step 3. - P2 — special-order propagation. On any bundle, expand the special-order card and tick one item (e.g. Edge gilding). Advance to review and confirm: (a) a gold informational banner above the form lists the flagged item, (b) the submit button reads "Continue to share your special-order request →", (c) the team email subject after submit reads
[STW Special Order]and the body has a "🛎 Special-order items — confirm before fulfilling" section, (d) the Sheet's column O for that row isyes. - P3 — tooltip accessibility. Tab through any step. Each pill should be followed in tab order by a small (i) icon with a 3px gold focus outline. Press Enter or Space on the icon — a dark tooltip should appear with the item's plain-English description. On mobile (or DevTools 375px width), tap the (i) icon — the tooltip should appear inline below the pill row, not overlap the next pill.
- Sign-opt-out. On Essentials step 2 or Life Group step 4, scroll to the bottom and tick Skip the back-cover ministry signing. The review summary on
bundle-builder.htmlshould read "Back-cover signing: SKIPPED (per gifter request)" and the team email after submit should include a "🖋 Back-cover signing: SKIPPED per gifter request — do not sign." line. - Backward-compat. Old
?c=URLs from before this change should still decode and render — the schema extensions are additive, with missing fields defaulting to[]orfalse.
If any of those don't behave as described, paste the screen state and we'll trace it from the live system. Most regressions show up in the Apps Script Executions tab — every doPost call logs the full payload + the script's reconciled isSpecialOrder decision.
🧯 Troubleshooting — if the site looks broken
Start here
Something looks wrong — what to check first
Hard-refresh
Ctrl+Shift+R / Cmd+Shift+R. Stale cache is the #1 cause. Fixed? Stop here.
Narrow it down
Scroll through the specific sections below: calendar, IG images, contact form, podcast, livestream, or something else.
Try the fix
Most issues resolve by checking the service's dashboard or re-running the relevant GitHub Action.
Still stuck? Ping the dev
Screenshot the browser console (F12) + Actions tab + what you tried. Send to the dev on duty.
Calendar is empty on news.html
- Check if Google Calendar has any upcoming events at all — maybe there just aren't any.
- Open the browser console (F12) on the news page and look for red errors mentioning "calendar" or "403/401."
- If the API quota is exceeded, calendar will silently fail. Wait an hour and retry, or check the Google Cloud Console for the API key.
Instagram images look broken
- Check the repo's Actions tab. Did the most recent "Scrape Instagram Feed" run succeed?
- If it's been failing for days, rss.app may have disconnected. Sign in to rss.app and re-authorize the Instagram source.
- Worst case: trigger a fresh run manually (see above).
Contact form isn't sending
- Sign in to formspree.io/forms and check the form's message log.
- Verify the form is still "Active" and the ministry email is confirmed.
- Free tier is 50 submissions/month — check the usage counter.
Podcast embed is blank or broken
- Visit the show page directly — if that loads, the embed will too.
- If the show is marked private, public embedding fails. Verify in the podcasters dashboard.
Livestream says "LIVE" when we're not, or vice versa
- The status is schedule-based, not from Twitch. It'll show LIVE Saturday 7–10 PM Pacific regardless.
- If the actual stream time moves, the code needs an update. Ping the dev on duty.
Something else looks wrong
- Hard-refresh the page first (
Ctrl+Shift+R/Cmd+Shift+R). Most "broken" reports are stale browser cache. - Check the repo's Actions tab for recent failed runs.
- Check the browser console (F12 → Console tab) for red errors. Screenshot them before pinging the dev.
📣 Announcing events to Telegram, Instagram & beyond
Every event on the News page has a single 📣 Share Event button in its modal. Tapping it opens a dropdown with multiple destinations — Telegram, WhatsApp, Instagram, the device share sheet, or plain clipboard — and pre-fills each one with an announcement following our ministry format. No retyping, no copy-paste dance.
3 steps to share
Announce an event anywhere in under 30 seconds
Open the event
On news.html, click the event on the calendar. The event modal opens.
Tap Share Event
A dropdown appears with destination options.
Pick your destination
Device share sheet, Telegram, WhatsApp, Instagram (copies caption), plain copy, or event-link-only. Each one is pre-filled with the ministry announcement template.
The announcement format
The Share button uses our ministry template. Every event auto-generates something like this:
❕! MAY 25, 10AM ! ❕
🌱Seed The Word! 🌱
@ Lincoln Rock State Park!
Address:
📍Lincoln Rock State Park, 13253 US-2, East Wenatchee, WA 98802, USA
🙏🏻🤍Hello youth!! 🎉
Memorial Day is coming up soon!
Sign ups are open now!
Date & time 📆 : May 25th, 10am
Cost💲: 20$
Sign up with : @Alina Matyuk-Pavlun
(contact @seedtheword admins for details)
We can\u2019t wait to see you there! \u2728
More Details (https://seedtheword.github.io/seedtheword/news.html#event=abc123)
How each field is generated (so Calendar edits drive the post)
- Time header — format is
❕! DAY-OR-DATE, TIME ! ❕. Close events get the friendlier label (TONIGHT, 6PM,TOMORROW, 7PM,SATURDAY, 10AM); events further than a week out show the date (MAY 25, 10AM). On-the-hour times drop the minutes. - Venue line "@ Lincoln Rock State Park!" — pulled from the event title if it ends with
@ Something, or falls back to the first part of the address. Add@ Lincoln Rock State Parkto the event's Google Calendar title to get that exact line. - Address or coordinates — just the text, no embedded maps links (Telegram auto-linkifies a bare address). If the Calendar event has no address but has coordinates in its extended properties, those are shown instead with a
Coordinates:label. - 🙏🏻🤍 description — pulled verbatim from the event's Description field, prefixed with 🙏🏻🤍. The team's emojis, line breaks, and sign-up instructions come through as written. Keep descriptions under 600 characters — longer ones get trimmed.
- More Details link — a deep link to the event's modal on news.html. Clicking it opens the News page with the event expanded.
Share destinations in the dropdown
- 📱 Device share… — appears on phones (and some desktop browsers). Opens the native share sheet with every installed app. Fastest if you're on your phone.
- 📣 Telegram — opens Telegram's share sheet pre-filled with the announcement and link. Pick the Announcements chat, hit send. Telegram generates a rich preview card from the News page's Open Graph image.
- 💬 WhatsApp — opens WhatsApp with the announcement pre-filled.
- 📷 Instagram — IG has no public share API, so this copies the caption to the clipboard and opens @seedtheword. From there you paste it into a new post, story, or DM.
- 📋 Copy announcement — full text to clipboard. Paste anywhere.
- 🔗 Copy event link only — just the URL, no message. Useful for inline mentions ("see ya at the @seedtheword study ↓ url").
Shareable event links
Every event has a direct URL: news.html#event=<event-id>. Clicking it opens the News page with that event's modal already expanded. The URL in your address bar updates automatically when you open an event, so you can grab it from there if you prefer. Closing the modal cleans up the hash.
Pro tip: edit the event in Google Calendar to control the post
Since everything in the announcement comes from the Calendar event, you can tune the wording by editing the Calendar entry:
- Title ending in
@ Place Name→ venue line reads "@ Place Name!" - Paste the full street address into Location → Apple Maps + Google Maps links are auto-generated
- Put the invite/description in the Description field → wraps with 🙏🏻🤍 automatically
Want fully-automated posting (no tap required)?
Done — see the next section for how the Telegram bot handles scheduled announcements automatically.
🤖 Telegram bots — announcements, Bible, prayer
We run three separate Telegram bots so members see clear senders in the chat and each bot can be paused independently. All three post to the @seedtheword supergroup, each into a different topic.
Shared config file
assets/data/telegram-bot.json
One file, three sections — "announcements", "bible", "prayer". Each has its own enabled, chatId, messageThreadId, and settings. The bot tokens live only in GitHub Secrets.
📸 How to attach a photo to a calendar event
Both the Telegram announcements bot and the News-page event cards pull photos from whatever image URLs appear in a calendar event's description. This is the one folder for everything pattern — set sharing once on a single Drive folder, and every file you drop in there inherits the correct sharing automatically.
Quick link
📂 Shared "STW Calendar Photos" Drive folder
Loading…
One-time setup (do this once, ever)
🗂 Create the shared "STW Calendar Photos" Drive folder
- Open drive.google.com, sign in as the ministry account.
- Click New → Folder. Name it STW Calendar Photos (or pick any name and note it in the config below).
- Right-click the new folder → Share.
- Under "General access", change Restricted to "Anyone with the link", then confirm role is Viewer. Click Done.
- Right-click the folder again → Share → Copy link. Save that URL somewhere the team can find it.
Why this matters: anything you drop into this folder from now on inherits the folder's "Anyone with the link: Viewer" access. No per-file sharing. No sign-in wall when Telegram or the website fetches the image.
Workspace account gotcha: if your ministry Google account is on a Workspace domain with "external sharing blocked", step 4 will fail. In that case either (a) ask your workspace admin to allow external link sharing on this one folder, or (b) use a personal Gmail account for the photos folder.
Everyday workflow
📅 Attach a photo to an event (the easy way)
The simplest path now: open the calendar event, click the 📎 paperclip icon, upload your image. Save. That's it. The Apps Script mirrorCalendarPaperclipAttachments_ trigger runs every 5 minutes — within that window it copies the attachment into the STW Calendar Photos folder, pastes the public URL into the event description, and removes the original paperclip. You walk away; it stages itself.
The classic upload-to-Drive-then-paste-URL workflow still works (described below) and is useful for events you want to attach photos from the public web to. But for everyday paperclip uploads, the mirror does the work.
Manual workflow (if you prefer)
📂 Drop in Drive, paste URL into description (4 steps)
- Upload the image into the STW Calendar Photos Drive folder (drag-and-drop works).
- Right-click the new file → Share → Copy link. The URL looks like
https://drive.google.com/file/d/<id>/view?usp=sharing. - Open the Google Calendar event. In the Description field, paste the URL on its own line. You can paste multiple URLs (up to 10) to send an album.
- Save. The next announcement tick (within 15 min for announcements, 8 AM next day for Bible-bot) picks it up automatically.
Also supported (no setup): any public .jpg/.jpeg/.png/.webp/.gif URL, or an <img src="..."> tag inside the description. Instagram and Facebook Page cover images work well (right-click the image in your browser → Copy image address).
Verify before going live
🧪 Test that your photo will actually attach
Before relying on the next scheduled run to surface a problem, fire a one-shot preview that pulls from the next future event with attached photos and posts to the announcements channel with a clear "(test)" header. Two ways to trigger:
- From Apps Script (recommended for admins): open the Apps Script editor → function dropdown → run
kickAnnouncementBotTest. The execution log returns immediately; the test post lands in Telegram within ~1 minute. - From GitHub: Actions tab → Telegram Announcements → Run workflow → set
test_run = true.
The test posts the actual caption shape members will see (only the header is replaced). If the photo doesn't show up in the test, the workflow log explains why — most often "URL not usable: served HTML, not image bytes" (Drive file isn't shared "Anyone with the link") or "no future events with reachable photo URLs found" (no upcoming event has a URL pasted in its description yet). Test runs do NOT touch the dedup log, so you can re-fire as many times as you want while iterating.
Nudge template
💬 Reminder message to paste in the group chat
Most of the time the paperclip "just works" because the Apps Script mirror handles it. But when a teammate uses a private Drive link or the mirror's upstream service is having a bad day, the photo silently won't appear. If that happens, paste this in the admin chat:
Hey — for event photos, the easiest way is the 📎 paperclip icon in the calendar event itself. The mirror handles the rest within 5 minutes.
If that doesn't work for some reason, the manual fallback is to upload to our shared STW Calendar Photos folder first:
{{FOLDER_URL}}
Then paste the file's share link into the calendar event's description. Thanks! 🙏
What happens under the hood
🔧 From Drive link to Telegram photo
- The script scans the event's description for image URLs and
<img src>tags. - Drive share links like
/file/d/<id>/vieware auto-rewritten tolh3.googleusercontent.com/d/<id>=w2000, Google's actual image CDN. The olddrive.google.com/ucendpoint is unreliable and has been deprecated — the script skips it. - Before posting, the Python script does a GET pre-flight to confirm the URL serves image bytes (not an HTML login page). If the pre-flight fails (file not public, 404, etc.), the script logs the reason and falls back to a text-only post so the announcement still fires.
- On the website, the same image URLs render as a hero banner on the event modal and as thumbnails on the compact announcement cards.
- The URLs themselves get stripped from the visible caption/description so they don't appear as literal text.
Troubleshooting
🔒 I posted but no image appeared
Most common cause: the Drive file inherited restricted sharing (e.g., you uploaded to a folder that isn't public, or your Workspace domain blocks external sharing).
How to check:
- Open the Drive file → Share button.
- Under "General access" it should say "Anyone with the link" · Viewer. If it says "Restricted" or "Only people with access", that's the problem.
- Fix by clicking the access selector → Anyone with the link → Done.
- Also check: open the file's share URL in a private/incognito window. You should see the image preview page without being asked to sign in. If it asks for a login, Google still considers it restricted.
Other causes:
- File over 25 MB: Google inserts a virus-scan interstitial. Downsize the photo to under 5 MB before uploading.
- Telegram caption over 1024 chars: the caption gets trimmed. No action needed.
- Red "🔒 Photo not public" banner on the website: same problem — fix Drive sharing.
The script logs its pre-flight decision into the workflow run output — look for lines like Image URL not usable for event <id>: <url> — content-type=text/html. That content-type string tells you Drive returned an HTML page instead of an image.
Bot 1 — Announcement bot
- What it does: Posts today's calendar events from the ministry Google Calendar.
- Where: Announcements topic (thread 553).
- Schedule: runs every 15 minutes, Mon–Sat, during quiet-hours window (default 07:00–21:00 PT). LIVE events bypass quiet hours. No Sunday runs.
- Anti-spam: Each event fires at most three posts over its lifetime — once as upcoming, once as a reminder (default 3 hours before start), once as live. Dedup log at
assets/data/telegram-announcement-log.json. - Event photos: paste any image URL into the event description and the post becomes a Telegram photo / album (up to 10 photos). See the "Attach a photo to a calendar event" section above for the Drive folder walkthrough.
- Secret:
TELEGRAM_BOT_TOKEN - Workflow:
.github/workflows/telegram-announcements.yml - Test it: Actions → Telegram Announcements → Run workflow → dry_run =
true.
Bot 2 — Daily Bible bot
- What it does: Mon–Fri posts the day's NT reading + optional Russian link + a Prayer & Thanksgiving block. Saturday posts a branded Study Saturday Live teaser with this week's review passages pulled from
study-saturday.json. - Where: General channel (
messageThreadId: null). Update the thread ID under Editor → Telegram bot config → Daily Bible bot if the destination changes. - Schedule: 08:00 Pacific, Monday–Saturday. No Sunday post.
- Reading plan (Mon–Fri): same one the homepage uses — anchored on Apr 30 2026 = Mark 11, advancing one chapter per weekday.
- Saturday template: dedicated Study Saturday Live teaser with TONIGHT banner, Twitch link, three body paragraphs, "This week's study focus / This week's reading" pulled from
study-saturday.json. Fully configurable under Saturday: … fields in the editor. - Saturday config knobs (in
telegram-bot.json→bible.saturday, or Editor → Telegram bot config → Daily Bible bot):enabled,twitchUrl,streamStartTimePT,rulesUrl, and four body paragraphs (bodyIntro/bodyReview/bodyGoal/bodyRulesNote). - Spotify links (Mon–Fri): per-chapter overrides in
assets/data/bible-spotify-map.json—chaptersfor English,russianChaptersfor Russian. Fallbacks:fallbackShowUrl+russianFallbackShowUrlinside the bible section. Russian line is omitted entirely when no Russian URL is configured. - Prayer block toggle: set
"includePrayerBlock": falseinside the bible section if you ever want posts without it. - Secret:
TELEGRAM_BIBLE_BOT_TOKEN - Workflow:
.github/workflows/daily-bible.yml - Test it: Actions → Daily Bible Reading → Run workflow → dry_run =
true.
Bot 4 — Weekly playlist digest
- What it does: Every Saturday at 8 AM Pacific, posts "What's new in the community playlist this week" into the Worship & Music topic, listing each track added in the past week with a one-tap Spotify link.
- Where: Worship & Music topic (thread 1064).
- Schedule: Saturday 08:00 PT. Lands alongside Saturday's Study Saturday Live teaser so members see both Saturday posts as a single weekly rhythm. If nothing was added that week, the post is skipped — we don't send empty digests.
- Reads from: the public Spotify playlist whose ID is set at
bible.playlist.playlistId. - Secret: posts via
TELEGRAM_BIBLE_BOT_TOKEN(same bot as daily Bible posts). - Workflow:
.github/workflows/weekly-playlist-digest.yml - Test it: Actions → Weekly Playlist Digest → Run workflow → dry_run =
true.
One-time setup
🔑 Add Spotify API credentials
The digest needs a (free) Spotify developer app to read the playlist. Setup takes 5 minutes:
- Open developer.spotify.com/dashboard and sign in with the ministry Spotify account.
- Click Create app. Name: Seed the Word Digest. Description: whatever. Redirect URI:
http://localhost(required but unused for us). APIs used: Web API. Accept the terms → Save. - In the app's dashboard, click Settings. Copy the Client ID and click View client secret → copy that too.
- Open GitHub repo → Settings → Secrets and variables → Actions. Add two secrets:
SPOTIFY_CLIENT_ID→ paste the Client IDSPOTIFY_CLIENT_SECRET→ paste the Client Secret
- Run the workflow once manually with dry_run=true to verify. When the Saturday run fires, newly-added tracks will post automatically.
Safety note: App-only credentials can only read public playlist data — they cannot add, remove, or modify tracks. Zero risk of the bot doing something to your playlist.
Paste & pin
📌 Pinned intro for the Worship & Music topic
Copy this message, paste it into the Worship & Music topic (https://t.me/seedtheword/1064), then tap the message → Pin. Members see it as the first thing when they open the topic.
🎵 STW Community Playlist Drop in songs that worship God, minister to your soul, or remind you who He is. We all listen together. ▶️ Listen to the playlist https://open.spotify.com/playlist/18QMpMTYrFt2KxW1Q9NsvX ➕ Join as a collaborator (one tap) https://open.spotify.com/playlist/18QMpMTYrFt2KxW1Q9NsvX?si=69aaa882a74d40c8&pt=3cf27aeb2c1ffec6bf48b7345e141c26 How to add: tap the green link above once (Spotify opens, accept the invite). After that, any song you're playing in Spotify has Add to playlist → STW Community Playlist in the three-dot menu — on phone or desktop. Every Saturday at 8 AM PT, a digest of the week's new additions lands in this topic automatically. 🎧
Telegram uses a simpler markup than the bots — just type **bold** and it bolds on send, or use the formatting menu on mobile. The URLs auto-preview the playlist cover so you don't need to attach an image.
Bot 3 — Prayer & Thanksgiving nudge bot (disabled)
- Status: Disabled as of May 2026. The Bible bot now posts a full Prayer & Thanksgiving blockquote as part of its daily message (Mon–Sat, 8 AM PT), so this separate nudge would double up. Re-enable under Editor → Telegram bot config → Prayer nudge bot if the day ever comes that we want the second touch again.
- What it did: Posted the daily prayer-and-thanksgiving reminder into the main channel.
- Secret:
TELEGRAM_PRAYER_BOT_TOKEN(still provisioned so flipping enabled: true brings it back without re-adding the bot to the group). - Workflow:
.github/workflows/daily-prayer-nudge.yml— still on the cron, but the script exits cleanly when enabled: false.
Bot 5 — Weekly prayer digest (disabled by default)
- What it does: Mon / Thu / Sat at 8 AM Pacific, posts a short summary of the prayer requests and thanksgivings shared in the Prayer & Thanksgiving topic since the last digest. Each entry is one bullet — attribution + a 1-2 sentence summary (~200 characters), never the full vulnerable text. Saturday's digest lands alongside the playlist digest so members see both as a single Saturday rhythm.
- Where: Prayer & Thanksgiving topic (thread 21).
- Schedule: 08:00 PT Monday, Thursday, Saturday. The first run after the system goes live (First_Run) does NOT post — it just marks the slot complete and starts the capture window from that moment, so going live doesn't dump a back-fill flood.
- Two-half pipeline: Telegram bots can't read history retroactively, so a continuous Poller runs every 15 minutes and writes captured prayers to
assets/data/telegram-prayer-log.json. The triweekly Digest_Poster reads that log and posts on Mon / Thu / Sat. Both halves gate on the sameprayer.digest.enabledflag. - Summarizer modes:
prayer.digest.summarizerintelegram-bot.jsonpicks how each entry gets compressed."rule-based"uses sentence-boundary truncation (no API needed, fully deterministic)."llm"calls Gemini 2.5 Flash via theGEMINI_API_KEYSecret for a 1-2 sentence summary in the human's voice — and gracefully falls back to rule-based on missing key, network failure, or content-filter trip. The digest always renders. - Secrets:
TELEGRAM_PRAYER_BOT_TOKEN(reused — same bot as the nudge above; no new bot needed). PlusGEMINI_API_KEYif running in"llm"summarizer mode. - Workflows:
.github/workflows/prayer-digest-poll.yml+.github/workflows/prayer-digest-post.yml - Test it: Actions → Prayer Digest Poller → Run workflow → dry_run =
trueto verify auth without mutating the log.
One-time setup
🔑 Disable BotFather privacy mode for the prayer bot
Without this, the bot only sees its own commands in the supergroup — non-command prayer messages are invisible to it and the digest stays empty. This is a one-time toggle:
- Open @BotFather in Telegram.
- Send
/mybots→ tap the prayer bot → Bot Settings → Group Privacy. - Tap Turn off. BotFather confirms with "Privacy mode is disabled for [bot]".
- Confirm the bot is in the
@seedthewordsupergroup as an admin with Manage Topics + Send Messages permissions on thread 21.
If you skip this, the Poller will run every 15 minutes and silently capture nothing. The fix is to come back to this card and flip privacy mode off — no code changes needed.
Optional setup
✨ Wire up Gemini for prettier digest summaries
This is optional. If you skip it, the digest still posts — it just uses the rule-based sentence-boundary truncator. With Gemini wired in, each bullet becomes a 1-2 sentence summary in the human's voice ("David asks for prayer over ministry direction.") instead of a truncation ("Members, what a pleasure it has…").
- Visit aistudio.google.com while signed in to the Google account that owns the ministry workspace.
- Click Create API key. Copy the key — you won't see it again. Keep it out of chats and never paste it into the codebase.
- In GitHub: Repo → Settings → Secrets and variables → Actions → New repository secret. Name it
GEMINI_API_KEY. Paste the key. Save. - Confirm
prayer.digest.summarizeris set to"llm"inassets/data/telegram-bot.json(the default after May 2026). - Run the next digest manually to verify: Actions → Weekly Prayer Digest → Run workflow → dry_run = false. Check thread 21 for the result.
What gets sent to Google: only the body text of messages that have already been posted to the public Prayer & Thanksgiving topic. [private]-tagged messages are filtered out at capture time and never leave our pipeline. [anon]-tagged messages have their FROM-field resolved to "Anonymous" before anything reaches Gemini.
Volume: at our pace (~3 digests/week × 15 entries) we use under 6% of Gemini's daily free quota. There is no expected scenario where this costs money.
If you ever want to disable it: flip prayer.digest.summarizer back to "rule-based" in telegram-bot.json and commit. The next digest reverts immediately, and the GEMINI_API_KEY Secret can stay in place dormant.
Per-member privacy
🔒 What members can do without asking an admin
Three opt-out paths are baked into the digest. None of them require admin action — they're discoverable in every digest's footer.
/skipdigest— any member can DM the prayer bot this command and they're permanently excluded from future digests. The bot replies once to confirm. To re-enable:/optindigest.[private]— prefix any single message in the topic with this tag and that one message is silently skipped (never makes it into the log, never appears in any digest). The member's other messages still flow normally.[anon]— prefix a message with this tag and it's still included in the digest, but rendered asAnonymousinstead of the member's display name. The original sender is recorded in the log for audit only and never rendered.
Members joining the supergroup will see these in the footer of every digest, so the privacy paths are always one tap away.
Going live
🟢 How to enable the digest
Default state on a fresh fork is disabled. To turn it on:
- Confirm the BotFather privacy step above is done.
- Confirm the sibling website prayer-intake form is also deployed (or accept that website-form submissions won't flow into the topic until it is — the digest still works for messages members post directly into Telegram).
- Open Editor → Telegram bot config → Prayer nudge bot → digest. Flip
enabledtotrue. Save → Commit & push. - Within ~15 minutes the Poller starts capturing. The first scheduled Mon/Thu/Sat morning after that, the digest posts.
To pause: flip the same flag back to false. Both the Poller and the Digest_Poster exit immediately at the enabled-flag short-circuit. Already-captured entries stay in the log; they'll be pruned by the 14-day cutoff if the system stays paused that long.
Recovery
🛟 If a digest didn't post on time
- Check Actions → Weekly Prayer Digest for a failed run. The error message is usually self-explanatory (most often a Telegram MarkdownV2 parse error from an unexpected character — rare but possible).
- Fix the cause. If you can't tell, replay with dry_run = true to see the rendered message Telegram rejected.
- Click Run workflow with dry_run = false to fire the digest now. The script computes the current Schedule_Slot in PT and is idempotent — if the slot is already marked complete, it logs "already posted this slot" and exits.
- If the digest posted but the log commit failed (rare race): manually edit
assets/data/telegram-prayer-log.jsonand add the slot tocompletedSlotsso the next run doesn't double-post. The slot ID format isYYYY-WNN-{mon|thu|sat}in PT (e.g.2026-W22-mon).
What to look for
📓 The on-disk Prayer_Log
Lives at assets/data/telegram-prayer-log.json. A healthy file looks like:
{
"schemaVersion": 1,
"lastUpdateId": 84321,
"lastModified": "2026-05-23T15:00:09Z",
"optOut": [],
"completedSlots": {
"2026-W21-thu": { "since": "...", "messageId": 4731, "entryCount": 4, "postedAt": "..." }
},
"entries": [
{ "chatId": -1001234567890, "messageId": 4521, "userId": 987654321,
"displayName": "Maria K", "kind": "telegram", "anonymous": false,
"text": "stripped body for summarizer", "ts": "2026-05-21T18:42:00Z" }
]
}
Don't edit by hand unless recovering from a stuck slot — the file is committed back by both workflows on every successful run, and any concurrent edit will lose to whichever workflow runs next.
Bot 7 — Saturday Discussion Icebreaker
- What it does: Every Saturday at ~9 AM Pacific, posts a single warm icebreaker question into the Discuss Scripture topic (thread 434), anchored in the week's three reading walks (NT, OT history, Poetry & Prophecy). The question is generated by Gemini from the 15 chapters Mon-Fri just covered; if Gemini is unreachable, falls back to a deterministic five-question rotation so the room always has a prompt.
- Where: Discuss Scripture topic (thread 434).
- Schedule: Saturday 09:00 PT, fired by the
kickSaturdayIcebreakerApps Script trigger. Lands ~1 hour after the daily Bible bot's morning post so the topic is the second thing members see, not competing for attention. - Secrets:
TELEGRAM_BIBLE_BOT_TOKEN(reused — same Bible bot as the daily reading post).GEMINI_API_KEY(optional; on missing key it gracefully degrades to the rule-based fallback rotation). - Workflow:
.github/workflows/saturday-icebreaker.yml - Test it from Apps Script: open the Apps Script editor, function dropdown → run
kickSaturdayIcebreakerTest. A clearly-labeled "(test)" preview lands in the Discuss Scripture topic within ~1 minute, using the most recent Saturday's readings if you're not running it on a Saturday. Bypasses the once-per-day dedup so you can hit the button as many times as you like while iterating on prompt or anchor. - Test it from GitHub: Actions → Saturday Icebreaker → Run workflow → set
test_run = true. Same outcome, available without opening Apps Script. - Privacy: The LLM only sees chapter references (e.g. "Mark 12, 1 Samuel 24") — never user content. Same approval level as the prayer digest summarizer.
- Idempotency: The script writes a once-per-day entry into
assets/data/telegram-bible-log.jsonafter a successful real (non-test) post, so a duplicate kick on the same Saturday is a no-op. Test posts never write the dedup log. - To turn it off: flip
bible.saturdayIcebreaker.enabledtofalseintelegram-bot.jsonand commit. The next kick will exit cleanly without posting.
If the icebreaker reads off this week
🧭 The walks driving the question
The icebreaker reads from the same three streams the Bible bot's daily footer renders: NT (anchored at bible-plan.js's 2026-04-30 = Mark 11), OT history (bible.layeredPlan.streams.otHistory.anchor), and Poetry & Prophecy (streams.poetryProphecy.anchor). If a stream's enabled flag is false, the icebreaker silently omits it from the prompt — no error, just narrower context for Gemini. Re-anchor any stream and the next icebreaker will reflect the new path the same Saturday.
Bot 8 — Bible Donate / Receive (disabled by default)
- What it does: A public-facing intake at
donate.html, reachable by a QR code printed on physical signs around Everett, Mukilteo, and Lynnwood. Two flows: Donate a Bible (coordination only — name, contact, count, drop-off vs pickup) and Receive a Bible (story-gated review — admin reads the story body, clicks Approve or Decline; decline is silent). Submissions land in a newBiblesSheet tab; admin notifications go toseedthewordministry@gmail.comlike every other email pipeline. Framed onstore.htmlas the inbound paired path of the same distribution mission. - Where: Form on
donate.html. Audit trail in theBiblesSheet tab. Admin notifications →seedthewordministry@gmail.com. Approve/Decline routes → web appdoGet. Handoff form (post-approval) → web appdoGet. - Schedule: Form is event-driven (one POST per submission). A 30-minute cron (
processBibleReviewReminders_) re-emails admins for anypending_reviewrow that's been waiting more than 48 hours. - Story gate: The receive flow's only required filter — beyond name + email + city — is the story field, bounded 80–1500 characters. Below 80 chars rejects (filters copy-paste spam visually). Above 1500 trims (bounds admin reading load). Anyone willing to write 80 honest characters about why they need a Bible passes.
- Decline behavior: Silent. The requester does not receive an email. The default-trust posture is "lean toward yes"; decline is reserved for clearly bad-faith submissions.
- Sign tracking: Each printed sign points at
donate.html?sign=<id>. The Apps Script writessign_idon every Bibles row so we can correlate which signs convert. Sign list lives atassets/data/sign-locations.json; QR SVGs atassets/images/sign-qr/<id>.svg. Re-runscripts/generate_sign_qr_codes.pyafter editing the JSON. - Privacy: Mailing addresses are collected ONLY post-approval, on the handoff form, and ONLY when the requester picks "mail". Pending and declined rows never have an address on file.
- Secret:
BIBLE_REQUEST_REVIEW_SECRET— new HMAC signing key for approve/decline/handoff tokens. Generate withopenssl rand -base64 32. - Lives in:
docs/apps-script/order-handler.gs(extends the existing web app) +assets/js/donate.js+donate.html+assets/data/sign-locations.json.
One-time setup
🚀 Deploy the donate / receive flow
Four steps. The handler reuses the existing web app — no new deployment URL.
- Open script.google.com → STW Order Handler. Copy the latest
docs/apps-script/order-handler.gsfrom the repo and paste overCode.gs. Save. - Project Settings → Script Properties → Add script property: name
BIBLE_REQUEST_REVIEW_SECRET, value the output ofopenssl rand -base64 32(any 32+ random characters work). Save. - Run
installAllTimeTriggersfrom the function dropdown to register the newprocessBibleReviewReminders_30-minute trigger. Confirm the log line says "installed 14 triggers". - In the admin editor: Telegram bot config → Bible donate / receive. Paste the deployed web app URL into
endpointUrl. Flipenabledto true. Save & commit. The CTA buttons ondonate.htmlgo live on the next deploy.
The first submission will auto-create the Bibles tab on the ministry Sheet via the openTab helper — no manual tab creation needed.
Reviewing a request
📖 What the admin team sees when a request lands
Every receive submission emails seedthewordministry@gmail.com with the full story body in a blockquote and two big buttons: Approve and email handoff form (green) and Decline silently (white). Click Approve and the requester gets an email with a link to the handoff form. Click Decline and the row is marked declined; the requester is not notified.
If you need to add a private decline reason for the audit trail, append &reason=<short note> to the decline link before clicking. The reason is stored in the Bibles row's decline_reason column (admin-eyes-only).
If a request sits unreviewed for 48 hours, the cron re-emails the admin team once with the same buttons. Only one reminder per request.
Where the signs are
🧭 Active sign placements
Loading…
Edit the list in the admin editor → Bible donate signs. After editing, run python scripts/generate_sign_qr_codes.py to (re)generate the QR SVGs in assets/images/sign-qr/. Print the SVG into the physical sign Sam Petrov designs.
Bot 6 — Prayer Request Intake (disabled by default)
- What it does: Two cards on
community.html(under the live Telegram dashboard) open a quiet modal where members can submit a prayer request or a thanksgiving announcement. Each Submission is appended to a privatePrayersSheet tab (audit-only — admin eyes only), then relayed into the Prayer & Thanksgiving Telegram topic (thread 21) using the same(via the website)marker the Bot 5 digest already recognizes. Submitters who shared an email get a four-step encouragement drip (Day 0 confirmation, Days 3, 7, 14) with one-click unsubscribe baked into every message. - Where: Form on
community.html. Telegram relay → Prayer topic (thread 21). Drip → email viaMailApp. Unsubscribe → web app GET handler. - Schedule: Form is event-driven (POST per submission). Drip cron fires every 30 minutes via Apps Script time trigger.
- Producer ↔ consumer contract: This intake is the producer half whose consumer is Bot 5. The two are wired together by the literal
(via the website)substring set atprayer.intake.markerhere AND atprayer.digest.websiteSubmissionMarkerin the digest spec. Editing one without editing the other to the identical value breaks recognition — the digest will silently skip every website submission. The_helpnote intelegram-bot.jsonrepeats this warning at the top of theprayer.intakeblock. - Audit-first ordering: The handler always appends the
Prayersrow BEFORE calling Telegram. If Telegram fails, the row stays withtelegram_status='failed'and the form shows a soft "received but not yet visible" message. A Submission can never produce a Telegram message without a corresponding audit row. - Secret:
TELEGRAM_PRAYER_BOT_TOKEN(reused — same bot as Bot 5; no new bot needed). Plus a new HMAC signing key,PRAYER_INTAKE_UNSUBSCRIBE_SECRET, used to sign the per-Submission unsubscribe tokens. - Lives in:
docs/apps-script/order-handler.gs(extends the existing web app) +assets/js/prayer-intake.js+docs/apps-script/prayer-drip-templates.json.
One-time setup
🚀 Deploy the prayer-intake action
Three steps; mostly Apps Script work. The installPrayerIntake bootstrap function does most of the heavy lifting — Sheet tab creation, headers, data-validation dropdown, the unsubscribe HMAC secret, and the every-30-min drip trigger — in a single run.
- Paste the updated
order-handler.gsinto Apps Script. Open script.google.com asseedthewordministry@gmail.com→ open the existing STW Order Handler project → copy the full contents ofdocs/apps-script/order-handler.gsfrom the repo, paste overCode.gs, save. - Run
installPrayerIntakefrom the editor. In the function dropdown at the top of the editor (next to the ▶ Run button), pickinstallPrayerIntake→ click ▶ Run. Authorize the Sheets + Triggers scopes when prompted. The execution log will show six green check lines and two deep-link URLs to the new tabs. This is the one step that creates thePrayers+PrayerDriptabs with their column headers, generates and storesPRAYER_INTAKE_UNSUBSCRIBE_SECRET, and installs the every-30-min drip trigger. Idempotent — safe to re-run; will not rotate the secret if one already exists. - Click Deploy → Manage deployments → ✏️ pencil → New version → Deploy. The web-app URL stays the same — no config edit needed. The form on
community.htmlis already enabled and pointed at the existing web-app URL, so the round-trip works as soon as the new version is published.
Verify it works: open the live community.html, click 🙏 Submit a Prayer Request, fill in the form with your own name + a short body, submit. Within ~30 seconds you should see (a) a new row in the Prayers tab with telegram_status='sent', (b) a Telegram message in thread 21 with the 💌 New prayer request from … (via the website): … shape.
To enable the four-step encouragement drip emails: in the in-browser admin editor → Telegram bot config → Prayer → Intake → flip dripEnabled to true. Save → Commit & push. New submissions with an email will start receiving the Day 0 confirmation immediately and Days 3 / 7 / 14 follow-ups via the cron. Submissions made while dripEnabled = false do not retroactively get a drip — the audit row records drip_status='disabled-by-config' and that's final.
The marker contract
🔗 prayer.intake.marker ⇔ prayer.digest.websiteSubmissionMarker
These two values form one contract. The intake produces messages containing the literal substring; the digest's regex matches it. Editing one without editing the other to the identical value breaks recognition. Always edit them together and commit in the same change. The default (via the website) works as-is; only change it if you have a reason to and you are touching both files.
Where the audit lives
📓 The Prayers Sheet tab
On the existing ministry Sheet (same one orders / contact / stories use). Append-only and admin-only — never surfaced on the website, never in Telegram, never in any drip email. The real submitter_name and submitter_email are recorded even when the submitter checked the anonymous box (per Requirement 5.5 — admins reviewing pastoral follow-up need this backstop). The anonymous column is TRUE in those rows so an admin can see the submitter chose the public-anonymity path.
No admin dashboard is built — pivot tables and filters live in the Sheet itself.
Recovery
🛟 An audit row shows telegram_status: failed — what now?
- Open the
Prayerstab. Find the row. - Copy the
bodyandsubmitter_namevalues. (Ifanonymous=TRUE, attribute the manual repost asAnonymousrather than the real name.) - From the prayer bot's account, post into the Prayer & Thanksgiving topic (thread 21) using one of the four marker shapes:
💌 New prayer request from <name> (via the website): <body>💌 New prayer request from Anonymous (via the website): <body>💌 New thanksgiving announcement from <name> (via the website): <body>💌 New thanksgiving announcement from Anonymous (via the website): <body>
- Leave the audit row alone — keep it as the historical record of what happened. Do not edit
telegram_statusmanually; the next failure would look like the same row.
If a PrayerDrip row is stuck in pending past its timestamp: check the Apps Script execution log for processPrayerDrip_. The row will retry on the next 30-min run. If it consistently fails, fix the underlying cause (template fetch error, mail quota, etc.) and the row will catch up on its own.
Privacy contract
🔒 What unsubscribe does and does not do
Every drip email carries a one-click unsubscribe link of the shape <web-app-url>?action=prayer-unsubscribe&token=<hmac>. The token is an HMAC of the Submission's UUID signed with PRAYER_INTAKE_UNSUBSCRIBE_SECRET, so a leaked link cannot be forged into a different Submission's unsubscribe.
- It DOES: flip every
PrayerDriprow for thatsubmission_idtounsubscribed=TRUE. Pending rows additionally havestatuschanged tounsubscribedso the cron skips them. Future drip cron runs observe the flag and skip the row. - It DOES NOT: remove or rescind the Telegram message that was already posted to the prayer topic at submission time. The Telegram post is irreversible from the intake side; an admin can manually delete the message via the prayer bot for pastoral reasons.
- It DOES NOT: auto-unsubscribe future Submissions from the same email address. Each Submission has its own token. A submitter who returns later with a new prayer can choose their privacy posture again.
The unsubscribe handler is idempotent — clicking the same link twice is a no-op the second time, and forged tokens render the "invalid or expired" page without modifying any row.
The drip cron
⏰ processPrayerDrip_ runs every 30 minutes
The Day 0 confirmation email fires inline at submission time (not via the cron). Days 3, 7, 14 are written as pending rows in the PrayerDrip tab with their scheduled fire times (received_at + N days). The cron is a one-pass scan of the tab:
- If
prayer.intake.dripEnabledisfalse, exit immediately. - For each row where
status='pending'ANDunsubscribed!=TRUEANDtimestamp<=now: take a per-row script lock (15-second wait), re-read the row inside the lock to confirm still pending, render the email, send viaMailApp, marksent(orfailedwith the error). - Always release the lock in a
finally.
The lock prevents two overlapping trigger invocations from double-sending a row. MailApp.sendEmail shares the existing daily-quota budget with order emails, contact replies, and the weekly digest — at the four-emails-per-Submission rate this is fine.
The 7-bot system at a glance
Each bot handles its own topic, on its own schedule
Announce bot
Calendar events → Announcements topic (thread 553). Runs every 15 min Mon–Sat, inside quiet-hours 07:00–21:00 PT. LIVE events bypass quiet hours.
Bible bot
Daily NT reading (Mon–Fri) + Prayer & Thanksgiving block + Saturday Study Saturday Live teaser → General channel. Runs 8 AM Mon–Sat.
Playlist digest
Weekly "what's new in the community playlist" → Worship & Music topic (thread 1064). Runs 8 AM Saturday. Skipped when nothing was added that week.
Prayer nudge (disabled)
Dormant — the Bible bot now carries the daily Prayer & Thanksgiving block. Re-enable anytime from the editor.
Prayer digest
Mon / Thu / Sat 8 AM PT, posts a short summary of recent prayers & thanksgivings into the Prayer topic (thread 21). Disabled by default; flip on after the BotFather privacy step.
Prayer Request Intake
Two cards on community.html → quiet modal → audit Sheet → relay to Prayer topic + four-step encouragement drip with one-click unsubscribe. Disabled by default; flip on after Bot 5 is live and the Prayers + PrayerDrip tabs exist.
Layered reading plan
Quiet "Going deeper today" row on community.html plus a matching footer on the Mon–Fri Bible Telegram post. Four optional companion streams (OT walk, Poetry & Prophecy, Psalm, Proverbs). Mon–Fri only — silent on Sat/Sun. Ships enabled.
Bot 7 — Layered Bible Reading Plan (ships enabled)
- What it does: Adds a quiet "🌿 Going deeper today" row on
community.htmldirectly below the existing weekly NT-strip card, and a matching short footer on the Bot 2 Bible post for Mon–Fri. Up to four pills per weekday: OT walk (Genesis–Esther, one chapter/day), Poetry & Prophecy (Job, Ecclesiastes, Song of Solomon, Isaiah–Malachi excluding Psalms and Proverbs, one chapter/day), Psalm of the day, Proverb of the day. Saturday and Sunday: the row does not render and the Telegram footer is omitted (Sat keeps its Study Saturday Live post unchanged; Sun the bot does not run). - What stays untouched: the existing NT walk in
assets/js/bible-plan.jsis frozen — same anchor (Mark 11 on Thu Apr 30 2026), same sequence, same date math. The Saturday Review block inassets/js/study-saturday.js+assets/data/study-saturday.jsonis also frozen — same two pills, same JSON keys, same admin schema. No admin action against either of those is part of this feature. - Where: Front-end:
community.html+assets/js/layered-plan.js+ CSS inassets/css/main.css. Telegram: extends.github/scripts/post_daily_bible_to_telegram.py(GitHub Actions Mon–Fri runner) AND thedailyBibleBotflow indocs/apps-script/order-handler.gs(Apps Script time-trigger backup). Both ports read the same config block and produce byte-equal chapter references. - Schedule: Website renders on every page load of
community.html. Telegram footer rides along on Bot 2's existing Mon–Fri 8 AM PT cron — no new schedule. - Newcomer page: a separate canonical page at
start-here.htmlrenders the locked 30-entry plan fromassets/data/newcomer-30day.json. Reader-relative — day 1 is whatever day the reader picks it up. The QR card on bundles (guide-newcomer-qr) points at this URL. - Lives in:
assets/js/layered-plan.js,assets/data/newcomer-30day.json,start-here.html, plus thebible.layeredPlanblock inassets/data/telegram-bot.json.
Daily configuration knobs
🎛️ bible.layeredPlan in telegram-bot.json
One config block, three consumers (browser renderer + Python Telegram bot + Apps Script port). Edits flow through to all three on the next render or the next scheduled post — no redeploy needed.
enabled(defaulttrue) — master switch.falsehides the row on the website AND drops the Telegram footer entirely, regardless ofincludeInTelegram.includeInTelegram(defaulttrue) — Telegram-only switch.falsedrops the footer from the Mon–Fri post; the website row keeps rendering normally.timezone(default"America/Los_Angeles") — IANA tz used for weekday detection and the Psalm/Proverb day-of-year / day-of-month math. Pin this and forget it.streams.otHistory.enabled(defaulttrue) — toggles just the Genesis–Esther pill. The other three streams stay rendered.streams.otHistory.anchor(default{date: "2026-04-30", book: "Genesis", chapter: 1}) — the calendar date paired with the chapter that should show on that date. Re-anchoring shifts every future day's reading by the date delta.streams.poetryProphecy.enabled+.anchor— same pattern, default anchor is Job 1 on 2026-04-30 so all three weekday walks advance together.streams.psalm.enabled/streams.proverbs.enabled(defaulttrue) — Psalm of the day uses((dayOfYear − 1) mod 150) + 1; Proverb of the day usesmin(dayOfMonth, 31). Both compute every day; rendering on weekends is suppressed at the row/footer level.
Operator playbook
🔧 Quiet a stream, re-anchor a walk, replace the QR card
- Disable a single companion stream — Editor → Telegram bot config →
bible→layeredPlan→streams→ pickotHistory/poetryProphecy/psalm/proverbs→ flipenabledtofalse→ commit. The website drops that pill and the Telegram footer drops that line on the next render / next scheduled post. - Disable the whole feature — flip
bible.layeredPlan.enabledtofalse. Both surfaces go silent immediately. - Disable just the Telegram footer — flip
bible.layeredPlan.includeInTelegramtofalse. Website row keeps rendering normally. - Re-anchor a weekday walk — edit
streams.otHistory.anchororstreams.poetryProphecy.anchor. Re-anchoring shifts every future date's reading by the date delta — confirm with the team before changing. - Where the QR card points — the
guide-newcomer-qrbundle item points atstart-here.htmlon the live site. The 30-entry plan is inassets/data/newcomer-30day.json; intermediatenotetext can be edited freely, the day-1 / day-30 labels (John 1 / 2 Timothy 3) and the four week themes are locked. - Regenerate the QR PNG/SVG — encode the URL
https://seedtheword.github.io/seedtheword/start-here.htmlwith any QR generator (qrencode -o newcomer.png "https://…"from the command line, or any free online generator). Test-scan it from a phone before sending bundles to print. - Adding audio recordings later (Phase 2) — open
assets/data/bible-spotify-map.json. Four optional buckets (oldTestamentChapters,poetryProphecyChapters,psalmChapters,proverbsChapters) ship empty. Adding a key like"Genesis 5"or"Psalm 23"lights up the 🎧 affordance on the matching pill on the next page load — no code change needed. Silence is honest while no recording exists.
Out of scope (don't ask the row to do these)
🚫 What this feature deliberately doesn't do
- No Russian-language pairing for the four companion streams. Russian deferred to a future
community-ru.html. - No per-user reading-progress tracking. The plan is reader-relative — you read it at your pace.
- No audio recordings for the four new streams in v1. Phase 2.
- No multi-post-per-day Telegram channel. The footer is one short block at the end of the existing Mon–Fri post.
- No admin-editor UI for the OT-walk weekday selections. The two walks are deterministic from their flat sequence and their anchor.
Adding a Spotify episode for a specific chapter
- Open
assets/data/bible-spotify-map.jsonin the repo. - Inside
"chapters", add an entry keyed by the exact reading label frombible-plan.js. Examples:"Mark 16","1 Corinthians 13","John 3". - Value is the full Spotify URL (episode preferred, show is fine too).
- Remove the
__example_Mark 16key that's there as a placeholder. - Commit. The next Bible post that matches that chapter will use your link; chapters you haven't mapped still post with the default show URL.
Troubleshooting — the Telegram workflow failed, what now?
Common situation
⏰ A scheduled bot didn't fire on time (morning post missing)
Most common cause: GitHub Actions free-tier cron triggers are "best-effort" — GitHub delays or silently drops scheduled runs during peak hours when runner capacity is saturated. Low-activity repos get deprioritized first.
Our mitigation: an hourly heartbeat workflow commits a timestamp to .github/heartbeat/last-tick.txt so the repo looks active. This greatly reduces (but doesn't eliminate) skipped runs.
If a post is missing:
- Open Actions.
- Pick the workflow that should have run (e.g. Daily Bible Reading).
- Click Run workflow → dry_run: false → Run. The post fires immediately.
- If the cron has been dropping runs for more than a day, open Actions → Heartbeat workflow and verify recent runs are green.
Manual Run workflow always executes right away — it bypasses the cron scheduler entirely.
Common error
403 Forbidden: bot is not a member of the supergroup chat
The bot account itself hasn't been added to (or was removed from) the target group yet. The bot token is fine; the bot just doesn't have a seat at the table.
Fix:
- Find the bot's
@username. Open Telegram → BotFather →/mybots→ pick the bot → its public handle is listed there. - Open the
@seedthewordsupergroup in Telegram. - Tap the group title → Members (or Subscribers) → Add Member.
- Search for the bot's
@usernameand add it. - Tap the bot in the member list → Promote to Admin. Toggle Post Messages ON, and (for forum-style groups with topics) Manage Topics ON.
- Re-run the workflow. The 403 will clear.
The announcements bot, Bible bot, and Prayer bot are three separate bot accounts with three separate tokens — each one needs to be added to the group on its own. Adding one doesn't cover the others.
Common error
400 Bad Request: message thread not found
The messageThreadId in telegram-bot.json points to a topic that no longer exists. Find the real id by opening the target topic in Telegram — the URL looks like https://t.me/<chat>/<threadId>/<messageId>. Copy the middle number. Update it from the ✏️ Editor under Telegram bot config, commit.
Common error
400 Bad Request: can't parse entities
A MarkdownV2 character got through unescaped — usually inside a link label or italic run. Re-run the workflow with dry_run=true to see the raw payload, then look at the line Telegram's error message points to. If the content came from a calendar event description, the fix is to remove the offending character from the event description.
Turn a bot off, change its topic, or change a schedule
- Pause a single bot: set
"enabled": falsein its section oftelegram-bot.json, commit. The scheduled workflow still runs but exits without posting. - Change destination: update
chatIdormessageThreadIdin the relevant section, commit. - Change a schedule: edit the cron line in that bot's workflow YAML. Reminder: cron uses UTC, so PT hours need a +7 (PDT) or +8 (PST) offset.
Adding a new bot to the system
- Create it with
@BotFather→/newbot. Save the token. - Add the bot to
@seedthewordand promote it to admin with "Post Messages" permission. - Add the token to repo Settings → Secrets and variables → Actions with a new name like
TELEGRAM_XYZ_BOT_TOKEN. - Ping the dev on duty to add a new section to
telegram-bot.jsonand a new workflow under.github/workflows/.
401 Unauthorized) or (2) the bot lost admin permission. Fix the permission or regenerate the token via BotFather /revoke, then re-run manually.
🔐 Where secrets live (NOT here)
We intentionally don't store API keys, passwords, or tokens on this page. Secrets live in:
- GitHub Secrets (repo → Settings → Secrets) — used by Actions
- Service dashboards (rss.app, Formspree, etc.) — each has its own login
- The ministry's shared password manager (Bitwarden, 1Password, or similar — set this up if you haven't)
📝 Recent changes
Big-picture history of what's on the site and when it was set up:
- May 2026 (current) — Site-wide reorganization. Homepage showcase renamed to "Featured Ministry Feed" with a 9-category rotation (announcements, outreach, highlights, store, ministry initiatives, Friends in Jesus, today's verse, How We S.E.E.D., tips/resources) pulling live data from each source. Path of Light waypoints renamed to Plant the Seed / Water to Grow / Gather the Harvest. About page restructured: "About S.E.E.D. Ministry" → "Our S.E.E.D Story"; the 9 stitch tiles' bottom row rewritten to the three phases; "How We S.E.E.D." cards rewritten (Bible reading, fellowship schedule, Study Saturdays, Prayer & Worship with Philippians 4:6-8 and John 4:23-24, Outreach); Contact heading renamed to "Contact us - @seedtheword". Community page got a 4-up platform CTA row (Telegram / Spotify / Twitch / Instagram) with equal billing, replacing the old Telegram-only big card; "Daily Touchpoints" renamed to "Learn more about us online". Study Saturday livestream card now merges the weekly review (Old/New Testament passages) into one card. Admin editor got a structured form for the Telegram bot config (all three bots + quiet hours + reminder minutes), the Recommendations legacy copy-paste flow was retired (
legacyCopyPasteflipped to false), and cache busters bumped to v=12. Mobile: seed-story tiles now open on tap (or keyboard Enter/Space) — no longer hover-only. - May 2026 — Homepage rebuilt as Hero → Featured From Our Ministry → Walking the Path of Light. Dropped the separate "Today at Seed the Word" dashboard section (Showcase Carousel already covers rotating content). News page gained "Ministry Outreach & Highlights" (3 carousel cards + existing 9-tile grid); removed "Ways to Help". Calendar events now have a 📣 Share Event dropdown (Telegram, WhatsApp, Instagram, Device share, Copy) that pre-fills the ministry's announcement template with Apple/Google maps links. News page has Open Graph tags for rich link previews on Telegram. Admin help got a searchable interface, Mermaid flowcharts, and an interactive Recommendations builder with live Spotify/YouTube previews.
- May 2026 — Store page rebuilt around three tone-differentiated bundles (Essentials, Life Group, Ministry calling). Each bundle card has a rotating slideshow powered by per-folder
images.jsonmanifests. Store header renamed to "Love. Gift. Repeat — that's Jesus." Added support-the-ministry PayPal quick-give panel. Bundle-builder page rebuilt to match aesthetic. - May 2026 — "A message from our friend, Ruslan Y." permanent video card added to Community page. Partner Ministries + What We're Listening To migrated from JS arrays to
assets/data/recommendations.json(admin-editable via the builder tool). Media drop folded into the News outreach callout with Google Forms integration. - May 2026 — Added Spotify podcast embed, 4-icon header/footer socials, mobile nav fix, announcement timezone fix, 3-column contact section with Formspree. Fixed livestream "OFFLINE" showing on Saturdays before 7 PM PT.
- April 2026 — Community page redesign (Discussion, Stay Connected, Friends in Jesus), Instagram feed via rss.app, bible-reading plan, Showcase Carousel on homepage, calendar event modal with 5-tier color codes, month-themed calendar backdrops.
- Earlier — Initial site build, Netlify CMS setup then removed, Google Calendar integration, ministry highlights grid.
🆘 If everything is on fire
- The site deploys from
main. To revert a bad change: repo → Commits → find the last good commit → "Revert." - If you can't figure out what broke, email the dev on duty. Include a screenshot of the broken page and the browser console if possible.
Seed the Word