Skip to content

interlinedlist-android

94 rows · Updated
interlinedlist-android
Issue #TitleBodyStateLabelsAssigneesLinkCreatedUpdated
46Push: device-token lifecycle (`POST /api/push/register` / `DELETE /api/push/unregister`)Part of #44 — Push notifications: real device push (FCM) to replace the WorkManager poll - Register the FCM token on first grant and on every token rotation. - Unregister on sign-out and on account deletion — a stale token means notifications for one account land on a device someone else is now signed in on. - Request `POST_NOTIFICATIONS` at a moment that earns the grant rather than on cold start, and keep the app fully functional when it is denied. - Reuse the existing session/auth lifecycle hooks in `:feature:auth` rather than adding a new one. **Tests:** register on new token; unregister on sign-out; denied-permission path. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP1area:notificationsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/46Sep 15, 2026, 10:58 AMSep 16, 2026, 8:18 PM
45Push: Firebase project + `google-services.json` wiringPart of #44 — Push notifications: real device push (FCM) to replace the WorkManager poll **Blocked on the repo owner.** - Create/obtain the Firebase project for the Android app. - Add `google-services.json` (gitignored; CI reads it from a secret), the `google-services` Gradle plugin, and the Firebase Messaging dependency in `gradle/libs.versions.toml`. - Confirm with the owner whether the server's push registration accepts FCM tokens for Android — `/help/notifications` documents push as iOS-only today. - Document the setup so a fresh checkout and CI both build without the file being committed. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openblockedP1area:notificationsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/45Sep 15, 2026, 10:58 AMSep 16, 2026, 7:57 PM
44Push notifications: real device push (FCM) to replace the WorkManager poll## Where this stands Android currently delivers notifications by **polling `GET /api/notifications` on a WorkManager job** and raising local system-tray notifications (per-category channels, grouping, deep links, a persisted last-seen marker, and the `push` channel of notification-preferences respected). That shipped deliberately because there is no Firebase project. It is a good fallback, but it is not parity: it costs battery, it is late by up to the poll interval, and it stops when the OS decides it should. ## What parity needs `POST /api/push/register` and `DELETE /api/push/unregister` exist on the server and are **not used by the app** — without FCM there is no device token to register. The help centre's notifications page currently documents push for iOS only, so the Android registration contract should be confirmed with the owner as part of this work. ## Blocked Needs a **Firebase project and `google-services.json`** from the repo owner. ## Done when A notification raised server-side appears on the device without the app polling, routed to the right channel and deep-linking to the right screen. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openepicparityblockedP1area:notificationsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/44Sep 15, 2026, 10:58 AMSep 15, 2026, 10:58 AM
43Cross-posting: surface failures and reply-threading behaviourPart of #38 — Social account linking (OAuth) and full cross-post destination support Web behaviour: if cross-posting fails, a transient error appears for ~5 seconds and the post **still lands on InterlinedList**. When replying to a cross-posted message, if the user follows the original author on that platform, the reply may be cross-posted automatically. There is no dedicated cross-post status endpoint — status rides the message-create response (`crossPosts` in the `POST /api/messages` body). ## What already works The per-network status **is** handled: `CreateMessageResponse` parses `crossPosts` into `CrossPostStatusDto`, `MessagesFeedViewModel` exposes `crossPostStatuses`, and `MessagesFeedScreen` renders a `CrossPostStatusBanner` on post. That half of this issue is done — it is recorded here so the epic's coverage is accurate. ## What is left - **Replies.** The status banner is wired into the feed composer. Verify the reply composer and the scheduled-post path surface it too, since a reply to a cross-posted message may be syndicated automatically and can fail the same way. - **Stale-connection cause.** A failure caused by an expired connection should point at re-verification rather than reading as a transient glitch — see the unlink/re-verify sub-issue. - **Scheduled posts** fire their cross-posts later, when no banner can be shown. Decide how a failure that happens hours after composing reaches the user at all (a notification, or a marker on the published message). **Tests:** reply path surfaces a failed network; scheduled-post failure is discoverable after the fact. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._ openparityP2area:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/43Sep 15, 2026, 10:58 AMSep 15, 2026, 10:58 AM
42Organizations: LinkedIn company-page managementPart of #38 — Social account linking (OAuth) and full cross-post destination support Web behaviour (`/help/organizations` → *LinkedIn company pages*): an organization can hold a LinkedIn credential and assign pages to members. None of these are called from Android: - `GET /api/organizations/{id}/linkedin/status` - `PUT /api/organizations/{id}/linkedin/assignments` - `DELETE /api/organizations/{id}/linkedin/credential` - `POST /api/organizations/{id}/linkedin/sync-pages` Add a LinkedIn section to the organization detail screen, visible only to roles allowed to manage it. **Tests:** status parse; assignment round-trip; credential removal confirmation. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP3area:integrationsarea:organizationsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/42Sep 15, 2026, 10:58 AMSep 16, 2026, 9:33 PM
41LinkedIn: personal vs company-page posting targetsPart of #38 — Social account linking (OAuth) and full cross-post destination support Web behaviour (`/help/cross-posting` → *Choosing where a post goes on LinkedIn*): a post can go to the user's personal LinkedIn feed and/or to company pages they administer. `POST /api/messages` accepts **`linkedInTargets[]`** — each entry `{ kind: "personal" }` or `{ kind: "orgPage", pageId }` — plus **`linkedInLinkAsFirstComment`** (post the link as the first comment instead of in the body). Android sends only the boolean `crossPostToLinkedIn`, which is the superseded form. - Fetch targets with `GET /api/linkedin/posting-targets` / `GET /api/linkedin/targets`; refresh the page list with `POST /api/linkedin/sync-pages`; persist the user's default selection with `PUT /api/linkedin/posting-targets`. - Add the destination picker to the composer **and** to the scheduled-post editor, as the web does. - Add the link-as-first-comment toggle (only meaningful when the content contains a URL). **Tests:** targets parse; composer sends `linkedInTargets[]`; first-comment toggle round-trip. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openparityP2area:messagesarea:integrationsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/41Sep 15, 2026, 10:58 AMSep 15, 2026, 10:58 AM
40Integrations: unlink and re-verify connected accountsPart of #38 — Social account linking (OAuth) and full cross-post destination support `DELETE /api/user/identities` is already declared in the app's API layer; `POST /api/user/identities/verify` is not called at all. Web behaviour (`/help/cross-posting` → *Keeping your connections active*): connections can go stale and need re-verification, and the user can unlink an account. - Add unlink (with confirmation — it stops cross-posting to that network) and re-verify actions to the connected-accounts screen. - Show connection health (`connectedAt` / `lastVerifiedAt` are already in the identities payload) so a stale connection is visible before a post silently fails to syndicate. **Tests:** unlink round-trip and list refresh; verify round-trip; stale state rendering. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP2area:integrationsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/40Sep 15, 2026, 10:58 AMSep 16, 2026, 8:42 PM
39OAuth: link a social account from Android via Custom TabsPart of #38 — Social account linking (OAuth) and full cross-post destination support **Blocked on registered Android redirect URIs.** Web behaviour: the Integrations page links Mastodon, Bluesky, LinkedIn, X/Twitter and GitHub. Android can only *read* what is already linked. - Launch `/api/auth/{provider}/authorize` in a **Custom Tab** (never a WebView — it must not see the user's credentials, and Custom Tabs is what the platform expects for OAuth). - Handle the redirect back with an App Link / custom-scheme intent filter, then confirm with `/api/auth/{provider}/status`. - Surface the failure paths clearly; an interrupted OAuth round-trip must not leave a half-linked identity. **Blocked until:** the owner registers redirect URIs for the Android app with each provider and on the InterlinedList side. **Tests:** callback parsing; status confirmation; cancelled-flow cleanup. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openparityblockedP1area:integrationsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/39Sep 15, 2026, 10:58 AMSep 16, 2026, 7:30 PM
38Social account linking (OAuth) and full cross-post destination support## Where this stands Cross-posting to **already-linked** networks works: the composer reads `GET /api/user/identities` and sends `mastodonProviderIds[]` + `crossPostTo{Bluesky,LinkedIn,Twitter}`. What is missing is everything around it — **linking an account in the first place**, unlinking, re-verifying, and LinkedIn's destination model. ## Blocked The OAuth connect flow needs **registered redirect URIs** for the Android app before it can be built. Everything else in this epic can proceed independently. ## Unused API surface - `GET /api/auth/{bluesky,github,mastodon,twitter,linkedin}/authorize|callback|status` - `POST /api/auth/{provider}/link` - `POST /api/user/identities/verify` - `GET|PUT /api/linkedin/posting-targets`, `GET /api/linkedin/targets`, `POST /api/linkedin/sync-pages` - `GET /api/organizations/{id}/linkedin/status`, `PUT …/assignments`, `DELETE …/credential`, `POST …/sync-pages` - `linkedInTargets[]` and `linkedInLinkAsFirstComment` on `POST /api/messages` Reference: `/help/cross-posting`. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openepicparityblockedP1area:messagesarea:integrationsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/38Sep 15, 2026, 10:58 AMSep 15, 2026, 10:58 AM
37Settings: profile location (`latitude`/`longitude`) with runtime permissionPart of #31 — Settings & preferences parity — most of `PATCH /api/user/update` is unreachable from Android The web's Settings has **Profile location** and a **Permissions** section; the stored lat/long drives the location and weather surfaces. - Add a location section that can set the coordinates from the device (with the `ACCESS_COARSE_LOCATION` runtime permission and a clear rationale) or by manual entry. - Setting location must stay optional — the app must be fully usable when it is refused. **Tests:** PATCH round-trip; permission-denied path leaves the app functional. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP3area:settingsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/37Sep 15, 2026, 10:57 AMSep 16, 2026, 9:42 PM
36Settings: theme preference synced to the account (`theme`)Part of #31 — Settings & preferences parity — most of `PATCH /api/user/update` is unreachable from Android Dark mode already works locally via `ThemeSettingsStore`/`ThemeMode` in `:core:datastore`, but the choice is device-local. The account carries a `theme` field on `PATCH /api/user/update`, so a user who sets dark on the web still lands in light on a fresh Android install. - Two-way sync: push local changes to `theme`, and adopt the account value on first sign-in. - Keep the local store as the source of truth while offline, reconciling on next sync. **Tests:** local → PATCH; account value applied at sign-in; offline change survives and syncs. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP2area:settingsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/36Sep 15, 2026, 10:57 AMSep 16, 2026, 9:06 PM
35Settings: notification tray limit (`notificationTrayLimit`)Part of #31 — Settings & preferences parity — most of `PATCH /api/user/update` is unreachable from Android The web's bell tray shows a configurable number of notifications (`notificationTrayLimit`). Android's notifications feed uses a hard-coded page size and the push-poll caps its tray group independently. - Expose the preference and have both the in-app notifications list and the system-tray grouping respect it. **Tests:** PATCH round-trip; list honours the limit. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP2area:notificationsarea:settingsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/35Sep 15, 2026, 10:57 AMSep 16, 2026, 8:48 PM
34Settings: private account toggle (`isPrivateAccount`)Part of #31 — Settings & preferences parity — most of `PATCH /api/user/update` is unreachable from Android Web behaviour (`/help/people`, `/help/account`): an account can be **private**, which turns follows into **follow requests** the owner approves or rejects. `:feature:profile` already implements the request side (send/approve/reject) but the account itself cannot be made private from Android — the setting only exists on the web. - Add the toggle, sending `isPrivateAccount`. - Explain the consequence in-line (existing followers keep access; new follows need approval). **Tests:** PATCH round-trip; UI reflects the current state after a refresh. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP1area:settingsarea:accountEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/34Sep 15, 2026, 10:57 AMSep 16, 2026, 8:17 PM
33Settings: View preferences (`viewingPreference`) and link previews (`showPreviews`)Part of #31 — Settings & preferences parity — most of `PATCH /api/user/update` is unreachable from Android - `viewingPreference` — All / My / Following / Followers, consumed by the feed (see the composer & feed epic; this issue owns the Settings half). - `showPreviews` — whether link-preview cards render at all. Today the app always renders them. **Tests:** PATCH round-trip; previews suppressed when off. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP1area:messagesarea:settingsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/33Sep 15, 2026, 10:57 AMSep 16, 2026, 7:53 PM
32Settings: Message settings group (default visibility, max length, messages per page, advanced post settings)Part of #31 — Settings & preferences parity — most of `PATCH /api/user/update` is unreachable from Android Adds the four message-behaviour preferences and makes the composer and feed honour them. | Field | Effect | |---|---| | `defaultPubliclyVisible` | The composer's starting visibility | | `maxMessageLength` | The account's character limit (default 666) | | `messagesPerPage` | Feed page size | | `showAdvancedPostSettings` | Whether the composer's advanced (gear) options are shown | Note the web places the character limit under **Profile**, not Message Settings — mirror the grouping users will have learned there, or make the search/label obvious either way. **Tests:** PATCH carries each field; composer/feed read them. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP1area:messagesarea:settingsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/32Sep 15, 2026, 10:57 AMSep 16, 2026, 8:07 PM
31Settings & preferences parity — most of `PATCH /api/user/update` is unreachable from Android## The gap `UpdateProfileRequest` in `:feature:profile` sends **`displayName` and `bio`**. `PATCH /api/user/update` accepts: `displayName`, `bio`, `avatar`, `theme`, `maxMessageLength`, `defaultPubliclyVisible`, `messagesPerPage`, `viewingPreference`, `showPreviews`, `showAdvancedPostSettings`, `latitude`, `longitude`, `isPrivateAccount`, `githubDefaultRepo`, `notificationTrayLimit`. Eleven of those fifteen have no Android surface at all. Several of them change behaviour the user will notice immediately — default post visibility, feed contents, whether the account is private. The web groups these under Settings (`/help/settings`): Profile settings, View preferences, Message settings, Notifications, Profile location, Subscription & Billing, Security, Integrations, AI features. ## Done when Android has a Settings surface that mirrors the web's groups, and every preference above is readable and writable — with the feed, composer and notification surfaces actually respecting them. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedepicparityP1area:settingsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/31Sep 15, 2026, 10:57 AMSep 16, 2026, 9:42 PM
30Tags: trending tags surface (`GET /api/tags/trending`)Part of #27 — Tags: tagging posts, autocomplete, trending and tag-filtered feeds - Surface the most-used tags across public messages within the trailing window the endpoint reports. - Place it where it is actually discoverable on a phone — a section in the feed or a discovery entry point — rather than a hidden screen. - Each trending tag opens the tag-filtered feed. **Tests:** parse the trending payload (including the window metadata); tap → tag feed. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP2area:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/30Sep 15, 2026, 10:57 AMSep 16, 2026, 9:40 PM
29Tags: tag-filtered feed (`GET /api/messages?tag=`)Part of #27 — Tags: tagging posts, autocomplete, trending and tag-filtered feeds - Tapping a tag on a message card opens a feed filtered to that tag via the `tag` query parameter. - The tag feed is a normal paged feed — it should share the feed implementation (including the cursor-pagination work) rather than forking it. - Deep-link support so a tag URL from elsewhere opens the right screen. **Tests:** tap → filtered query; paging works on the filtered feed. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP1area:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/29Sep 15, 2026, 10:57 AMSep 16, 2026, 9:20 PM
28Tags: add `tags[]` to the composer with prefix autocompletePart of #27 — Tags: tagging posts, autocomplete, trending and tag-filtered feeds - Add a tag input to the composer and send `tags[]` on `POST /api/messages`. - Wire `GET /api/tags/autocomplete` for prefix suggestions as the user types (case-insensitive, literal prefix — do not fuzzy-match client-side and misrepresent the server's semantics). - Debounce, and cancel in-flight requests on each keystroke. - Show tags on the message card. **Tests:** request carries tags; autocomplete debounce/cancel; card renders tags. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP1area:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/28Sep 15, 2026, 10:57 AMSep 16, 2026, 8:55 PM
27Tags: tagging posts, autocomplete, trending and tag-filtered feeds## What the web app has Messages carry **tags**. `POST /api/messages` accepts a `tags[]` array, `GET /api/messages` accepts a `tag` query parameter to filter the feed, and two discovery endpoints back the UI: | Endpoint | Purpose | |---|---| | `GET /api/tags/autocomplete` | Tags on public messages matching a case-insensitive literal prefix | | `GET /api/tags/trending` | Most-used tags across public messages within a trailing window | AI's **Suggest tags** action also feeds this. ## What Android has Nothing. `tags` is absent from `CreateMessageRequest`, neither `/api/tags/*` endpoint is called, and the feed cannot be filtered by tag. ## Done when A user can tag a post from Android with autocomplete, tap a tag to see that tag's feed, and browse trending tags. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedepicparityP1area:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/27Sep 15, 2026, 10:57 AMSep 16, 2026, 9:40 PM
26Messages: use `GET /api/messages/{id}/metadata` for lightweight link previewsPart of #17 — Message composer & feed parity gaps The app calls `POST /api/messages/{id}/metadata`, which fetches *and attaches* link metadata. The spec also has **`GET /api/messages/{id}/metadata`** — *"Returns ONLY the message's stored link metadata (lightweight)"* — and a standalone **`GET /api/link-metadata?url=`**. Using the POST for display re-fetches server-side on every read. - Read stored metadata with the GET; keep the POST for the compose/attach path. - Use `GET /api/link-metadata` to preview a URL while composing, before the message exists. - Respect the account's `showPreviews` preference (Settings epic). **Tests:** GET path used on render; POST path used on attach. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openparityP3area:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/26Sep 15, 2026, 10:57 AMSep 15, 2026, 10:57 AM
25Messages: full thread view and reply countsPart of #17 — Message composer & feed parity gaps Web behaviour: replies nest under their parent, and when a reply has sub-replies a **"+N replies"** link opens the full thread view — the original post at the top with every reply chained below, each with its own Reply and Dig buttons. Android has `GET /api/messages/{id}/replies` and a detail screen, but no nested "+N replies" affordance and no reply counts. - `POST /api/messages/{id}/reply-counts` (unused today) supplies the counts. - Render the "+N replies" link and the chained thread view. **Tests:** counts parse; link navigates to the full thread. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openparityP2area:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/25Sep 15, 2026, 10:57 AMSep 15, 2026, 10:57 AM
24Composer: honour the account's character limit and the tightest cross-post limitPart of #17 — Message composer & feed parity gaps Each account has a configurable message length limit (default **666** characters), set in Settings → Profile and exposed as `maxMessageLength` on `PATCH /api/user/update`. Additionally, **when cross-post targets are selected the binding limit is the tightest one among them** — Bluesky's 300 is usually the constraint — not InterlinedList's own. - Read the account limit and enforce it in the composer with a live counter. - Recompute the effective limit as destinations are toggled, and show which platform is binding. - Over-limit drafts should offer the thread path rather than a hard block, matching the web. **Tests:** counter reflects the account limit; toggling Bluesky tightens it to 300. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openparityP2area:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/24Sep 15, 2026, 10:57 AMSep 15, 2026, 10:57 AM
23Scheduled posts: edit content, time and destinations (today only cancel is possible)Part of #17 — Message composer & feed parity gaps Web behaviour: each scheduled post shows its content, scheduled time and selected destinations, and an edit affordance changes **the time, the message content, or which networks receive it** — including the LinkedIn destination picker. Deleting cancels it. `ScheduledMessagesScreen` only offers `onCancel`. - Add editing of content, `scheduledAt` and cross-post targets to the scheduled list. - The scheduling dialog itself should offer a checkbox per connected cross-post account, as the web one does. **Tests:** edit round-trip; destination changes persist. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openparityP2area:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/23Sep 15, 2026, 10:57 AMSep 15, 2026, 10:57 AM
22Feed: switch to cursor (keyset) paginationPart of #17 — Message composer & feed parity gaps `GET /api/messages` exposes an opaque **`cursor`** for keyset pagination, with the explicit instruction to *"pass back the `nextCursor` from the previous response verbatim — never construct, parse, or modify it."* `MessagesApi.getMessages` uses `limit`/`offset`, which drifts and duplicates rows as new posts arrive at the head of the feed — exactly the case a social feed hits constantly. - Move the feed (and search, if it supports it) to `cursor`/`nextCursor`, keeping the cursor opaque. - `:feature:directmessages` already does this correctly — mirror that implementation. **Tests:** repository paging test that a new head insert does not duplicate or skip rows. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP2area:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/22Sep 15, 2026, 10:57 AMSep 16, 2026, 7:58 PM
21Messages: Share — copy permalink and Get embed codePart of #17 — Message composer & feed parity gaps Every web message has a permanent page at `/user/<username>/status/<id>`, and a **Share** button offering **Copy link** and **Get embed code** (an HTML snippet for pasting into a blog — viewers need no account for a public message). Only **public** messages can be linked or embedded for other people; a private message's link and embed work only for its owner while signed in. - Add a Share action to the message action bar: copy link, get embed code, and the Android system share sheet (which the web has no equivalent of but users will expect). - Suppress/clearly mark the options on a private message. **Tests:** permalink construction from username + id; embed snippet shape; private message behaviour. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openparityP2area:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/21Sep 15, 2026, 10:57 AMSep 15, 2026, 10:57 AM
20Messages: Push (repost) and Quote (`pushedMessageId`)Part of #17 — Message composer & feed parity gaps Two ways to re-share someone else's message on the web: - **Push** (repost) — re-shares the original as-is, no added comment. - **Quote** — re-shares the original *with* the user's own note; a banner confirms the post will be public before sending. Both are **always public**. Neither exists on Android; `pushedMessageId` is never sent. - Add Push and Quote actions to the message action bar. - Render pushed/quoted messages in the feed with the embedded original. - Quote composition reuses the normal composer with the quoted message attached. **Tests:** push sends `pushedMessageId` with no content; quote sends both; feed renders the embedded original. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP1area:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/20Sep 15, 2026, 10:56 AMSep 16, 2026, 8:37 PM
19Feed: view preferences (All / My / Following / Followers) and `onlyMine`Part of #17 — Message composer & feed parity gaps Web behaviour (`/help/messages` → *Viewing preferences*): Settings → View Preferences chooses what the Home feed shows — - **All Messages** — the user's messages plus all public messages - **My Messages** — only the user's own - **Following Only** — people the user follows, plus their own - **Followers Only** — people who follow the user, plus their own Android has no feed filter at all; `getMessages` sends only `limit`/`offset`. - Read the account's `viewingPreference` and apply it to the feed query, using `onlyMine` where that is the server-side mechanism (confirm the exact query contract live for the following/followers variants before building). - Offer an in-feed switcher as well as the Settings control, and persist the choice. **Tests:** each preference produces the expected query; switching refreshes the feed. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP0area:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/19Sep 15, 2026, 10:56 AMSep 16, 2026, 8:16 PM
18Composer: public/private visibility toggle (`publiclyVisible`)Part of #17 — Message composer & feed parity gaps Every message on the web is **Public** (visible to anyone on the feed and the author's profile) or **Private** (visible only to the author), with a toggle in the composer and a default taken from Settings → Message Settings. Android never sends `publiclyVisible`, so the server default silently decides — the user has no control and no indication of what they posted. - Add the toggle to the composer and send `publiclyVisible`. - Default it from the account's `defaultPubliclyVisible` preference (see the Settings epic), with a per-message override. - Show the resulting visibility on the message card, so a private post is unmistakable. - Push and Quote posts are **always public** — force and explain it there. **Tests:** request carries the flag; default comes from preferences; override wins. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP0area:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/18Sep 15, 2026, 10:56 AMSep 16, 2026, 7:47 PM
17Message composer & feed parity gaps## Summary `:feature:messages` covers posting, replies, digs, delete, search, media upload, scheduling (create + cancel), reporting, link-preview metadata, edit and author moderation. Several first-class web behaviours are still missing, and two of them (**visibility** and **feed view preferences**) change what a post *is* and who sees it — so they rank P0. ## Verified gaps `CreateMessageRequest` in `feature/messages/.../dto/MessagesResponse.kt` sends `content, parentId, imageUrls, videoUrls, scheduledAt, mastodonProviderIds, crossPostTo{Bluesky, LinkedIn,Twitter}`. `POST /api/messages` also accepts — and the app never sends — **`publiclyVisible`**, **`tags[]`**, **`pushedMessageId`**, **`organizationId`**, **`linkedInTargets[]`** and **`linkedInLinkAsFirstComment`**. `GET /api/messages` accepts `onlyMine`, `tag` and an opaque **`cursor`** for keyset pagination; `MessagesApi.getMessages` still sends `limit`/`offset` only. ## Done when Every sub-issue is closed and a post made from Android can carry the same properties as one made on the web. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openepicparityP0area:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/17Sep 15, 2026, 10:56 AMSep 15, 2026, 10:56 AM
16Create from…: "To Message" prefills the composer instead of creatingPart of #10 — "Create from…" (materialize) — turn messages, lists, rows and documents into new lists/documents/posts **To Message** is the one destination that creates nothing by itself — it hands the user a draft in the normal composer, where they edit it, add cross-post targets or a schedule, and post it themselves. - Route from any Create-from entry point into the composer with the generated draft prefilled. - A message cannot be converted to a message (use Quote or Push for that) — hide the option there. - Character-limit interaction: if the draft is longer than the limit the composer shows it as usual so the user can shorten it or let it post as a thread. **When cross-post targets are selected the binding limit is the tightest one among them** (Bluesky's 300 is usually it), not InterlinedList's own — see the composer epic. **Tests:** prefill navigation carries the draft; option hidden on a message source. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openparityP1area:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/16Sep 15, 2026, 10:56 AMSep 15, 2026, 10:56 AM
15Create from…: the preview / edit / confirm windowPart of #10 — "Create from…" (materialize) — turn messages, lists, rows and documents into new lists/documents/posts Web behaviour: choosing a destination opens a finalization window showing exactly what will be created, before anything is saved. The destination can be switched from inside the window. **For a list** the user can set the title and optional description, edit the columns (rename, change type, remove, **Add column**), and choose whether to make it public — with a live table preview of the rows and columns. **For a document** the user can set the title and file name, choose public/private, and adjust the list style (numbered or bulleted) and whether row data appears inline or as sub-items — with a rendered preview. On success, links appear to open the new list and/or document immediately. **Tests:** column edits survive into the create request; destination switch preserves edits where they still apply; success state exposes both navigation targets. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP1area:listsarea:documentsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/15Sep 15, 2026, 10:56 AMSep 16, 2026, 8:52 PM
14Create from…: entry points on documents, including a highlighted selectionPart of #10 — "Create from…" (materialize) — turn messages, lists, rows and documents into new lists/documents/posts Web behaviour: Create-from works on a whole document **and** on a highlighted passage inside the editor — highlight some markdown (headings, bullets) and use the **Selection** menu to turn just that part into a list or a new document. - + Create on the document browser row and in the editor. - A selection action in `DocumentEditorScreen` for the highlighted range. - Document → list: **each heading and bullet point becomes its own row**. - Document → message: markdown converted to plain text with paragraphs preserved; **code blocks are dropped**. **Tests:** heading/bullet → rows mapping; markdown → plain-text conversion incl. code-block removal. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP1area:documentsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/14Sep 15, 2026, 10:56 AMSep 16, 2026, 9:29 PM
13Create from…: entry points on lists and list rowsPart of #10 — "Create from…" (materialize) — turn messages, lists, rows and documents into new lists/documents/posts Web behaviour: Create-from is available on a single list (overview or detail), on several selected lists, on a single row inside a list, and on several selected rows. - + Create menu on the lists overview rows and on the list detail screen. - Row-level menu in the list detail table, plus row multi-select with the three pills. - **To Message** drafts differ by source: a *list* yields its title, description, up to five row headlines and the true total ("340 items in total"); *rows* yield the selected rows' headline values with an "…and N more" line past five. **Tests:** list→draft and rows→draft text generation (including the >5 truncation and total). --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openparityP1area:listsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/13Sep 15, 2026, 10:56 AMSep 15, 2026, 10:56 AM
12Create from…: entry points on messages (single message and multi-select)Part of #10 — "Create from…" (materialize) — turn messages, lists, rows and documents into new lists/documents/posts Web behaviour: a **+ Create ▾** menu on a single message, and — in **Select** mode on the feed — *To List / To Doc / To List & Doc* pills in the selection bar for several messages at once. - Add the + Create menu to `MessageCard` / the message detail screen. - Add a multi-select mode to the feed with a selection bar. This is a new interaction for the Android feed and is the larger half of this issue. - Default columns when messages become a list: **Content, Author, Posted, Links, Tags**, one row per message. **Tests:** selection state ViewModel test; menu → materialize call carries every selected id. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openparityP1area:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/12Sep 15, 2026, 10:56 AMSep 15, 2026, 10:56 AM
11Create from…: `POST /api/materialize` plumbing + domain modelPart of #10 — "Create from…" (materialize) — turn messages, lists, rows and documents into new lists/documents/posts Shared foundation for the other Create-from sub-issues. - Capture the real `POST /api/materialize` request/response shapes against the live API before writing DTOs (the spec only types `source` loosely). - Model the four destinations (`To List`, `To Doc`, `To List & Doc`, `To Message`) and the four source kinds (message(s), list, rows, document/selection) as a single domain type. - Decide the module home — a small shared `:feature:materialize`, or a `:core:` helper consumed by messages/lists/documents. It is called from three feature modules, so it must not live inside any one of them. - Subscriber gating: the menu opens for everyone; confirming on a free account routes to the existing subscription-required handling. **Tests:** MockWebServer round-trip per destination; free-account confirm → upsell, no write. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP1area:listsarea:documentsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/11Sep 15, 2026, 10:56 AMSep 16, 2026, 8:08 PM
10"Create from…" (materialize) — turn messages, lists, rows and documents into new lists/documents/posts## What the web app has **Create from…** (`/help/create-from`) turns almost anything the user already has into a new **List**, a new **Document**, **both at once**, or a **Message** — without retyping it. It is one of the platform's signature cross-object features and it is reachable from nearly every surface. ## What Android has Nothing. `POST /api/materialize` is not called anywhere in the app. ## API surface `POST /api/materialize` — *"Create a new List, a new Document, or both from a source object (message(s), list, rows, document)."* The body carries a `source` descriptor. ## The four destinations | Destination | Result | |---|---| | **To List** | A new list, one row per item; columns adjustable before creating | | **To Doc** | A new markdown document containing the item's content | | **To List & Doc** | Both, in a single step | | **To Message** | Opens the composer **prefilled** — creates nothing on its own | ## Subscriber gating Creating lists and documents needs an active subscription; **To Message** goes through the normal composer, so posting itself is free while cross-posting/scheduling stay subscriber features. Free accounts can open the menu but confirming prompts to subscribe. ## Done when All sub-issues are closed and a user can materialize from a message, a list, a set of rows and a document on a device, with the preview/edit step behaving as the web one does. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openepicparityP1area:messagesarea:listsarea:documentsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/10Sep 15, 2026, 10:56 AMSep 15, 2026, 10:56 AM
9AI: Powered Document (standalone / from list / from document / research URL)Part of #3 — AI Writing Assistance is entirely missing from the Android app Web behaviour: a **Powered Document** button on the Documents page drafts a full document in one of four modes: 1. **Standalone article** from a topic the user describes. 2. **Derived From List** — a write-up based on one of the user's lists. 3. **Derived From Article** — a new document based on an existing one. 4. **Research URL** — a document based on a web page the user points at. For the derived and research modes the user picks the source list / document / URL. The draft is always previewed before it is saved. - Source pickers should reuse the existing lists and documents pickers rather than new ones. - `POST /api/ai/suggest` → preview; `POST /api/ai/generate` → saved document. **Tests:** one ViewModel test per mode (source selection → suggest → confirm); preview is not persisted if the user backs out. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP1area:aiarea:documentsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/9Sep 15, 2026, 10:56 AMSep 16, 2026, 8:59 PM
8AI: Powered Templates tab on list creationPart of #3 — AI Writing Assistance is entirely missing from the Android app Web behaviour: the New List page has a **Powered Templates** tab (Subscriber only). The user describes the list they want — e.g. *"a reading list with title, author, status, and rating"* — and AI drafts a complete list structure **plus a few starter rows**. The user reviews, then creates. - Add the tab to the Android list-creation flow, hidden when the AI gate is closed. - The generated schema must round-trip through the List Schema DSL that `POST /api/lists` accepts (`{ name, description?, fields[] }`, each field `{ key, type, label, … }`). - Starter rows arrive with the suggestion and are created alongside the list. **Tests:** suggestion → schema DSL mapping; confirm → list with schema + rows created. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openparityP1area:aiarea:listsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/8Sep 15, 2026, 10:56 AMSep 15, 2026, 10:56 AM
7AI: Article Series planner (brief → folder of documents)Part of #3 — AI Writing Assistance is entirely missing from the Android app Web behaviour: the composer's **Article Series** planner turns a brief into a set of related documents. Generating creates **a folder with one document per part, in order**. - `POST /api/ai/suggest` → reviewable outline; `POST /api/ai/generate` → folder + documents. - The created folder and documents must show up in the existing documents folder browser without a manual refresh. **Tests:** outline parse; confirm → folder + ordered documents created and visible in the browser. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openparityP1area:aiarea:documentsEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/7Sep 15, 2026, 10:56 AMSep 15, 2026, 10:56 AM
6AI: Message Series planner (brief → sequence of scheduled posts)Part of #3 — AI Writing Assistance is entirely missing from the Android app Web behaviour: once a draft has roughly a sentence or two, the composer offers a **Message Series** planner. It turns the brief into a sequence of short connected posts; confirming creates **scheduled** posts spaced a few minutes apart, ready to review and adjust. - `POST /api/ai/suggest` for the plan, `POST /api/ai/generate` to persist. - The user reviews the plan before anything is created. - Results are ordinary scheduled posts afterwards — editable and deletable through the scheduled-posts screen (which currently only supports cancel; see the composer/feed epic). **Tests:** plan-parse; confirm → N scheduled messages appear in the scheduled list. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openparityP1area:aiarea:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/6Sep 15, 2026, 10:56 AMSep 15, 2026, 10:56 AM
5AI: writing assistant in the message composer (rewrite / tighten / expand / fix grammar / make a thread / suggest tags)Part of #3 — AI Writing Assistance is entirely missing from the Android app Web behaviour (`/help/ai` → *Writing Assistant in the composer*): an AI control in the composer toolbar offers six actions on the current draft. | Action | Result | |---|---| | Rewrite | A cleaner version of the same message | | Tighten | Shorter, punchier | | Expand | Longer, more detail | | Fix grammar | Corrections only, voice intact | | Make a thread | Splits a long draft into a numbered sequence of posts | | Suggest tags | Recommended tags based on the draft | Each result is a **suggestion**, not an edit. Text suggestions offer **Replace draft** or **Use in draft**; tag suggestions offer **Add tags**. The composer does not change until the user picks one. - Wire through `POST /api/ai/suggest` with the appropriate `feature` value per action. - "Make a thread" must produce a reviewable numbered sequence, not a single blob. - "Suggest tags" depends on composer tag support — see the Tags epic. **Tests:** ViewModel test per action (suggestion state, accept → draft mutated, discard → draft untouched); Compose test that the control is absent when the AI gate is closed. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openparityP1area:aiarea:messagesEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/5Sep 15, 2026, 10:56 AMSep 15, 2026, 10:56 AM
4AI: status, gating and quota plumbing (`:feature:ai` scaffold)Part of #3 — AI Writing Assistance is entirely missing from the Android app Foundation for every other AI sub-issue. - New self-contained `:feature:ai` module (own Retrofit `AiApi` built from the shared authed `Retrofit`, own DTOs) matching the per-feature module convention used elsewhere in this repo. - `GET /api/ai/status` → an `AiAvailability` domain model: provider configured? subscriber? remaining daily quota? - A single `AiGate` observable the feature UIs consume, so composer/list/document surfaces can hide their AI affordances entirely when AI is unavailable (unconfigured provider **or** free account) rather than showing a control that errors. - Central mapping for the quota-exhausted response to a user-readable "daily AI limit reached" state. - Capture the real `/api/ai/status` body before writing the DTOs — see the repo's API-envelope conventions (`data` wrapping, explicit nulls, `coerceInputValues = true`). **Tests:** MockWebServer for status parse (configured / unconfigured / non-subscriber); ViewModel test that the gate flips the UI off. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._closedparityP1area:aiEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/4Sep 15, 2026, 10:56 AMSep 16, 2026, 7:50 PM
3AI Writing Assistance is entirely missing from the Android app## What the web app has AI writing assistance (`/help/ai`) is a headline Subscriber feature, powered by Claude and included with the subscription. It spans the message composer, list creation and document creation, and every action is a **preview → confirm** flow: nothing is written until the user approves it. ## What Android has Nothing. No `:feature:ai`, no calls to any `/api/ai/*` endpoint. ## API surface (all unused today) | Endpoint | Purpose | |---|---| | `GET /api/ai/status` | Which AI providers are configured; drives whether the UI appears at all | | `POST /api/ai/suggest` | Run a feature and return a **validated preview** — writes nothing | | `POST /api/ai/generate` | Persist a confirmed artifact produced by `/suggest` | Both write endpoints take a `feature` discriminator in the body. ## Constraints to honour - **Subscriber-only.** Free accounts must not see the AI controls at all. - **50 AI actions per day per account**, and *both* the preview and the confirmed action count. - If the server's AI provider is unconfigured, the features **hide themselves** rather than failing partway through. - Preview-then-confirm is non-negotiable — never write straight from a suggestion. ## Done when Every sub-issue below is closed and an AI action can be run end-to-end on a device against the live API, with the daily quota and the subscriber gate both enforced. --- _Filed from the parity review of `feature/parity-wip` @ `14d7795` against interlinedlist.com (OpenAPI `/api/openapi.json`, 233 paths) and the public help centre._openepicparityP1area:aiEmptyhttps://github.com/CompositeCode/interlinedlist-android/issues/3Sep 15, 2026, 10:55 AMSep 15, 2026, 10:55 AM