Skip to content

Ranked feed, announcements, event analytics, org management, event addresses and covers, runtime iOS env, UI and swipe fixes (#100, #119, #120, #122, #123, #124) - #21

Merged
theobong merged 85 commits into
mainfrom
feat/feed-and-polish-batch
Sep 23, 2026
Merged

theobong merged 85 commits into
mainfrom
feat/feed-and-polish-batch

Conversation

@theobong

@theobong theobong commented Sep 16, 2026 •

Copy link
Copy Markdown
Member

Resolves civfix/issue-tracker#119
Resolves civfix/issue-tracker#120
Resolves civfix/issue-tracker#123
Resolves civfix/issue-tracker#124
Part of civfix/issue-tracker#100 and civfix/issue-tracker#118
Client half of civfix/issue-tracker#122

What changed

On web and mobile: (1) the home feed adopts the new ranked order and gains live updates — a "new posts" pill while you're on the feed, and like/reply counts that tick up in place; (2) event hosts can send announcements — a composer with an audience picker and a live recipient count, a list of what they sent, an announcements section on the event page, and a page per announcement anyone can open from a link; (3) "Open full console" is gone from the app and an "Analytics" card with a swipeable set of panels takes its place on host tools, with a full "Event analytics" page behind it; (4) host tools no longer repeats its own header buttons as rows, and "Invite a team member" is folded into "Team"; (5) "Email attendees" and "Message attendees" are replaced by "Open group chat" and "Make announcement"; (6) organizations can be managed in the app — name, logo, description, website, donation and social links, and members — with the handle left permanent; (7) the iOS app picks its API at runtime, so a TestFlight install talks to staging and an App Store install to production from one binary; (8) the donation card on an event page moved under the title; (9) three fixes — a photo no longer vanishes from a chat message you just sent, the quote composer shows the post you are quoting, and social links render as brand icons on profiles, people and organization pages; (10) a wide UI polish pass plus mobile navigation fixes (back-swipe, map pin flicker, edge-drag vs row swipe).

Then a second batch, all about addresses and the event page: (11) every event now carries a meeting address a human confirmed — creating or editing one shows the street address for the pin you dropped and asks you to confirm or correct it, and where no street address can be found you have to type one before the event can be published; (12) addresses on an event page, a report page and your ticket are now live — tap one to centre it on the map, copy it, or open it in a maps app; (13) the report composer offers the address it worked out for your pin, never blocks you from filing, and no longer files an address you never touched as if you had typed it; (14) hosts can put a cover image on an event, which replaces the map picture at the top of the event page and becomes the picture that shows when the event is shared; (15) the event page was rebuilt to lead with what the event is and who is hosting, share and repost became round icon buttons beside the title, "Message crew" moved under "View ticket", "Who's going" is gone and the attendee list now lives on the host's row — which also works after the event has ended; (16) the "Donation link" row is gone from the event dashboard; (17) list cards no longer clip their first and last rows against the rounded corner.

A third batch tidies the host surfaces: (18) an organization's "Upcoming events" and "Past events" both stay open and grow three at a time behind a "Load more" link, share moved up next to the name as a round icon, the blue verified check marks are gone (the wording that says what an organization is verified AS stays), and "Manage" runs the full width on a phone; (19) on the event dashboard the "Needs attention" card is gone and a graph-first analytics carousel for your most relevant hosted event takes the space, while pending invitations get a card of their own on your profile that only shows when there are any; (20) the event page no longer carries a "Guests" section - guest contact now lives only in the host's roster export.

A fourth batch, from the follow-up session on this PR: (21) iOS push notifications survive switching accounts on one phone — signing out now releases the device's notification registration and ends the session on the server before clearing the phone, and a registration left behind by a previous sign-in can be taken over again instead of failing silently forever; (22) the feed gets a gentle reshuffle on every pull-to-refresh so it never looks identical twice, while pages you already fetched stay consistent — and nearby content now outweighs everything else in the ranking; (23) beta (TestFlight) builds say which backend they talk to on the about card, and store builds carry the correct notification environment; (24) trying the TestFlight beta no longer signs you out of the App Store app when you return to it; (25) announcement, analytics and organization-manage notifications tapped from inside a thread open in place instead of collapsing the stack; (26) sharing an unlisted event no longer puts its cover image in the link preview, organization pages show a loading state instead of a blank flash on a cold open, and the small social-link buttons are easier to tap; (27) analytics was redesigned around the whole portfolio — the dashboard card sums ALL your events over the last 30 days (sign-ups, check-in ring, hours by event, impact rows), the full page swaps the lifecycle tabs for an event picker plus a 7/30/90-day/up-to-a-year range with a tappable per-event breakdown, the uninterpretable cumulative and reach charts are gone, the funnel starts at sign-ups so it always matches the tiles, and 'View full analytics' presses with a proper edge-to-edge highlight.

Before you start

  • Where: staging after this and the backend PR merge — civfix.dev for web, a TestFlight build for mobile. The feed pill, announcements and the analytics card all need the backend PR deployed.
  • Sign in as: two citizens (A watches, B acts); A follows B. A hosts an event that B is registered for, and owns an organization. On staging use two real mailboxes (local OTP mail only works for the reviewer account).
  • Data: a dozen mixed posts (some by B), one event with shifts and a donation link, one report with a photo, one organization with at least two members. For the address steps you also need two map spots: one ordinary street address with a house number, and one with no street address at all (the middle of a large park, a beach, open water). For the organization page you need MORE than three upcoming and more than three past events on that organization. For the guest steps, at least one guest on one of your events - RSVP to it from a signed-out browser with "Continue as guest".
  • Related PRs: backend Ranked home feed, event announcements, event analytics, chat media serving, street-level addresses (#100, #118, #122) civfix-backend#53 · admin Report list photos, image lightbox, announcement label (#118) civfix-admin#22
  • Size: 17,428 counted lines — single PR at the author's request.

⚠️ Merge order is a correctness gate — backend #53 first, then this PR, then admin #22.

This is no longer just a "things look broken" ordering. This app now always sends the new address field when an event is created or edited, and a backend that has not taken the matching contract rejects that request outright. Deploy this app before the backend and creating or editing an event is completely broken — not degraded, broken. The analytics card and the announcements section have the same dependency, more gently. The same order applies to the eventual production release. The shared contract version all three PRs need is already on the registry, so backend and admin CI resolve it either way.

Verify

Ranked feed + live updates — [Web] [Mobile]

  1. As A, open "Home". — Expect: the feed under "Your Feed" is no longer strictly newest-first; a fresh post from B (followed) outranks an older popular stranger post.
  2. Stay on the feed, scrolled down a few cards. As B, tap "New post" and publish. — Expect: within a couple of seconds a pill appears at the top of A's feed reading "1 new post" (a second post makes it "2 new posts"); the list itself does not jump.
  3. Tap the pill. — Expect: the list scrolls to the top, refreshes, B's post is there, the pill is gone.
  4. Repeat, but scroll manually back to the very top instead. — Expect: the pill clears silently, without a refresh.
  5. As B, tap "Like" on a post visible in A's feed, then unlike it. — Expect: ~2s later the count on A's card ticks up in place, then back down — no reordering, no flash.
  6. As B, tap "Comment" and reply to a post in A's feed. — Expect: the parent card's reply count updates on A's device; opening the post shows the same number.
  7. Scroll to the bottom through several pages. — Expect: no duplicate cards; "You're all caught up" at the end.
  8. Sign out and open "Home" as a guest. — Expect: a ranked feed loads and pages; no pill ever appears for a guest.

Making an announcement — [Web] [Mobile]

  1. As A, open your event and tap the "Host dashboard" row, landing on "Host mode". — Expect: a "Communicate" group with the rows "Open group chat" ("Talk with everyone at the event") and "Make announcement" ("Notifies the people you pick, by email and push") — or, if "Make announcement" is already the button at the top of the screen, only "Open group chat" is left in the group.
  2. Tap "Make announcement". — Expect: the "Make announcement" screen opens with the caption "A short, one-way note for your event. It goes out by email and notification, and it stays on the event page for everyone.", a "Title" field placeholdered "Title (optional)", a "Message" field placeholdered "Write your announcement…", and a "Notify" row.
  3. Tap through the "Notify" choices — "Everyone registered", "Checked in", "Not checked in", "Waitlist", "By shift". — Expect: the selected one shows a count beside it: "Counting…" briefly, then a line like "12 people". The send button changes to "Send to 12 people".
  4. Pick "By shift". — Expect: the event's shift names appear as chips; until you tick one the send button stays disabled. On an event with no shifts: "This event has no shifts to pick from."
  5. Pick a choice that nobody matches (for example "Waitlist" with an empty waitlist). — Expect: the note under the button reads "No one to notify yet - it will still appear on the event page."
  6. Go back to "Everyone registered", type a message, tap "Preview", then "Edit". — Expect: the preview renders the formatting; an empty body previews as "Nothing to preview yet."
  7. Send it. — Expect: a toast "Announcement sent." and you land on the announcement's own page, showing the event chip, your name, "Sent … ago", the message, a line like "Sent to Everyone registered · 12 notified" and an "Open event" button.
  8. Tap "Open event". — Expect: the event page, with an "Announcements" section lower down the page carrying the announcement — and no delivery counts there.
  9. Go back to "Host mode" and scroll down. — Expect: an "Announcements you sent" section listing the announcement with its audience and a line like "12 of 12 notified".
  10. Send three more, then look at that section again. — Expect: only the newest three rows, plus a link "All announcements (4)"; tapping it opens the "Announcements" list, where "Show more" pages through the rest.
  11. As B (registered, not a host), open the event. — Expect: the same "Announcements" section, without counts. Tap a row. — Expect: the announcement page, with no delivery line.
  12. As B, open "Notifications". — Expect: a row for the announcement; tapping it lands on the same announcement page.
  13. Copy the announcement page's address, sign out, and open it. — Expect: it still renders for a signed-out visitor.
  14. On an event with no announcements at all, open the event page. — Expect: no "Announcements" heading and no empty gap.
  15. Open the announce screen as someone who is not a host. — Expect: "Not allowed to message" / "Only the event's hosts can message attendees."

Event analytics after the redesign — [Web] [Mobile]

  1. As A, open your own profile, then "Event dashboard". — Expect: an "Analytics" card labeled "Last 30 days" below "Up next" — it no longer names any single event.
  2. Swipe the card sideways (web: also the chevrons and dots). — Expect: four panels — sign-ups ("Sign-ups across … events") with daily bars and week dates along the bottom, a check-in ring ("… of … sign-ups · … events held"), volunteer hours with one bar per event, and an impact panel of plain stat rows ("… resolved", "… posts about your events"). No "Reach" panel, no shifts bar-chart panel, and no "issues this event will address" line anywhere.
  3. Look at the card on an account with no activity in the last 30 days. — Expect: quiet empty lines ("No sign-ups in the last 30 days", "No events ran in the last 30 days") rather than zeros or blanks.
  4. Press and hold "View full analytics". — Expect: the pressed highlight fills that row edge-to-edge, clipped to the card's rounded corners — no floating grey rectangle inside the card.
  5. Release. — Expect: the "Analytics" page opens with "Updated just now" (or "Updated … ago"), an "All events" picker, and a range control reading "7 days / 30 days / 90 days / Up to a year". The old "Lead-up / Event day / Follow-up / All" tabs are gone.
  6. Read the all-events view. — Expect: tiles "Sign-ups", "Check-in rate" ("… of … · … events held"), "Volunteer hours" ("… people"), "Events held" and "Reports linked"; then "Sign-ups over time" bars with dates along the bottom; then "By event" with one bar per event; and at the bottom "Groups smaller than 5 people are hidden. A blank cell is not a zero."
  7. Tap a row under "By event". — Expect: the page switches to that event — the picker shows its name, the range control gains "Whole event" (selected), the title reads "Event analytics", and the sections "Sign-ups", "From sign-up to hours", "Where sign-ups came from", "Shifts & capacity", "Event day" and "Impact & follow-up" follow. Two events sharing the same name open their own separate pages.
  8. In the single-event view, check the funnel. — Expect: "From sign-up to hours" starts from the same sign-ups number the "Sign-ups" tile shows — there is no page-view step, and no chart on the page draws as a giant solid block.
  9. Switch the range between "Whole event" and "7 days". — Expect: the daily bars re-slice to the window; the tiles keep their whole-event numbers (the Sign-ups tile is marked "Whole event").
  10. As B (registered, not a host), open that event's analytics address directly. — Expect: "Not a host here" / "Only the event's hosts and staff can see its analytics."
  11. Turn the network off and reopen the dashboard card. — Expect: "Numbers are unavailable" / "We could not load your analytics. Everything else still works." with "Try again"; the rest of the dashboard still works.

Host tools no longer repeats itself — [Web] [Mobile]

  1. As A, open "Host mode" on an event more than two days away. — Expect: the button at the top reads "Share event" — and there is NO "Share link" row lower down; each action appears once.
  2. Open "Host mode" on an event starting within two days that has sign-ups. — Expect: "Make announcement" at the top, and the "Communicate" group shows only "Open group chat".
  3. Look for "Invite a team member" anywhere on the screen. — Expect: it is gone.
  4. Tap the "Team" row under "Configure". — Expect: the "Team" screen, with "Invite" at the top of the members list — that is where inviting now happens.
  5. Open "Host mode" on an event that has already happened. — Expect: the "Team" row is still there (it used to disappear after the event).
  6. Look for "Open full console" on "Host mode" and on "Event dashboard". — Expect: gone from both.

Organization settings in the app — [Web] [Mobile]

  1. As A (owner or admin of an organization), open the organization's page inside the app and tap "Manage". — Expect: the "Organization settings" screen, with a line at the top reading "@ · handles are permanent. Contact CivFix support to change one." and no handle field to type in.
  2. Under "Profile", tap "Change logo" and pick an image. — Expect: "Uploading…" while it goes up, then the new logo in the preview and a "Remove logo" option.
  3. Edit "Name" and "Description". — Expect: a "Discard" / "Save changes" bar appears; "Save changes" gives the toast "Organization saved".
  4. Clear "Name" and try to save. — Expect: "Name is required." under the field and nothing is saved.
  5. Under "Links", set "Website" to something that does not start with https. — Expect: "Must start with https://".
  6. Set "Donation link" to an https address and save, then open your public organization page. — Expect: the donation link is live there.
  7. Fill in "Instagram" and "WhatsApp" under "Links" and save. — Expect: they save; entering a WhatsApp number with punctuation gives "Enter the number with country code, digits only."
  8. Scroll to "Team". Tap "Invite", pick "Handle" or "Email", choose a "Role" and "Send invite". — Expect: toast "Invitation sent" and the person appears under "Pending invitations" with "Revoke" beside them.
  9. As the owner, open a member's actions ("Actions for ") and use "Make Admin", then "Make Member". — Expect: the role changes in place, with no confirmation step.
  10. Open a member's actions and tap "Remove from organization", then "Remove". — Expect: they leave the list.
  11. Look at the owner's own row and at your own row. — Expect: neither offers an actions button.
  12. As an admin (not the owner), open the same screen. — Expect: you can edit the profile and the links and invite or remove people, but there are no "Make Admin"/"Make Member" items.
  13. As B, who is only a member (or not in the organization at all), open that same settings address. — Expect: "Not an admin here" / "Only an organization's owner and admins can change its settings."
  14. Open the "Event dashboard" and look for a "Donation link" row. — Expect: there is none any more. Your own donation link is still set from Settings, and an organization's under "Links" on "Organization settings".

The organization page shows both event runs — [Web] [Mobile]

  1. As A, open your organization's page inside the app. — Expect: "Upcoming events" and "Past events" both sit open, one under the other. Neither heading is a button and neither has a chevron — "Past events" used to start collapsed behind one.
  2. Count the rows under each heading. — Expect: three at a time, with a "Load more" link under the list.
  3. Tap "Load more" under "Upcoming events". — Expect: it reads "Loading…" for a moment, then three more events join the list and the link stays while more remain. (With a screen reader the two links announce as "Load more upcoming events" and "Load more past events".)
  4. Keep tapping until each list runs out. — Expect: the link disappears, and no event appears twice.
  5. Look at the organization's name at the top. — Expect: a round share button sits beside it (a screen reader announces "Share a link to this organization"). Tap it. — Expect: the same share sheet that the old "Share this organization" text link at the bottom of the page used to open; that text link is gone.
  6. On a VERIFIED organization, look for a blue check mark. — Expect: there is none — not beside the name in the app, not on the public web page, and not on the organization line of the web host console's event overview. The wording stays: the in-app page still shows the kind under the handle ("Nonprofit", "Government" or "Community group") and the public web page still lists "Verified nonprofit" among its stats.
  7. Narrow the window to phone width (or use a phone) as an owner or admin. — Expect: "Manage" spans the full width; on a wide window it stays a small button.
  8. Open an organization with no events. — Expect: "No upcoming events yet." and "No past events yet.", both readable without expanding anything, and no "Load more".

Invitations sit on your own profile, and the dashboard leads with analytics — [Web] [Mobile]

  1. As A, open your OWN profile with NO invitation waiting. — Expect: no "Invitations" card — the "Event dashboard" row ("Your events, analytics and tools") sits where it always did, with nothing new above it.
  2. Have B invite A to an event's "Team" and to an organization, then reopen A's own profile. — Expect: an "Invitations" card directly above that "Event dashboard" row, carrying both rows, each reading like " invited you as " ("Someone invited you as …" when the inviter has no name) with "Accept" and "Decline" beside it.
  3. Accept one and decline the other. — Expect: each row leaves the card, and when the last one goes the card disappears completely rather than leaving an empty one behind.
    3a. As B, open A's profile while A still has an invitation pending. — Expect: no "Invitations" card there — invitations only ever appear on your own profile, never on someone else's.
  4. Turn the network off and reopen your own profile. — Expect: "Could not load your invitations" / "We could not reach civfix. Check your connection and try again." with "Try again".
    4a. With an invitation still pending, open "Event dashboard" and scroll it end to end. — Expect: NO "Invitations" card anywhere on it — invitations have left the dashboard entirely — and no "Needs attention" card either; that whole section, including its "Log volunteer hours" nudge, is gone.
  5. With at least one event you host, look below "Up next" on the dashboard. — Expect: the "Analytics" card labeled "Last 30 days", summing all your events — it does not name a single event, whichever event is next.
  6. Swipe through it and tap "View full analytics". — Expect: the four panels from "Event analytics after the redesign" above, and the all-events "Analytics" page behind the row.
  7. Open the dashboard on an account that has never hosted anything. — Expect: the first-event teaching card; if the analytics card shows, its panels carry the quiet empty lines, not numbers.
  8. Open "Host mode" on an event you have checked people into and look for "Log volunteer hours". — Expect: it is still there — that is now the only place the app offers it, since the dashboard no longer nudges you.

A photo you just sent stays in the message — [Web] [Mobile]

  1. As A, open a direct message from the "Messages" tab.
  2. Tap the paperclip ("Attach a photo or video") → "Photo library", choose a photo, and wait for the thumbnail above the input.
  3. Tap send. — Expect: the photo is in your own bubble straight away and STAYS there — it does not blink out and leave an empty row a second later. (That empty row was the bug.)
  4. Leave the conversation and come straight back without reloading. — Expect: the photo is still in the message.
  5. As B, open the same conversation within a few seconds of the send. — Expect: the photo is there. If the checks have not finished and nothing else is in that message, B sees a small pill reading "Attachment processing…" with an image icon instead of an empty bubble.
  6. Wait a minute, then reload the page (web) or force-quit and reopen the app (mobile). — Expect: the photo is in the message for both people.
  7. Repeat steps 2–5 in an event's "Crew chat", a "Group chat" and a "Report chat". — Expect: the same in all four.
  8. Turn the network off mid-send. — Expect: the bubble keeps the photo and shows "Failed - tap to retry"; tapping it resends with the photo intact.
  9. Send a text-only message, a poll, and a message that is only a link. — Expect: none of them ever shows "Attachment processing…". A deleted message still reads "Message removed".

Quoting a post shows what you are quoting — [Web] [Mobile]

  1. On any post in the feed, tap the repost button. — Expect: a small menu (accessible name "Repost options") with "Repost" and "Quote post".
  2. Tap "Quote post". — Expect: the "Quote post" composer opens with "Add your take..." in the input AND the post you are quoting rendered underneath it — avatar, author name, handle, time and the text, clamped to a few lines. (Previously the composer showed nothing but the empty input, which is what the issue reported.)
  3. Quote a post that has a photo. — Expect: the photo shows inside the quoted block in the composer too.
  4. Do the same from a post's own page and from a reply inside a thread. — Expect: the same quoted block appears.
  5. With the input still empty, look at "Post". — Expect: it is disabled — a quote still needs your own words (or a photo, an event or a report).
  6. Type something and tap "Post". — Expect: the new post appears in the feed with the same quoted block; tapping the block opens the quoted post.
  7. [Web] Paste the address of a quote composer for a post that has since been deleted (or whose author blocked you). — Expect: "That post isn’t available." where the quoted block would be; you can still type, and posting then fails with "Post couldn't be published. Try again." and keeps your draft.
  8. Open a plain "New post" and a "Reply". — Expect: neither shows a quoted block.

Social links show as brand icons — [Web] [Mobile]

  1. As A, open Settings → "Account" and tap the "Social links" row (it reads "Add your social links" when empty).
  2. Fill in a username for "Facebook", "Instagram", "TikTok" and "X" and a number for "WhatsApp", then "Save links". — Expect: the sheet closes and the row now counts your links. Typing a full URL instead of a username gives "Check your entries - use just your username, no links or spaces."
  3. Open your own profile. — Expect: a compact row of round brand icons sitting between your bio and your follower/following counts — one icon per link you filled, nothing for the blanks, and no spelled-out platform names anywhere. They always read left to right in the same order: Facebook, Instagram, TikTok, X, WhatsApp.
  4. Tap one. — Expect: it opens that platform — a new tab on web, the in-app browser (with a readable address bar) on mobile. With a screen reader on, each announces as "Open Instagram", "Open TikTok" and so on.
  5. From the feed, open another person's page who has social links set. — Expect: the same icon row on their page (this page never showed social links before).
  6. From inside the app, open an organization's page whose "Links" include social handles. — Expect: the same icon row, plus the separate "Website" chip when a website is set.
  7. Open a profile and an organization with no social links at all. — Expect: no row and no empty gap where it would be.
  8. Narrow the window to phone width (or use a phone) on a profile with all five links. — Expect: the icons wrap instead of clipping.

Donation prominence — [Web] [Mobile]

  1. As a NON-host, open the event with a donation link. — Expect: the "Donate" card sits directly under the "Host" section and above the sign-up area; "Open donation page" opens the external site (new tab on web, in-app browser on mobile).
  2. As the host, open your own event. — Expect: "You're hosting" shows and there is NO "Donate" card.
  3. As the host, cancel the event ("Cancel the event" in host settings), then view it as the non-host. — Expect: "Cancelled by the host" shows and the "Donate" card is gone.
  4. Open an event with no donation link anywhere. — Expect: no card and no empty gap under the header.

Confirming the meeting address before you publish — [Web] [Mobile]

  1. As A, start a new event and work through "What's happening?" and "When is it?" to "Where do you meet?".
  2. Before you drop the pin, look under the map. — Expect: a "Meeting address" field you cannot type in, with the note "Drop the pin first and we'll look up the address."
  3. Drop the pin on an ordinary street address. — Expect: a spinner and "Looking up the address for this pin...", then the field fills with the street address and the note becomes "This is what attendees will see and navigate to. Edit it if it's not right."
  4. Tap "Next" without touching the field. — Expect: you advance; an address that was looked up and left alone counts as confirmed.
  5. Come back to this step, clear the field, and tap "Next". — Expect: you stay on "Where do you meet?" and see "Confirm the meeting address before you continue."
  6. Type one character. — Expect: "Enter at least 3 characters so attendees can find the spot." under the field, and "Next" still holds you here.
  7. Drag the pin into the middle of a large park, a beach or open water. — Expect: the field empties, its placeholder becomes "Type the meeting address", and a warning reads "We couldn't find a street address for this pin - enter the meeting address." — or the same sentence naming the area, e.g. "We couldn't find a street address for this pin in Los Angeles, CA - enter the meeting address."
  8. With the field still empty, tap "Next". — Expect: blocked with "Confirm the meeting address before you continue." Type a real address and it advances.
  9. Drop a pin beside a named place rather than a street number (a park building, a pier). — Expect: if the lookup finds the place rather than a postal address, the field prefills as "Near " rather than presenting it as an address you could post a letter to.
  10. Type your own wording over a looked-up address, then move the pin a little way down the street (far enough to be a genuinely different spot — a metre or two counts as the same place on purpose). — Expect: your wording is kept and the note changes to "Pin moved - double-check the address." Do the same on an address you never edited. — Expect: no warning; it quietly looks the new spot up and replaces itself.
  11. Put something into "Name the spot" (for example "Boathouse dock"), then finish the wizard and tap "Publish event". — Expect: it publishes, and the event page shows the spot and the address together under the date, e.g. "Boathouse dock, 123 Main St, …".
  12. On the last step, "Ready to publish?", check the summary row labelled "Meet location". — Expect: it shows the confirmed address with the spot underneath. Use "Edit" on it, clear the address and come back. — Expect: "Publish event" is disabled. (Its hint still reads "Add a title, a meeting point, a date, and a time to publish." and does not mention the address — that wording was not changed.)

Editing an event keeps the address you saved — [Web] [Mobile]

  1. As A, open one of your events and tap "Edit event" — one long form, no wizard steps. — Expect: "Meeting address" already holds the address you confirmed — it is not re-looked-up and silently replaced.
  2. Change something unrelated (the title) and save. — Expect: the saved address is unchanged on the event page.
  3. Edit the address by hand and save. — Expect: the event page shows your text.
  4. Move the pin to a different street and save. — Expect: the address updates to the new spot (and if you had typed your own, it is kept with "Pin moved - double-check the address." while you are still editing).
  5. Duplicate an event from the "Event dashboard" row menu ("Duplicate" → "Create copy"). — Expect: toast "Copy created", and the copy's edit screen opens with the SAME meeting address already filled in — not looked up again, not blank.
  6. Open an event created before this change that has NO address at all, change only its title and try "Save changes". — Expect: you are held until an address is filled in — either the looked-up one or one you type. This is a new constraint on old events; check the field offers you something rather than trapping you.
  7. Open an event created before this change that DOES have an address, change only its title and save. — Expect: the old address survives untouched.

Tapping an address — [Web] [Mobile]

  1. Open any event that has a meeting address. — Expect: the address sits under the date line at the top of the page.
  2. Tap the address text. — Expect: the map centres on the event's pin. (On an event with no address at all the line reads "Meeting point on the map", or "Route cleanup" for a route event.)
  3. Tap the copy icon beside it. — Expect: a toast "Address copied"; paste it somewhere to confirm. If copying fails you get "Couldn't copy the address" instead.
  4. Tap the maps icon (its accessible name is "Open in a maps app"). — Expect: [Web] a new tab opens Google Maps on that address, with no chooser. [Mobile, iOS] a sheet slides up over a dimmed backdrop offering "Open in Apple Maps", "Open in Google Maps" and "Copy address" — it has no visible heading and no Close button; tapping the backdrop dismisses it. [Mobile, Android] the system's own app chooser appears for the location.
  5. Do the same on a report's page — the address row sits under the reference code. — Expect: tapping centres the report's pin; copy and maps behave the same.
  6. Open "Your ticket" for an event you signed up for. — Expect: the same address row under the date, with copy and maps but no map-centring (there is no map on the ticket).
  7. Open a report whose address the reporter typed themselves rather than one worked out from the pin, and one that was worked out only roughly. — Expect: both still open in a maps app; the roughly-located one lands on the coordinates rather than searching for the text.
  8. Long-press the address (mouse-hold on web). — Expect: the three-option sheet. Note this is the one place web and the maps icon disagree: the icon goes straight to Google Maps, the long-press offers the chooser.

The report composer offers the address it already knows — [Web] [Mobile]

  1. As A, tap "Report", pick a category, take or choose a photo, and reach the review step.
  2. Look at "Where is it?". — Expect: it is prefilled with the street address for your pin (or "Near " when only a place could be found), instead of being blank.
  3. Move the pin. — Expect: the prefilled line follows the new pin.
  4. Type your own wording over it, then move the pin again. — Expect: your wording is kept — the prefill never overwrites what you typed.
  5. File a report without touching that field at all, then open the report you just filed. — Expect: it files normally and the report page still shows an address under the reference code — the one the server worked out for your pin, not your typing. (Right after filing, the card under "Track it in your reports" may briefly show the prefill; it settles on the server's line.)
  6. File another report after typing your own wording into the field. — Expect: the report page shows your words, and opening it in a maps app searches for that text rather than dropping on the coordinates.
  7. File a report from a spot where nothing can be looked up (open water, deep inside a park), leaving the field blank. — Expect: "Where is it?" simply stays empty and filing is never blocked — no spinner, no error, no extra step — and the report page simply has no address line.
  8. Look at the "Where this goes" card under the field while all of this happens. — Expect: its own "Resolving where this routes..." line and its routing sentences behave exactly as they did; it is a separate thing from the address.

A cover image on your event — [Web] [Mobile]

  1. As A, start creating an event; on the first step, "What's happening?", find "Cover image", captioned "Shown at the top of your event page instead of the map."
  2. Tap "Add a cover" and pick a photo. — Expect: "Uploading..." while it goes up, then the photo in the preview with "Replace cover" and "Remove cover" beside it.
  3. Try to pick a video. — Expect: "Pick an image - a video can't be used as a cover."
  4. Save and open the event page as someone else. — Expect: the cover photo is at the top of the page where the map picture used to be.
  5. Open "Edit event" on that event, change only the title, and tap "Save changes". — Expect: the cover survives — an unrelated edit must not wipe it.
  6. Tap "Remove cover", save, and open the event page again. — Expect: the map picture is back — the map is still the fallback.
  7. [Web] Paste the event's link into a chat app or a link-preview checker. — Expect: the preview picture is the cover photo; an event with no cover still previews with the CivFix brand picture.
  8. Upload a very large or unusual image and watch for a failure. — Expect: one of "That image could not be used. Try a different photo.", "Too many uploads. Wait a moment and try again." or "Could not upload that image. Try again." — and the form stays filled in either way.

The event page leads with what the event is — [Web] [Mobile]

  1. As someone not signed up, open an event that has a description and a "Bring" list. — Expect: reading down the page: title, date and address at the top; then the description and "Bring"; then "Host"; then the "Donate" card if there is one; then the sign-up area. The details and who is hosting are now ABOVE the sign-up slots, not below them.
  2. Look beside the title. — Expect: two round icon buttons — "Share this event" and "Repost this event". Tap each. — Expect: the share sheet and the repost menu open exactly as they did when they were text buttons.
  3. Sign up, then look under "View ticket". — Expect: "Message crew" is directly below it. Tap it. — Expect: the event's chat opens.
  4. Look for a "Who's going" heading anywhere on the page. — Expect: it is gone, along with the list of names and the "View all" link that used to sit there.
  5. As the organizer of an event that has at least one guest RSVP, scroll the whole page. — Expect: there is NO "Guests" section anywhere — no guest count, no expandable guest list, no guest email or phone. It is gone for the organizer too, not just for everyone else.
    5a. Open "Host mode" on that event and look at the roster, then run check-in. — Expect: the guest is there by name in both, tagged "Guest".
    5b. On the sign-up slots on the event page, look for that guest. — Expect: they are NOT listed against any shift — a guest is only counted in the number going. Export the roster from the web host console instead. — Expect: the guest's row carries their email and phone; that export is now the only place a host gets guest contact.
  6. Now sign in as someone you added to the event as STAFF (invite a second account from "Team", accept the invitation from the "Invitations" card on that account's own profile) and open the event while it is live. — Expect: a row reading "Check in attendees" that opens check-in.
  7. Open the same event as that staff account AFTER it has ended. — Expect: the row is still there, now reading "View attendee list", and it still opens the same screen. (It used to disappear once the event was over.)
  8. As the organizer, look for that row. — Expect: you do not get it — you get "Host dashboard" instead. A plain attendee gets neither.

List cards clear their corners — [Web] [Mobile]

  1. Open your own profile with at least one invitation pending, so the "Invitations" card has rows. — Expect: the first row's text and the last row's text sit clear of the card's rounded corner — nothing is clipped or crowded against the curve.
  2. Look at "Top volunteers" and at "Events" with the "Upcoming"/"Past" control. — Expect: the same — first and last rows breathe, dividers still line up, and the control at the top of "Events" is not jammed against the edge.
  3. Turn the network off and reopen your own profile so the "Invitations" card shows its error. — Expect: the "Try again" notice is inset from the card's edges rather than touching them.
  4. Open Settings and look at the "Account", "App" and "About" groups, then Settings → "Account" ("Identity", "Your data") and Settings → Privacy ("Visibility", "Safety"). — Expect: the same — the first and last row of every group clears the rounded corner, dividers still line up, and every row still opens what it always did.
  5. Open an event's "Host mode" and look at "Grow", "Communicate", "Operate", "Configure" and "Danger zone". — Expect: same rows in the same order, just breathing at the top and bottom of each card.
  6. Throttle the connection and reload "Event dashboard". — Expect: the placeholder rows do not jump vertically when the real rows arrive.

iOS runtime API selection — [Mobile]

  1. Build the store binary (scripts/store-build.sh appstore). — Expect: the build gate passes and prints that the API is resolved at runtime.
  2. Install it via TestFlight and sign in. — Expect: the app is on the STAGING environment (staging-only content/accounts work; production accounts don't).
  3. (Post-release) install the same build from the App Store. — Expect: the app is on production. There is no in-app indicator either way — verify by which accounts/content exist.

UI polish spot checks — [Web] [Mobile]

  1. [Web] Settings → Appearance → "Dark", then Tab through the sign-in form and any buttons. — Expect: a visible focus ring on every control, in both schemes.
  2. [Web] Open the host console in dark mode. — Expect: the tinted stat/panel surfaces render dark-appropriate colors, not light pastels.
  3. [Mobile] In dark appearance, open the ticket scanner and the report camera. — Expect: the scan reticle and framing guide are clearly visible (white) over the camera feed.
  4. [Mobile] Enter a wrong sign-in code. — Expect: the error line renders in the red/danger color and "Resend code" behaves as a real button (announces disabled during cooldown).
  5. [Mobile] Check the search fields on "Search events", "Search your reports", "Search by name or @handle", and the search tab. — Expect: all comfortably tall (44pt), no clipped placeholder at large text sizes.
  6. [Web] Set the browser to Spanish and open a guest RSVP-cancellation link. — Expect: the entire page is in Spanish ("¿Cancelar tu asistencia?"), including error states; same for German and Korean.
  7. Switch the app to Spanish, German and Korean and reopen "Make announcement", "Event analytics" and "Organization settings". — Expect: every label, section heading and button is translated; no English leaks and no missing-key placeholders.

Swipe navigation — [Mobile]

  1. Open a report pin, then the reporter's profile, then drag slowly from the left edge and hold mid-swipe. — Expect: the report page is fully drawn behind the sliding screen — not blank — and completing the swipe lands on it with state intact.
  2. Repeat for event → host screen, and org/person pages. — Expect: same.
  3. On the map, tap one pin then another. — Expect: only the two affected pins change; the rest don't flicker.
  4. In a conversation, drag a bubble right starting mid-screen. — Expect: swipe-to-reply engages ("Reply"). Drag right starting AT the left edge. — Expect: the OS back gesture runs instead.
  5. In "Messages", swipe a row left. — Expect: "Mute"/"Unmute", "Mark read" (unread rows), "Delete" appear and work; with a row open, a right drag from mid-screen closes it, while a right drag from the left edge navigates back.
  6. Switch to the report tab from another tab. — Expect: the transition is smooth and the camera appears just after it settles.

The feed reshuffles on refresh, and still ends cleanly — [Web] [Mobile]

  1. Open "Home" ("Your Feed") and note the order of the first ten cards.
  2. Pull down to refresh, three or four times, noting the order each time. — Expect: the order varies between refreshes among cards of similar weight while the strongest cards stay near the top; never the same order every time, and never nonsense (top cards vanishing entirely).
  3. After a refresh, scroll steadily to the very end. — Expect: no card appears twice, and the list ends with "You're all caught up".
  4. With location allowed and posts near you present, compare a nearby post against a similar far-away one. — Expect: the nearby one ranks clearly higher.

Push notifications survive switching accounts — [Mobile] (TestFlight build, two accounts)

  1. As A, sign in and allow notifications when asked. From another device, have B reply to one of A's posts. — Expect: a notification banner arrives for A.
  2. Sign out: tap your avatar, then the gear ("Settings"), then "Sign out". — Expect: you land back signed out, within a few seconds, no error.
  3. Sign in as B on the SAME phone, allowing notifications. From the other device, have A reply to one of B's posts. — Expect: B receives the banner. Before this fix, the second account on a phone silently never received another notification.
  4. Tap the banner. — Expect: the app opens on the reply.

An announcement notification opens the announcement — [Mobile]

  1. As B (registered to A's event), background the app. As A, send an announcement on that event.
  2. Tap the arriving banner. — Expect: the "Announcement" page for that note opens directly; going back returns to where you were.

A beta build says which backend it uses — [Mobile]

  1. On the TestFlight build, tap the civfix brand pill at the top of the map. — Expect: the about card shows a line reading "API: api.civfix.dev (beta)".
  2. Same steps on an App Store build. — Expect: no API line on the card.

Trying the beta no longer logs out the App Store app — [Mobile] (one device, both builds)

  1. Signed in on the App Store app, install the TestFlight build and open it once; then reopen the App Store app. — Expect: still signed in, appearance and onboarding preferences intact.

Regression

Posting, likes, replies, reposts — [Web] [Mobile]

  1. Like/unlike, reply, repost, "Undo repost" on your own device. — Expect: instant optimistic updates, no flicker ~2s later, failures roll back as before.
  2. Write a plain "New post" and a "Reply". — Expect: neither shows a quoted block; attachments, mentions and the character counter behave as before.

Feed, thread and profile cards — [Web] [Mobile]

  1. Scroll the feed past reposts, quotes, replies, posts with photos and posts with an event or report attached. — Expect: every card renders as before; "Open quoted post" and "Open reposted post" still work.
  2. Open a post's page and read the focal post and its replies. — Expect: unchanged layout and actions.
  3. "Load more" on saved posts and on a profile's posts/events. — Expect: pages load; the pill shows a pressed state; a failed page shows a red error line with an accent retry.
  4. On a post page, "View conversation". — Expect: accent-colored button, opens the thread.

Chat and inbox — [Web] [Mobile]

  1. Send text-only messages, a message with a photo AND text, a poll, and a reply. — Expect: all render as before; only a bubble with no text and no attachment yet shows "Attachment processing…".
  2. Tap and long-press bubbles, react from the context menu, open links, tap the header avatar. — Expect: all unchanged; the avatar and menu items now dim on press.
  3. Open an older conversation you have not visited this session. — Expect: its photos load as before.
  4. [Web] Tab to an inbox row action. — Expect: focus ring, Enter activates.

Event sign-ups, shifts and tickets — [Web] [Mobile]

  1. On a ticketed event, register; on a slot event, claim a slot; as an attendee open "Your ticket"; as a host run check-in. — Expect: all unchanged; spacing looks right with and without the donate card and with and without the announcements section.
  2. [Web] As a host, open the "Tickets" row on "Host mode". — Expect: the host console opens as before; page builder, exports and messaging still work there. In its "Messages" list, announcements now appear alongside host messages, tagged "Announcement" against "Host message" — opening a host message and its "Deliveries" is unchanged.

Event dashboard — [Web] [Mobile]

  1. Open "Event dashboard" from your profile. — Expect: "Up next", "Impact", "Top volunteers" and "Events" with the "Upcoming"/"Past" filter all behave as before, and "Create event", "Duplicate", "Edit" and "Team" still work — with the analytics carousel where "Needs attention" used to be and no "Invitations" card on the dashboard at all — invitations now live on your own profile, above the "Event dashboard" row.
  2. Open an event row's actions menu. — Expect: "Open group chat" and "Make announcement" in place of the old "Email attendees", with "Host tools", "Duplicate" and "Edit" unchanged.
  3. Tap "Open group chat" from that menu. — Expect: the event's conversation opens, and going back returns you to the dashboard.

The event page in its other states — [Web] [Mobile]

  1. Cancel an event and open it as a visitor. — Expect: "Cancelled by the host"; the repost icon button beside the title is gone while share remains; no "Message crew" row and no "Donate" card.
  2. Open an event that has ended. — Expect: "This event has ended"; "Message crew" is there but disabled with "Only people who signed up can open this chat" for anyone who never signed up.
  3. Open an event with no description, no "Bring" list and no linked reports. — Expect: the details block is skipped entirely and "Host" sits straight under the header — no empty gap.
  4. Open an event with no sign-up slots as a non-host while it is live. — Expect: the usual "no slots yet" line where the slot board would be.

Link previews for everything else — [Web]

  1. Paste a report link, a person's link, a sign-up page link and an organization link into a preview checker. — Expect: every one of them previews exactly as it did before; only event links changed.
  2. Paste a link to a hidden report or a private event. — Expect: no preview, as before.

Creating and editing an event, end to end — [Web] [Mobile]

  1. Create a slot event, a ticketed event and a route event, each with linked reports and a "Bring" list. — Expect: every step behaves as before; the address step never blocks you once an address is in the field.
  2. Use the place search on the "Where do you meet?" step instead of dragging the pin. — Expect: picking a suggestion moves the pin AND fills "Meeting address" once — not a doubled-up line.
  3. Create an event in a different time zone from your own. — Expect: times and the time-zone notice behave as before.
  4. Cancel an event, then reopen it. — Expect: the cancellation notice as before.

Filing a report, end to end — [Web] [Mobile]

  1. File a photo report, a report with no photo, and one where you refuse location permission and drop the pin by hand. — Expect: all file as before, and the routing line under the pin ("Auto-forwarded once verified - usually 1-3 business days." and its siblings) still says what it used to.
  2. Use the place search on the location step. — Expect: picking a suggestion moves the pin; the "Where is it?" field follows.
  3. [Web] Sign out and file a report as a guest, through the check-you-are-human step. — Expect: it files as before; the address behaves exactly as it does signed in.
  4. Open your own report from "My reports" and from the map. — Expect: the page renders as before, now with the address as a tappable row.
  5. Long-press the map to drop a pin and read the label under it. — Expect: still the rough "City, ST" line (or the coordinates) — this is deliberately NOT the new street lookup.
  6. Drag the pin around quickly many times in one minute on the event and report steps. — Expect: the address, the map label and the "Where this goes" line all keep working; if you hit the limit they recover on their own shortly after.

Your ticket and check-in — [Web] [Mobile]

  1. Open "Your ticket" for a ticketed event and a slot event; show the code and have a host scan it. — Expect: unchanged; the new address row does not push the code or the ticket type off the card.
  2. Page through a multi-seat ticket, look at a waitlisted one, and use "Cancel registration". — Expect: all unchanged.
  3. Open a ticket with the network off or very slow. — Expect: the code and the seat pager still work; the address row simply does not appear rather than breaking the screen.
  4. As the host, run check-in on a live event, then open the roster after it ends. — Expect: the same roster both times.
  5. From an event's sign-up area, use the link that opens everyone signed up. — Expect: the attendee list still opens — that route replaced the "View all" that used to sit under "Who's going".
  6. On a ticketed event as a non-host, check you get the registration block and NOT a second "View ticket" row beside it. — Expect: one path to your ticket, not two.

Organization and person pages — [Web] [Mobile]

  1. Open a public organization page while signed out and from a shared link. — Expect: name, logo, description, website, donation link, members and events render as before; no "Manage" button appears for someone who cannot manage it; the event lists page with "Load more" here too.
  2. Open a person's page and your own profile. — Expect: avatar, bio, counts, follow button and tabs unchanged.

Team on an event — [Web] [Mobile]

  1. Open "Team" from "Host mode", invite someone by handle and by email, and revoke a pending invite. — Expect: unchanged behavior and copy.

Settings and map layers — [Web] [Mobile]

  1. Open Appearance/Language/Donation link rows; on web hover and Tab them. — Expect: hover/pressed/focus feedback; disabled rows inert.
  2. In the map layers popover, toggle events, individual categories, and select-all. — Expect: filtering works; toggles animate cleanly; nothing overflows.

Deep links and back navigation — [Web] [Mobile]

  1. [Web] Walk feed → event → announcements → an announcement → back, watching the address bar. — Expect: the address changes at every step and Back retraces it exactly.
  2. [Mobile] Open an announcement from a link and from a notification, then use the hardware/system back gesture. — Expect: you land on the event, not on a blank screen.
  3. Open an organization page from a link, then its settings screen, then go back. — Expect: the organization page is still there underneath.
  4. Open an address of the old shape /cleanups/<id>/broadcast. — Expect: you land on the event page, not a blank shell.

Report camera — [Mobile]

  1. Toggle "PHOTO" / "VIDEO · 10s" (VoiceOver announces selection; disabled while recording), "Choose from library", capture and submit a photo report. — Expect: flow completes; photo publishes.

Feed states and chat socket — [Web] [Mobile]

  1. Feed with the API down: — Expect: "The feed could not load" / "Try again". Guest empty state: "Nothing here yet" with "Sign in".
  2. Bounce between the feed and a conversation repeatedly, then stay in the conversation 5 minutes. — Expect: chat realtime keeps working throughout.
  3. Sign out from the feed while a pill is pending, sign in as another account. — Expect: the new account never sees the old account's pill count.

Web visual sweep in both schemes — [Web]

  1. Walk map, a report, an event, host console, legal pages in light AND dark. — Expect: no invisible text, no light panel in dark mode; pins and category chips agree in color; the light accent coral is a slightly deeper shade everywhere (consistent, one shade of coral on screen at a time).
  2. Narrow the window to phone width and open the analytics card, the analytics page and the organization settings screen. — Expect: charts, the carousel and the save bars fit without horizontal scrolling.

Signing out with no connection — [Mobile]

  1. Turn on Airplane Mode, then sign out via your avatar → "Settings" → "Sign out". — Expect: sign-out completes within a few seconds and you are back at the signed-out map; signing in again later works normally.

The push toggle — [Mobile]

  1. Open "Settings" → "Notifications". Under "Push", switch "Push notifications" off, leave the screen, and come back. — Expect: the toggle kept its state; switching it back on asks nothing new.

Not covered

  • How the new organization page, the reshaped dashboard and the profile actually LOOK on a device — the always-open event lists, the round share button, the "Invitations" card in the profile's flow and the analytics carousel in the dashboard's flow were not run in a simulator or a browser in this session.

  • Visual confirmation of the rebuilt analytics carousel is device-only. The graph-first panels, the measured-width paging and the fix for charts rendering tiny or the "Shifts" bars painting outside the card were reasoned from the code and covered by unit tests; nobody watched them draw on a real phone or in a browser in this session.

  • Guest contact now leaves the product through the host's roster export only. Whether hosts who relied on the on-page "Guests" list will find that export is a product question this change does not answer.

  • Guests are deliberately absent from sign-up slot lists — they are only counted in the going number. That is existing behaviour, not something this change fixed, and it is worth confirming a host is not surprised by it.

  • How any of the new work actually LOOKS on a device: the list-card spacing fix, the round header icon buttons, the cover image at the top of an event page and the address sheets were not run in a simulator or a browser in this session. Every claim about them here is read from the code, not seen.

  • The external-maps step per platform: the iOS "Address" sheet, Android's own app chooser and the clipboard were not exercised on either OS. Which maps app Android offers depends on what the tester has installed.

  • What a real lookup returns. The street / intersection / place / city-only rungs were built and tested against canned responses only — no live provider was called in this session, so the exact wording a real address comes back with (and how often the "we couldn't find a street address" path fires in a given city) is unverified.

  • On Android the "open in a maps app" control hands the location to the system chooser, and nothing in this session ran on Android. Worth watching: if the tap appears to do nothing at all on Android, that is the case to report, not a tester error.

  • A host standing at a pin that cannot be given an address can no longer publish until they type one, and the same now applies to editing an old event that never had an address. That is the intended behaviour, but how often it bites real hosts in a real city was not measured.

  • Which lookup provider answers is a server config and spend question, not something this PR decides — see the backend PR's note. It matters here only because it changes how often a pin gives a real street address rather than sending the host to type one.

  • Whether an address that is only roughly located should be shown at all, or coarsened, is a product and privacy decision left open; today it is shown with the "Near …" wording.

  • Setting a donation link for ONE event is still not possible anywhere in the app — only your own (Settings) and your organization's (Organization settings). Removing the dashboard row did not change that, but it does mean the app now has no row at all that mentions donation links outside Settings.

  • The social/link preview picture can only be checked against a deployed web build; a local export does not inject it. A cover whose image address is signed or carries a query string falls back to the brand picture — not exercised here.

  • Real push notifications and real announcement emails, and tapping a push to land on an announcement — no device and no mailer in this session; the in-app notification row is what the plan checks.

  • How the charts, the carousel and the social brand icons actually look — no simulator or browser run was made in this session, so their rendered appearance (spacing, colours, legibility of the bar/area/ring charts at small sizes) is unverified.

  • The full lifecycle of a chat photo the checks hold or reject: there is no practical way to make the checker reject a photo on demand, so only the normal "still being checked" path is a tester step. Note the app keeps a copy of a photo you just sent only for the current session and only for the last thirty messages — after a full reload it depends on the backend PR being deployed, and if the checks do reject your photo you will keep seeing it for the rest of that session while nobody else does.

  • The public organization page as it loads from a shared link on the web is a separate page from the one you reach inside the app, and it still lists social links as words rather than icons. Same address, two renderings — a known gap, not fixed here.

  • The organization's "needs at least one admin" refusal is not reachable from this screen: the actions are hidden before you can hit it, and an owner's seat already counts as an admin seat. Covered by the backend PR.

  • The web host console at its own address still exists and still works, but nothing in the app links to its dashboard any more (the product decision that removed "Open full console"); only the "Tickets" row on "Host mode" and the footer link on the public organization page reach it, both web-only.

  • Sharing or exporting analytics was deliberately cut from this change — the analytics page has no share or export action. CSV export still lives in the web host console only.

  • Real TestFlight→App Store and App Store→TestFlight install transitions (receipt detection and the one-time storage cleanup) — cannot be exercised before the build is on both channels; the logic is unit-tested, and the first upgrade off a build older than this one is documented as signing the user out rather than cleaning up preemptively.

  • App Review runs against staging by design now — before submitting, staging needs the reviewer sign-in enabled and a visible "Donate" card (noted in the review notes file).

  • Android install-source behavior: unchanged (always production for store builds) — not re-tested.

  • The seen-post rotation on repeated refreshes and popularity caps — server-side behavior, covered by the backend PR's plan.

  • Screen-reader passes beyond the specific announcements listed.

  • Deep links to civfix.dev generated by TestFlight sessions do not deep-link back into the app (pre-existing; one-line note in the app README).

  • Real notification delivery end-to-end (including the account-switch recovery) needs a TestFlight install on a physical phone — simulators cannot obtain push tokens, so this session verified the entire server half directly instead, plus every client flow the simulator can reach.

  • The App-Store↔TestFlight storage round trip is proven by tests, not by installing two real builds in sequence on one device.

  • The unlisted-event link preview: paste an unlisted event's link into a chat app that unfurls links and confirm no cover image appears (a public event's cover still should).

  • The analytics "Page views" and "Donation-link taps" numbers only appear with real web traffic (production web beacons); local and TestFlight runs will not show those tiles — absence there is correct, not a bug.


function apsEnvironmentMode() {
const profile = process.env.EAS_BUILD_PROFILE
if (!profile) return "development"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Use production APNs entitlement

The documented prep:testflight and prep:appstore archive paths run expo prebuild without EAS_BUILD_PROFILE, so this fallback selects the development APNs mode for both targets. The generated TestFlight and App Store archives therefore receive the sandbox entitlement and production APNs will not deliver their push notifications. Pass the archive target into the config or select production mode for store-distributed local archives.

Artifacts

Evidence from the check

  • The executed Node script loads Expo config for the TestFlight and App Store local archive environments with no EAS build profile, proving the exact conditions under review.

Command output from the check

  • The executed validation output shows both targets resolving expo-notifications to development mode with EAS_BUILD_PROFILE unset, confirming the production APNs entitlement is wrong.

Command output from the check

  • The executed git check shows no diff for the relevant tracked configuration and archive-script files, confirming validation did not edit tracked files.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/community-mobile/app.config.js
Line: 40

Comment:
**Use production APNs entitlement**

The documented `prep:testflight` and `prep:appstore` archive paths run `expo prebuild` without `EAS_BUILD_PROFILE`, so this fallback selects the development APNs mode for both targets. The generated TestFlight and App Store archives therefore receive the sandbox entitlement and production APNs will not deliver their push notifications. Pass the archive target into the config or select production mode for store-distributed local archives.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code

@greptile-apps

greptile-apps Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Comments Outside Diff

These findings sit on lines the diff does not cover, so they could not be posted inline. Each one leaves this list once its file changes.

  • P1 Verify Existing Tag Commit scripts/check-shared-version.mjs:93 ▶

    When an earlier release attempt creates the version tag but publishing fails, a later commit with the same package version reuses that tag without confirming it identifies the current HEAD. The package can therefore be published from the later commit while its release tag still points at the earlier commit, leaving consumers unable to match the published contract to its tagged source. Refuse a release when an existing tag differs from HEAD, or publish the tagged commit instead.

  • P1 Release accepts a stale existing version tag instead of requiring it to match HEAD ▶

    • Bug
      • With the version absent from the mocked registry, an existing @civfix/shared@0.56.0 tag at commit A and HEAD at descendant commit B caused node scripts/check-shared-version.mjs --release to exit 0 and report readiness. The pushed remote tag still resolved to A, not B.
    • Cause
      • Lines 90-92 only create a tag when it does not exist. Line 93 unconditionally pushes the existing tag. Earlier validation establishes only that the tag is an ancestor of origin/main, not that it equals the release commit/HEAD.
    • Fix
      • Before the release push, resolve HEAD and the existing tag commit; refuse the release when they differ (or otherwise explicitly require the tag to resolve to the intended release commit).

Comment thread apps/community-mobile/app/_layout.tsx
title={t("card.view_full")}
titleLines={1}
chevron
pressedHighlight={false}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Restore pressed feedback

The “View full analytics” row sets pressedHighlight={false}, disabling the ListRow hover and pressed backgrounds. The row remains clickable, but users receive no visual response while hovering over or pressing this navigation action; restore the highlight or provide equivalent feedback.

Suggested change
pressedHighlight={false}
pressedHighlight
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/ui/src/bodies/host/dashboard/AnalyticsCarouselCard.tsx
Line: 102

Comment:
**Restore pressed feedback**

The “View full analytics” row sets `pressedHighlight={false}`, disabling the `ListRow` hover and pressed backgrounds. The row remains clickable, but users receive no visual response while hovering over or pressing this navigation action; restore the highlight or provide equivalent feedback.

```suggestion
      pressedHighlight
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

const isEnded = hasEventEnded(cleanup, now)
const isLive = !isCancelled && !isDone
const isUpcoming = isLive && !isEnded
const donation = useMemo(() => donationLinkFor(cleanup), [cleanup])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Hide invalid donation cards

The event page now resolves donation links without considering whether the viewer hosts the event or whether the event is cancelled. An eligible link therefore renders the Donate card and action for both an event host and a cancelled-event visitor. This can solicit donations through an event that is no longer taking place; restore the host and cancellation visibility checks when selecting the link.

Artifacts

Evidence from the check

  • Authored and executed focused test constructs an eligible cleanup for a host and a cancelled visitor, renders the resulting card markup, and compares it with the prior visibility gate; both restricted viewers now receive the card.

Command output from the check

  • Captured output of the Vitest command run in `/home/user/repo`, showing the focused test passed with exit code 0; the host and cancelled visitor cases were exercised successfully.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/ui/src/bodies/EventDetailBody.tsx
Line: 216

Comment:
**Hide invalid donation cards**

The event page now resolves donation links without considering whether the viewer hosts the event or whether the event is cancelled. An eligible link therefore renders the Donate card and action for both an event host and a cancelled-event visitor. This can solicit donations through an event that is no longer taking place; restore the host and cancellation visibility checks when selecting the link.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code

@theobong
theobong merged commit 3b72775 into main Sep 23, 2026
3 checks passed
@theobong
theobong deleted the feat/feed-and-polish-batch branch September 23, 2026 01:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant