dba002cbfd
Compose now prompts Keep / Discard on Cancel when the top-level body is non-empty. The default action keeps the draft (matching the silent behaviour we had before), but users who want to start fresh can now do so from inside the sheet without having to tap-and-hold the TextEditor to select and delete. Profile view becomes `.searchable()` with a case-insensitive substring match on post body + tags. The filter runs locally on the parsed profile so it's cheap even on large feeds. TODO.org gains a "Post-1.0 enhancements" section summarising every interaction-fidelity, timeline, compose, render, network and test change landed during this review pass.
8.6 KiB
8.6 KiB
Release QA Pass (2026-04-21)
- Test account
- Flows exercised end-to-end
- Bugs caught and fixed during this pass
- Production readiness
- Open questions / next work
- Interaction QA pass (2026-04-21, five accounts qa1-qa5 on host)
- Ready for release candidate
- Date audit vs Org Social spec (DONE 38b7314)
- Post-1.0 enhancements (2026-04-24)
Test account
- Host: host.org-social.org
- Nickname: qatest042126
- Feed: https://host.org-social.org/qatest042126/social.org
- Created via Welcome flow → Create my social.org
Flows exercised end-to-end
Welcome + signup
- Fresh install → Welcome screen renders
- "Create my social.org" → Nickname form
- Signup POSTs to host.org-social.org/signup, auto-fills Settings
- Landing tab is Timeline with empty state
Tab smoke test (empty state)
- Timeline "No posts yet" + Discover/Show-all buttons
- Notifications "No Notifications" (not yet indexed by relay)
- Discover lists up to 100 random relay feeds
- Groups lists every group advertised by the relay (discovery, not user subscriptions)
- Profile renders header only, no Following/Posts sections
Follow
- Tap Follow on 3 users in rapid succession
- Server file shows ALL three `#+FOLLOW:` entries in tap order
- Discover repaints Unfollow on the followed rows
- Coming back to Timeline reloads (via FollowCoordinator.feedVersion)
Compose + Edit + Delete
- Plain post publishes, appears with `:CLIENT: iOS` in the uploaded file
- Edit preserves original timestamp, only text/properties change
- Delete removes the post block
- Pin writes `#+PINNED:` with header-matching timestamp
- Delete on a pinned post now also clears `#+PINNED:` (fix in
a40f0c0)
Profile + Settings
- Edit Profile sheet writes NICK/TITLE/BIO/LOCATION/BIRTHDAY/LANG/LINKS/CONTACTS
- Announce account migration modal renders (not posted)
- Export feed opens the iOS share sheet with the .org attachment
Relay settings
- Use Relay ON + Show all relay feeds OFF (default): Timeline = follows only, Discover = relay-wide
- Toggle wiring via @AppStorage; Timeline reloads on change
Poll
- Unit tests cover NewPostOptions.poll + POLL_END output (13 assertions)
- UI drive blocked: axe tap does not flip SwiftUI Toggle; real human tap works
Bugs caught and fixed during this pass
- `#+PINNED` dangled after deleting the referenced post. Now
cleared during deletePost. If the edit moves a pinned post to a new
timestamp, PINNED is retargeted. Commit:
a40f0c0.
Production readiness
- 132 Kit tests pass
- PrivacyInfo.xcprivacy declares NSPrivacyAccessedAPICategoryUserDefaults (CA92.1)
- ITSAppUsesNonExemptEncryption=NO in Info.plist
- NSAppTransportSecurity strict (no NSAllowsArbitraryLoads)
- No TODO/FIXME, no print() debug, no hardcoded tokens
- Welcome flow gates first-run
- Per-write feedVersion bump drives Profile/Timeline auto-refresh
Open questions / next work
- Date format normalisation (resolved in commit
38b7314: compact local-timezone form via PostWriter.generateTimestamp + shared parseTimestamp helper; parser and App reuse it) - Poll creation via automation (still blocked by axe Toggle tap behaviour; covered by 13 Kit assertions — deferred, not a blocker)
- Groups tab: "My groups" vs "Discover groups" — product decision, not a bug; keeping current discovery-only behaviour for release
Interaction QA pass (2026-04-21, five accounts qa1-qa5 on host)
- Reply emits `:REPLY_TO: <url>#<ts>` + body (qa2 → qa1)
- Reaction emits `:REPLY_TO:` + `:MOOD:` + empty body (qa3 ❤️ → qa1)
- Boost emits `:INCLUDE: <url>#<ts>` (qa4 → qa1)
- Quote boost: `:INCLUDE:` + body text (qa5 with commentary)
- Deep reply: reply-of-a-reply chain resolves (qa4 → qa2 → qa1)
- Reaction on a reaction (qa5 🔥 → qa3 ❤️)
- Double-react: two separate post blocks with same REPLY_TO + MOOD; relay aggregates by emoji (as expected)
- Boost of a boost (qa5 → qa4's boost of qa1)
- Long body + Org markup preserved as raw text
- Emoji-only body round-trips (no LANG, no body trimming issues)
- Reply compose sheet now shows `@nick · timestamp` (commit
8613a71) - Thread view renders qa1 root + qa2 reply; deeper levels appear as relay indexes (background task)
Ready for release candidate
- 133 Kit tests pass
- All interaction types emit spec-compliant Org Social
- Timestamps in Emacs-compatible compact local form (`+HHMM`)
- PrivacyInfo.xcprivacy + ITSAppUsesNonExemptEncryption in place
- PINNED kept in sync on delete and re-timestamp edits
- Poll UI still needs a real-hand smoke test (axe limitation only)
Date audit vs Org Social spec (DONE 38b7314)
Protocol (/Users/androsfenollosa/workspace/opensource/org-social/README.org) accepts two RFC 3339 variants for the post `** <ts>` header:
- `2025-12-30T20:30:15+00:00` (colon in timezone)
- `2025-12-30T18:30:15-0200` (compact, no colon)
`Z` for UTC is also accepted by our parser; spec examples prefer the offset form.
Current library behaviour:
- `PostWriter.generateTimestamp` uses `ISO8601DateFormatter` with `.withInternetDateTime + .withColonSeparatorInTimeZone`. UTC. Emits `2026-04-21T12:24:12Z` (the `Z` form, not `+00:00`).
- POLL_END: same formatter, same result.
- `editPost` drops the `:ID:` property from preserved props and rewrites the header from `post.timestamp` (which is the parser's read-back string, so it preserves whatever format the feed had when parsed).
Risks to decide on:
- Emitting `Z` when the spec examples use `+HH:MM`. Parsers are lenient but a strict third-party client may reject. Cheap to change.
- `:ID:` gets dropped on edit. Header format is preserved in practice because we round-trip the parsed string. Edge case: a feed written by another tool with `:ID: <compact>` and `** <colon>` in the header would lose the compact `:ID:` after our edit. No known impact.
- PINNED/MIGRATION/reply URL matching is string-based; if a post is ever re-serialised in a different format the relay index can miss it. Today we always preserve the parser's string, so we are safe.
Post-1.0 enhancements (2026-04-24)
Interaction fidelity
- Un-boost (INCLUDE post deletion) — symmetric with unreact/unvote
- Reaction removal
- Poll vote removal (with prominent vote buttons hidden after voting)
- Instant toggle boost/react/vote (no confirmation dialogs)
- OwnInteractionCache in UserDefaults — survives relaunches, avoids CDN-staleness false negatives, self-heals when the cached post is gone
- Bidirectional relay cross-check with 120s grace window for "relay hasn't indexed my just-written action yet"
- Surface boost/react/vote errorMessage via alerts in PostRowView
Timeline & profile polish
- Hide body-less posts (pure reactions / boosts / votes / migrations / accidentally empty) — single rule `post.isPureInteraction`
- mergeOwnFeed applies the same filter as TimelineFetcher
- Boost count badge next to the Boost icon
- Reaction chips tappable -> sheet listing reactor hostnames
- Voter hostnames under each poll option
- Profile view has pull-to-refresh (bypasses CDN cache)
- Profile view has .searchable() filter on body + tags
Compose & edit
- Edit sheet covers every compose field (schedule, visibility, language, mood, tags) via a single @Observable EditFormModel
- NewPostOptions.poll accepts lang/tags/mood/visibility
- Warn before publishing mention-only posts that have no `nick` links (nobody would see them)
- Compose drafts persist in UserDefaults for top-level posts; Keep / Discard dialog on Cancel when body is non-empty
- @nick autocomplete in Compose and Edit: typing `@` shows a dropdown of follows (plus relay users when the "show all relay feeds" setting is on). Selection inserts `@nick` and on publish the spec's `nick` form is written. Edit decodes existing links back to `@nick` so they round-trip.
Rendering
- OrgBodyRenderer renders mentions inline as `@nick` (keeping the sentence readable) and attaches the feed URL as an AttributedString .link so SwiftUI renders it accent-coloured and tappable
- `OpenURLAction` in PostRowView intercepts feed URL taps and navigates to ProfileView within the NavigationStack
- Strips `- [ ] Option` checkbox lines from poll body (the poll card renders options with vote counts separately)
Networking
- Proactive feed self-registration on launch (RelayClient.registerFeed) so brand-new accounts are indexed within ~60s instead of waiting for the daily follow-discovery crawl. Gated by UserDefaults flag per (relay, feed) pair to run once per account.
Tests
- 166 Kit tests pass (added full-edit integration, poll-factory shared-field coverage, mention render round-trip)