Problem Statement
Connecting a social account currently authorizes publishing and then sends the user straight to the composer or account settings. OpenPost does not explain or ask whether it may also collect direct messages, comments, replies, analytics, or Grow recommendations for that account.
Inbox synchronization has a one-off account setting, while Analytics and Engagement run automatically and Grow has no saved choice. These rules differ across modules, provider support is easy to flatten, and reconnecting an account can replace capability state that currently contains the Inbox choice. Users need one clear, durable place to choose which optional features OpenPost may use for each connected account.
Solution
After a genuinely new social account connection, show a short account setup page before continuing to the composer or account settings. List only the optional features the connected account supports. Every choice starts off. The user can enable any feature, save all choices as off, and change them later in Account details.
Store feature choices separately from provider capability state. Resolve each feature from provider support, the user's saved choice, required scopes, and plan access. Missing or invalid choices fail closed. Each background or user-triggered feature must check the effective state before scheduling work and again before a Job contacts the provider.
The initial account features are Direct messages, Comments and replies, Analytics, and Grow. The setup page may group the first two under Inbox, but OpenPost saves and enforces them separately.
User Stories
- As a Workspace editor, I want to choose optional features after connecting an account, so that OpenPost does not start extra provider activity without my decision.
- As a Workspace editor, I want every optional feature to start off, so that enabling one requires an explicit action.
- As a Workspace editor, I want to keep every optional feature off with one action, so that I can finish connecting the account without enabling more access.
- As a Workspace editor, I want OpenPost to remember an explicit off choice, so that it does not ask me again on routine reauthorization.
- As a Workspace editor, I want OpenPost to ask only after a genuinely new account is added, so that token renewal and reconnect recovery stay focused.
- As a Workspace editor, I want each connected account to have its own choices, so that accounts sharing one provider grant can behave differently.
- As a Workspace editor connecting several LinkedIn identities, I want to review the supported features for each identity, so that one shared authorization does not erase account-level control.
- As a Workspace editor, I want unsupported features omitted from the choice list, so that every offered switch can work.
- As a Workspace editor, I want a precise explanation when a feature needs more provider permission, so that I know why I cannot enable it.
- As a Workspace editor, I want plan restrictions kept distinct from provider restrictions, so that OpenPost does not imply that reconnecting will fix billing access.
- As a Workspace editor, I want Direct messages and Comments and replies to be separate choices, so that I can use one Inbox function without enabling the other.
- As a Workspace editor, I want enabling Direct messages to start an initial durable synchronization, so that the Inbox can populate without another setup step.
- As a Workspace editor, I want disabling Direct messages to stop message collection and sending, so that OpenPost no longer uses that account for messaging.
- As a Workspace editor, I want enabling Comments and replies to start collection for eligible published Renditions, so that the Engagement view can populate.
- As a Workspace editor, I want disabling Comments and replies to stop new collection and provider actions, so that old data remains visible without new provider activity.
- As a Workspace editor, I want enabling Analytics to start an initial durable refresh, so that account and Publication metrics begin to populate.
- As a Workspace editor, I want disabling Analytics to stop new account and Rendition metric collection, so that OpenPost no longer polls those provider endpoints.
- As a Workspace editor, I want enabling Grow to start initial recommendation discovery, so that the Grow page does not begin empty.
- As a Workspace editor, I want disabling Grow to remove that account from the Grow account selector, so that only enabled accounts appear eligible.
- As a Workspace editor, I want Grow follow actions to require Grow to remain enabled, so that stale pages cannot bypass my choice.
- As a Workspace editor, I want OpenPost never to follow an account automatically, so that each follow remains a separate user action.
- As a Workspace editor, I want queued work to recheck my latest choice before contacting a provider, so that disabling a feature takes effect even when a Job already exists.
- As a Workspace editor, I want previously collected messages, replies, metrics, and recommendations to remain available after disabling a feature, so that changing collection settings does not silently delete history.
- As a Workspace editor, I want OpenPost to state that disabling a feature does not revoke provider authorization, so that I understand the difference between an OpenPost choice and provider-side permission.
- As a Workspace editor, I want to change feature choices later from Account details, so that the post-connect step is not my only chance.
- As a Workspace editor, I want Account details to use the same support and choice rules as post-connect setup, so that the two views cannot disagree.
- As a Workspace editor who leaves setup before saving, I want a quiet reminder in Accounts, so that I can finish the undecided choices later.
- As a Workspace editor, I want the setup page to survive reload, so that a browser refresh does not lose the connected account or my route back into OpenPost.
- As a Workspace editor connecting the first destination in a Workspace, I want setup to continue to a fresh composer, so that activation still follows the existing journey.
- As a Workspace editor connecting a later destination, I want setup to return to the account manager I started from, so that direct Accounts and embedded Settings flows remain consistent.
- As a keyboard user, I want to review and save every choice without a pointer, so that the setup flow remains accessible.
- As a phone user, I want account and feature sections to fit narrow screens with touch-safe controls, so that connecting an account works in the responsive app and Android wrapper.
- As a Portuguese-speaking user, I want all setup, status, error, and recovery copy localized, so that the flow does not fall back to English.
- As a self-hosted operator, I want existing account behavior preserved during upgrade, so that the migration does not unexpectedly stop current Analytics, Engagement, or Messaging collection.
- As an API client, I want additive compatibility for the existing messaging fields during migration, so that generated clients do not break without warning.
- As a maintainer, I want new optional account features to register support and use the same preference module, so that adding a feature does not require another one-off account flag and settings flow.
Implementation Decisions
- Treat optional account features as a product concept distinct from provider capability state. Provider capability state remains provider-reported data. User choices live in dedicated persistence and must survive reconnects and capability refreshes.
- Define the initial feature keys as
messaging, engagement, analytics, and grow. The interface and persistence accept a feature key so later account features can use the same module.
- Model three separate facts for each feature: runtime support, the saved user choice, and the effective enabled state. Effective state requires support, an enabled choice, required provider scopes, and plan access.
- Store one row per social account and feature. A row records enabled or disabled, the deciding user, the source of the decision, and decision timestamps. No row means undecided.
- Treat missing, malformed, or unknown choices as off. Feature Jobs and provider actions must fail closed.
- Backfill existing accounts so upgrades preserve current behavior. Existing Inbox opt-ins become Messaging choices. Existing Analytics and Engagement behavior remains enabled. Grow becomes enabled only where stored Grow synchronization state proves prior use. Other existing accounts receive explicit off rows so they do not enter the new-account prompt.
- Keep the legacy messaging fields in the HTTP contract as a temporary compatibility shim. Read and write them through the new preference module rather than capability-state JSON. Remove the old storage dependency only after generated clients and frontend callers migrate.
- Add an account-features module whose interface resolves account feature state, saves a batch of choices atomically, and returns effective enabled state for one or more accounts. It owns authorization, account ownership checks, feature-key validation, support resolution, scope checks, plan checks, and preference persistence.
- Resolve support from the existing optional provider adapters for Messaging, Engagement, and Analytics. Resolve Grow from its discovery and follow adapters after the Grow branch lands. Do not maintain provider-name allowlists in account handlers or the frontend.
- Expose authenticated Huma operations to read feature settings for selected accounts and save a batch of account choices. The save operation validates the complete batch before writing any row.
- Return stable availability states and reason codes. Distinguish available, missing provider permission, unsupported, and plan-restricted states. Do not rely on arbitrary English backend prose for localized UI decisions.
- Normalize every account connection result to include the Workspace, all connected account IDs, genuinely new account IDs, whether the Workspace should open a fresh composer, and whether feature setup is required.
- Distinguish a new account row from the Workspace's first connected destination. The existing first-destination claim continues to control composer continuation. It must not decide whether setup is shown.
- Make direct OAuth callbacks redirect new accounts to the setup route. Account-selection, Mastodon code, Bluesky login, and Discord webhook flows return enough connection data for the frontend to navigate to the same route.
- Use a dedicated authenticated setup page built with the established standalone shell. Do not use a blocking modal or mobile-only sheet.
- Show a compact section per newly connected account. List only available or actionable features for that account. Group Messaging and Engagement under an Inbox heading without merging their stored choices.
- Start every offered choice unchecked. Saving with no selected features and the explicit Keep all off action both write disabled choices before continuing.
- If a connection has no supported optional features, continue without presenting an empty setup step.
- If setup remains undecided because the user leaves, show a non-blocking reminder in Accounts. Explicit disabled choices suppress the reminder.
- Preserve the current continuation. The Workspace's first destination opens a fresh composer with the connected destination selected. Later connections return to direct Accounts or the Settings account tab according to the stored account-management continuation.
- Validate all Workspace and account IDs from setup URLs against the authenticated actor. URL values never establish support, authorization, or effective state.
- Queue an initial durable refresh when Messaging, Engagement, Analytics, or Grow changes from off to enabled. Grow discovery may run, but following always requires a separate explicit action.
- Check effective state before enqueueing provider work and again at Job execution. User-triggered send, reply, follow, refresh, and similar provider actions also require the feature to remain enabled.
- Disabling a feature stops future provider reads and writes but does not delete stored data. Re-enabling may show stored data immediately and queues a fresh synchronization.
- Account details replaces the one-off Inbox setting with the shared account-feature presentation. It uses the same read and save operations as post-connect setup.
- Feature-specific pages use feature-aware empty and disabled states with a direct route to Account details. They must not mislabel a disabled feature as missing provider data.
- Preference writes use last-write-wins upserts inside one transaction for a submitted batch. The response returns the stored and effective state after the write.
- Record the actor and decision time for consent evidence. Do not store provider tokens, content, or raw provider responses in preference rows.
- Add English and Portuguese messages for setup, each feature, availability reasons, disabled states, recovery actions, and the provider-authorization clarification.
- Regenerate OpenAPI and TypeScript contracts from Huma definitions. Do not hand-edit generated output.
- Land or rebase the Grow implementation before integrating its feature resolver and enforcement. Avoid parallel edits to the shared account contract, generated types, setup page, and account settings.
Testing Decisions
- Test the feature at two public seams: authenticated Huma integration tests and browser E2E tests. Lower-level tests exist only where migration or provider-specific support logic cannot be proved clearly through those seams.
- Huma integration tests cover reading feature state, saving atomic batches, authorization, cross-Workspace rejection, unknown feature rejection, support and scope states, compatibility messaging fields, and normalized connection results.
- Huma and Job integration tests prove effective-state enforcement before enqueue and again at execution. Provider fakes record calls so a disabled feature proves zero provider contact, including after a Job was queued while enabled.
- Migration tests run against fresh and populated SQLite and PostgreSQL databases. They prove old Messaging choices, current Analytics and Engagement behavior, Grow prior-use detection, explicit disabled backfills, idempotence, and foreign-key cleanup.
- Account-saver integration tests distinguish new rows, reactivated rows, active reauthorization, shared grants, and multi-account selection without changing the first-destination invariant.
- Browser E2E tests cover one supported feature, several supported features, no supported features, explicit Keep all off, selected choices, reload, abandoned setup reminders, Account details changes, and provider permission states.
- Browser E2E tests cover first-destination continuation, later direct Accounts continuation, later embedded Settings continuation, and routine reauthorization without another prompt.
- Browser E2E tests cover multi-account OAuth with different support per account and prove that one saved batch produces the expected account-level states.
- Browser E2E tests prove that disabled accounts disappear from Grow selection and that disabled Inbox, Analytics, and Engagement views show the correct recovery path.
- Browser tests run at desktop, 390 px, and 320 px widths. They check light and dark themes, keyboard order, visible focus, dialog-free navigation, readable overflow, and touch-safe controls.
- Localization checks cover English and Portuguese message generation and prevent untranslated literal copy in the new route and shared presentation.
- Existing account-selection tests, account-management mode tests, OAuth callback redirect tests, messaging behavior tests, analytics Job tests, engagement behavior tests, and Grow E2E tests are the closest prior art. Extend their public behavior rather than testing private helper calls.
- Contract verification confirms Huma, OpenAPI, and generated TypeScript remain synchronized.
Out of Scope
- Adding new provider implementations for Messaging, Engagement, Analytics, or Grow.
- Automatically following any Grow recommendation.
- Adding recurring Grow discovery beyond the initial refresh and existing explicit refresh behavior.
- Deleting previously collected messages, Engagement items, analytics snapshots, or Grow recommendations when a feature is disabled.
- Revoking or narrowing provider-side OAuth scopes when an OpenPost feature is disabled.
- Workspace-wide or Organization-wide master switches. Choices remain per connected social account.
- Changing publishing availability, scheduling, Publications, Renditions, or provider account connection permissions.
- Replacing the existing provider readiness system or plan entitlement system.
- Building separate native Android or iOS screens. The adaptive frontend and Android wrapper use the same route.
- Prompting all existing accounts after upgrade.
Further Notes
- The main branch does not yet contain the complete Grow implementation. Its account-specific discovery and follow flow currently lives on the Grow implementation branch and must land before final integration.
- Capability-state JSON currently mixes a provider fact with the Messaging choice. Moving the choice prevents reconnection or capability refresh from silently resetting consent.
- The user-facing term Inbox is broader than one backend collector. Keeping Direct messages and Comments and replies separate preserves provider truth while still allowing the UI to group related choices.
- Disabling a feature changes OpenPost behavior only. Account removal or provider settings remain the way to revoke provider authorization.
- Behavior changes require a changelog fragment and updates to account, Inbox, Analytics, Engagement, Grow, and provider documentation under Unreleased.
Problem Statement
Connecting a social account currently authorizes publishing and then sends the user straight to the composer or account settings. OpenPost does not explain or ask whether it may also collect direct messages, comments, replies, analytics, or Grow recommendations for that account.
Inbox synchronization has a one-off account setting, while Analytics and Engagement run automatically and Grow has no saved choice. These rules differ across modules, provider support is easy to flatten, and reconnecting an account can replace capability state that currently contains the Inbox choice. Users need one clear, durable place to choose which optional features OpenPost may use for each connected account.
Solution
After a genuinely new social account connection, show a short account setup page before continuing to the composer or account settings. List only the optional features the connected account supports. Every choice starts off. The user can enable any feature, save all choices as off, and change them later in Account details.
Store feature choices separately from provider capability state. Resolve each feature from provider support, the user's saved choice, required scopes, and plan access. Missing or invalid choices fail closed. Each background or user-triggered feature must check the effective state before scheduling work and again before a Job contacts the provider.
The initial account features are Direct messages, Comments and replies, Analytics, and Grow. The setup page may group the first two under Inbox, but OpenPost saves and enforces them separately.
User Stories
Implementation Decisions
messaging,engagement,analytics, andgrow. The interface and persistence accept a feature key so later account features can use the same module.Testing Decisions
Out of Scope
Further Notes