Conversation
iOS will not surface any subscription / billing UI. Subscriber-only features are hidden for non-subscribers (no disable-with-paywall, no "upgrade" CTA, no plan info). Subscription management lives entirely on the web at interlinedlist.com. - subscription-permissions-update.md: record user-confirmed answers (degraded subscribers see nothing; strict silence on copy; help links via SFSafariViewController OK; no subscription mention anywhere in the iOS bundle). - GAP-NEXT-STEPS.md: add the principle to the top; drop "Subscriber CTA on profile" from Phase 3; switch Phase 4 cross-post controls from "disable + paywall" to "hide entirely"; remove the "Subscription status + manage subscription" row from Phase 12 Settings. - GAP-ENDPOINTS.md: withdraw B1 (subscription plans catalog) — the iOS app no longer needs a plans-catalog endpoint because it has no in-app paywall to render. Mark the Subscriptions (Stripe) endpoint row as intentionally unused by iOS. The code changes that align with this direction land in the next three commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Folders are a subscriber-only feature per the iOS-free-app direction. Free users get no folder UI at all — no "New Folder" menu item, no folder tree, no folder navigation. Any data they already had in folders (e.g. from a prior subscription) still exists on the server; the iOS UI just surfaces it at root. ListsView: - Add canCreateFolders predicate based on authState.user.isSubscriber. - treeNodes passes an empty folder array for non-subscribers so lists that were nested in folders surface at root via buildTree's orphan rule. - "New Folder" menu item gated on canCreateFolders. - CreateListFolderView: drop the paywallMessage constant and the special 403 catch arm. The button is unreachable for free users; if a 403 ever sneaks through (e.g. subscription state shifts mid-session) we surface a generic "Failed to create folder." DocumentsView (root + DocumentFolderView nested): - allFolders returns [] for non-subscribers; rootDocuments returns every document (regardless of folderId) so nothing is hidden. - "New Folder" menu item gated in both the root view and the nested folder view. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ComposeView: - New canUseSubscriberFeatures predicate based on authState.user.isSubscriber. - Gear button (toggles the advanced bar) is hidden for free users — no point in a toggle that reveals nothing. - The advanced HStack (image/video picker, M/BS/in cross-post stubs, calendar/schedule button) is gated as a whole. Free users only see the "characters remaining" label. - schedulePicker section in the body is also gated; even if showSchedulePicker were toggled by some other code path, free users never see the picker. FeedView: - "Scheduled posts" calendar button in the toolbar is hidden for non-subscribers. Composing remains visible to everyone. No paywall strings touched here — those come out in the next commit once the UI paths that triggered them are all unreachable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
With the prior commits, every UI path that previously surfaced a subscription paywall string is now hidden for free users. The remaining 403 catch arms exist only as defensive belt-and-suspenders; strict silence on subscription state means none of them should ever expose subscription copy. ComposeView (uploadVideo / uploadPhoto): - Drop the `catch APIError.status(403)` arms that surfaced "Video upload requires an active subscription." / "Image upload requires an active subscription." The pickers are hidden for non-subscribers; 403 falls through to the generic "Failed to upload …" branch. DocumentsView (CreateDocumentView.save / DocumentDetailView.save): - Drop the `catch APIError.status(403)` arms that surfaced "Requires active subscription." `POST /api/documents` is not documented as subscriber-only, and free users can't pass a folderId anyway (folder UI is hidden). Falls through to the existing generic catch. ScheduledMessagesView.load: - Drop the 403 paywall string. The calendar entry point that launches this view is hidden in `FeedView` for free users, so this view is unreachable for them. 403 falls through to the generic "Could not load scheduled posts." AppDataStore.refreshDocuments: - Drop the 403 paywall string. `GET /api/documents` is not documented as subscriber-only; a 403 here is an unexpected condition treated as a generic load failure. After this commit, `grep -rn "subscription" InterlinedList/` returns only the explanatory doc-comment on `User.customerStatus` and no user-facing string. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t suite The Phase 2/3 auth + account-management work (committed in 067a057) didn't build or test cleanly: - Seven new source files were never registered in the Xcode target, so the app failed to compile (ResetPasswordView, OAuthCoordinator, ChangeEmailView, ForgotPasswordView, plus the three views added here). Added their PBXBuildFile / PBXFileReference / group / Sources-phase entries. - MainTabView, LoginView and RegisterView referenced three views that were never created. Added them, matching existing patterns: - EmailVerificationBanner — resend-verification banner, hidden unless user.emailVerified == false. - LinkedIdentitiesView — list/disconnect linked OAuth providers and link new ones via the OAuth flow with link=true. - OAuthSignInButton — shared provider row used by Login and Register. - Fixed five pre-existing/new broken tests whose assertions were wrong while the production code was correct: - Avatar/Image png-extension: decoded a binary multipart body as UTF-8 (nil) — now search the raw Data for the filename. - Video 403: expected .status but checkResponse correctly returns .server when the 403 body carries an error message — stub an empty body to exercise the status path. - People encoding: inspected URL.path (percent-decoded) — now assert on absoluteString. - Profile update: expected camelCase displayName but /api/user/update uses the default snake_case encoder (like register) — assert display_name. Build succeeds; full suite is 325 tests, 0 failures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- GAP-NEXT-STEPS.md: add a "✅ Shipped phases" table, refresh the status snapshot with the new auth + profile capabilities, collapse the Phase 2/3 detail to shipped stubs, and update the effort summary. Notes the one loose end: the "Change email" entry row in EditProfileView is still a TODO (view + API + deep link exist; only the presenting row is unwired). - GAP-ENDPOINTS.md: backend gaps unchanged (all still standing), but the "What backend has now" usage table now reflects the endpoints iOS consumes after Phases 2/3 (OAuth, reset/verify, identities, orgs, avatar, email change, delete account). §B7 notes the iOS re-fetch workaround is now in place. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Account section showed the email read-only behind a TODO. Replace it with a tappable "Change" row that presents ChangeEmailView as a sheet (posts to /api/user/change-email/request; the verify-email-change deep link completes the change). ChangeEmailView, the API call, and the deep link already existed — this wires the entry point. Drops the "change-email entry row pending" caveats from GAP-NEXT-STEPS.md and GAP-ENDPOINTS.md; Phase 2 is now fully closed. Build succeeds; full suite is 325 tests, 0 failures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the networking and model layer for the now-unblocked roadmap
(backend resolved B0/B2/B3/B5):
- Structured list schema (B0): updateListSchemaStructured + ?force,
SchemaPropertyInput, DraftProperty round-trips defaultValue/helpText/
placeholder, slugifyKey + structuredProperties serializer.
- Follow surface (5): followers/following (paginated), mutualCounts,
removeFollower + FollowUser/MutualCounts models.
- Watchers (6): list/add/setRole/remove/search + ListWatcher, WatcherRole.
- Public browse (7): publicListDetail/Data/Documents/Document + tolerant
PublicBrowse decoders (flat and {list,ancestors} shapes).
- Organizations (8): full org + member CRUD + join; Organization expanded,
OrganizationMember, OrgRole.
- Compose (4): postMessage cross-post params + PostMessageResult,
patchScheduledMessage, refreshMessageMetadata, cross-post body types.
- Settings/notifications (12): updateUserSettings, notificationPreferences
GET/PATCH against the real catalog (push/inApp only) + models.
- Message search (13): searchMessages.
- APIError.conflict for 409 force-delete handling; patchCamel helper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…es 4, 13) - ComposeView: real cross-post toggles (Mastodon multi-instance picker, Bluesky, LinkedIn, X) hidden entirely for non-subscribers; repost mode (pushedMessageId + optional commentary + quoted preview); crossPostResults summarized in the success toast. - EditMessageView: edit a scheduled message's send time via PATCH. - ScheduledMessagesView: rows open the editor; reload on dismiss. - FeedView: Repost action on rows; .searchable feed backed by GET /api/messages/search (Phase 13 search half; tag discovery still blocked on §B6). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- FollowListView: paginated followers/following, swipe-to-remove on the current user's own followers, tap-through to a profile sheet. - UserProfileView: tappable follower/following counts, mutual-count strip, public list rows navigate to PublicListDetailView, new Documents segment. - PublicListDetailView: read-only rows (schema-aware, falls back to raw keys), sub-list navigation, Watch/Unwatch CTA (Phase 6 endpoint). - PublicDocumentsView + PublicDocumentReader: read-only public documents. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WatchersListView, presented from the owner's ListDetailView: lists current watchers with their role, a role picker (watcher/collaborator/manager), swipe-to-remove, and an add-watcher flow backed by the candidate search endpoint. Read-only public lists get the Watch CTA in Phase 7. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OrganizationsListView / OrganizationDetailView / OrganizationMembersView plus create/edit sheets: full org CRUD (owner-only edit/delete), member role management (owner/admin/member), the last-owner-cannot-be-removed guard enforced client-side, and join-public-org. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ases 12, B0) - SettingsView: theme (PATCH /api/user/update, applied via RootView.preferredColorScheme), default visibility, advanced-post toggle, connected accounts, About links via SFSafariViewController, sign-out. - NotificationPreferencesView: renders the real server catalog (dig/push/ follow; push/inApp only, no email — per §B3 divergence), toggles persist. - ListSchemaEditorView: saves via the structured endpoint (round-trips isVisible/isRequired/order) with a 409 force-delete confirmation (B0). - MainTabView: Profile tab gains Settings (gear), Followers/Following, and Organizations entries. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- APIClientGapPhasesTests: path/method/decode coverage for follow lists, watchers, public browse, organizations, structured schema (incl. 409 → conflict and ?force), notification prefs, message search, scheduled-edit, cross-post results, link metadata. - GapModelsTests: schema serializer (slug/new-vs-existing/reorder), role ordering + capabilities, notification channel support, follow display. - GAP-NEXT-STEPS.md: Phases 4–8, 12, B0 and feed search marked shipped. Full suite green: 365 tests, 0 failures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…PLE ship guide - GAP-ENDPOINTS.md: repurposed from "missing endpoints" to a catalog of live-but-ambiguous contracts found while building the GAP phases (OpenAPI-vs-help shape disagreements, "no body" responses, watcher /me-has-no-role + non-standard pagination, cross-post/link-metadata shapes, avatar-returns-user), plus the still-blocked B4/B6/B8/B9. - GAP-APPLE.md: step-by-step App Store signing & submission guide tailored to this project (bundle id, version/build, automatic signing, icon, Info.plist fixes for encryption + armv7, anti-steering/account-deletion/ UGC compliance, archive+upload GUI and CLI paths, TestFlight, review). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er linking Per the backend native-auth contract: - GitHub's OAuth callback has no mobile/custom-scheme branch (it sets a web session cookie and redirects to /dashboard), so it can never return interlinedlist://oauth/callback?token=… and dead-ends inside ASWebAuthenticationSession. Added OAuthProvider.supportsNativeAuth and filtered it out of the sign-in lists in LoginView and RegisterView. - In-app account linking (?link=true) authenticates via the web session cookie, not the Bearer token, so a Bearer-only native client can't link a new provider. Gated the "Link another provider" UI in LinkedIdentitiesView behind `linkingEnabled = false` (list + disconnect still work); shows a note to link via the web. Flip the flag when a Bearer link endpoint exists. Build + 365 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Documents created "in a folder" landed at root, and opening a folder
showed unrelated root documents. The cause: `GET /api/documents` and
`POST /api/documents` are root-only — the GET ignores `?folderId=` and
the POST has no `folderId` field — so the iOS client must use the
folder-scoped endpoints and camelCase bodies (verified against the live
OpenAPI spec).
- documents(folderId:) reads `GET /api/documents/folders/{id}/documents`
- createDocument posts to the folder-scoped endpoint when a folder is set
(folder conveyed by path, not body) via postCamel so isPublic survives
- updateDocument/createDocumentFolder use camelCase (patchCamel/postCamel)
so folderId/isPublic/parentId aren't dropped server-side
- expand APIClientDocumentsTests to lock in the corrected paths and bodies
Bundled repository housekeeping in the same snapshot:
- docs: rewrite README to match the shipped feature set (OAuth, lists,
documents, orgs, social, notifications, settings) instead of the stale
"placeholder" copy; overhaul CLAUDE.md (DI/data-store/OAuth/deep-link
architecture, the 401 re-validation contract, build/test, gotchas)
- test: disable parallelization in InterlinedList.xctestplan — the E2E
suite shares a static login token that parallel sim clones defeat
- chore: remove Codex assets (AGENTS.md, .codex/) in favor of .claude
- chore: add /comment-and-commit and /commit-and-pr slash commands
- chore: extend .claude/settings.local.json permission allowlist
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Brings the
devbranch up tomain, shipping the bulk of the InterlinedList iOS app (Phases 2–13) plus a correctness fix for document folders and a refresh of the repo's docs/tooling. 19 commits.What's included
/api/documents/folders/{id}/documents) and camelCase bodies, so documents created in a folder no longer land at root and folders no longer show unrelated root documents (6d7a4fb)./comment-and-commit+/commit-and-prcommands, disable xctestplan parallelization (6d7a4fb); build wiring + green suite (8d20d31).Testing
xcodebuild ... test -only-testing:InterlinedListTests/APIClientDocumentsTests— 14 passed; build succeeded (this session)./api/openapi.jsoncontract; earlier phase commits landed with their own model/API test coverage.⌘Upass before merge.Caveats / follow-ups
folderId: nil); moving into a folder works.🤖 Generated with Claude Code