@prologic@twtxt.net Even without the gamification and AI, I donāt host anything on GitHub. Just the fact that itās owned by Microsoft spells trouble, in my opinion.
I still have an account, for occasionally contributing to a few specific projects, since a lot of developers wouldnāt know what to do with a patchset, and since many users of GitHub/Lab and Codeberg use send-only email aliases, so git send-email probably wouldnāt work.
Hmm ā¦
My project pages have this āDownloadsā section and I offer tarballs for each release:
https://movq.de/git/jenny/#downloads
I added this many years ago because an OpenBSD dev asked for it (but the project in question didnāt end up in their ports after all).
What are your thoughts on this? Is it necessary to offer tarballs?
I noticed that some distributions have already moved away from using tarballs and instead always clone from Git. š¤
jenny stuff aside, I received zero bug reports or code contributions since leaving GitHub in 2018.
@prologic@twtxt.net Oh, yes, just for clarity: This wasnāt a āpro GitHubā argument. š I wonāt go back there (or to another āforgeā).
I think weāre totally on the same page: Decentralized Git hosting is good, but we havenāt solved the ācollaborationā part yet. Or rather: We have to modernize it. The tools are already there, but (many) people donāt want to use them and think theyāre clunky.
Deleted some old no longer used pixelart from my galley and added this as an alternative version, of the sprite one.

@movq@www.uninformativ.de Oh, thatās actually quite a bit. I didnāt expect that. You definitely have useful things. :-)
Exactly, I now also send links to my fork. :-D
jenny stuff aside, I received zero bug reports or code contributions since leaving GitHub in 2018.
How many tickets and merge requests did you get before moving to your own server?
Hm, these were probably some of the more active projects:
- https://github.com/vain/lariza/issues?q=is%3Aissue%20state%3Aclosed
- https://github.com/vain/pdfPres/issues?q=is%3Aissue%20state%3Aclosed
- https://github.com/vain/xiate/issues?q=is%3Aissue%20state%3Aclosed
So itās not a ton of stuff and there are many āissuesā I opened myself, but itās certainly more than these days.
Havenāt used
git send-emaileven once. I definitely have to look into that soon. Wanted to do that for several years. I typically upload the patch to my server and send a link via IRC.
git send-email is good for sending patches to a mailing list (because then the individual patches can be discussed easily), but since those are dying out as well, it loses its appeal. If you want to send patches to a single person, just sending the link to your repo is far easier. š¤
jenny stuff aside, I received zero bug reports or code contributions since leaving GitHub in 2018.
@movq@www.uninformativ.de Finally, your software is just perfect and finished by now, no need to report non-existing bugs or send in code changes. :-) How many tickets and merge requests did you get before moving to your own server?
I have to admit that I use git format-patch so rarely, I always have to pull it up from my shell history. Havenāt used git send-email even once. I definitely have to look into that soon. Wanted to do that for several years. I typically upload the patch to my server and send a link via IRC.
Maybe I was just very unlucky, but my experience is that you can perfectly ignore people and their work who only do it for the āfameā. Itās almost always been from inferior quality to say the least.
@movq@www.uninformativ.de If only somebody had warned us! Luckily, no TV in this household. :-)
@prologic@twtxt.net Hmmm, I have no idea how to solve that problem. š
Some jenny stuff aside, I received zero bug reports or code contributions since leaving GitHub in 2018.
I thought I had made that super easy, because you can just send me an email ā no sign-up process, nothing. But thatās way too old-school, people donāt know how to use git format-patch (let alone git send-email) and they also donāt understand that they can just send me a link to their forked Git repo (which can be hosted anywhere). Git is super flexible and powerful, but those features are hardly ever used.
Maybe people even need some kind ārewardā, or āfameā. Like those āachievementsā that you can unlock on GitHub. (Something to put in their CV ⦠?)
@movq@www.uninformativ.de Thanks for that. Iād been hearing about this in the context of browser plugins turning computers into residential proxies for web scrapers, but I hadnāt considered that a TV could be used the same way. ā¹ļø
Are you aware of the Residential Proxy pest?
https://spur.us/blog/smart-tv-apps-residential-proxy-sdks
This explains the access patterns that I see in my logs.
Itās funny, because I used to warn years ago that this might happen, and then I lost track of this topic. Now here we are and itās a real issue. š¤¦āāļø
@lyse@lyse.isobeef.org A small portion of people might do the headphone thing because of autism or sensory issues, to isolate them from annoying noises. I used to do that myself. š¤
(1) Why The Oceanās Top Predator Refuses to Hunt Us - YouTube
ā The first half of this documentary is amazing! š® ā The remaining half however is sad and depressing, making me never want to visit another zoo again š¢
Hurray! Finally, the thunderstorm is right over us. The last days we always got skipped. The rain smells so great. :-)
We strolled up our backyard mountain. Visibility wasnāt the best, despite the rain we got yesterday. Oh well, scenery was really beautiful, though.
I found it super funny that we almost overtook a jogger before she turned off to another path right in front of us (yeah, weāve got a smart pace, but this girl was slow as a snail).
š£ ACTION REQUIRED: Hey folks š For those of you whom are using the Twtxt App either via the Hosted option or on your own twtd instnace or via Github/Gitea or any other publishing backend (doesnāt amtter). Please read.
Please open the app and you should be prompted to save your recovery code for your device. This basically is all of your settings, follows, etc in the app itself. This is synced to the Origin everytime you make a change, and also stored on-device. This is what makes it possible to sync your setting across services, move to another device, etc.
Please save a copy of the recovery code somewhere. This is only your only way to recover your settings.
Thank you š
@balloon-fu-sen@tw.fus.f5.si Huh? š§ This hasnāt changed. What has is the default proxy used depending on your publishing backend.
there us a recovery path
@david@daiwei.me Ha, I just noticed that I changed Newsboatās defaults. Right from the factory, new items are just bold, while read ones arenāt. No different colors, white on black. Focused items are bold yellow on blue. No matter the read status. I think thatās why I started to play with the config to differentiate them. Itās been so long ago that I didnāt know anymore I even messed with that.
And yes, youāre right, the red on black is borderline readable. Also, my white on green focus is rather silly. But Iām sooo used to it, I donāt realize how bad the contrast is. To be fair, I donāt spend a lot of time in the lists. Aha, there are new articles, Enter to hit the artice view, read it, press n to immediately jump to the next unread article, rince and repeat, finally Iām back in the article list view.
Default theme, read focused:

Default theme, unread focused:

Lyseās schlimmbesserung, read focused:

Lyseās schlimmbesserung, unread focused:

@prologic@twtxt.net It works! Using it right now! Thank you!!!
@GabesArcade@gabesarcade.com I may be blocking that provider due to abuse from bots using VPN(s) ā What was your last IP?
@david@daiwei.me I had to look these up, horror isnāt my genre at all. :-D No idea what the cool kids use today, but I still have zsh as my interactive shell. For shell scripts, though, I try to stick to POSIX and only resort to bash if really needed or it would be too cumbersome.
@david@daiwei.me Not sure if you only mean the code segments or in general. In theory, a general darker text color for read messages would probably work. The thing is that regular white on black is quite standard. In Newsboat, new articles are red (I opted for yellow here) and read ones white. I found that useful and kinda copied it for tt.
@prologic@twtxt.net @movq@www.uninformativ.de Same with tt, hash v2 has to be used right from the epoch onward. (And now replying to a message with a timestamp before the epoch still results in a v1 hash.)
@prologic@twtxt.net Thatās more-or-less it. If I could do it without Codeberg (just using SCP, SSHFS, or something similar), that would be even better, but probably out-of-scope for the project.
My thinking behind how twtxt.app worked was that I could give it access to a repo, in order to add to my twtxt.txt, but that the alternative feed URL would then be presented to other users instead of the Codeberg URL; unless, of course, this is already part of the twtxt metadata.
In short, the feed URL would just be a dummy.
@prologic@twtxt.net No. After the cutoff-date, hashes are expected to be v2. End of story.
(In jenny, a twt can only ever have one thing that can be used for threading.)
@dce@hashnix.club So let me get this straight⦠You want to store your feed on a Codeberg repo right? But you want to clone that repo down somewhere else to serve it on a different location. Right? And youād like to use the Twtxt App (https://twtxt.app) to front all of this? Right?
You may have noticed I am not as active as I used to be here. Old @bender@twtxt.net isnāt going anywhere, though! But⦠I am Hyde, and my Jekyll resides at @david@daiwei.me. Jekyll is having his turn more often now, so if you follow my pure, unrestrained evil, you may as well follow my dark desires, hypocrite counterpart. šš¤£
Follow @david@daiwei.me, or I will bend you!
@david@daiwei.me Yes, but then I have to create and maintain an account Iāll never use š¤£
@prologic@twtxt.net I was actually trying to use twtxt.app, with Codeberg as a backend, but I kept getting a 403 error. As for twtd, well, my twtxt is hosted on a pubnix. The people behind Hashnix are really nice, but Iād rather not bother them with software requests if thereās a simpler solution, and I imagine it would be rude of me to run a deamon from my homedir without permission. Could be worth asking, though, unless I decide to write a client myself.
@dce@hashnix.club which version of Twet are you using? I might see if I can get it updated for you.
@david@daiwei.me Those two twtxts were separate replies. However, I am currently using twet, which doesnāt support hash v2.
@movq@www.uninformativ.de The nice thing about properties is that you can compute and cache things on the fly at first attempt and also ensure validation for writing. But like you said, since itās not obvious that reading or writing might do some more things, itās strongly advised to avoid doing expensive stuff disguised as properties.
I reckon the vast majority of property use cases is to provide read-only access. At least that was my impression when I was doing a lot more in Python.
Personally, I think that this just reads a lot nicer:
oink.my_property
oink.my_property = 42
Than:
oink.get_my_property()
oink.set_my_property(42)
Btw, any field access is implemented using method calls. I might be wrong, but I believe thereās always __getattr__ and __setattr__ involved. 8-)
Iāve been coding again, and this time it might actually be useful to other people! https://codeberg.org/hellfire103/golden-retriever
I trip over this in our code at work all the time.
Python has this concept of āpropertiesā:
class Oink:
def __init__(self):
self._foo = 3
@property
def my_property(self):
return self._foo
a = Oink()
print(a.my_property)
my_property() is a method but it can be used as if it were a field.
This can also be used to define a setter:
class Oink:
def __init__(self):
self._foo = 3
@property
def my_property(self):
return self._foo
@my_property.setter
def my_property(self, value):
self._foo = 123 * value
Because, for some reason, Python people donāt like getters and setters. Instead, they hide it behind a property.
The result is, when you read this:
a.my_property = 5
print(a.my_property)
You have no idea that this actually calls a method.
@movq@www.uninformativ.de My grief with Java is that itās sooo verbose. Sure, all the enterprise garbage makes it a hell lot more terrible, but even regular Java feels always so lengthy. And back in the days when I was using it daily, I missed so many convenient things in the stdlib after having experienced Pythonās ābatteries includedā. Not sure if or how recent Java versions caught up.
tt has a "draft" mode right? You didn't publish, then edit over and over did you? š
@prologic@twtxt.net Not sure if this really counts as a draft mode or this is what you had in mind. I just was in the editor for ages and didnāt close it. tt provides an integrated preview for the rendered message in there. It automatically updates every second.
Hereās a screenshot of the compose view with the conversation context on the top to which to reply to, the editor in the middle and the almost-live preview at the bottom, I hope itās big enough: 
But itās not like I hit the āAdd messageā button in the compose view (the one currently selected on the screenshot), see the message in the conversation tree and then come back into the compose view to continue editing. Thereās no edit functionality in tt. Once the message is appended to my twtxt.txt file on disk, all I can do is edit it with vim. The U+2028 line breaks are really annoying to deal with (Iām sure I could do something about that if I spent the time), so I try to avoid that at all costs.
Once new messages have been added to my local file, I then manually upload the file to my server in a separate terminal. Thereās no upload command integrated into tt. Right from my very first message in the beginning, Iāve always done it exactly like that. Iām used to this and it really doesnāt bother me. But I can see that others might not be fans of that at all. I might add an upload mechanism to tt at some point in the future.
I really think I should go back to Java.
Writing programs in Python is so exhausting. I want a compiler and I want static typing. No, linters and type checkers and IDEs are not good enough. Compilers catch way more errors in advance.
Rust is also exhausting. Theyāre constantly adding language features and, at the same time, the runtime library remains tiny and you need 3rd party libraries for everything. Many of those are still at version 0.x (SemVer!) and you canāt rely on anything. Often times, you need the latest Rust nightly compiler.
Go is ⦠I donāt like it. And huge binaries.
I like C as a language, but itās too fragile. I want to have a proper HashMap every now and then.
None of the above have good GUI libraries, at least not on Linux.
And then thereās Java. This is my fractal renderer that I wrote over 17 years ago:
https://movq.de/v/fcd3c4e557/vid-1784121825.mp4
Itās fast. It has a GUI with custom widgets and those werenāt even hard to make. It still works without changing a single line of code. The source code files have timestamps from 2009 and I just noticed that the JAR file Iām using in the video was compiled in 2010.
Java as a language is relatively easy to learn and to master. There are few surprises. The source code organization with packages is good. Java API docs are clear and well written.
The JVM ramp-up times have improved considerably:
https://movq.de/v/e7314e521e/vid-1784121998.mp4
This isnāt like the Dark Ages anymore. Might even be usable for some CLI tools.
The only thing where Java really sucks is anything close-ish to the kernel. Try issuing an ioctl() ⦠I couldnāt have made my TUI framework in Java, but then again, I wouldnāt have needed to because Swing already exists and it just works.
@david@daiwei.me Thatās a good thing. I still use it heavily, but I also realize that it is addictive. This whole idea of getting likes and boosts is horrible. Seeing ānumber goes upā is inherently addictive design, if you ask me. This should never have been added to a Free Platform like Mastodon, and Iām glad that twtxt doesnāt have anything like it.
also, just to clarify, we built the hosted Service as the last lowest rung ladder for non-technical people. I fully expect most technical people will spin up their own publishing backend or use Github or similar so that long-term the ecosystem still remains very much decentralized.
@david@daiwei.me You mean, you mean⦠like mowing down a whole rain forest in a thunderstormās brutal heat? :-?
Show us todayās rain. :-)
@david@collantes.us heads up š that verification code never reached you ā outbound email was broken on my end (my mail relay was rejecting twtxt.net senders š¤¦āāļø). Fixed + deployed now š„³ give the hosted feed another go, itāll land this time š¤
@prologic@twtxt.net You also have to tell us the username!
Hurray, I can now press gg instead of g to go to the top in tt. Much better! :-) Other multi-key combinations are also easily possible now.
I should probably write a real article about this at some point, but here we go. The only downside with my new key binding system is that it breaks tviewās established pattern. Youāve got an InputHandler(), that is implemented using WrapInputHandler(ā¦). It typically then directly implements the switching logic depending on the key press. Something like this:
func (w *Widget) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
// WrapInputHandler allows for intercepting key events with SetInputCapture(ā¦)
// from the outside for customization. This handles the default key bindings.
return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
switch event.Key() {
case tcell.KeyRune:
if event.Modifiers() == tcell.ModNone {
switch event.Rune() {
case 'k':
w.scrollUp()
return // we already handled the event, stop processing
case 'j':
w.scrollDown()
return
}
}
}
// We didn't handle the key event. Maybe the parent
// widget knows what to do with it.
if handler := w.parent.InputHandler(); handler != nil {
handler(event, setFocus)
}
})
}
From the outside, you can intercept and either stop or continue the widgetās original key handling with a potentially rewritten key event using SetInputCapture(ā¦):
w := NewWidget()
// customized or additional key bindings
w.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyUp:
// Rewrite the event, so the "cursor up" key is an alias
// for the vim key binding "k", that is handled by the
// wrapped input handler above. (I know, I know, this is a
// completely unrealistic example, why would anyone use
// cursor keys when there are vim key bindings available?!)
return tcell.NewEventKey(tcell.KeyRune, 'k', tcell.ModNone)
case tcell.KeyRune:
if event.Modifiers() == tcell.ModNone {
switch event.Rune() {
case 'q':
app.Stop()
// we already handled the event, do not pass it
// to the wrapped input handler above
return nil
case 'r':
toggleMessageReadStatus()
return nil
}
}
}
// we didn't handle the event, pass it to the wrapped
// input handler above
return event
}
Since they all expect a single key, Iāve noticed that using multiple dedicated KeyBindings of mine on these different levels kinda breaks multi-key handling with common prefixes. The outer-most KeyBinding captures the prefix, but it canāt transfer it to the inner one if not handled by the outer one. At least not without some more (potentially ugly) changes. So, I now have to work with just a single KeyBindings object for the entire widget chain (if it consists of multiple other widgets or the regular input handler and input capture are in the game). The outside needs to register all its key bind customizations or extensions at the same level that the original widget handles its default ones. Doable by exposing the widgetās KeyBindings instance, but not pretty. You always have to keep this in mind.
With the KeyBindings, it will look like that:
type Widget struct {
parent tview.Primitive
// make it available to children or the outside either by
// direct field access or by providing a getter method
KeyBindings *bind.KeyBindings
}
func NewWidget() *Widget {
w := &Widget{KeyBindings: &bind.KeyBindings{}}
w.KeyBindings. // default key bindings
Bind0(bind.KeySequence('k', w.scrollUp).
Bind0(bind.KeySequence('j', w.scrollDown)
return w
}
func (w *Widget) InputHandler() InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
// also note the missing support for focus transfer at the moment
event = w.KeyBindings.Capture(event)
if event == nil {
return
}
if handler := w.parent.InputHandler(); handler != nil {
handler(event, setFocus)
}
}
}
And then from the outside, or in a child widget:
w := NewWidget()
w.KeyBindings. // additional or customized key bindings
Bind1(bind.KeySequence(tcell.KeyUp), func(*tcell.EventKey) *tcell.EventKey {
return tcell.NewEventKey(tcell.KeyRune, 'k', tcell.ModNone)
}).
Bind0(bind.KeySequence('q'), app.Stop).
Bind0(bind.KeySequence('r'), toggleMessageReadStatus)
When directly working with tview primitives that are not part of custom widget implementations, the following works well so far:
textView := tview.NewTextView().
SetWordWrap(true).
SetText("ā¦")
SetScrollable(true)
textView.SetInputCapture((&bind.KeyBindings{}).
Bind0(bind.KeySequence('q'), app.Stop).
Bind1(bind.KeySequence('g', 'g'), func(*tcell.EventKey) *tcell.EventKey {
return tcell.NewEventKey(tcell.KeyHome, 0, tcell.ModNone)
}).
Capture)
I need to sleep on this some more.
Also, writing very long messages like this one is really not all that fun in ttās editor. I should absolutely provide a way to shell out to vim.
(Took me about one and a half hours to compose, holy crap. But not only because of not using vim. Although, that might have saved me a quarter hour or so for sure. Proof-reading this message also uncovered quite a few bugs in my real documentation. So, thatās a big win!) Good night!
@david@daiwei.me The one with www is correct: https://www.uninformativ.de/twtxt.txt is the canonical URL used in the url = metadata field. (For historical reasons, it also works without www and even with http.)
+00:00 vs Z should be treated as equivalent UTC š¤¦āāļø I'll take a look at the timestamp parsing in Yarnd š§
@prologic@twtxt.net For what itās worth, the twt hash extension is specifically modeled after yarndās implementation with all the quirks coming from Goās stdlib: https://twtxt.dev/exts/twt-hash.html#timestamp-format
āAll timezones representing UTC must be formatted using the designated Zulu indicator Z rather than the numeric offsets +00:00 or -00:00. If the timestamp does not explicitly include any timezone information, it must be assumed to be in UTC.ā
@prologic@twtxt.net I really like how these two apps pair-up. Seeing as Iām still tweaking TwtKpr, Iām considering adding support for the same APIs as twtd (so maybe it can be used as another backend for Twtxt.App). š¤
@balloonfu-sen@yarn.girlonthemoon.xyz I could in toehry publish a specification for what twtd implements, which forms the basis of the API between twtxt.app <-> twtd, if you wanted to write your own twtd / twtxt.app compatible publishing backend (assuming you didnāt want to use Github/Gitea, etc).
@balloonfu-sen@yarn.girlonthemoon.xyz Unfortunately I tried to support SFTP but ripped this out as Browsers (which the Swag framework uses under the hood as a framework to build PWA(s)) doesnāt support raw TCP connections. So FTP / SFTP is not possible without hacks like a proxy. Which I donāt really want to support. So only things that have some kind of HTTP API are possible viable publihsing backends right now. That is Github/Gitea, twtd, Yarn, etc.
š definitely using the Twtxt App as my daily-driver now for Twtxt/Yarn. Not using twtd however, as I just pair the app with my already existing yarnd powered profile on twtxt.net