# I am the Watcher. I am your guide through this vast new twtiverse.
# 
# Usage:
#     https://watcher.sour.is/api/plain/users              View list of users and latest twt date.
#     https://watcher.sour.is/api/plain/twt                View all twts.
#     https://watcher.sour.is/api/plain/mentions?uri=:uri  View all mentions for uri.
#     https://watcher.sour.is/api/plain/conv/:hash         View all twts for a conversation subject.
# 
# Options:
#     uri     Filter to show a specific users twts.
#     offset  Start index for quey.
#     limit   Count of items to return (going back in time).
# 
# twt range = 1 15156
# self = https://watcher.sour.is?uri=https://www.uninformativ.de/twtxt.txt&offset=13075
# next = https://watcher.sour.is?uri=https://www.uninformativ.de/twtxt.txt&offset=13175
# prev = https://watcher.sour.is?uri=https://www.uninformativ.de/twtxt.txt&offset=12975
@prologic As per https://dev.twtxt.net/doc/metadataextension.html, it’s the first URL. 😅 I think using the *last* URL was one of the new proposals. Or has that already been implemented? 🤯
@prologic As per https://dev.twtxt.net/doc/metadataextension.html, it’s the first URL. 😅 I think using the *last* URL was one of the new proposals. Or has that already been implemented? 🤯
@prologic I don’t understand that behavior either, but I’m afraid stuff like that is always going to happen. (I think it already happened once in the past?) As long as we have an easy way to ignore such a feed and remove it from our databases, we should be fine. 🤔
@prologic I don’t understand that behavior either, but I’m afraid stuff like that is always going to happen. (I think it already happened once in the past?) As long as we have an easy way to ignore such a feed and remove it from our databases, we should be fine. 🤔
@prologic I don’t understand that behavior either, but I’m afraid stuff like that is always going to happen. (I think it already happened once in the past?) As long as we have an easy way to ignore such a feed and remove it from our databases, we should be fine. 🤔
@prologic I don’t understand that behavior either, but I’m afraid stuff like that is always going to happen. (I think it already happened once in the past?) As long as we have an easy way to ignore such a feed and remove it from our databases, we should be fine. 🤔
@sorenpeter As a *replacement*, it should be doable. But it won’t work *together* with hashes. So the community has to agree on one or the other first.
@sorenpeter As a *replacement*, it should be doable. But it won’t work *together* with hashes. So the community has to agree on one or the other first.
@sorenpeter As a *replacement*, it should be doable. But it won’t work *together* with hashes. So the community has to agree on one or the other first.
@sorenpeter As a *replacement*, it should be doable. But it won’t work *together* with hashes. So the community has to agree on one or the other first.
@prologic I’m not sure this isn’t a bug. The feed’s URL must have changed at some point but Yarn is still using the old URL for hashing. And it’s *inconsistent* now:

curl -s -H 'Accept: application/json' https://twtxt.net/twt/mowsvgq

This gets you a twt which, when hashed again *now* using all the information from that API reply, does not yield the hash mowsvgq but bjs6aua.

But when you use the URL http://prismdragon.net/twtxt.txt for hashing instead of http://twtxt.prismdragon.net/twtxt.txt, it’s mowsvgq.

So I would expect Yarn to *either* know about mowsvgq (showing the new URL http://prismdragon.net/twtxt.txt) *or* about bjs6aua (showing the old URL http://twtxt.prismdragon.net/twtxt.txt). But not mowsvgq with the old URL. 😅

I don’t see how the second # url = metadata field is relevant here. 🤔
@prologic I’m not sure this isn’t a bug. The feed’s URL must have changed at some point but Yarn is still using the old URL for hashing. And it’s *inconsistent* now:

curl -s -H 'Accept: application/json' https://twtxt.net/twt/mowsvgq

This gets you a twt which, when hashed again *now* using all the information from that API reply, does not yield the hash mowsvgq but bjs6aua.

But when you use the URL http://prismdragon.net/twtxt.txt for hashing instead of http://twtxt.prismdragon.net/twtxt.txt, it’s mowsvgq.

So I would expect Yarn to *either* know about mowsvgq (showing the new URL http://prismdragon.net/twtxt.txt) *or* about bjs6aua (showing the old URL http://twtxt.prismdragon.net/twtxt.txt). But not mowsvgq with the old URL. 😅

I don’t see how the second # url = metadata field is relevant here. 🤔
@prologic I’m not sure this isn’t a bug. The feed’s URL must have changed at some point but Yarn is still using the old URL for hashing. And it’s *inconsistent* now:

curl -s -H 'Accept: application/json' https://twtxt.net/twt/mowsvgq

This gets you a twt which, when hashed again *now* using all the information from that API reply, does not yield the hash mowsvgq but bjs6aua.

But when you use the URL http://prismdragon.net/twtxt.txt for hashing instead of http://twtxt.prismdragon.net/twtxt.txt, it’s mowsvgq.

So I would expect Yarn to *either* know about mowsvgq (showing the new URL http://prismdragon.net/twtxt.txt) *or* about bjs6aua (showing the old URL http://twtxt.prismdragon.net/twtxt.txt). But not mowsvgq with the old URL. 😅

I don’t see how the second # url = metadata field is relevant here. 🤔
@prologic I’m not sure this isn’t a bug. The feed’s URL must have changed at some point but Yarn is still using the old URL for hashing. And it’s *inconsistent* now:

curl -s -H 'Accept: application/json' https://twtxt.net/twt/mowsvgq

This gets you a twt which, when hashed again *now* using all the information from that API reply, does not yield the hash mowsvgq but bjs6aua.

But when you use the URL http://prismdragon.net/twtxt.txt for hashing instead of http://twtxt.prismdragon.net/twtxt.txt, it’s mowsvgq.

So I would expect Yarn to *either* know about mowsvgq (showing the new URL http://prismdragon.net/twtxt.txt) *or* about bjs6aua (showing the old URL http://twtxt.prismdragon.net/twtxt.txt). But not mowsvgq with the old URL. 😅

I don’t see how the second # url = metadata field is relevant here. 🤔
@bender That broken mention *might* have the same cause as the other issue you mentioned. Changed URL and all that. 🤔
@bender That broken mention *might* have the same cause as the other issue you mentioned. Changed URL and all that. 🤔
@bender That broken mention *might* have the same cause as the other issue you mentioned. Changed URL and all that. 🤔
@bender That broken mention *might* have the same cause as the other issue you mentioned. Changed URL and all that. 🤔
There might be a bug in jenny that causes it to re-fetch archived feeds on every run. Probably happens on edits/deletes. I’ll look into it soon-ish.
There might be a bug in jenny that causes it to re-fetch archived feeds on every run. Probably happens on edits/deletes. I’ll look into it soon-ish.
There might be a bug in jenny that causes it to re-fetch archived feeds on every run. Probably happens on edits/deletes. I’ll look into it soon-ish.
There might be a bug in jenny that causes it to re-fetch archived feeds on every run. Probably happens on edits/deletes. I’ll look into it soon-ish.
@sorenpeter I like that. It pretty much matches what I already had in mind. (The implications of part two of point 0 are obviously controversial and I don’t know if we can ever agree on that. 😅)
@sorenpeter I like that. It pretty much matches what I already had in mind. (The implications of part two of point 0 are obviously controversial and I don’t know if we can ever agree on that. 😅)
@sorenpeter I like that. It pretty much matches what I already had in mind. (The implications of part two of point 0 are obviously controversial and I don’t know if we can ever agree on that. 😅)
@sorenpeter I like that. It pretty much matches what I already had in mind. (The implications of part two of point 0 are obviously controversial and I don’t know if we can ever agree on that. 😅)
@quark Oh dear. 😅 I’m one of the first around here, though. 🤪
@quark Oh dear. 😅 I’m one of the first around here, though. 🤪
@quark Oh dear. 😅 I’m one of the first around here, though. 🤪
@quark Oh dear. 😅 I’m one of the first around here, though. 🤪
Time to put up the christmas decoration, I guess. 🤷✨
Time to put up the christmas decoration, I guess. 🤷✨
Time to put up the christmas decoration, I guess. 🤷✨
Time to put up the christmas decoration, I guess. 🤷✨
@quark At some point, Yarn must have fetched the feed and used the correct URL (otherwise it wouldn’t have gotten the correct hash). And at that point, it should have updated the uri field for this twt in its database, I guess? Disclaimer: I know nothing about the internals of Yarn. 😅
@quark At some point, Yarn must have fetched the feed and used the correct URL (otherwise it wouldn’t have gotten the correct hash). And at that point, it should have updated the uri field for this twt in its database, I guess? Disclaimer: I know nothing about the internals of Yarn. 😅
@quark At some point, Yarn must have fetched the feed and used the correct URL (otherwise it wouldn’t have gotten the correct hash). And at that point, it should have updated the uri field for this twt in its database, I guess? Disclaimer: I know nothing about the internals of Yarn. 😅
@quark At some point, Yarn must have fetched the feed and used the correct URL (otherwise it wouldn’t have gotten the correct hash). And at that point, it should have updated the uri field for this twt in its database, I guess? Disclaimer: I know nothing about the internals of Yarn. 😅
@quark That’s confusion on Yarn’s part, I’d say.

Yarn’s API says that twt comes from the URL http://twtxt.prismdragon.net/twtxt.txt – but when using that URL for hashing, I get the hash bjs6aua instead of mowsvgq. That’s not the correct hash, so jenny says the twt could not be found.

Inspecting the feed using jenny -D … yields the correct hash. When looking at the raw feed, we can see:


# nick = gallowsgryph
# description = Green living and permaculture enthusiast, writer, otherkin, weird.
# url = http://prismdragon.net/twtxt.txt
# url = https://dreamwidth.org/gallowsgryph/
# avatar = http://prismdragon.net/img/gallows.png#20241025


So it’s a different URL. When I use http://prismdragon.net/twtxt.txt for hashing, I get the correct hash.
@quark That’s confusion on Yarn’s part, I’d say.

Yarn’s API says that twt comes from the URL http://twtxt.prismdragon.net/twtxt.txt – but when using that URL for hashing, I get the hash bjs6aua instead of mowsvgq. That’s not the correct hash, so jenny says the twt could not be found.

Inspecting the feed using jenny -D … yields the correct hash. When looking at the raw feed, we can see:


# nick = gallowsgryph
# description = Green living and permaculture enthusiast, writer, otherkin, weird.
# url = http://prismdragon.net/twtxt.txt
# url = https://dreamwidth.org/gallowsgryph/
# avatar = http://prismdragon.net/img/gallows.png#20241025


So it’s a different URL. When I use http://prismdragon.net/twtxt.txt for hashing, I get the correct hash.
@quark That’s confusion on Yarn’s part, I’d say.

Yarn’s API says that twt comes from the URL http://twtxt.prismdragon.net/twtxt.txt – but when using that URL for hashing, I get the hash bjs6aua instead of mowsvgq. That’s not the correct hash, so jenny says the twt could not be found.

Inspecting the feed using jenny -D … yields the correct hash. When looking at the raw feed, we can see:


# nick = gallowsgryph
# description = Green living and permaculture enthusiast, writer, otherkin, weird.
# url = http://prismdragon.net/twtxt.txt
# url = https://dreamwidth.org/gallowsgryph/
# avatar = http://prismdragon.net/img/gallows.png#20241025


So it’s a different URL. When I use http://prismdragon.net/twtxt.txt for hashing, I get the correct hash.
@quark That’s confusion on Yarn’s part, I’d say.

Yarn’s API says that twt comes from the URL http://twtxt.prismdragon.net/twtxt.txt – but when using that URL for hashing, I get the hash bjs6aua instead of mowsvgq. That’s not the correct hash, so jenny says the twt could not be found.

Inspecting the feed using jenny -D … yields the correct hash. When looking at the raw feed, we can see:


# nick = gallowsgryph
# description = Green living and permaculture enthusiast, writer, otherkin, weird.
# url = http://prismdragon.net/twtxt.txt
# url = https://dreamwidth.org/gallowsgryph/
# avatar = http://prismdragon.net/img/gallows.png#20241025


So it’s a different URL. When I use http://prismdragon.net/twtxt.txt for hashing, I get the correct hash.
I think it could be yarnd’s avatar caching. That’s why my avatar field has a #20240102 at the end: To trick yarnd into reloading it.
I think it could be yarnd’s avatar caching. That’s why my avatar field has a #20240102 at the end: To trick yarnd into reloading it.
I think it could be yarnd’s avatar caching. That’s why my avatar field has a #20240102 at the end: To trick yarnd into reloading it.
I think it could be yarnd’s avatar caching. That’s why my avatar field has a #20240102 at the end: To trick yarnd into reloading it.
My first PC as a kid had an amber monitor, so this feels right at home:

https://www.youtube.com/watch?v=PeuH0YmWkI4

💛
My first PC as a kid had an amber monitor, so this feels right at home:

https://www.youtube.com/watch?v=PeuH0YmWkI4

💛
My first PC as a kid had an amber monitor, so this feels right at home:

https://www.youtube.com/watch?v=PeuH0YmWkI4

💛
My first PC as a kid had an amber monitor, so this feels right at home:

https://www.youtube.com/watch?v=PeuH0YmWkI4

💛
@aelaraji As long as you’re having fun, you’re doing the right thing. 😅 (Kind of. 😂)
@aelaraji As long as you’re having fun, you’re doing the right thing. 😅 (Kind of. 😂)
@aelaraji As long as you’re having fun, you’re doing the right thing. 😅 (Kind of. 😂)
@aelaraji As long as you’re having fun, you’re doing the right thing. 😅 (Kind of. 😂)
@lyse Now I feel like an ant. 🐜 (Imagine how ants must feel when they see this! 🤯)
@lyse Now I feel like an ant. 🐜 (Imagine how ants must feel when they see this! 🤯)
@lyse Now I feel like an ant. 🐜 (Imagine how ants must feel when they see this! 🤯)
@lyse Now I feel like an ant. 🐜 (Imagine how ants must feel when they see this! 🤯)
@bender Consider me stumped! 😅 Yeah, I can imagine this not being super fast. 🥴
@bender Consider me stumped! 😅 Yeah, I can imagine this not being super fast. 🥴
@bender Consider me stumped! 😅 Yeah, I can imagine this not being super fast. 🥴
@bender Consider me stumped! 😅 Yeah, I can imagine this not being super fast. 🥴
I *guess* crashing the program with a SIGBUS is intentional. Here’s a blog post that describes this exact thing when running binaries off of NFS:

https://rachelbythebay.com/w/2018/03/15/core/

It’s just that this also happens locally nowadays and, thus, much easier and more often (I bet few people run programs via NFS these days). 🫤

Not a fan of this. (Time will tell if I have the energy to discuss this on the Linux kernel mailing list.)
I *guess* crashing the program with a SIGBUS is intentional. Here’s a blog post that describes this exact thing when running binaries off of NFS:

https://rachelbythebay.com/w/2018/03/15/core/

It’s just that this also happens locally nowadays and, thus, much easier and more often (I bet few people run programs via NFS these days). 🫤

Not a fan of this. (Time will tell if I have the energy to discuss this on the Linux kernel mailing list.)
I *guess* crashing the program with a SIGBUS is intentional. Here’s a blog post that describes this exact thing when running binaries off of NFS:

https://rachelbythebay.com/w/2018/03/15/core/

It’s just that this also happens locally nowadays and, thus, much easier and more often (I bet few people run programs via NFS these days). 🫤

Not a fan of this. (Time will tell if I have the energy to discuss this on the Linux kernel mailing list.)
I *guess* crashing the program with a SIGBUS is intentional. Here’s a blog post that describes this exact thing when running binaries off of NFS:

https://rachelbythebay.com/w/2018/03/15/core/

It’s just that this also happens locally nowadays and, thus, much easier and more often (I bet few people run programs via NFS these days). 🫤

Not a fan of this. (Time will tell if I have the energy to discuss this on the Linux kernel mailing list.)
Just tried it: It did indeed crash my Wayland session and, since Wayland compositors are sensitive and critical, it froze all input devices. Only way to recover was to SSH into that machine and reboot it. 🤦
Just tried it: It did indeed crash my Wayland session and, since Wayland compositors are sensitive and critical, it froze all input devices. Only way to recover was to SSH into that machine and reboot it. 🤦
Just tried it: It did indeed crash my Wayland session and, since Wayland compositors are sensitive and critical, it froze all input devices. Only way to recover was to SSH into that machine and reboot it. 🤦
Just tried it: It did indeed crash my Wayland session and, since Wayland compositors are sensitive and critical, it froze all input devices. Only way to recover was to SSH into that machine and reboot it. 🤦
Not sure I’m happy with this.

Take this, for example:

https://codeberg.org/dwl/dwl/src/branch/main/Makefile#L64

The install target of a Wayland compositor uses cp to copy the compiled binary to your bin directory. So, as of Linux 6.11, when you recompile this compositor and reinstall it, it will crash your entire Wayland session. 🧟💀🧟

One way to avoid this crash is to use install instead of cp. install calls unlink() before copying the data, thus avoiding this situation entirely. Not all Makefiles do that, though.
Not sure I’m happy with this.

Take this, for example:

https://codeberg.org/dwl/dwl/src/branch/main/Makefile#L64

The install target of a Wayland compositor uses cp to copy the compiled binary to your bin directory. So, as of Linux 6.11, when you recompile this compositor and reinstall it, it will crash your entire Wayland session. 🧟💀🧟

One way to avoid this crash is to use install instead of cp. install calls unlink() before copying the data, thus avoiding this situation entirely. Not all Makefiles do that, though.
Not sure I’m happy with this.

Take this, for example:

https://codeberg.org/dwl/dwl/src/branch/main/Makefile#L64

The install target of a Wayland compositor uses cp to copy the compiled binary to your bin directory. So, as of Linux 6.11, when you recompile this compositor and reinstall it, it will crash your entire Wayland session. 🧟💀🧟

One way to avoid this crash is to use install instead of cp. install calls unlink() before copying the data, thus avoiding this situation entirely. Not all Makefiles do that, though.
Not sure I’m happy with this.

Take this, for example:

https://codeberg.org/dwl/dwl/src/branch/main/Makefile#L64

The install target of a Wayland compositor uses cp to copy the compiled binary to your bin directory. So, as of Linux 6.11, when you recompile this compositor and reinstall it, it will crash your entire Wayland session. 🧟💀🧟

One way to avoid this crash is to use install instead of cp. install calls unlink() before copying the data, thus avoiding this situation entirely. Not all Makefiles do that, though.
It’s intentional:

- https://lwn.net/Articles/982034/
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=2a010c412853

> Matching the behavior of most Unix systems, the Linux kernel has traditionally prevented writes to an executable file that is in use by a process somewhere in the system; that is the source of the "text file busy" message that some readers may have seen. This restriction is intended to prevent unpleasant surprises in running programs. Kernel developers have been phasing out this restriction for a few years, mostly because it does not really protect anything. As of 6.11, the kernel will no longer prevent writes to busy executable files; see this changelog for a lot more details.

Hm.
It’s intentional:

- https://lwn.net/Articles/982034/
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=2a010c412853

> Matching the behavior of most Unix systems, the Linux kernel has traditionally prevented writes to an executable file that is in use by a process somewhere in the system; that is the source of the "text file busy" message that some readers may have seen. This restriction is intended to prevent unpleasant surprises in running programs. Kernel developers have been phasing out this restriction for a few years, mostly because it does not really protect anything. As of 6.11, the kernel will no longer prevent writes to busy executable files; see this changelog for a lot more details.

Hm.
It’s intentional:

- https://lwn.net/Articles/982034/
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=2a010c412853

> Matching the behavior of most Unix systems, the Linux kernel has traditionally prevented writes to an executable file that is in use by a process somewhere in the system; that is the source of the "text file busy" message that some readers may have seen. This restriction is intended to prevent unpleasant surprises in running programs. Kernel developers have been phasing out this restriction for a few years, mostly because it does not really protect anything. As of 6.11, the kernel will no longer prevent writes to busy executable files; see this changelog for a lot more details.

Hm.
It’s intentional:

- https://lwn.net/Articles/982034/
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=2a010c412853

> Matching the behavior of most Unix systems, the Linux kernel has traditionally prevented writes to an executable file that is in use by a process somewhere in the system; that is the source of the "text file busy" message that some readers may have seen. This restriction is intended to prevent unpleasant surprises in running programs. Kernel developers have been phasing out this restriction for a few years, mostly because it does not really protect anything. As of 6.11, the kernel will no longer prevent writes to busy executable files; see this changelog for a lot more details.

Hm.
This changed between linux-6.10.10.arch1-1 and linux-6.11.arch1-1 … Don’t have the time now to do a proper bisect. 🫤
This changed between linux-6.10.10.arch1-1 and linux-6.11.arch1-1 … Don’t have the time now to do a proper bisect. 🫤
This changed between linux-6.10.10.arch1-1 and linux-6.11.arch1-1 … Don’t have the time now to do a proper bisect. 🫤
This changed between linux-6.10.10.arch1-1 and linux-6.11.arch1-1 … Don’t have the time now to do a proper bisect. 🫤
@prologic I haven’t narrowed it down yet. I’m running 6.11.4 at the moment.
@prologic I haven’t narrowed it down yet. I’m running 6.11.4 at the moment.
@prologic I haven’t narrowed it down yet. I’m running 6.11.4 at the moment.
@prologic I haven’t narrowed it down yet. I’m running 6.11.4 at the moment.
When you try to change a file that’s currently running, it used to say text file busy. Example:

First terminal:

$ cc -Wall -Wextra -o test test.c
$ cp test run
$ ./run

Second terminal:

$ cp test run
cp: cannot create regular file 'run': Text file busy

But on my machines today, it *crashes* the running program. 🤨 As soon as I run the cp, I get a coredump:

$ ./run
... time passes, I do "cp test run" in a second terminal ...
Bus error (core dumped)

How odd. Another mystery to solve …
When you try to change a file that’s currently running, it used to say text file busy. Example:

First terminal:

$ cc -Wall -Wextra -o test test.c
$ cp test run
$ ./run

Second terminal:

$ cp test run
cp: cannot create regular file 'run': Text file busy

But on my machines today, it *crashes* the running program. 🤨 As soon as I run the cp, I get a coredump:

$ ./run
... time passes, I do "cp test run" in a second terminal ...
Bus error (core dumped)

How odd. Another mystery to solve …
When you try to change a file that’s currently running, it used to say text file busy. Example:

First terminal:

$ cc -Wall -Wextra -o test test.c
$ cp test run
$ ./run

Second terminal:

$ cp test run
cp: cannot create regular file 'run': Text file busy

But on my machines today, it *crashes* the running program. 🤨 As soon as I run the cp, I get a coredump:

$ ./run
... time passes, I do "cp test run" in a second terminal ...
Bus error (core dumped)

How odd. Another mystery to solve …
When you try to change a file that’s currently running, it used to say text file busy. Example:

First terminal:

$ cc -Wall -Wextra -o test test.c
$ cp test run
$ ./run

Second terminal:

$ cp test run
cp: cannot create regular file 'run': Text file busy

But on my machines today, it *crashes* the running program. 🤨 As soon as I run the cp, I get a coredump:

$ ./run
... time passes, I do "cp test run" in a second terminal ...
Bus error (core dumped)

How odd. Another mystery to solve …
You can pry OpenBSD’s httpd + acme-client from my cold dead hands. Set it up years ago and it never failed (unlike all the fancy stuff we tried at work).
You can pry OpenBSD’s httpd + acme-client from my cold dead hands. Set it up years ago and it never failed (unlike all the fancy stuff we tried at work).
You can pry OpenBSD’s httpd + acme-client from my cold dead hands. Set it up years ago and it never failed (unlike all the fancy stuff we tried at work).
You can pry OpenBSD’s httpd + acme-client from my cold dead hands. Set it up years ago and it never failed (unlike all the fancy stuff we tried at work).
@lyse We always had the famous Kaffee und Kuchen around 4pm or even a bit later. 😅 Guess we’re not certified Germans. 😢
@lyse We always had the famous Kaffee und Kuchen around 4pm or even a bit later. 😅 Guess we’re not certified Germans. 😢
@lyse We always had the famous Kaffee und Kuchen around 4pm or even a bit later. 😅 Guess we’re not certified Germans. 😢
@lyse We always had the famous Kaffee und Kuchen around 4pm or even a bit later. 😅 Guess we’re not certified Germans. 😢
This is so funny – and very true. 😃 The ancient German art of complaining: https://youtu.be/FcFmVfAg8V0?t=720
This is so funny – and very true. 😃 The ancient German art of complaining: https://youtu.be/FcFmVfAg8V0?t=720