For this project, I performed problem analysis, made wireframes, interactive designs, and the MVP implementation. The CTO helped me cut scope for the MVP and implemented some follow-up changes.
Trouble getting around the codebase
Dark lays its constructs (code and data schemas) in floating boxes. Users spend their time panning, dragging, jumping around their project space instead of coding. Why do they do that?
1. They do so to verify DB schemas
Often users need to drag databases close to their code because DB::set requires the data inserted to match the schema exactly.
When a function has code referring to a DB, the experience is even worse!
2. To read/write in functions
Users can create functions, which will be accessible throughout their project. But these functions are NOT accessible within the main space.
3. To understand workers
Workers are like async functions that appear in the main space. But that doesn't make using them any easier.
How did we get here?
Problems we face today are created by people in the past with the best intentions
The floating boxes (of code and DBs) are one day suppose to have dots/lines between them to show live traffic. They will organize code for users, replacing folders and files. File systems, he theorized, are arbitrary ways of splitting code. They give users more problems to worry about (such as dependency cycles) than solutions.
Let's think through the city analogy
If the endpoints and datastores are landmarks, then the path data takes are streets and highways.
The path of a single trace is a trip.
Over time we can construct a cumulative flow illustrating the popularity of certain paths. The user will know which parts of the codebase to devote more resources.
Unfortunately, most of our users don't have live traffic yet*
At the time of this project (early 2019). Now we have many users with live traffic
First, we must pave the streets
How can we help users understand their codebase? So they may write, debug, and edit code with ease?
For this case study, we'll be using a simple habits tracker application.
City planning!
In order to create a useful interface for writing & editing code, our solutions needs to answer the following questions:
What are all the possible paths a trace can take?
How do we navigate through these paths?
What information should be available for each construct type?
Let's consider one path
POST /track gets hit it when you've completed a habit. Most of the execution is offloaded to a queue (the "didHabit" worker). The worker will add an entry to the Tracker DB, and send you a meme if you've met your weekly goal.
Adding points along the path
We start off with POST /track, its code it references Habits DB with DB::get. And emits an event to "didHabit" worker.
Going down the path
Are we there yet?
Have we mapped out all the paths we want to capture? When POST /track gets hit, most of its execution is offloaded to a queue (the "didHabit" worker). The worker will add an entry to the Tracker DB, and send you a meme if you've met your weekly goal.
What is it that is missing? Send meme or encouragement!
... and how do we get there?
Looks like they are functions. And a problem our users face is:
Functions are NOT accessible within the main space; they exist in their own little world. Nor are they accessible from the code that uses them. They can only be reached through the sidebar.
Seems like functions references will be useful
Now, when the user clicks into a function's reference, they can see the code.
How to represent landmarks?
So far references are just boxy links. How can we use the space to provide useful information to the user?
Sign designers anticipate a driver's needs as they drive by. Do they need a bathroom break? More gas? A snack? What are our user's needs?
Reach out @aliceaskwhy, if you want to share badly designed highway signs.
Users drag DBs closer to their code to check schema.
Users want to see what happens to their code when it passes through a function.
Users want to know what a worker expects for its input (event : Any)
Designing our city
Let's bring some color consistency and typographic hierarchy.
Now how should we arrange our references in relation to the code?
Considering constraints
Going down the path horizontally might not work. Live values are on the left!
For now let's draw all the references on the right side with incoming and outgoing arrows. We'll think more about their ideal placement later.
It is more important to bring the value of references to users sooner.
Putting it all together
Making it real!
Scope cuts:
Only show references on the right for HTTP code blocks.
Don't show a worker's event structure yet (because it involves complex program analysis)
Don't derive the return types for functions, because functions will have static return types in the future.
Instead of expanding to see the code in workers and functions, just pan to them spot in the project space.
After scope cuts, this is our implemented solution
Field test
Overall the feedback was positive. New users pointed out it was one of the features they felt made Dark unique. Existing users even discovered an unintended use case. They used it to depreciate functions and migrate DB schemas.
But there were several things they didn't like:
The (in/out) arrows are too subtle.
Viewing a function's code in another space is still disruptive to workflow
They want to be able to see caller's code and reference's code side by side.
Revisions
A simple fix can be to color the arrow and box differently for incoming and outgoing references.
Idea 1: Move it
Now that references have proven their worth, we might be able to move live values.
Idea 2: Show depending on user intentions
The cursor is at a different place when users look at references than when they look at live values.
A user looks at incoming references to see its usage. The code (and consequently live values) can be a black box. We don't need to show live values in that case.
When a user edits code, they want to know what is happening to the data. We fade out the incoming references and show the live value then.
Retrospective
After the scope was cut, and we implemented references for workers and functions. But there was no plan to implement other cut features nor execute on revisions.
Without moving our incoming references on the opposite side of outgoing referencesm, the map/navigation analogy is broken.