Assignment 3: Convergent Design
a. Pitch
Ounce is a social media app intended for people who want to take control over their social media usage. Ounce does so in a variety of ways. First and foremost, users are limited to a single post a day. No more media companies swamping your feed. Next, Ounce has no backend ML algorithm to suck you in and keep you scrolling. A prof's feed consists only of accounts they have chosen to follow. For greater granulartiy, prof's can tag accounts under a custom label for seamless content filtering. Lastly, Ounce will put large emphasis on each prof's individual usage via a daily usage timer that will be present at all times. A person can get greater insight into their usage habits with a breakdown of varying timescales and metrics (i.e Total Weekly Usage, Avg. Monthly Usage, etc.). The motivations behind most popular social media companies have strayed away from prof experience, and instead focused on corporate profit. Ounce is here to offer an alternative for daily prof interavtions to be of quality versus quantity.
b. Functional Design
User
concept User
purpose:
authenticate users
principle:
after a prof registers with a username and password, they
can authenticate as that prof by providing a matching
username and password.
state
registered: set User
username, password: registered -> one String
actions
register(u: String, p: String): Void
# if u not in registered.username,
# add User(u, p) to registered
authenticate(u: String, p: String): User
# if (u, p) in registered,
# return registered.username == u
concept User
purpose:
authenticate users
principle:
after a prof registers with a username and password, they
can authenticate as that prof by providing a matching
username and password.
state
registered: set User
username, password: registered -> one String
actions
register(u: String, p: String): Void
# if u not in registered.username,
# add User(u, p) to registered
authenticate(u: String, p: String): User
# if (u, p) in registered,
# return registered.username == u
Label[Item]
concept Label[Item]
purpose
organize with overlapping
principle
- add and delete labels
- find an item that associates with a set of labels
state
labels: Item -> set Label
actions
add (i: Item, l: Label): Void
remove (i: Item, l: Label): Void
find (ls: set Label): set Item
concept Label[Item]
purpose
organize with overlapping
principle
- add and delete labels
- find an item that associates with a set of labels
state
labels: Item -> set Label
actions
add (i: Item, l: Label): Void
remove (i: Item, l: Label): Void
find (ls: set Label): set Item
Limit[Item]
concept Limit[Item]
purpose
impose limitations on an Item
principle
- use createLimit to add a Limit to an Item
- use hasLimit to avoid redundancy in checking if an Item
already has a limit
- use validateIncrement to return whether increasing by a certain
amount exceeds the limit (and if it does not, then increment)
- use delete to remove the Limit on an Item
- use reset to change the count to 0 after some amount of time
state
const limit: Number
counter: Number
timer: Date
items: set Item
itemCount: items -> one Number
actions
underLimit(c: Number): Boolean
# True if c < limit else False
createValidLimit(i: Item, c: Number): Void
# if underLimit(c)
# add i to items,
# add (i, c) to Itemcount
validateIncrement(i: Item, count: Number): Boolean
# add count to itemCount[i] if belowLimit()
delete(i: Item): Void
# remove i from items
reset(i:Item, t: Date): Void
# set itemCount[i] to 0 after some period of time
concept Limit[Item]
purpose
impose limitations on an Item
principle
- use createLimit to add a Limit to an Item
- use hasLimit to avoid redundancy in checking if an Item
already has a limit
- use validateIncrement to return whether increasing by a certain
amount exceeds the limit (and if it does not, then increment)
- use delete to remove the Limit on an Item
- use reset to change the count to 0 after some amount of time
state
const limit: Number
counter: Number
timer: Date
items: set Item
itemCount: items -> one Number
actions
underLimit(c: Number): Boolean
# True if c < limit else False
createValidLimit(i: Item, c: Number): Void
# if underLimit(c)
# add i to items,
# add (i, c) to Itemcount
validateIncrement(i: Item, count: Number): Boolean
# add count to itemCount[i] if belowLimit()
delete(i: Item): Void
# remove i from items
reset(i:Item, t: Date): Void
# set itemCount[i] to 0 after some period of time
Profile[User, Post, Label]
concept Profile[User]
purpose:
associate data to a specific profile
principle:
- create and delete profiles
- get the user associated with this Profile
- use followAccount to follow another user Profile
- use addTimeActive to increment the total amount of time that a
person has been active
state:
user: one User
following: set Profile
posts: set Post
labels: set Label
timeActive: one Time = 0
actions
createProfile(u: User): Profile
deleteProfile(): Void
getUser(): User
followAccount(p: Profile): Void
# add p to following
unfollowAccount(p:profile): Void
# remove p from following
addTimeActive(t: Time): Void
# timeActive += t
addPost(p: Post): Void
# add p to posts
removePost(p: Post): Void
# remove p from posts
concept Profile[User]
purpose:
associate data to a specific profile
principle:
- create and delete profiles
- get the user associated with this Profile
- use followAccount to follow another user Profile
- use addTimeActive to increment the total amount of time that a
person has been active
state:
user: one User
following: set Profile
posts: set Post
labels: set Label
timeActive: one Time = 0
actions
createProfile(u: User): Profile
deleteProfile(): Void
getUser(): User
followAccount(p: Profile): Void
# add p to following
unfollowAccount(p:profile): Void
# remove p from following
addTimeActive(t: Time): Void
# timeActive += t
addPost(p: Post): Void
# add p to posts
removePost(p: Post): Void
# remove p from posts
Session[User]
concept Session[User]
purpose:
authenticate a user for extended period of time
principle:
- after a session starts (and before it ends) getUser action returns
the user identified at the start
- store the timestamp of when a session starts
- use end to return the total time of a session
state
startTime: one Time
active: set Session
sessions: active -> one User
actions
start(u: User): Session
# startTime = Time.now,
# add u to active,
# prof = u,
getUser(s: Session): User
# if s in active, return sessions[s]
end(s: Session): Time
# if s in active:
# remove s from active,
# return Time.now - s.startTime
concept Session[User]
purpose:
authenticate a user for extended period of time
principle:
- after a session starts (and before it ends) getUser action returns
the user identified at the start
- store the timestamp of when a session starts
- use end to return the total time of a session
state
startTime: one Time
active: set Session
sessions: active -> one User
actions
start(u: User): Session
# startTime = Time.now,
# add u to active,
# prof = u,
getUser(s: Session): User
# if s in active, return sessions[s]
end(s: Session): Time
# if s in active:
# remove s from active,
# return Time.now - s.startTime
Post[Content, Profile]
concept Post
purpose:
enables people to share content
principle:
- use create to initialize a new piece of content
- use delete to remove a post
- getOwner will return the profile that shared a particular post
- addComment adds a username-string mapping to a post's comments section
state:
content: one Content
owner: one Profile
comments: set Profile -> one String
actions
create(c: Content, p: Profile): Post
delete(): Void
getOwner(p: Post): Profile
addComment(p: Profile, c: String): Void
# add u -> c to comments
concept Post
purpose:
enables people to share content
principle:
- use create to initialize a new piece of content
- use delete to remove a post
- getOwner will return the profile that shared a particular post
- addComment adds a username-string mapping to a post's comments section
state:
content: one Content
owner: one Profile
comments: set Profile -> one String
actions
create(c: Content, p: Profile): Post
delete(): Void
getOwner(p: Post): Profile
addComment(p: Profile, c: String): Void
# add u -> c to comments
App-Level Actions: Ounce
app Ounce
include User
include Post
include Profile[User.User]
include Session[User]
include Limit[Profile[User.User]] as LimitedProf
include Limit[Post] as LimitedPost
sync registerProfile(u, p: String): Profile
# when user:= register(u, p)
# prof:= createProfile(user)
sync getProfile(s: session): Profile
# return p where p.getUser == s.session
sync login(u, p: String): (Profile, s)
# when user:= authenticate(u, p)
# s:= Session.start(user)
# prof:= getProfile(s)
# return (prof, s)
sync logout(s: Session): Void
# when timeEnded:= Session.end(s),
# sessionTime: Time = timeEnded - s.startTime
# Session.getUser(s).addTimeActive(sessionTime)
sync getUsage(s: Session, p: Profile): Time
# prof.timeActive + (Time.now - s.startTime)
// limited number of daily posts + post char limit
sync postContent(c: Content, prof: Profile): Void
# if LimitedPost.belowLimit(len of c)
# AND LimitedProfile.belowLimit(__daily posts by prof__):
# post:= Post.create(c), prof.addPost(post)
sync deletePost(prof: Profile, post: Post): Void
# prof.removePost(post), post.delete()
sync makeComment(prof: Profile, post: Post, c: String): Void
# post.addComment(prof, c)
sync markItem(prof: profile, i: Post|Profile, l: Label): Void
# prof.labels.add(i, l)
// not only feed on labeled accounts/posts but also
// covers the concept of "favorites"
sync filterFeed(prof: Profile, ls: set Labels):
# display prof.labels.find(ls)
app Ounce
include User
include Post
include Profile[User.User]
include Session[User]
include Limit[Profile[User.User]] as LimitedProf
include Limit[Post] as LimitedPost
sync registerProfile(u, p: String): Profile
# when user:= register(u, p)
# prof:= createProfile(user)
sync getProfile(s: session): Profile
# return p where p.getUser == s.session
sync login(u, p: String): (Profile, s)
# when user:= authenticate(u, p)
# s:= Session.start(user)
# prof:= getProfile(s)
# return (prof, s)
sync logout(s: Session): Void
# when timeEnded:= Session.end(s),
# sessionTime: Time = timeEnded - s.startTime
# Session.getUser(s).addTimeActive(sessionTime)
sync getUsage(s: Session, p: Profile): Time
# prof.timeActive + (Time.now - s.startTime)
// limited number of daily posts + post char limit
sync postContent(c: Content, prof: Profile): Void
# if LimitedPost.belowLimit(len of c)
# AND LimitedProfile.belowLimit(__daily posts by prof__):
# post:= Post.create(c), prof.addPost(post)
sync deletePost(prof: Profile, post: Post): Void
# prof.removePost(post), post.delete()
sync makeComment(prof: Profile, post: Post, c: String): Void
# post.addComment(prof, c)
sync markItem(prof: profile, i: Post|Profile, l: Label): Void
# prof.labels.add(i, l)
// not only feed on labeled accounts/posts but also
// covers the concept of "favorites"
sync filterFeed(prof: Profile, ls: set Labels):
# display prof.labels.find(ls)
c. Dependency Diagram
Note: this diagram is under assumption that this represents the dependencies IN RELATION TO MY APP. For example, in theory Post + Profile do not necessarily need to be connected. However, there will not be anonymous posts in Ounce
d. Wireframes
e. Design Tradeoffs
1. The "Profile Problem"
The notion of a Personal Profile threw me for a loop. I wanted to store various data points for a user (i.e posts, following, usage, etc.) and struggled to find the balance between contextualizing concepts versus focusing on reusability.
Options
- "jam" all of this information into a "User" concept
- break down into "User", "Session", and "Profile" concept
- meet somewhere in the middle i.e have a "Profile" concept and store the "Session" info in "User"
I ended up choosing the 2nd option of separating this notion of a Profile into 3 concepts. This came down to the disctinct purposes of each. "User" authenticates, "Session" tracks usage, and "Profile" stores relevant info. Although I likely won't reuse any of these concepts, as described in lecture this is best practice in general design.
2. Permission to Follow?
Another choice I had to make was based on a Profile a following another Profile b. I had to decide whether to Options
- b authorize a to follow them (and in turn view their content)
- b have no control over who follows them (in this case being Profile a)
My decision ended up being the ladder, with any user being able to follow another without permission. In theory, I think that the users should have more control over their "profile security". However, I came to this decision purely based on implementation feasability. I think the cost of introducing a "Security" concept would introduce complexity, that isn't a central purpose of the app. Maybe I will change this decision down the line though.
3. Customizable Experience?
When ideating my concept, I liked the notion of each person being able to "toggle" (or rather customize) their experience with the app. For instance, being able to View Posts from "suggested" content, enable/disable a catalog of their posts, etc. Thus, my choice was between Options
- a user being able to customize their app environment
- all users have the same, default environment
I went with the last choice, where the user interface and features were identical across various profiles. One reason was similar to above -- because of time constraints. Further, after some deeper thought, I did not think this was actually going to provide much value to users, and instead create complications in users' interactions. The principle of customization may make sense in some contexts; however, I feel that in a time sensitive project, this is putting energy towards a niche community, and not focusing enough on optimizing the experience for common users.