feature: Thread improvements, round 1 #74
No reviewers
Labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: cadence/out-of-your-element#74
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "Guzio/out-of-your-element:mergable-fr-fr"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This PR contains the 1st round of improvements to thread UX
As promised some time ago in OOYE's Matrix space.
Some note about terminology: Over the course of this description, I'll refer to threads that appear on Discord as attached to a message as „attached” or „branched” (tho I tried to use only the 2nd term in the code itself), whereas threads that were created using the button as „standalone”, „detached” or „headless”.
To remind everyone, the conversation that resulted in this PR, went more-or-less like this:
Identified issues:
The discussion begun when someone pointed out a possible thread UX improvement: icon change.
My reply:
I can think of one more: Create „dummy” threads on Matrix side and handle in-Matrix thread creation.
In the next message, an explanation why and what would they do:
The current OOYE thread implementation has 2 issues:
In the next message, an elaboration on point 1:
First issue, elaborated: When you look at all those thread-rooms in this space, you have no way of telling which one originated from what room/channel. Likewise, when you're in a room, you have no way of telling what threads does it have, unless you explicitly search for /thread or something. The first scenario (not knowing from where a thread came) may impact communication, when a given thread title may mean something different in different contexts. For example, I accidentally joined the "interaction loading" thread, thinking it originated from ooyelement, as a technical discussion about bridging interactions (like "Channel gating" originated from ooyelement, as a technical discussion about, well, channel gating), only to discover that it was someone's error from help-getting-started. Another (now hypothetical) situation I can think of is a person joining a "pics" thread, thinking it's about IRL photos, without knowing that it's a thread in the Minecraft channel/room, and being hit with in-game screenshots instead. This isn't a big deal, but it just looks somewhat awkward to join a thread, realise it's not your thing, and then leave without saying a word. Knowing threads' origins may help to somewhat limit this. The second scenario (not knowing that a given room has threads) is purely a parity thing. On Discord, you can tell it at a glance (you have a convenient button). So you should be able to see that on Matrix, too.
In the next message, an elaboration on point 2:
Second issue is self-explanatory. Just look at help-getting-stalrted. A lot of people create threads for their issues there. But these are Matrix-only threads, so Discord users can't help in them. And - crucially - the bot provides no feedback at all, that a given thread is not gonna be bridged to Discord, so unless you already know about
/thread, you wouldn't have any idea that some people in the community have no way of seeing your messages.Proposed solution (starting with my own message):
I think this could be implemented in stages, from simplest contributions to most complex.
/threadinstead.Note that this message is also kinda a timeline for how I'd implement this. Because I tbh want to work on this right now, given how the exam session is finally over and I have some time.
Some time later, Cadence mentioned a UX concern:
Thread titles. [HINDSIGHT: I had no idea why would this be a problem („Just reuse the thread name from Matrix!”, I thought), but now I know that the problem is that Matrix DOESN'T HAVE thread names. Hence this was, indeed, a UX problem.]
Bonus round - what to do about
/threadwith no args:The discussion begun when someone accidentally ran
/threadwithout args.Ellie noted:
miiiiiiight be a good feature to allow unnamed threads :p
My reply:
I'm already messing around with threads on my fork (for all its worth, the first series of patches is almost ready, I think - just haven't gotten around to PRing it yet [HINDSIGHT: Boy, could I be more wrong! 😅]), so I'll take care of it, if I may.
Some time later:
I can't really think of a good UX for auto-determining thread names when /thread is called without arguments, so I think I'll just make it return a help-usage message instead of an "ugly error".
Cadence disagreed:
thread names can be changed, maybe you could set a placeholder initially and then update the thread name after the next message is sent in it or something idk
either is fine
...And so did Ellie:
I'd just name it "untitled thread", yeah
Out of this, the following were implemented (or not and why):
From the „Proposed solution” message exchange:
/thread”. Basically - instead of a command, you use a button in your client, and the bot responds to that action by doing exactly what you asked for. (Seeing it through that lenses, having the bot tell you „that's not how you do threads!!1!” would indeed be „an extra stage of friction for no obvious reason”: you just created a thread, why do you have to create it again, but differently?) But given that there is no such button, the equation is now somewhat different: you didn't create a thread, you already sent a message in it. You already did „the bad thing”, due to how threads look somewhat broken on various clients and on Discord (as stated above). Now, the role would be to educate you to not do it again - hence the message whose role is to „annoy you into compliance” is sent. Crucially, that message isn't just annoying, but also informative. It teaches you how to use/thread. The idea is that after 2 or 3 times, people will naturally resort to/threadright away, without the intermediate step of sending an in-thread message. If we were to auto-create a thread instead, then we'd teach the users to rely on that mechanism, which would lead to them always annoying Discord/Cinny/ElementX/Commet/etc. users with at least a single message (the one that they used for thread creation). This 1000% must be revisited if Matrix ever introduces a system for creating „standalone” threads (like you can have on Discord), but until then, I think that this annoy-but-also-educate approach is the better choice. I am, however, open for discussion.From the „Identified issues” message exchange:
/thread” problem is fixed by the fact that now there's a message that tells you that you're doing threads wrong.From the „Bonus round”:
I settled on a middle-ground solution:
/threadis ran standalone, ie. in such a way that the thread would've branched from/threaditself (more on that later), it shows the help message.I decided to keep the „show help message” logic on standalone because:
/ooyehelpcommand, or something like that, could be added - but for now, there are only 2 cmds, so it would be silly to make a whole help system for just that).The „dummy thread” system and branching explained:
So... It's time to address the „more on that later” from paragraphs above. Basically - the previous UX was „you run
/thread <title>(no matter where/how) -> the bot creates a new standalone thread on Discord and sends a notification on the main room timeline” or „someone creates any thread (headless or otherwise) -> the bot bridges a Matrix room to it and sends a notification on the main room timeline”. Now, the system is more sophisticated:For threads that originated on Discord...
m.notice, to have that tiny notice-bar like there's on Discord, and then it could branch the thread from it with its standardm.emote? Maaaayyybeeee, but then clients that don't support threads well will be hit with 2 messages that basically say the same thing, just in 2 different styles. Not ideal. Once again, this is something that should be revisited if Matrix ever introduces a system for creating „standalone” threads.For threads that originated on Matrix (as in:
/threadwas ran).../thread [name]was a reply, then the new thread is branched (on both Discord and Matrix - this is the same for all other branching cases from now on, so I won't be repeating that) from the thread to which/threadwas replying. You can - but not have to - specify a name. This ensures parity with/thread <name>was ran on the main room timeline, and not as a reply, then a thread with the specified name is created on Discord, and it will branch from the very message that was this command. I'm aware that branching from a command may look somewhat goofy, but by branching from anything, we get all the discoverability benefits that were specified above in „For threads that originated on Discord...” - and if we have to branch from something, then the command that created the thread is the least atrocious choice./threadwas ran on the main room timeline, and not as a reply, then (as established) help is shown./thread [name]was ran in a thread - see below...For threads that originated on Matrix (as in: were created there)...
Well, we already know what happens! Because you can just create a thread on Matrix - you must send a message - the bot will scream at you for sending messages in threads, which looks bad for both some Matrix users and on Discord. The devil is in the details - you're directed to either:
/threadin that thread. No arguments, no nothing (tho you can specify a thread name, it's just not needed). If you do that, the created thread will act as if you ran/threadas a reply to the message from which the Matrix thread branched from. It doesn't matter whether it's the 1st message in the thread or 1000th; doesn't matter if it's a reply or not. If ran in a Matrix thread - branch the created Discord thread from the message from which the Matrix thread already is branching. Also, the message tells you that there are other ways to run/threadand you can discover them by running/thread(without args; not as a reply) on the main room timeline./threadabove would've branched - you're simply linked to it and directed to talk there.It also handles errors!
/threadwould've created branched from a message that already has a thread-room branched to a thread on Discord, it directs you there instead of erroring out.Auxiliary changes / The structure of this PR
I tried to structure my changes in such a way that no commit squashing is needed when merging-in this PR, by using „standard” commit names (ie. in present tense and short) and also grouping multiple changes into „logical” squash-commits on my end. This is the result of all of that:
1st commits:
Changes to
.gitignore, as per Cadence's request. Not much to document here, all of this was basically verbatim instruction-following from OOYE's space/server.2nd and 3rd commits:
These were mostly focused on type changes. This was before I realised that squashing is a thing (I'm sorry, I'm not exactly all that proficient in Git; so far having only used it for mostly personal projects where a clean tree isn't all that necessary), so these are unfortunately spread across 2 commits. Anyway, these changes were fairly minor, hence the „Small” in the 2nd commit title - one of them was to simply add a couple of
@ts-ignores in places where TS was a bit silly, and the other 2 were:Support of the
m.threadsub-type ofm.relates_to:Implemented entirely within the 3rd commit. Notably, said commit included the following blunders:
m.threadasm.replacein the commit message. This makes no sense -m.replacealready was supported. The line that says „To support "rel_type":"m.replace" relation-events (added "m.replace" option to existing key "rel_type" and a new "is_falling_back" key)” should actually be „To support "rel_type":"m.thread" relation-events (added "m.thread" option to existing key "rel_type" and a new "is_falling_back" key)” but I must've been to sleepy-or-something to notice.boolean„bool” in a.d.tsfile. And TS didn't flag it (.d.tses treat all type errors as implicitanys). This was later corrected in the next commit, but the damage's been done - there is now an embarrassing mistake in the commit log. Whoops! 😅Overall, between that broken commit message, and the fact that type changes are spread over 2 commits for no good reason - it makes me think that the best course of action would be to squash those two particular commits (to prevent needless commit-spam and to override that wonked message on the 3rd commit) while preserving the 1st and last as a distinct entry. I'm not sure, however, if this is possible. If it is - great! If not - well, I'm worried that it may jeopardize this PR's ability to get merged without complete squashing, even tho I tried to organise the commits nicely (except this blunder). Tho I guess it's on me for writing so long commit descriptions that I get attached to them; no sane person puts so much stuff in there as my serial-yapping ass.
Narrowing-down of of
guard()'s type definition.I'll admit, I had no reason to touch this. But I did, anyway. It was in the early „messing around, getting a feel for the codebase” stage and I'll admit that I may have gone a bit off the rails. At any rate,
typewas restricted to a{string}andfnwas restricted to a function that takes in 2anys and returns anany. This is a huge improvement over „everything is anany, good luck”, but I nevertheless tried to restrict it further (ideally, limit what parameters that passed-in function has). However, I ran into some difficulties with that, so I left it asany.In the last commit, I was finally able to get it to behave as I wanted (mark the
eventparameter as an event-type), but that came at a trade-off of making the type signature look pretty atrocious (I had to manually mark any type-error-inducing keys asany). Also, it makes the type signature be somewhat detached from reality:guard()doesn't really care about the exact type ofevent- all it needs is for it to have aroom_idkey and any other key usually associated with Matrix events isn't strictly required.Basically, the point I'm getting at is: „What if restricting the type of
eventis a bad idea? Maybe I should've made it as broad as possible (make sure that it's an object with aroom_idkey, and don't care otherwise)?”. ...Or, you know, I shouldn't've touched a random type that's largely unrelated to my PR. That's also an option. At any rate, I'm open for feedback.I then did some merges...
I'm not even counting those as real commits, as I'm pretty sure neither does GitDab.
Finally - commit 4:
This is where all the juicy stuff happened. That's the one that contains all the thread UX improvements that make up the core of this PR. Beyond that, it also includes:
guard()), as well as fixes tod2m thread-to-announcement-converter'sconst context.matrix-command-handler(please refer to the commit message for a detailed explanation)9b3707baa1might be to blame (I merged all new commits into my branch as they appeared on origin, to prevent any potential merge conflicts in the future - so that's how I ended up testing their changes) - it seems like they did a pretty big overhaul to the sticker subsystem, but changes to any test files are notably missing.* To better reflect reality ("m.in_reply_to" will not always be present - it's not (always?) found on "rel_type":"m.replace" relation-events) * To support "rel_type":"m.replace" relation-events (added "m.replace" option to existing key "rel_type" and a new "is_falling_back" key) AFFECTED TYPES: M_Room_Message, M_Room_Message_File, M_Room_Message_Encrypted_File BREAKS: Nothing, as .d.ts files don't affect buisness logic. In terms of lint errors: Marking "m.in_reply_to" as optional is indeed technically a "breaking change" (TypeScript may complain about „is probably undefined” in places where it didn't before), but from early "testing" (ie. looking at VSCode's errors tab), it doesn't seem like anything broke, as no file that imports any of those 3 types (Or their Outer_ counterparts) has „lit up” with errors (unless I missed something). There was one type error found in m2d/converters/event-to-message.js, at line 1009, but that seemed unrelated to types.d.ts - nevertheless, that error was also corrected in this commit, by adding proper type annotations somewhere else in the affected file.@Guzio you can add the
WIP:prefix to the PR which auto-flags it as work in progress :)feat.: Thread improvements, round 1to WIP: feat.: Thread improvements, round 1@beanie wrote in #74 (comment):
I know - GitDab told me. I just decided it wouldn't be necessary because „surely, I'll finish that message in a few minutes; noone will ever even notice that this was WIP for a short while”.
...Cue-in me returning from uni, taking multiple-hour-long nap, and also taking much longer then anticipated to write the message. 😅
Marked this as WIP for now, will hopefully un-mark it over the course of the next 15 minutes.
WIP: feat.: Thread improvements, round 1to feature: Thread improvements, round 1took a tiny-bit longer than anticipated lol
But I'm done here, yaaayyy! Now awaiting feedback...
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.