You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bug(account): accountStatus is unmodelled — new/restricted/suspended accounts get opaque failures #42
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.
Summary
Every InterlinedList account carries an
accountStatusthat decides what it may do — independently of subscription tier and of email verification. The live API returns it onGET /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
restrictedorsuspendedaccount 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:The
Userschema inGET /api/openapi.jsondeclaresaccountStatus: string(46 properties; ours decodes ~27 of them)./help/accountdocuments the closed set and its consequences:What we ship today
UserDTO(Packages/InterlinedKit/Sources/InterlinedKit/DTOs/UserDTO.swift:77–103) has noaccountStatus.Division of labor
Kit
accountStatus: String?toUserDTO. Keep it a raw string on the wire — an unknown future value must not fail the whole decode.Domain
AccountStatusenum with the five documented cases plus an.unknown(String)escape hatch, and a mapper from the raw string (case-insensitive).CurrentUser.restrictedstill cannot post..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
new,restricted,suspended; hidden foractive. Include the documented next step — verify email (link to Settings ▸ Account) fornew, contact support forrestricted/suspended.restricted/suspendedwith the banner as the explanation, rather than letting each action fail on its own.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.bannedcannot 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
active→ no banner, everything enabledrestricted→ banner, write actions disabled, reads worknew→ plain posting enabled, the documented locked set disabled, verify-email CTA presentAcceptance criteria
accountStatusnever disables anything.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.