Friendly-Community.com
Administrator manual β key procedures, warnings, and delicate handling. Compiled July 2026.
Overview
This is a private community forum for residents of 100 South Doheny, built and maintained by Pierre as a solo, non-technical developer working with Claude as development partner. It is a single-page HTML frontend deployed via FTP, backed by a Google Apps Script server and a Google Sheets database β no traditional hosting, no formal backend framework.
This manual exists to capture what would otherwise only live in scattered conversation history: the conventions that keep the site stable, the places where a small mistake can cause real damage, and the reasoning behind decisions that might otherwise look arbitrary to a future reader (including a future Pierre).
Architecture
| Layer | What it is |
|---|---|
| Frontend | One large HTML file (index.html), plain JavaScript, no framework. Edited in BBEdit, uploaded via FileZilla. |
| Backend | Google Apps Script (Code.gs). Deployed as a Web App; the frontend calls it via fetch() to a hardcoded script URL. |
| Database | A single Google Sheet with many tabs β residents, chat threads, contractors, mailing lists, logs, and site file registrations. |
Sent via GmailApp.sendEmail(), authenticated as Pierre's real Google Account, using noreply@friendly-community.com as a display alias. |
Critical Terminology β Never Conflate These
| Term | Meaning |
|---|---|
| Suspend | Blocks login. Reversible. Visible to members as [Post suspended by moderator]. |
| Delete | Former resident. Permanent (soft-delete flag, not row removal). |
| Unsubscribe | Stops mass communications. Keeps full forum access and login. Stored as a status value on the Registered sheet β an unsubscribed resident is still fully registered. |
| Total Hide ("occult") | Completely invisible to members, no trace, no notice β different mechanism from Suspend. Set via the Cleaning Posts admin tool. |
| Registered vs Members | Members sheet = the resident roster (imported/maintained by Pierre). Registered sheet = who actually created an account. A person can be a Member without being Registered. |
| TEST_MODE | One master Script Property. Controls WHO receives mass email (test addresses vs real residents). Does not control what counts as new activity β see the Testing section below. |
Deployment Rules
FIND/REPLACE only β never full-file rewrites
Full-file rewrites have caused repeated regressions historically. Every code change should be a precise, verified FIND/REPLACE against the actual current file β never a wholesale rewrite.
Verify before editing
Always view the actual current file/line before proposing an edit. Guessing at code shape from memory or from an earlier session has caused wasted cycles and incorrect fixes more than once.
Syntax + duplicate-function check before every deployment
Run a syntax check and a duplicate-function scan before any file goes live. Only updateAdminBtn and doLogout are known-harmless pre-existing duplicates β see Known Harmless Duplicates.
BBEdit quirks
BBEdit's Find/Replace can silently fail on multi-line blocks containing em-dashes, emoji, or middle-dot characters β even when nothing looks unusual. When Find reports 0 results but the text is visibly present, switch to Go-To-Line plus manual select/paste rather than repeatedly retrying Find/Replace.
Apps Script: saving β deploying
Testing Safely
weeklyBroadcastLastRun is a single Script Property. Every execution of weeklyBroadcast overwrites it to "now," with no distinction between a manual test and the real Sunday trigger. Testing repeatedly in quick succession, close to a real scheduled send, can cause the real send to miss activity that happened between tests. It is only safe to test freely when the last real send is well in the past and the next one is days away.
Safe testing checklist:
- Check whether a real (Live Mode) broadcast has already gone out this week. If yes, testing today is safe regardless of timestamp effects.
- If testing close to a real scheduled send: note the current
weeklyBroadcastLastRunvalue before testing (Apps Script β Project Settings β Script Properties), then restore it afterward. - To guarantee a test run finds "new" activity without fighting the timestamp: post a genuinely new test message immediately before running β it will always be newer than any prior cutoff.
- Clean up test content (chat posts, contractor entries) before the next real send if it shouldn't reach real residents.
Tests Prior to Communications
Every mass or targeted send carries real risk: wrong content, wrong recipients, or an incomplete send that looks successful. The checklist below distills lessons from real incidents (July 12β13, 2026) into a repeatable pre-send routine.
Before sending to more than a handful of people:
- Send a test copy to yourself first. Confirm the subject, body text, images, and every link/button actually work β including clicking through to the destination page, not just visually inspecting the email.
- Re-verify the recipient list is current, not stale. If any exclusion was computed earlier in the session (e.g. "everyone except those who already registered"), rebuild it fresh immediately before sending β registration status changes in real time, and a list computed even an hour earlier can already be wrong.
- Use "Paste emails to select" for any list built outside the app. Manually checking boxes one at a time for anything beyond a handful of people is slow and error-prone; paste the exact email list instead and confirm the "Selected X of X" count matches exactly.
- The Recipient Selector's save/restore was fixed July 19, 2026 β selections are now keyed by unit+email pair (not email alone), so saving and reopening correctly restores exactly what was checked, including for multi-unit owners with only some units selected.
- After sending, reconcile against reality β the Targeted_Send_Progress tab for batched sends, or a Gmail Sent-folder search for older-style sends β before assuming everyone on the list received it.
after:/before: operators accept exact epoch timestamps, not just dates β to isolate one send from the other.For any send to roughly 100+ recipients: use the batched sending system (see Targeted Letters) rather than a single-pass send. It sends in small batches via a time-based trigger, staying safely under the 6-minute execution limit, and logs each recipient's status in real time in the Targeted_Send_Progress tab β turning "did everyone get this?" into a simple question to check rather than an investigation to run.
Backups & Recovery
Three backup copies of all site files: FTP (live), Dropbox, and a local non-cloud Working Folder.
/d/ and /edit) between the two locations; if identical, it's the same file wearing two hats.For a genuinely independent structural backup ("Skeleton"): File β Make a copy in Google Sheets creates a truly separate document with its own ID. Delete all data rows, keep only headers, to get a safe reference for the schema alone.
For a genuinely independent data backup: File β Download β Microsoft Excel (.xlsx) β this actually exports the data out, with zero ongoing connection to the live sheet.
Moderation: Hide / Suspend / Delete
| Action | Visible to members? | Reversible? | Where |
|---|---|---|---|
| Total Hide | No β completely invisible, no trace | Yes, via Show All | Cleaning Posts tool |
| Suspend | Yes β shows [Post suspended by moderator] | Yes | Standard moderation controls |
| Delete (member) | Removed from roster | Soft-delete, flag-based | Master List Maintenance |
Total Hide is implemented via a hidden column value of "occult" on chat rows, matched by email extracted from the 3-part userId (lastName|unit|email). It never touches rows where hidden === "true" (a real Suspend), keeping the two mechanisms independent.
admin|1104), not 3-part. Code that extracts an email from the last |-segment of userId must special-case this, or Administrator's own posts/actions will silently fail to match.Registration & Login
Session persistence is implemented via localStorage (fc_session), with a sliding 30-minute idle timeout. Login also remembers the last-used Last Name and Unit (fc_lastLogin_lastName / fc_lastLogin_unit) for convenience on return visits β deliberately excluded for Administrator, since "Admin"/"1104" pre-filling on a resident's screen would be confusing.
admin|1104 β code checking admin identity should use .startsWith('admin|1104'), not an exact match, consistent with the 3-part key migration applied to all other residents.Registration: One Screen, Two Modes
There is only one registration screen and one doRegister() function β it branches on whether the incoming URL has an ?email= parameter, giving two distinct behaviors from the same code path:
- Invited mode (URL has
?email=X&unit=Y): the form is pre-filled and the email field is locked. This is the correct mode for anyone reached via a personalized link. - Walk-up mode (bare
index.html, no parameters): a blank form, fully self-service. Correct for someone finding the site on their own.
index.html link with no parameters β which fixed the immediate breakage, but silently meant every recipient landed in Walk-up mode instead of Invited mode. It's a legitimate emergency fallback, but not the correct long-term pattern.The correct pattern for any new targeted or invitation-style letter is buildRegisterLink(email, unit), which generates a proper personalized link β the same helper used by both Hello Neighbor and Targeted Letters (see below). Using {{registerLink}} in a letter template and letting the send function substitute it per-recipient is the supported approach; hand-building a link, or falling back to a bare link, should be treated as a temporary measure only.
Contact Administrator
Non-registered visitors on the bare landing page see a "Kindly registerβ¦" prompt instead of the real contact form β this only applies to the landing page specifically; Login, Register, and Forgot Password screens still allow real contact (visitors mid-registration aren't blocked).
Guests who do reach the real form must pass a server-side check: at least 2 of 3 of Email / Unit / Last Name must match the same row in the Registered sheet before their message is allowed through. Logged-in users skip this check entirely β their password login is already stronger verification.
Weekly Broadcast
Runs via a Sunday 2:00 AM Pacific time-driven trigger, calling weeklyBroadcast(). Scans all Chat_ and Contractor_ sheets for activity since the last recorded run, and β only if there's something new β emails a recap to Mass_Mail_List, filtered by exclusions and opt-outs.
Each recipient gets a different button and message depending on whether they're already registered:
- Already registered: a "Go to the Forum" button linking to
?login=1, pre-filled with their last name and unit (never their password) β plusmessage_to_already_registered.html, fetched live from the site. - Not yet registered: the original "Join Our Community Forum" registration button, pre-filled with email/unit β plus
message_to_non_registered.html.
Both message snippets are separate, editable HTML files fetched fresh on every send (same pattern as sendTargetedLetter), with safe built-in fallback text if either file is ever missing or unreachable.
Contractors
Adding a contractor recommendation requires: Trade/Category (routing key β determines which Contractor_ sheet tab the entry is written to; cannot be optional without risking an orphaned sheet), Contact Name, and a Phone Number (at least 10 digits). Company Name, Email, and Comments are optional.
Hello Neighbor
There are two separate, complementary ways to send the Hello Neighbor letter β not duplicates of each other, and not a "pick one and retire the other" situation:
| π΅ Blue "Send hello neighbor" button | Targeted Letter + Recipient Selector | |
|---|---|---|
| Recipients | Automatic β everyone eligible in Mass_Mail_List | Manual β you choose exactly who |
| Best for | A genuine mass invite to everyone unregistered | A specific person, unit, or scoped group (e.g. one floor) |
| Batching / timeout safety | Not batched β same 6-minute execution risk the old GlitchFix bug had | Batched, trigger-driven β safe at any size |
| Test Mode | Fully supported β dry-runs against Test_Mail_List | Not available β refuses to run unless Live Mode is already on |
| Reporting | Hello Neighbor report (Reports card) | Targeted Letter report (Reports card) |
Both paths send the same underlying Hello_Neighbor.html template and personalize it identically via buildRegisterLink(email, unit).
Targeted Letters
The Recipient Selector shows every Member except those marked deleted on the Members roster. The Status column reflects the person's actual Registered-sheet standing: Not Registered, β Registered, β Registered (Suspended), or β Registered (Unsubscribed) β registration is always shown as true when it's true, with Suspended/Unsubscribed layered on as a qualifier rather than replacing it (a person can absolutely be both registered and unsubscribed).
Multi-unit checkbox behavior
Each Members row (one unit + one email) is tracked independently. Checking a row you haven't touched yet auto-selects every other row sharing that same email β a deliberate convenience for multi-unit owners. Unchecking any single row only affects that row β sibling units stay exactly as they were. This means a scoped selection (e.g. "everyone on floors 3β12 for an HVAC notice") won't be derailed just because one of those owners also has an unrelated unit elsewhere in the building β check the range, then individually uncheck anything that doesn't belong.
Show selection (left action panel) opens a live list of everyone currently checked β unit, name, email β so you can review the full picture before committing to Save or Send. It stays live while open: further checkbox changes, Save, or Clear all update it immediately rather than showing a stale snapshot.
Communications Report
Admin Panel β Reports card β Open. A full-page, read-only viewer covering all four mass-communication functions, so "did this actually send?" is a lookup instead of an investigation through the Gmail Sent folder or Apps Script's Executions log.
| Report | Shows |
|---|---|
| Targeted Letter | Every send since the report was added β status, error (if any), letter, recipient, unit, mode, timestamp |
| General Letter | Same shape, from the existing General_Letter_Log |
| Hello Neighbor | Same shape, new dedicated report tab |
| Weekly Broadcast | One summary row per run β timestamp, recipient count, mode (no per-recipient detail, by design) |
Mode: Test β a safe way to verify the reporting pipeline without touching real residents. Targeted Letter cannot be tested this way: it refuses to run at all while Test Mode is on, so nothing reaches its report until a real Live Mode send.Credentials
Email Deliverability
SPF, DKIM, and DMARC are all confirmed passing for friendly-community.com. Yahoo-specific spam-folder placement has still been observed, including outside of testing bursts. Since technical authentication is already clean, this points to sender reputation and engagement history rather than a fixable code or configuration issue.
Sender Photo / Privacy
Gmail recipients may see Pierre's personal Google Account profile photo next to forum emails β this is standard Gmail behavior tied to the authenticated sending account, independent of the display alias/name used. Yahoo and most other providers do not show this at all.
To suppress it: Google Account β Personal info β under "Choose what others see," click Go to About me β find the Profile picture entry β set to Only you. Note this is an account-wide setting, affecting all uses of that Google Account, not scoped to forum mail specifically.
Known Harmless Duplicates
doLogout β two declarations exist; only the second (later) one actually runs in JavaScript. It's missing a stopAutoSync() call the first copy has, currently harmless because both end in a full page reload.
updateAdminBtn β two declarations; the one that runs lacks a null-safety check the other has.
Cleaning these up properly (keeping the better half of each) is a real, deliberate task for a dedicated low-traffic session β not a quick delete.
Cleanup Backlog
- Template editor UI wiring for the two Weekly Broadcast message snippets (functionally complete without it β files can be edited directly and re-uploaded via FTP)
doLogout/updateAdminBtnduplicate cleanup (see above)- Yahoo deliverability β monitor, no action currently available
- Revisit test-data-pollutes-real-broadcast behavior after more real-world usage
sendHelloNeighborToAllandsendGeneralLetterstill lack the batched, trigger-driven redesign Targeted Letter received β both remain exposed to the same 6-minute execution timeout on large sendssendHelloNeighborToAllchecks registration off the deduplicated Mass_Mail_List row (one row per email, lowest unit number survives dedup) β a multi-unit owner registered on one unit won't get invited for a different, still-unregistered unit if the registered one has the lower number. Agreed fix (not yet built): check registration per-unit and generate a separate invitation link for each unregistered unit under an email- Targeted Letter has no Test Mode dry-run β it refuses to run at all while Test Mode is on. A scoped addition (route to Test_Mail_List / BCC-only when a test flag is passed, without touching the batching logic) would close this gap if it becomes worth the effort
Emergency Rollback
If a deployment causes a serious regression: FileZilla still holds every prior numbered version of both index.html and the Apps Script file. Re-upload the last known-good HTML version via FileZilla (Overwrite mode), and for Apps Script, redeploy the prior version via Deploy β Manage deployments, selecting the earlier version number. The project has recovered from a full authentication outage this way before by rolling back to a stable baseline rather than trying to patch forward under pressure.