Problem: When replying to a private thread we already know who the
recipients are, and they're already going to get the notification, so
there's really no reason to add a mention.
Solution: Remove the mention when the message is private.
Problem: Previously we were only counting likes from people you follow,
but showing messages from anyone. This was backward, and could
potentially show messages from blocked authors that were liked by people
you follow.
Solution: Move the `socialFilter()` invocation down the pipeline so that
it sorts the output messages, not the likes.
Problem: Any error thrown while looking for thread ancestors was
throwing a "message not found" error, which was incorrect and useless.
This error caused me to his refresh repeatedly, not understanding that I
was publishing multiple messages. Super bad. While investigating this I
found that there's a slightly different problem where someone can post
an *invalid* message link, which we don't currently have handling for.
Solution: Only show that error when it's actually happening, and add
support for just ignoring when we see an invalid message link as `root`
or `fork`.
Problem: In the old `cooler.read()` implementation, sources returned
promises, but that isn't the case in the native SSB-Client promise
impelementation. This means that in at least one place, there's a bug
where it tries to call `.then()` on a non-promise.
Solution: Remove promise-based code from streams, which don't require
any special handling anymore.
Problem: Your posts show up in Extended, which is unexpected because I'm
the center of my network, not some rando at the periphery.
Solution: Use the `socialFilter()` function to make sure that the
extended view only shows people in your extended network, not you. :)
Problem: We started using `cooler.get()` and `cooler.read()` because it
was impossible to use promises with SSB-Client.
Solution: I made some downstream pull requests into the MuxRPC module
and the SSB-Client module, which means that both of them now natively
support promises. This commit removes the weird convenience methods and
replaces them with the native promise support, which should hopefully
make the code easier to read and write.
Problem: There was one missing component that would filter out nicknames
from other people. This caused a problem where we could get a name for a
feed but it could've been assigned by a friend, which we don't want
right now.
Solution: Ensure that the subject of the message is the same as the
author of the message.
Problem: Search engines are controversial and my understanding is that
most people on SSB don't want their messages indexed by search engines.
If that's the case, we should probably disable it.
Solution: Add basic `robots.txt` file to ask search engines to stay away
and please don't save info. I'm concerned that, like `publicWebHosting`
redactions, it gives a false sense of privacy, but it seems like this is
probably what most people would want?
Problem: The SSB-About plugin is incompatible with our needs. More info
in the GitHub issue linked below and in the code comments.
Solution: Unfortunately, roll our own alternative to the SSB-About
plugin so that we can be 100% sure that it pulls the latest 'about'
published by an author about themselves and doesn't just skip `false`.
Problem: The `socialFilter()` function wasn't documented and contained a
bug where it wouldn't show your posts when `following = true`. This is
because you usually don't follow yourself, so `following = true` was
basically equivalent to `me = false`.
Solution: Add some documentation and resolve the bug by adding special
handling for when the message is from us *before* passing to the general
implementation for follow/block checking.
Resolves https://github.com/fraction/oasis/issues/155
Resolves https://github.com/fraction/oasis/issues/177
Problem: It's hard to show off Oasis or take screenshots without
respecting the `publicWebHosting` convention. While `publicWebHosting`
lacks a formal specification and I'm a bit confused about what its
boundaries are, it sounds like some of our friends would like to avoid
us publishing any of their content on the public web if we can avoid it.
Solution: Add --public option that turns Oasis into a public web viewer.
This makes it **slightly inconvenient** to see these public posts, but
should absolutely not be mistaken for a privacy guarantee. Only HTTP GET
endpoints are allowed, so random people can't publish or change
settings. The name, avatar, description, content warning, and message
contents are replaced with "Redacted", but again, this is all public
information that we can never provide real privacy for.
Resolves https://github.com/fraction/oasis/issues/48
Problem: We use nosniff to keep the web browser from getting confused
about what kinds of content we're serving in Oasis, but this causes
problems for blob URLs that have arbitrary data.
Solution: Remove nosniff on blob URLs to let the browser figure out what
kind of content we're serving.
Resolves https://github.com/fraction/oasis/issues/138
Problem: The regular space in the sidebar emoji was breaking the line at
a specific viewport width and the emoji were being shown as the wrong
font.
Solution: Use a non-breaking space and `font-family: initial` for
full-color emoji instead of using the system font.
Resolves https://github.com/fraction/oasis/issues/150
Resolves https://github.com/fraction/oasis/issues/153
Problem: The socialFilter was hiding posts published by the user, which
felt weird and uncanny.
Solution: Fix the default so that `{ me }` isn't hidden from a view
unless the model specifically wants that to happen.
Resolves https://github.com/fraction/oasis/issues/156
Problem: The Private and Mentions page didn't have view labels yet, and
Publish should be its own page instead of being at the top of every
page.
Solution: Inspired by @cinnamon-bun's work to add friendly view labels,
plus a new Publish page. This also moves the period selection from the
popular page into the view label, which felt better to me with the
previous `<section>` background. I also tried a different text format
for describing the pages, using a common form and using `<strong>` to
draw attention to any change from "Posts from people you follow, sorted
by recency" which feels like the expected default for most people.
@cinnamon-bun: To me this feels like a fun back-and-forth where I'm
riffing on your work and hoping that you do the same, but if it feels
wrong/rude please let me know. Trying to work on designs with a system
like C4 is super new for me and I wouldn't be surprised if there are
pain points to fix!
Resolves https://github.com/fraction/oasis/issues/160
Problem: There was no way to onboard new users since we couldn't redeem
invites.
Solution: Add basic follow-back invites to the settings page. This takes
an invite string, runs it through invite.accept, and either returns the
error in full *or* redeems the invite quietly.
Problem: @masukomi pointed out that the 'latest' view doesn't show the
awesome slice of content that you'll see if you just look at root posts.
Solution: Let's experiment with them! This commit adds a 'Topics' page
that has the latest root posts from people you're following.
Problem: The nav is aligned to the left of the screen on mobile, which
feels off-center and unbalanced on mobile. https://github.com/fraction/oasis/issues/135
Solution: Center the menu to optimize for space around the links.
Problem: We shouldn't be showing any strangers on the popular or latest
pages. We shouldn't be showing anyone who's been blocked on any of those
feeds.
Solution: Don't show blocked people on any pages and don't show
strangers on the popular / latest pages.
Problem: If the user tries to see a thread and the link points to a
message we don't have, then we don't have any way to display anything in
the thread. How could we even know which thread it's in?
Solution: Throw the error but make it more useful and fix the "non-error
thrown" verbiage that we've inherited from a dependency trying to throw
a non-error.
Problem: I think during a refactor this code was changed and ended up
breaking the "fake image" that we return when the user doesn't have an
image. We also don't see image errors because they aren't in the browser
viewport if they return text and we don't `console.error()` our errors.
Solution: Fix the image code to return a PNG as a buffer and duplicate
errors to stderr.
Problem: SSB-Search has a bug where too few characters just hangs the
search indefinitely and never returns. https://github.com/fraction/oasis/issues/107
Solution: Enforce a minimum length of 3 characters in the search. I
bumped into another bug where HyperScript, a dependency of HyperAxe,
doesn't support the `minlength` attribute, so I had to deploy a small
workaround for that too. The fixes aren't very pretty but they're better
than just ignoring the problem.
Problem: The previous commits didn't persist language choices and the
drop-down's initial value wasn't respecting the language you selected.
Solution: Persist the language choice in a cookie, defaulting to
English, and build the drop-down with the selected language. This also
changes the word "Spanish" to "Español", and slightly refactors
`http.js` to accept *middleware* rather than just routes. This lets us
add other middleware, such as the language selection middleware added in
this commit.
Problem: The previous commit added English as a language but didn't add
other languages or ways to switch between them.
Solution: Add the most primitive language selection possible and a few
small translations contributed by @bramdroid during a totally unrelated
conversation. This does not persist the language selection and doesn't
auto-select the current language from the dropdown, but those should be
easy to add in the future.
Problem: It was impossible to do any internationalization because
strings were all embedded throughout Oasis.
Solution: Add an internationalization submodule that provides
language-specific strings for the text elements in views. In future
commits we can add language selection and fallbacks for when the
selected language doesn't support the text we need to have translated.
Problem: The 'Following' page was super slow because we were doing a
MuxRPC request on every single message.
Solution: Do one request to see who we have relationships with, filter
out the people we aren't following, and then check against that list
instead of doing a bunch of MuxRPC calls.
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `curl http://localhost:3000/public/latest/following` | 500.9 ± 163.4 | 392.1 | 881.4 | 1.00 |
| `curl http://localhost:3456/public/latest/following` | 4663.7 ± 184.6 | 4438.6 | 5075.2 | 9.31 ± 3.06 |
Problem: Most browsers parse SVG files as XML and refuse to display it
in an `<img>` tag. It's usually unsafe to have browsers try to sniff the
file type themselves, because they can be tricked into running unsafe
code, so we want to set the file type ourselves in the server.
Solution: Use the Is-SVG library for a quick-n-dirty check for whether a
buffer is an SVG. If so, we set the file type accordingly.
Problem: The model code was doing our Markdown rendering, which feels to
me like a layer violation because *generally* the model is meant to be a
thin abstraction over the underlying database without any concept of the
presentation layer.
Solution: Move the Markdown renderer to the view module and manage
Markdown rendering in the presentation layer.
Problem: We don't always want to see all of the messages on our
computer, sometimes we just want to see messages from the people we're
explicitly following. The 'Popular' and 'Latest' pages don't help with
that.
Solution: Add a super basic page that just shows the latest messages
from the people you're explicitly following.
Problem: The popular page is ungodly slow.
Solution: Make it faster! This is done by checking the timestamp before
the other constraints that we add to messages, which is mostly useful
because most messages that fail the filter will fail on the timestamp
check.
Problem: Showing private posts on profiles is scary and may give people
the impression that these posts are visible.
https://github.com/fraction/oasis/issues/113
Solution: Hide private messages when rendering public profiles and
change the method name to be very clear that it only returns public
messages.
Problem: Stopping the networking would sometimes allow peers to remain
connected, which was confusing.
Solution: Upgrade to latest SSB-CONN to pull in a bugfix for this
behavior. Huge thanks to @staltz for the quick fix!
Problem: On short pages, like the search page, there's no scrollbar
gutter on the right side of the page, which moves everything a few
pixels to the right. After banging my head against this for an hour I finally
realized that it only happens when your viewport is taller than the
content on the page, which creates the scrollbar. This was reported as:
https://github.com/fraction/oasis/issues/96
Solution: Change the display so that we consistently show a scrollbar
gutter on the right side of the page regardless of whether we need it
(e.g. on very short pages or on very tall monitors). This means that
when we center the content on the page it won't move depending on the
height of the page, which was frustrating and janky.
Problem: The `<section>` elements on some pages have different sizes,
which means that the sidebar is doing wonky stuff.
Solution: Make the sections all have a consistent width. Note that
there's still a few pixels of jank on the 'search' page, I'm not sure
why that's happening. The element inspector is reporting that everything
is the same width but that isn't true.
Problem: Putting the navigation at the top of the screen makes it
impossible to use when you're scrolling through a page, which isn't a
good user experience. It was never meant to be permanent, and I think
everyone has pointed out that it's been a pain.
Solution: Super simple sidebar nav when people are on bigger screens.
This doesn't solve the problem on mobile, and it doesn't incorporate the
'popular' page's interval settings, but I think it's a step in the right
direction.
Problem: Using all-lowercase-everything isn't really a standard around
the web and it might be better to use consistent capitalization. This
was brought up in: https://github.com/fraction/oasis/issues/98
Solution: This changes the main navigation to use links with the first
letter capitalized, like how Patchwork + Twitter + Mastodon + etc do it.
This means that we're consistently using sentence case everywhere, which
I think is our best option. Originally I tried experimenting with
all-caps for actions, which I found aesthetically pleasing, but you have
to reduce the font size to make it look good (bad!) and I was reading
that all-caps text is harder for friends with dyslexia or vision
impairments.
Problem: We were overwriting SSB-Tangle with itself, which is fine, but
kind of confusing and requires some knowledge on what is and isn't safe
for Secret-Stack.
Solution: Only inject SSB-Tangle if it doesn't already exist, which
makes the code easier to reason about.
Problem: The --offline documentation doesn't mention that networking
status can be changed, which may give the false understanding that the
networking is permanently offline when you use that flag.
Solution: Add a note that mentions that the 'meta' page lets you change
your networking status, and that --offline is only applicable to the
starting state of Oasis.
Problem: Sometimes you want to disconnect from the network but you don't
want to have to restart Oasis with the `--offline` flag
(https://github.com/fraction/oasis/issues/89). Sometimes networking gets
stuck and you need to run the equivalent of `ssb gossip reconnect`
(https://github.com/fraction/oasis/issues/63).
Solution: Buttons on the 'meta' page that let you start, stop, or
restart SSB-CONN whenever you want. Note that this commit includes an
update to SSB-CONN, but this version and the previous version both have
a bug where hitting 'stop' twice in a row will throw an error. This
commit implements a workaround for the bug, but it's something we'll
want to remove later once the underlying bug is fixed.
Problem: Patchwork is missing a plugin that we need to set the `branch`
property when posting a message. This property is important because it
helps us sort threads, so we're throwing an error when it isn't
available. See: https://github.com/fraction/oasis/issues/21
Solution: HACK THE ~~PLANET~~ API. This commit injects the plugin we
need via Oasis, which is a bit of a duct tape solution but it *is* a
solution.
Problem: When we don't have any documentation for variable types it's
difficult for both humans and machines to parse our code.
Solution: As discussed in https://github.com/fraction/oasis/issues/78,
adding some JSDoc information on function signatures would be a nice
step in the right direction and could make debugging easier.
Problem: The /inbox page was being rendered super slowly because it was
reading through tons of messages.
Solution: There isn't a way to query the database for "private messages
for me", although maybe there should be, but one way we can get
something close is querying for "messages that reference me". Every
message that's encrypted for us will have a `.value.content.recps`
property that includes our feed ID, so we just have to filter out the
public messages and we're about 4 times faster than the previous
implementation.
```console
$ hyperfine 'curl -I http://localhost:4515/inbox' 'curl -I http://localhost:3000/inbox'
Benchmark #1: curl -I http://localhost:4515/inbox
Time (mean ± σ): 3.352 s ± 0.093 s [User: 2.0 ms, System: 4.3 ms]
Range (min … max): 3.231 s … 3.483 s 10 runs
Benchmark #2: curl -I http://localhost:3000/inbox
Time (mean ± σ): 811.8 ms ± 88.3 ms [User: 2.7 ms, System: 2.9 ms]
Range (min … max): 709.1 ms … 972.5 ms 10 runs
Summary
'curl -I http://localhost:3000/inbox' ran
4.13 ± 0.46 times faster than 'curl -I http://localhost:4515/inbox'
```
Problem: Trying to reply to some messages sent with Patchbay fail
because the schema check is throwing an error.
Solution: When we encounter `recps` like `{ name, link }`, normalize it
to just `link` and publish a well-formed message that passes the schema
check.
Problem: Some of the icon was being clipped in my browser. I think that
this is because the SVG text actuall extends down under the line (like a
`g` or `y`).
Solution: Change SVG viewBox dimensions and SVG size to fit correctly.
There are very few reasons you'd ever want to do this and I think it's
more helpful to just disable it altogether. A reply should be thought of
as **creating a new thread**, and if you have a response to the thread
that's created then it should be posted as a **comment**.
Making a new thread as a response to an existing thread is an advanced
action and I think the simplicity is worth the small reduction in the
degrees of freedom for advanced users. Maybe I'll change my mind?
This changes some phrasing for clarity and adds some helpful
explanations when you're publishing a comment or a reply.
This also fixes comments on replies, which were previously just being
added as a sibling reply. This doesn't really matter because it has the
same layout in the UI and it's also very rare, but it allows us to have
separate threads for each reply.
I think I was overusing borders and it gave the UI a wireframey skeleton
feel that wasn't very fleshed out. The dependence on thin borders also
caused trouble when using themes with low color contrast, since you'd
have thin lines that were *also* low-contrast. Bad!
Instead, I'm using a "card UI" style with varying colors, which I think
looks better (???) and seems to be more compatible with more themes.
Happy to roll this back if others don't dig it.
Previously each time you liked something it added 1 point to that post.
That's fine, but it meant that if someone posted 10x more likes then
they'd have 10x more influence that others. I tried to reduce this,
making sure that everyone has exactly 1 influence, but it meant that
when someone only liked 1 thing then it's a *very* powerful like.
I think it's a nice middle ground to divide each point by (1 + ln(x)),
where x is the total number of likes that someone has made. This means:
- 1 like = 1 point
- 2 likes = 1.69 points (0.84 each)
- 4 likes = 2.38 points (0.59 each)
- 8 likes = 3.07 points (0.38 each)
- 16 likes = 3.77 points (0.23 each)
- 32 likes = 4.46 points (0.14 each)
- 64 likes = 5.15 points (0.08 each)
Instead of trying to remove ssb-dev content explicitly I'm going to try
to experiment with a "popular" page that count all of the votes in the
past 24 hours and shows the posts with the most votes.
The SSB network is full of discussion about the SSB network, which isn't
very accessible for the majority of people. SSB developers use SSB to
talk about SSB, a practice often called "eating your own dog food",
which exascerbates the problem.
This commit filters the "dogfood" from the public thread and comment
views, which can be avoided by appending `/dogfood` to the URL.
- http://localhost:3000/public/threads/dogfood
- http://localhost:3000/public/comments/dogfood
This is a hack, and should be resolved with sorting and filters and all
sorts of fancy options for each list of messages, but I want to start
experimenting with this view (and talking to people who aren't talking
about SSB).
Not being able to see your own activity in the thread and comment pages
made me feel like a ghost. This change will probably make it slightly
harder to find new content but I think being able to see your own posts
is an important part of the community feedback loop.
This should probably be separated into a few commits, but honestly I'm
feeling a bit lazy and I don't think this will hurt anything.
The `<nav>` was getting pretty hectic so I've removed "readme" and
"likes". The readme is now in the "meta" page, previously called
"status", and the likes are now available on each author's profile.
The big change here is that the default view is now the thread view, not
the comment view, so by default you're only going to see new threads
rather than random comments. This makes the feed a bit slower and more
cohesive, so you aren't seeing random comments on posts from 2 years
ago.
To be decided: should the comments view show root posts from threads? Or
should it just show comments? Right now it's basically a firehose view,
but I'm not sure that "firehose" is very accessible language for most
people.
Serving HTML under the same domain is dangerous, because it means that a
malicious user could serve JavaScript that could act on other pages on
the domain. This could allow a malicious user to read or publish
information from a blob URL.
This commit stops that behavior by delegating blobs to their own blob
subdomain and adding HTTP headers for security so that they can't access
the application.
The blob.want() command is an asynchronous function that waits for the
blob by default, which isn't what we wanted. This was hanging HTTP
threads and slowing the server down a bunch.
I don't think "target" is the right word to use, but when you click a
message and you're taken to the thread I think it's useful to actually
highlight which message you selected.
Previously when you liked a post it would redirect you back to the
previous page with the top of the post at the top of the screen. This
was jarring and weird and I didn't like it at all.
This change makes it so that the footer of the message you liked
appears in the center of the screen, which is (in my experience) much
closer to where it usually is when you click the like button.
Before: when you click the like button the scroll resets so the top of the
message is at the top of the screen.
After: when you click the like button the scroll resets so that the footer of
the message is in the center of the screen.
Wanted: a way to redirect back to the original page *without* resetting the
scroll position.
I was playing around with Madge and noticed that the previous dependency
graph looked almost *exactly* like a bowl of spaghetti I had last week.
After a few hours on Wikipedia I got interested in refactoring the code
so that each `require()` imported a module from one level deeper into
the tree.
I don't know if this is actually useful, but it's better than spaghetti.
In the future I think I should probably refactor the database
convenience functions out of the "models" since they really aren't
models but it's the best name I could come up with for what they are and
how they're used. This will probably go through some more evolution when
I rip out EJS and replace it with something much smaller.