Skip to content

Assignment 3: Convergent Design

Pitch

SocialInfo is a social media app for journalists and others who want to stay up-to-date on breaking news. Many people already obtain their news from social media, but that news is mixed with tons of misinformation due to issues like realistic parody accounts, Photoshop, or plainly incorrect statements that are taken as fact. My app’s features are designed to cut down on that misinformation through both community and AI-based “context” statements for posts, where users can upvote posts to indicate that they like the post, or upvote context statements to indicate their helpfulness, as well as follow users who they would like to see more posts from.

Concepts

concept User  
    Purpose  
        authenticates an individual user of the app  
    Principle  
        after a user registers with a username and password,
        they can authenticate as that user by providing a matching username
        and password  
    State  
        registered: set User  
        username, password: registered -> one String  
    Actions  
        register (n, p: String, out u: User)  
            Adds a User `u` with username `n` and password `p` to the
            set `registered`  
        authenticate (n, p: String, out u: User)  
            If a User with username `n` and password `p` is in
            `registered`, return the User
concept User  
    Purpose  
        authenticates an individual user of the app  
    Principle  
        after a user registers with a username and password,
        they can authenticate as that user by providing a matching username
        and password  
    State  
        registered: set User  
        username, password: registered -> one String  
    Actions  
        register (n, p: String, out u: User)  
            Adds a User `u` with username `n` and password `p` to the
            set `registered`  
        authenticate (n, p: String, out u: User)  
            If a User with username `n` and password `p` is in
            `registered`, return the User
concept Session [User]  
    Purpose
        provides a way for users to remain authenticated for an
        extended period of time  
    Principle
        after a session starts (and before it ends), the getUser
        action returns the user identified at the start  
    State  
        active: set Session  
        user: active -> one User  
    Actions  
        start (u: User, out s: Session)  
            Add a new Session to `active` for user `u`  
        getUser (s: Session, out u: User)  
            Given Session `s`, confirm that the `s` is in `active`
            and return the user `u` it belongs to  
        end (s: Session)  
            Remove Session `s` from set `active`
concept Session [User]  
    Purpose
        provides a way for users to remain authenticated for an
        extended period of time  
    Principle
        after a session starts (and before it ends), the getUser
        action returns the user identified at the start  
    State  
        active: set Session  
        user: active -> one User  
    Actions  
        start (u: User, out s: Session)  
            Add a new Session to `active` for user `u`  
        getUser (s: Session, out u: User)  
            Given Session `s`, confirm that the `s` is in `active`
            and return the user `u` it belongs to  
        end (s: Session)  
            Remove Session `s` from set `active`
concept Post [User]  
    Purpose  
        publish viewable content  
    Principle  
        A user can create a post, which can be viewed in
        date-order with allPosts, or with a filter such that only  
        posts from certain users appear (using getPostsByUsers).
        The posts can also be deleted.  
    State  
        posts: set Post  
        author: posted -> one User  
        body: posted -> one String  
        date: posted -> one Date  
    Actions  
        create(u: User, s: String, out p: Post)  
            Creates a new Post `p` with author `u` with body `s`
            where `date` is the current date and adds it to the set  
            of `posts`  
        allPosts(out p: set Post)  
            Returns the set of all `posts` in order sorted on `date`  
        getPostsByUsers(u: set User, out p: set Post)  
            Returns the set of all `posts` by users in set `u` in  
            order sorted on `date`  
        delete(p: Post)  
            Removes post `p` from the set of posts
concept Post [User]  
    Purpose  
        publish viewable content  
    Principle  
        A user can create a post, which can be viewed in
        date-order with allPosts, or with a filter such that only  
        posts from certain users appear (using getPostsByUsers).
        The posts can also be deleted.  
    State  
        posts: set Post  
        author: posted -> one User  
        body: posted -> one String  
        date: posted -> one Date  
    Actions  
        create(u: User, s: String, out p: Post)  
            Creates a new Post `p` with author `u` with body `s`
            where `date` is the current date and adds it to the set  
            of `posts`  
        allPosts(out p: set Post)  
            Returns the set of all `posts` in order sorted on `date`  
        getPostsByUsers(u: set User, out p: set Post)  
            Returns the set of all `posts` by users in set `u` in  
            order sorted on `date`  
        delete(p: Post)  
            Removes post `p` from the set of posts
concept Follow [User]  
    Purpose  
        allows a user to observe changes made by another user  
    Principle  
        a user can follow another user or unfollow them if already
        followed, and getFollowing returns all users that a given
        user follows  
    State  
        following: User -> set User  
    Actions  
        follow(u: User, v: User)  
            Add `v` to `following` set for user `u`  
        unfollow(u: User, v: User)  
            If `v` is in the set `following` for User `u`, remove `v`
            from the `following` set for User `u`  
        getFollowing(u: User, out v: set User)  
            Return set of all users `v` that User `u` maps to
concept Follow [User]  
    Purpose  
        allows a user to observe changes made by another user  
    Principle  
        a user can follow another user or unfollow them if already
        followed, and getFollowing returns all users that a given
        user follows  
    State  
        following: User -> set User  
    Actions  
        follow(u: User, v: User)  
            Add `v` to `following` set for user `u`  
        unfollow(u: User, v: User)  
            If `v` is in the set `following` for User `u`, remove `v`
            from the `following` set for User `u`  
        getFollowing(u: User, out v: set User)  
            Return set of all users `v` that User `u` maps to
concept Upvote [User, Item]  
    Purpose  
        indicates approval value of an item  
    Principle  
        a user can upvote an item, or unupvote to remove their vote
        if they already cast one. The countVotes action returns the
        number of votes that an item has.  
    State  
        voters: Item -> set User  
    Actions  
        upvote(u: User, i: Item)  
            Adds user `u` to the set of `voters` for item `i`  
        unupvote(u: User, i: Item)  
            If `u` is already in set `voters` for item i, remove user
            `u` from the set `voters` for item `i`  
        countVotes(i: Item, out n: Integer)  
            Returns the size of set `voters` for a given item `i`
concept Upvote [User, Item]  
    Purpose  
        indicates approval value of an item  
    Principle  
        a user can upvote an item, or unupvote to remove their vote
        if they already cast one. The countVotes action returns the
        number of votes that an item has.  
    State  
        voters: Item -> set User  
    Actions  
        upvote(u: User, i: Item)  
            Adds user `u` to the set of `voters` for item `i`  
        unupvote(u: User, i: Item)  
            If `u` is already in set `voters` for item i, remove user
            `u` from the set `voters` for item `i`  
        countVotes(i: Item, out n: Integer)  
            Returns the size of set `voters` for a given item `i`
concept Context [User, Item]  
    Purpose  
        provides additional info for an item  
    Principle  
        an autogenerated context can be added to an item, or a user
        can add a context to an item. The getParentItem action returns
        the item a context belongs to, while the getContextsForItem
        returns all the contexts that an item has. A context can be shown
        or hidden, as well as deleted.  
    State  
        contexts: set Context  
        items: Context -> one Item  
        author: Context -> one User  
        body: Context -> one String  
        visible: Context -> one Boolean  
    Actions  
        autogenerate(i: Item, out c: Context)  
            Create new Context `c` with an automatically generated body
            for item `i` and add it to `contexts`  
        add(u: User, s: String, i: Item, out c: Context)  
            Create new Context `c` with author `u` and body `s` for item
            `i` and add it to `contexts`  
        getParentItem(c: Context, out i: Item)  
            Returns the item `i` that `c` maps to  
        getContextsForItem(i: Item, out c: set Context)  
            Returns the set of all contexts that map to item `i`  
        show(c: Context)  
            Set `visible` for context `c` to True  
        hide(c: Context)  
            Set `visible` for context `c` to False  
        delete(c: Context)  
            Remove context `c` from set of `contexts`
concept Context [User, Item]  
    Purpose  
        provides additional info for an item  
    Principle  
        an autogenerated context can be added to an item, or a user
        can add a context to an item. The getParentItem action returns
        the item a context belongs to, while the getContextsForItem
        returns all the contexts that an item has. A context can be shown
        or hidden, as well as deleted.  
    State  
        contexts: set Context  
        items: Context -> one Item  
        author: Context -> one User  
        body: Context -> one String  
        visible: Context -> one Boolean  
    Actions  
        autogenerate(i: Item, out c: Context)  
            Create new Context `c` with an automatically generated body
            for item `i` and add it to `contexts`  
        add(u: User, s: String, i: Item, out c: Context)  
            Create new Context `c` with author `u` and body `s` for item
            `i` and add it to `contexts`  
        getParentItem(c: Context, out i: Item)  
            Returns the item `i` that `c` maps to  
        getContextsForItem(i: Item, out c: set Context)  
            Returns the set of all contexts that map to item `i`  
        show(c: Context)  
            Set `visible` for context `c` to True  
        hide(c: Context)  
            Set `visible` for context `c` to False  
        delete(c: Context)  
            Remove context `c` from set of `contexts`
app SocialInfo  
    includes  
        User  
        Session [User.User]  
        Post [User.User]  
        Context [User.User, Post.Post]  
        Upvote [User.User, Post.Post]  
        Upvote [User.User, Context.Context]  
        Follow [User.User]  
    sync register (username, password: String, out user: User)  
        u = User.register (username, password)  
    sync login (username, password: String, out user: User, out s: Session)  
        when User.authenticate (username, password, user)  
        s = Session.start (user)  
    sync logout (s: Session)  
        Session.end (s)  
    sync authenticate (s: Session, out u: User)  
        u = Session.getUser (s)  
    sync makePost(session: Session, str: String)  
        u = Session.getUser(session)  
        p = Post.create(u, str)  
        c = Context.autogenerate(p)  
        Context.show(c)  
    sync submitContext(session: Session, str: String, p: Post, out c: Context)  
        u = Session.getUser(session)  
        if p.author != u  
        c = Context.add(u, str, p)  
    sync deleteContext(s: Session, c: Context)  
        u = Session.getUser(s)  
        if u == c.author  
        Context.delete(c)  
    sync upvoteContext(s: Session, c: Context)  
        u = Session.getUser(s)  
        Upvote.upvote(u, c)
        p = Context.getParentItem(c)  
        ctxs = Context.getContextsForItem(p)  
        top = i that has the max Upvote.countVotes(i) for i in ctxs  
        rest = ctxs \ top  
        Context.show(top)  
        Context.hide(r) for r in rest  
    sync unupvoteContext(s: Session, c: Context)  
        u = Session.getUser(s)  
        Upvote.unupvote(u, c)  
        p = Context.getParentItem(c)  
        ctxs = Context.getContextsForItem(p)  
        top = i that has the max Upvote.countVotes(i) for i in ctxs  
        rest = ctxs \ top  
        Context.show(top)  
        Context.hide(r) for r in rest  
    sync upvotePost(s: Session, p: Post)  
        u = Session.getUser(s)  
        Upvote.upvote(u, p)  
    sync unupvotePost(s: Session, p: Post)  
        u = Session.getUser(s)  
        Upvote.unupvote(u, p)  
    sync deletePost(s: Session, p: Post)  
        u = Session.getUser(s)  
        if u == p.author  
        contexts = Context.getContextsForItem(p)  
        Context.delete(c) for c in contexts  
        Post.delete(p)  
    sync getGlobalFeed(out feed: set Post)  
        feed = Post.allPosts()  
    sync getUserFeed(s: Session, out feed: set Post)  
        u = Session.getUser(s)  
        f = Follower.getFollowing(u)  
        feed = Post.getPostsByUsers(f)
    sync getPostsByUser(u: User, out posts: set Post)  
        posts = Post.getPostsByUsers({u})
app SocialInfo  
    includes  
        User  
        Session [User.User]  
        Post [User.User]  
        Context [User.User, Post.Post]  
        Upvote [User.User, Post.Post]  
        Upvote [User.User, Context.Context]  
        Follow [User.User]  
    sync register (username, password: String, out user: User)  
        u = User.register (username, password)  
    sync login (username, password: String, out user: User, out s: Session)  
        when User.authenticate (username, password, user)  
        s = Session.start (user)  
    sync logout (s: Session)  
        Session.end (s)  
    sync authenticate (s: Session, out u: User)  
        u = Session.getUser (s)  
    sync makePost(session: Session, str: String)  
        u = Session.getUser(session)  
        p = Post.create(u, str)  
        c = Context.autogenerate(p)  
        Context.show(c)  
    sync submitContext(session: Session, str: String, p: Post, out c: Context)  
        u = Session.getUser(session)  
        if p.author != u  
        c = Context.add(u, str, p)  
    sync deleteContext(s: Session, c: Context)  
        u = Session.getUser(s)  
        if u == c.author  
        Context.delete(c)  
    sync upvoteContext(s: Session, c: Context)  
        u = Session.getUser(s)  
        Upvote.upvote(u, c)
        p = Context.getParentItem(c)  
        ctxs = Context.getContextsForItem(p)  
        top = i that has the max Upvote.countVotes(i) for i in ctxs  
        rest = ctxs \ top  
        Context.show(top)  
        Context.hide(r) for r in rest  
    sync unupvoteContext(s: Session, c: Context)  
        u = Session.getUser(s)  
        Upvote.unupvote(u, c)  
        p = Context.getParentItem(c)  
        ctxs = Context.getContextsForItem(p)  
        top = i that has the max Upvote.countVotes(i) for i in ctxs  
        rest = ctxs \ top  
        Context.show(top)  
        Context.hide(r) for r in rest  
    sync upvotePost(s: Session, p: Post)  
        u = Session.getUser(s)  
        Upvote.upvote(u, p)  
    sync unupvotePost(s: Session, p: Post)  
        u = Session.getUser(s)  
        Upvote.unupvote(u, p)  
    sync deletePost(s: Session, p: Post)  
        u = Session.getUser(s)  
        if u == p.author  
        contexts = Context.getContextsForItem(p)  
        Context.delete(c) for c in contexts  
        Post.delete(p)  
    sync getGlobalFeed(out feed: set Post)  
        feed = Post.allPosts()  
    sync getUserFeed(s: Session, out feed: set Post)  
        u = Session.getUser(s)  
        f = Follower.getFollowing(u)  
        feed = Post.getPostsByUsers(f)
    sync getPostsByUser(u: User, out posts: set Post)  
        posts = Post.getPostsByUsers({u})

Dependency Diagram

Dependency diagram

Wireframing

Figma Wireframe

Design Tradeoffs

  1. Text-only (Add image and video attachments, or not): No media like videos, images, etc… All that must be posted as external links (which are included in String), rather than attached directly as files. I wanted to reduce the number of concepts I used in order to leave some room for my novel ones.
  2. No Profile (Allow user profile customization or not): No profile picture, description, pronouns, display name, etc, only username. I was a bit torn on this one, since it can make users harder to distinguish at a glance, but it’s one less concept that I’d have to add, and since I don’t have verification, then hopefully people will be less likely to fall for impersonations if there are fewer aspects available to imitate, and usernames must be unique.
  3. Dislike Button? (Upvote only or upvote + downvote): I decided not to have a dislike button, only a like button. On the one hand, disliking could help show other users what is unhelpful. On the other, sometimes dislikes can be abused by other users. I decided to only add upvotes, not downvotes, to avoid the possibility of abuse, especially in the case of brigading where users gang up to intentionally dislike a post for an arbitrary reason.
  4. Default Feed (Default to following vs global feed): I decided to default to following because I think most users would prefer that. Even journalists have certain topics they care about more and would probably like to curate their experience somewhat, so having Following be the default feed makes it more convenient. This could promote echo chambers caused by following only people you agree with, but I don't think the global feed is nearly useful enough to be promoted as the default.