Skip to content

bug(account): accountStatus is unmodelled — new/restricted/suspended accounts get opaque failures #42

Description

@Adron

Summary

Every InterlinedList account carries an accountStatus that decides what it may do — independently of subscription tier and of email verification. The live API returns it on GET /api/user. The macOS client does not decode it, does not model it, and shows nothing about it.

The practical result: a brand-new account (the state every new user starts in) has DMs, media upload, cross-posting, scheduled posts, and list/document/organization creation locked, plus an hourly and daily posting cap. In the macOS app those all present as unexplained server failures. A restricted or suspended account is read-only, and the app will keep offering write actions that cannot succeed.

Evidence (live, 2026-09-07)

GET /api/user (Bearer, test account) returns, among the other fields:

"accountStatus":"active"

The User schema in GET /api/openapi.json declares accountStatus: string (46 properties; ours decodes ~27 of them).

/help/account documents the closed set and its consequences:

status what it means
New (on probation) Read/browse/follow/block/mute/report work. Plain posting is rate-limited (a few per hour, a small number per day). Locked: direct messages, image and video uploads, cross-posting, scheduled posts, and creating lists, documents, and organizations. Verifying email is the fastest way off it.
Active Normal. Everything works, subject only to subscription tier.
Restricted Temporarily read-only while under review. Cannot post, reply, react, follow, send messages, or create content. Appealable.
Suspended Same as restricted, applied by the team. Appealable.
Banned Closed; cannot sign in.

"When your account is new or locked, a banner at the top of your home page explains the current status and, where relevant, links you to verify your email or to contact support."

What we ship today

$ grep -rn "accountStatus" Packages/ App/
(no matches)

UserDTO (Packages/InterlinedKit/Sources/InterlinedKit/DTOs/UserDTO.swift:77–103) has no accountStatus.

Division of labor

Kit

  • Add accountStatus: String? to UserDTO. Keep it a raw string on the wire — an unknown future value must not fail the whole decode.
  • Contract test against the live shape.

Domain

  • AccountStatus enum with the five documented cases plus an .unknown(String) escape hatch, and a mapper from the raw string (case-insensitive).
  • Add it to CurrentUser.
  • A capability projection: for a given status, which actions are permitted. Compose it with the entitlement gate and the email-verification gate so the composer asks one question, not three. Precedence matters — status is the hardest gate: a subscriber who is restricted still cannot post.
  • Fail open on .unknown. A status value we have never seen must behave as .active, so a server-side rename cannot brick a paying user's app.

App

  • A status banner on the timeline root, mirroring the web: shown for new, restricted, suspended; hidden for active. Include the documented next step — verify email (link to Settings ▸ Account) for new, contact support for restricted/suspended.
  • Disable write affordances for restricted/suspended with the banner as the explanation, rather than letting each action fail on its own.
  • For new, disable exactly the documented locked set (DMs, media, cross-post, schedule, create list/doc/org) and leave plain posting enabled — it is rate-limited, not blocked.
  • banned cannot sign in, so it is a sign-in-failure path, not a banner. Confirm what the auth route returns for a banned account before writing that copy.

Tests

  • happy — active → no banner, everything enabled
  • invalid — restricted → banner, write actions disabled, reads work
  • upstream-failure — field absent or an unrecognised value → treated as active, no banner, nothing disabled
  • boundary — new → plain posting enabled, the documented locked set disabled, verify-email CTA present

Acceptance criteria

  • A new or restricted account is told why an action is unavailable before attempting it.
  • An unrecognised accountStatus never disables anything.
  • Full E2E gate green.

Notes

New finding from the 2026-09-07 parity sweep — not previously recorded in work-consolidation.md. It is the third leg of the "know the answer before asking the server" trio, alongside the entitlement-matrix issue and the email-verification issue; build it after those two so the composer has one place to consult.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingparityWeb-parity gap with the InterlinedList web app

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions