|
8651
|
Untitled project by @mohammeddowas | Stardance
|
mohammeddowas
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Untitled project by @mohammeddowas | Starda Title: Untitled project by @mohammeddowas | Stardance...
|
1793224159.2669892
|
1793828959.2669892
|
|
8652
|
Market Regime Dashboard + Backtester by @jacksalee Market Regime Dashboard + Backtester by @jacksaleeby | Stardance...
|
jacksaleeby
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Market Regime Dashboard + Backtester by @ja Title: Market Regime Dashboard + Backtester by @jacksaleeby | Stardance...
|
1793220749.844133
|
1793825549.844133
|
|
8653
|
NULL
|
NULL
|
0
|
0.0
|
0
|
[]
|
[]
|
|
1792932843.7728288
|
1793796843.7728288
|
|
8654
|
My awesome macropad! by @jhwh212 | Stardance
|
jhwh212
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: My awesome macropad! by @jhwh212 | Stardanc Title: My awesome macropad! by @jhwh212 | Stardance...
|
1793436541.6800172
|
1794041341.6800172
|
|
8655
|
DAISY-P by @Doodle | Stardance
|
Doodle
|
0
|
0.36
|
0
|
[]
|
[{"post_id": 3263, "ts": "2026 [{"post_id": 3263, "ts": "2026-06-06T03:28:55Z", "hours": 0.3608, "text": "So, it turns out the DAISY format is shockingly normal. On the plus side, it seems like I’ll be able to use a lot of HTML parsing principles, and even include some unmodified HTML parsing with attribution. On the downside, I’m on chapter 3 and I already see about 5 different potential memory leaks if I’m not careful. Wish me luck :/"}]...
|
Title: DAISY-P by @Doodle | Stardance
Devlogs:
So Title: DAISY-P by @Doodle | Stardance
Devlogs:
So, it turns out the DAISY format is shockingly normal. On the plus side, it seems like I’ll be able to use a lot of HTML parsing principles, and even include some unmodified HTML parsing with attribution. On the downside, I’m on chapter 3 and I already see about 5 different potential memory leaks if I’m not careful. Wish me luck :/...
|
1793155920.6598265
|
1793760720.6598265
|
|
8656
|
Gamer controls by @ackl510 | Stardance
|
ackl510
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Gamer controls by @ackl510 | Stardance
|
1793220783.2116368
|
1793825583.2116368
|
|
8657
|
My awesome macropad! by @AstroEmu | Stardance
|
AstroEmu
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: My awesome macropad! by @AstroEmu | Stardan Title: My awesome macropad! by @AstroEmu | Stardance...
|
1793433161.6325817
|
1794037961.6325817
|
|
8658
|
My awesome macropad! by @arihantkumar | Stardance
|
arihantkumar
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: My awesome macropad! by @arihantkumar | Sta Title: My awesome macropad! by @arihantkumar | Stardance...
|
1793296461.397036
|
1793901261.397036
|
|
8659
|
graphcontext by @vedansh | Stardance
|
vedansh
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: graphcontext by @vedansh | Stardance
|
1793321588.4643357
|
1793926388.4643357
|
|
8660
|
NULL
|
NULL
|
0
|
0.0
|
0
|
[]
|
[]
|
|
1792943570.9322133
|
1793807570.9322133
|
|
8661
|
Rimotics by @omarmium | Stardance
|
omarmium
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Rimotics by @omarmium | Stardance
|
1793393389.7207012
|
1793998189.7207012
|
|
8662
|
Genesis a new P.A.T.H by @knieserfamily2 | Stardan Genesis a new P.A.T.H by @knieserfamily2 | Stardance...
|
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Genesis a new P.A.T.H by @knieserfamily2 | Title: Genesis a new P.A.T.H by @knieserfamily2 | Stardance...
|
1793235242.8376656
|
1793840042.8376656
|
|
8663
|
NULL
|
NULL
|
0
|
0.0
|
0
|
[]
|
[]
|
|
1792943503.289455
|
1793807503.289455
|
|
8664
|
Untitled project by @joseph_chamdani | Stardance
|
joseph_chamdani
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Untitled project by @joseph_chamdani | Star Title: Untitled project by @joseph_chamdani | Stardance...
|
1792792375.1778758
|
1793397175.1778758
|
|
8665
|
Tower Defence by @tobyh | Stardance
|
tobyh
|
0
|
9.42
|
0
|
[]
|
[{"post_id": 7368, "ts": "2026 [{"post_id": 7368, "ts": "2026-06-12T08:49:55Z", "hours": 4.8669, "text": "Added enemy class, which has methods for its movement algorithm, and an entity scanning method, which iterates through the towerLayout array and checks if the range of that tower will damage the enemy, and subtracts that damage from it. The method also checks if there are towers within range of the enemy, (3x3 square centred about the enemy) and does damage to those towers as well. If the enemy makes it to the end of the board, it will do damage to the player until it dies or the player dies, ending the game."}, {"post_id": 3862, "ts": "2026-06-06T22:41:43Z", "hours": 2.8003, "text": "Added classes for the cursor (represented as an _ ), player and tower. The player class has credits to build towers and a health attribute which would depend on whether enemies finish the path through the board. The tower superclass has attributes for its position, its damage per second, range and health. There are three subclasses for types of towers (represented by ▲, ✸, ◎) which each have different attributes and prices. The cursor moves in each cardinal direction and will not move outside the walls or write over any towers. It has the same interface as the path direction (0 for North - 3 for West) and 6, 7 and 8 to place towers."}, {"post_id": 3565, "ts": "2026-06-06T15:44:53Z", "hours": 1.7561, "text": "Simple board gen algorithm completed, the cardinal direction of play (0 for North - 3 for West) as well as the dimensions of the board, 5x5, 7x7 etc."}]...
|
Title: Tower Defence by @tobyh | Stardance
Devlog Title: Tower Defence by @tobyh | Stardance
Devlogs:
Simple board gen algorithm completed, the cardinal direction of play (0 for North - 3 for West) as well as the dimensions of the board, 5x5, 7x7 etc.
Added classes for the cursor (represented as an _ ), player and tower. The player class has credits to build towers and a health attribute which would depend on whether enemies finish the path through the board. The tower superclass has attributes for its position, its damage per second, range and health. There are three subclasses for types of towers (represented by ▲, ✸, ◎) which each have different attributes and prices. The cursor moves in each cardinal direction and will not move outside the walls or write over any towers. It has the same interface as the path direction (0 for North - 3 for West) and 6, 7 and 8 to place towers.
Added enemy class, which has methods for its movement algorithm, and an entity scanning method, which iterates through the towerLayout array and checks if the range of that tower will damage the enemy, and subtracts that damage from it. The method also checks if there are towers within range of the enemy, (3x3 square centred about the enemy) and does damage to those towers as well. If the enemy makes it to the end of the board, it will do damage to the player until it dies or the player dies, ending the game....
|
1793371941.2022822
|
1793976741.2022822
|
|
8666
|
DevDeck by @Dani6767 | Stardance
|
Dani6767
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: DevDeck by @Dani6767 | Stardance
|
1793289193.1998975
|
1793893993.1998975
|
|
8667
|
Gesture controlled macropad! by @vaishal_anil | St Gesture controlled macropad! by @vaishal_anil | Stardance...
|
vaishal_anil
|
0
|
0.28
|
0
|
[]
|
[{"post_id": 3531, "ts": "2026 [{"post_id": 3531, "ts": "2026-06-06T14:48:10Z", "hours": 0.2839, "text": "DevBlog #1: Gesture Hackpad Hey everyone! It has been an absolute grind, but I have some massive news to share for my very first devblog. Here is where my custom macropad project stands right now, and a sneak peek at what’s coming next. The Progress: The Core PCB is Complete! My biggest milestone yet 😭(am still new to this). Getting here wasn’t easy! I had to clear a few major engineering hurdles during the routing phase: Shorts: I successfully redirected our trace lines to completely clear the massive physical mounting holes and pins for the mechanical switches, ensuring zero electrical interference. Ratsnest Lines: The layout is 100% complete with clean copper paths, passing the KiCad design checks with zero errors in the 3D viewer. 📦 Next Phase: Case Design & Enclosures Now that the electronics are locked down, I am moving straight into 3D modeling the physical enclosure to hold everything together. I am adopting a robust, sandwich-style case structure featuring a secure top switch plate and a protective bottom shell with a built-in cutout for the USB-C port. Future Modifications: A Total Surprise! I know a standard 3-key macropad is cool, but I want to make this thing truly unique. I am already planning out a series of custom hardware and design modifications to elevate it way beyond the basic tutorial guidelines. What are they? Well… that is a complete surprise for now. 🤫 Stay tuned for DevBlog #2, where I will show the first physical 3D prints and revealing our secret upgrades! What modifications do you think we’re adding? Let us know your guesses!"}]...
|
Title: Gesture controlled macropad! by @vaishal_an Title: Gesture controlled macropad! by @vaishal_anil | Stardance
Devlogs:
DevBlog #1: Gesture Hackpad Hey everyone! It has been an absolute grind, but I have some massive news to share for my very first devblog. Here is where my custom macropad project stands right now, and a sneak peek at what’s coming next. The Progress: The Core PCB is Complete! My biggest milestone yet 😭(am still new to this). Getting here wasn’t easy! I had to clear a few major engineering hurdles during the routing phase: Shorts: I successfully redirected our trace lines to completely clear the massive physical mounting holes and pins for the mechanical switches, ensuring zero electrical interference. Ratsnest Lines: The layout is 100% complete with clean copper paths, passing the KiCad design checks with zero errors in the 3D viewer. 📦 Next Phase: Case Design & Enclosures Now that the electronics are locked down, I am moving straight into 3D modeling the physical enclosure to hold everything together. I am adopting a robust, sandwich-style case structure featuring a secure top switch plate and a protective bottom shell with a built-in cutout for the USB-C port. Future Modifications: A Total Surprise! I know a standard 3-key macropad is cool, but I want to make this thing truly unique. I am already planning out a series of custom hardware and design modifications to elevate it way beyond the basic tutorial guidelines. What are they? Well… that is a complete surprise for now. 🤫 Stay tuned for DevBlog #2, where I will show the first physical 3D prints and revealing our secret upgrades! What modifications do you think we’re adding? Let us know your guesses!...
|
1793404380.5794444
|
1794009180.5794444
|
|
8668
|
Untitled project by @kitekite | Stardance
|
kitekite
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Untitled project by @kitekite | Stardance
|
1793181377.7496793
|
1793786177.7496793
|
|
8669
|
Queue Meet by @jieclarkm | Stardance
|
jieclarkm
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Queue Meet by @jieclarkm | Stardance
|
1793296216.0624893
|
1793901016.0624893
|
|
8670
|
Rumble by @bad-indentation | Stardance
|
bad-indentation
|
1
|
5.0
|
26
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-06-13T01:08:07Z", "hours": 5.0, "multiplier": 4.97, "payout": 26, "blessing": "neutral", "text": "Rumble is a Rust CLI tool to solve Jumbles and anagrams. It is very fast (as one can expect from Rust) and easy to use. I am relatively new to Rust, and so this was a nice opportunity to finally create a full command-line project from start to finish and publish it on crates.io. Installation instructions are in the repo README. Please consider writing an issue if you find any bugs!", "repo": "https://github.com/bad-indentation/rumble", "demo": "https://crates.io/crates/rumble-cli"}]...
|
[{"post_id": 7806, "ts": "2026 [{"post_id": 7806, "ts": "2026-06-13T00:55:42Z", "hours": 1.2236, "text": "Finally published to crates.io."}, {"post_id": 4687, "ts": "2026-06-08T02:55:47Z", "hours": 0.8144, "text": "Serious refactor - DFS + pruning was actually not an efficient solution to the problem; simple character counting using HashMaps much faster."}, {"post_id": 4637, "ts": "2026-06-08T00:59:20Z", "hours": 3.1231, "text": "Finished first iteration of solver; working on optimizations. Successfully implemented depth-first search to generate all possible anagrams of the scrambled input. Used a pre-computed HashSet of valid prefixes to quickly prune any branches that wouldn’t lead to valid English words. Implemented CLI interface using Clap. Added –verbose flag for logging and –include-partial for words that do not use all available letters. Initial optimizations include removing words that could never be made using the available letters- that is, words that are too long or contain invalid letters. Further optimizations include using concurrency when reading the file and using smart pointers to avoid excessive cloning. Also considering the possibility of iterating over candidate WORDS and determining whether they are a valid solutions instead of using combinatorics."}]...
|
Title: Rumble by @bad-indentation | Stardance
Dev Title: Rumble by @bad-indentation | Stardance
Devlogs:
Finished first iteration of solver; working on optimizations. Successfully implemented depth-first search to generate all possible anagrams of the scrambled input. Used a pre-computed HashSet of valid prefixes to quickly prune any branches that wouldn’t lead to valid English words. Implemented CLI interface using Clap. Added –verbose flag for logging and –include-partial for words that do not use all available letters. Initial optimizations include removing words that could never be made using the available letters- that is, words that are too long or contain invalid letters. Further optimizations include using concurrency when reading the file and using smart pointers to avoid excessive cloning. Also considering the possibility of iterating over candidate WORDS and determining whether they are a valid solutions instead of using combinatorics.
Serious refactor - DFS + pruning was actually not an efficient solution to the problem; simple character counting using HashMaps much faster.
Finally published to crates.io.
Ships:
Rumble is a Rust CLI tool to solve Jumbles and anagrams. It is very fast (as one can expect from Rust) and easy to use. I am relatively new to Rust, and so this was a nice opportunity to finally create a full command-line project from start to finish and publish it on crates.io. Installation instructions are in the repo README. Please consider writing an issue if you find any bugs!...
|
1793407669.9619005
|
1793494069.9619005
|
|
8671
|
gardening game by @knineserfamily2 | Stardance
|
knineserfamily2
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: gardening game by @knineserfamily2 | Starda Title: gardening game by @knineserfamily2 | Stardance...
|
1793249480.9742649
|
1793854280.9742649
|
|
8672
|
My first slack bot! by @dn1u | Stardance
|
dn1u
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: My first slack bot! by @dn1u | Stardance
|
1792724011.5419657
|
1793328811.5419657
|
|
8673
|
Untitled project by @VivekPandey | Stardance
|
VivekPandey
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Untitled project by @VivekPandey | Stardanc Title: Untitled project by @VivekPandey | Stardance...
|
1793188276.928951
|
1793793076.928951
|
|
8674
|
My first slack bot! by @abdullahjvd384 | Stardance
|
abdullahjvd384
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: My first slack bot! by @abdullahjvd384 | St Title: My first slack bot! by @abdullahjvd384 | Stardance...
|
1793198864.0273364
|
1793803664.0273364
|
|
8675
|
Untitled project by @Seekers | Stardance
|
Seekers
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Untitled project by @Seekers | Stardance
|
1793224166.8557777
|
1793828966.8557777
|
|
8676
|
Untitled project by @zmuhammadrr | Stardance
|
zmuhammadrr
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Untitled project by @zmuhammadrr | Stardanc Title: Untitled project by @zmuhammadrr | Stardance...
|
1793422332.0726702
|
1794027132.0726702
|
|
8677
|
Basic Chess Engine Integrating Algos ! by @ElMOF | Basic Chess Engine Integrating Algos ! by @ElMOF | Stardance...
|
ElMOF
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Basic Chess Engine Integrating Algos ! by @ Title: Basic Chess Engine Integrating Algos ! by @ElMOF | Stardance...
|
1793278184.2044768
|
1793882984.2044768
|
|
8678
|
Live Lyrics by @liamwilley | Stardance
|
liamwilley
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Live Lyrics by @liamwilley | Stardance
|
1792730987.9052935
|
1793335787.9052935
|
|
8679
|
My awesome macropad! by @anthonydicosta | Stardanc My awesome macropad! by @anthonydicosta | Stardance...
|
anthonydicosta
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: My awesome macropad! by @anthonydicosta | S Title: My awesome macropad! by @anthonydicosta | Stardance...
|
1792756264.4615312
|
1793361064.4615312
|
|
8680
|
Untitled by @shrimanjadhav07 | Stardance
|
shrimanjadhav07
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Untitled by @shrimanjadhav07 | Stardance
|
1793249452.5144536
|
1793854252.5144536
|
|
8681
|
Hack me Yash by @zoyashrj | Stardance
|
zoyashrj
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Hack me Yash by @zoyashrj | Stardance
|
1792799564.411043
|
1793404364.411043
|
|
8682
|
My awesome macropad! by @kksahu444 | Stardance
|
kksahu444
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: My awesome macropad! by @kksahu444 | Starda Title: My awesome macropad! by @kksahu444 | Stardance...
|
1792799313.9175498
|
1793404113.9175498
|
|
8683
|
Untitled project by @xVenve | Stardance
|
xVenve
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Untitled project by @xVenve | Stardance
|
1792724083.4729323
|
1793328883.4729323
|
|
8684
|
GO Tournament Tracker by @just_a_squid | Stardance
|
just_a_squid
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: GO Tournament Tracker by @just_a_squid | St Title: GO Tournament Tracker by @just_a_squid | Stardance...
|
1792777748.7580407
|
1793382548.7580407
|
|
8685
|
My awesome macropad! by @iota | Stardance
|
iota
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: My awesome macropad! by @iota | Stardance
|
1793292558.6258173
|
1793897358.6258173
|
|
8686
|
King & Co. by @MrMoney | Stardance
|
MrMoney
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: King & Co. by @MrMoney | Stardance
|
1793371729.2515173
|
1793976529.2515173
|
|
8687
|
Chronovisor by @greywolfcode | Stardance
|
greywolfcode
|
0
|
19.97
|
0
|
[]
|
[{"post_id": 23739, "ts": "202 [{"post_id": 23739, "ts": "2026-07-06T21:12:11Z", "hours": 2.1297, "text": "Extracting Archives Previously, I was planning to load the each archive into memory and process it there, but I have since realised that this is not a great option; Most of the archive is taken up with files other than the main message files (i.e. images), and most of those files will not need to be loaded into memory, as they will not be shown on screen. The down side is of course that this trades ram for storage, but that seems like an okay tradeoff. A fairly large archive I am testing with is only about 1.5 GB. It may be worth adding an in-memory only option later, however. Part 1: Splitting the current function The first thing I did was split the current function I had to load and process the archives into one for processing (which is now incomplete), and another to extract them. Based on the archive library’s docs, it was very simple to convert the in-memory code to extraction code. Part 2: Foldering At this point, the extraction code extracted just fine, but it recreated the folder structure from the zip archive, when all I wanted was the “Google Chat” folder. I decided to give each new loaded archive its own folder, numbered based on when it was added. (The archives do have a user number inside, but finding it would involve reading through the archive while it was in memory, and it didn’t appear to have any benefit.) This is the point where I got stuck for a while; I was attempting to get the number of folders in the directory, and the asynchronous function for getting that number never returned. I spent a while messing with print statements and try/catch statements trying to find a solution. Eventually, I found a synchronous version that solved the problem, as I am not trying to run this async, but I am still not sure what the real problem was. Either way, it is now extracting successfully. I need to make a note to look into this further. Part 3: Screenshot In writing this devlog, I really wasn’t sure what to put as a screenshot; nothing about the Ui has changed. Eventually I settled on showing the output save directory. to demonstrate how it properly extracts to a new folder. Part 4: Time I’m honestly not sure exactly what went on with he time on this devlog. When I started working on extraction, there were 10-15 minutes (it has been a while since I looked at the exact number) already listed‚ but i couldn’t’ figure out what I had been working on; I ended up reverting those changes and starting over. After that I got sick and went to a Bible school, so it has been a while since I made any meaningful progress. I am fairly certain that the time on this devlog is too high, but I am not exactly sure why (maybe the time I spent trying to work when I wasn’t feeling well?). Either way, my apologies, and I’m hoping to get this project back on track in the next devlog."}, {"post_id": 7703, "ts": "2026-06-12T21:16:38Z", "hours": 2.3119, "text": "Making Exporting Functional This will be the final session (for now) on the archive generator. There are still plenty of bugs, but after this session, it is at the point where it is actually functional, so I merged the generator into the main branch. According to GitHub, this is a Python project now. Part 1: object Message has no attribute “person” Most of the time in this session was spent trying to fix this attribute error. This is where using textual makes debugging slightly more difficult, as there is no terminal to print to. There is a debug terminal that can be attached, but it still is more complicated. I tried to make Labels pop up with debug information, but the never appeared or caused more errors. Part 2: Logging I got so frustrated with the attribute error that I added logging to the entire application. I started to write my own logging script, but then learned that Python comes with a logging library. I initalised it to use all logging level, although I only use Debug and Info. This helped fix a few other bugs, such as not actually writing messages to the output (once the attribute error was fixed). I used logging to print all the vars in the messages being processed; from this I found that none of the instance variables existed. Part 3: The solution I turned to Google to try to fix my problem. After Googling attribute errors, I saw a suggestion that passing the class, not an initalised instance, could be the cause. As it turns out, when adding a new message to the messages list, I had capitalised the “M”, adding the object not the instance. I spent quite a lot of time for a single character mistake. I’m glad I found it, but it took a frustratingly long time to find for how simple it was. Finished? The archive generator is not done. There are still bugs, it hasn’t been stress tested, the UI looks bad, and only basic messages can be created. But it is functional enough so that I can work on the actual application for the time being."}, {"post_id": 7144, "ts": "2026-06-11T22:41:40Z", "hours": 0.9989, "text": "Producing Output Zips It is about time to wrap up the archive generator, so it is time to actually generate archives. The basic steps are as follow: Parse the Archives Write the archives to the zip file. Obviously the basics steps are not that complicated; Parsing the archives in the complicated part… Part 1: Parsing Dates I wrote a quick datetime parsing tool for converting to/from strings. The chat archives have a fairly standard format for dates, so it was pretty easy to implement. The only really weird thing is there is a single narrow no-break space character ( U+202F ) for some reason. All other spaces are normal spaces. Part 2: Parsing Archives parsing archives is split into creating group_info.json and creating messages.json. group_info.json contains the details for every person in the group. This includes their name, email, and if they are Human or a Bot. messages.json contains the data for every messages. For each message, there is a json object detailing who created the message (name, email, and user type), and further data of when it was created, the message text, and some ID values. Message IDs Each message in the main group gets its own topic ID; messages in threads use the topic ID of the message the thread is linked to. The message’s own ID is created using the Group ID, the Topic ID, and an ID unique to the message (if it is a top level message, this is the same as the topic ID). #Part 4: Writing to Archives This is a fairly simple step; the most interesting part is generating the archive name. I based it on the actual exported zip names by making the name chronovisor-generator_takeout- + the time it was exported."}, {"post_id": 7003, "ts": "2026-06-11T19:10:00Z", "hours": 0.5283, "text": "Message Adding Menu In this session, I added the menu for adding messages. This went surprisingly smoothly; I had to learn how to use the Select widget, but most of the other parts of the UI I had already used, or could piece together from other menus. The biggest UI section, the data and time editing, can simply be pulled from the group editing menus. Overall, this session went smoothly, and I was able to focus on how to do make person selection work. Part 1: Person Selection Each message obviously needs a person who sent it; for this I use a Select widget, which is essentially a drop down menu. After my previous problems with passing the wrong type into a field, I wasn’t exactly sure how to make the selection box work; I really wanted to pass Person objects in, as it would remove needing to use a person’s name as a key to try to find the right Person object afterwords. I tried it and… it totally worked. After overriding the str method to return the person’s name and email, the Select widget was functional. Sometimes it is nice when code just works."}, {"post_id": 6614, "ts": "2026-06-11T03:02:06Z", "hours": 1.7028, "text": "Group Editing I was going to start doing messages in this session, and realised that there was an important element that I was missing: group settings. This is important for one sort of sneaky reason: message history is dated. At the beginning of chat history, there is a small informational message that says when the chat was created, and then all messages must come afterwards. Without defining a set start time, there would be no way to validate the dates set when new messages are added. To go along with this, I also added the ability to set the name for spaces. I am also storing if the archive is a DM or Space in Archive objects so I know what fields to ignore when saving occurs. Part 1: Date and Time Entry Date and time entry is split into a box for each Month, Day, Year, Hour, Minute, Second. Each of these has a corresponding label to detail what each field is for. All of these are arranged in a Horizontal container so that they line up. I also experimented with data validation; I at first started to right my own validation classes, then realised that textual has a built in validator for numbers. There are a few potential bugs, the biggest is that days are not validated based on the selected month, so entering dates like February 31st is possible. Also, a year that is too late causes an error, it may be something to do with python’s datetime, but it isn’t a priority to experiment with at the moment. Part 2: Name Entry The type (DM or Space) is passed to the container; this is used to enable or disable editing the group. The was I have set this up, the name editing is below the save button name, but I really am trying to get features in and not waste my time on UI for the moment. It works, and for now that is what matters. And speaking of the Save button… Part 3: The Save Button The Save button is fairly simple; while editing, values are stored locally in the container. When the save button is pressed, these values are actually written to the correct archive. Part 4: Silly Mistakes I don’t know what I was thinking today; I made a bunch of silly mistakes. The worst was trying to pass an integer where a string was required. I was setting up the base for the date & time inputs, and as I was thinking about time, so I just tried to input numbers. I got back an error about translation; it took a bit to figure out what was causing the error."}, {"post_id": 6008, "ts": "2026-06-10T02:17:02Z", "hours": 2.0028, "text": "People Editing! In this session, I implemented the menus for adding, removing, and editing people from a DM or Space. I decided to make the editing menus popup menus to avoid having to redo the sidebar with more menu switches. To make the popups work better, I separated the styling for popups into their own tcss file from the licence popup styles. Part 1: Editing Buttons The first order of business was to add the buttons for adding, editing, and removing people. They are setup so that the first person cannot be edited or removed, as they are the main user in all DMs and Spaces. A maximum number of people is passed to the people editing widget- two people for DMs, and 400 people for Spaces. 400 is the limit for personal use, and I don’t think any more than that is really required. Part 2: Adding People The people adding popup has two labels, one for the name and one for the email. The default values are generated based on the number of items so there are hopefully no duplicates. The UI design has each input labelled, and buttons to cancel or save the new person. Designing this was really interesting, as I found that nesting containers using their constructor, not a with statement, allows event bindings to bind correctly to the main container. This nested structure is surprisingly quite a bit like the Flutter child/children structure. Horizontal( Label(\"Name: \"), Input(placeholder=self._name, id = \"name\") ), In order to make the table reload, I just switch back to the menu with a switch command; this works, although that fact that there is no default menu so it returns to the void menu means it isn’t a perfect solution. Part 3: Removing People This was fairly simple; the index for the person is passed into the constructor, so the person’s name can be referenced in the confirmation message. An option to remove the person or cancel is provided. Part 4: Editing People This popup is in part a modification of adding people and removing people. The person’s index is passed into the constructor so they can be found, and text boxes are provided to modify the person’s name. Part 5: Adding people to Spaces I was originally going to stop at adding people to DMs, but I thought that the larger number of people in Spaces would make a better demo. I unfortunately hadn’t actually made it easy to add this to spaces; I just forgot I would need to make this work for the separate Spaces and DMs. Making this separate is required as I store DMs and Spaces in separate dictionaries, and the command to return to the correct menu needs to be different for spaces and menus. To fix this, I pass a type to the constructor, which is used to determine which dictionary to use, and which call to make when closing the popup"}, {"post_id": 5822, "ts": "2026-06-09T19:14:03Z", "hours": 0.9486, "text": "Adding the People Viewer The purpose of this session was to add a way to view the people currently in the Space/DM. Part 1: Person Storage Up to this point, there was no way to actually store the created people. To fix this, I created a Person class, a simple object to store the name and email. I then added a list to the Archive class to store the people. Part 2: Dynamically Adding Widgets I am using a more complicated system than with User Creation to dynamically switch what you can edit; I am hoping that this will give me more control, and hopefully make it easier to make the UI look good. The new method is to dynamically add a container with the right widgets inside when a button is pressed. To this end, I created a class based on the VerticalScrollBar to control the person editing. I didn’t fully understand how to dynamically add widgets at first; my first idea was to have a instance variable containing a container that would be yielded in the compose method; I could then update the variable to update the UI. As it turns out, this is not how texual works; I did try a bunch of methods to refresh the screen, but then I learned that you can call a mount method to add new widgets dynamically. Part 3: The DataTable The final step to getting this to work was to add a DataTable. I originally tried to generate the table in the compose method, but I switched to generating it upon creation of the container, as that appears to be the intended way to do it. I am a bit disappointed that the data table is rendered far to the right; Another UI detail for later (actually for later this time) I guess."}, {"post_id": 5155, "ts": "2026-06-08T20:47:57Z", "hours": 0.8292, "text": "Adding Licence Information The main feature of this session was to add a popup box bound to “l” that displays the licence info. I opted to use the short licence info text that was designed to be at the top of source files, as I won’t have a popup on startup, and having multiple commands is not very user friendly. To go along with another bound command, I added the Footer widget to all menus, as it lists avaliabel commands. I also added licence comments to the top of my source files, including the dart files on the main branch. The Licence Box The texutal docs explain that the ModalScreen class can be used as the base of a popup message. Some tcss is used to centre and align everything; this took multiple attempts to figure out where in the container hierarchy to put the align properties. You can see that the button on the bottom is still not centred, but after failing for a bit, I decided that it looks fine. Another UI element to revisit later? Binding the Box I bound the box to “l”, as it makes sense for “licence”. i spent a little bit trying to figure out why the hotkey didn’t do anyhting; turns out it was a typo. It appears textual does not throw an error if you bind a function that does not exist to a key, maybe because you pass a string to it not ana actual function. Another feature I added was to make “l” close the popup box; whatever screen is on top takes priority."}, {"post_id": 5049, "ts": "2026-06-08T18:26:12Z", "hours": 0.87, "text": "DM Creation Menu Base It is time to start on the menus for actually creating archives. Spaces and DMs have the same basic elements, so I am planning to create a few custom widgets that I can reuse for both of them. I decided that I would start with creating the DM creation, as DMs are a bit simpler than spaces. Hub Menu The first thing I created was a hub menu that will be used to create new DMs, Spaces, or Export the archive. It is fairly similar to the Main Menu, except that I used tcss instead of a container to centre the buttons. i think that this makes the code cleaner, and makes more sense with how textual works. I had a small problem where nothing was happening after I added content-align to my tcss file for the Hub Menu; as it turns out I needed normal align to align buttons. DM Creation Menu This menu has a basic docked sidebar just like the User Creation Menu. I am hoping that the plan of using single widgets that “plug in” will prevent UI issues like the user Creation Menu has. ID and Archive Handling I added a scripts folder with two small scripts: id_handeler to create the IDs for each message archive archive_handeling for classes to store archive, and, in the future, handle exporting The ID handler contains a single Enum with values for DMs and Spaces. It also has a single static function to create a custom ID using Python’s uuid and base64 libraries, adding the correct DM or Space value based on the passed type. The archive handler currently contains two stub classes, one for storing archives, and another for storing messages. As more features are added, these classes will grow as well."}, {"post_id": 4669, "ts": "2026-06-08T02:10:48Z", "hours": 0.8422, "text": "Attempting to fix layout issues In my last devlog, I created the Create User Menu, and stated that I wasn’t a fan of how the UI looked, but would work on it after the rest of the app was done. Then I thought I knew how to fix it, so I spent some time working on the UI. I also added a text label for the input box, and docked the sidebar on the left side so other widgets won’t affect it. Attempt 1 My idea was to take advantage of Python’s multiple inheritance and add the Horizontal container as a parent class; this would allow the Screen class to still be used for rendering, but everything would be aligned horizontally. This worked okay; it looked much like the attached screenshot. Unfortunate, the input was pushed over to the right. Attempt 2 I didn’t like having everything pushed over to the right, so I thought that “maybe I can wrap the label and input in a Vertical container to align the label and input box; maybe that would allow them to move over and align with the docked sidebar. While the input and label did line up, it had two problems: The input and label were still pushed over on the right side Wrapping with a Vertical container is done with the Python “with” statement. This makes the Vertical widget capture all the events the input boxes generate Attempt 3 Next I tried to use the grid layout. I have used the grid layout in Python Tkinter, so thought that it might be the way to go here. Using textual tcss, I setup a grid with one row and two columns, and didn’t dock the sidebar on the left. I attempted to make one column fill more of the screen than the other with grid-columns: 1fr 2fr; , unfortunately this didn’t work; this maybe could be a problem with how I read the docs; but I thought I understood how this works. They have an example of how to use the grid layout after all. Conclusion At this point, I have spent 49 minutes failing to make the layout look better. I was feeling frustrated, and realised that I was wasting my time. My goal is to make a Google Chat archive time machine in Dart Flutter, this is only a side part to make it easier to use and demonstrate. I fixed it up so it works, it is functional, and I am going to leave it. For real this time. Once the app is functional I might have a better idea of how to use textual to make a good UI, but my main focus should be making the main viewer."}, {"post_id": 3946, "ts": "2026-06-07T02:22:49Z", "hours": 0.8039, "text": "Add Create User Menu UI I added all the required elements for the CreateUsemenu, the menu for creating the “user” who owns the chats in the archive. The menu has: A side bar that contains buttons for selecting to edit the email or name, or selecting to create the user A input box for the actual editing ContentSwitcher I spent most of my time on using a ContentSwitcher to switch which editor can be used based on which button was clicked. I completely missed how their demonstration worked the first time I tried to implement it in my app. Input Boxes My second problem was in saving the input boxes’ values so they can be used later. I was reading the API and completely missed how you were supposed to implement the on_input_changed method. They listed the variables (and their types) which were available when receiving the input. i thought that I had to create a method in my class with those inputs, then got very confused why I got so many argument errors. It turns out that you use event: Input.Changed parameter just like the button. Organisation The organisation is not entirely how I would like; the input widget is at the bottom not next to the buttons. I tried to wrap them in a Horizontal container, but this messed up using the self parameter. I might come back to this later after the rest of this app is done."}, {"post_id": 3895, "ts": "2026-06-07T00:06:50Z", "hours": 1.2328, "text": "Starting the Archive Generator I started making an application for generating archives that will be able to be used with the main app. This solves two problems: I will want to show the app working further into development without showing my personal chats It will allow people who don’t use Google Chat to test the app Design I am using the textual library for Python to create this helper app. I am using Python not Dart as I am faster with it, and the main focus of this project should be a Dart app. I am using textual not just a simple console app because I saw textual and thought it looked like a really cool library Progress In this session I made the first two menus- the main menu and the create player menu. I was doing some experimenting with how I wanted to design the app, and eventually settled on separate Screens for each menu. When implementing the Screens, however, I didn’t fully understand the api, and couldn’t figure out why my buttons were not rendering. It turned out, I just had to install and switch to the screen, not just push to it or switch to it."}, {"post_id": 3768, "ts": "2026-06-06T20:19:58Z", "hours": 1.1275, "text": "Setting up basic GUI I made a mistake in my initial plan for this project. I wanted to get all the data loading parts done first as I have done json handling in other languages. This would have worked were I familiar with flutter. However, as I am still very new to flutter, I have been spending time trying to figure out how to process the data so it can easily be converted to flutter GUI; I am not sure what tools I have available as I just don’t have a lot of experience. Therefore, I decided I need to work on the UI along with the data processing. I created an app bar that moves the upload button onto it with an IconButton, and I am using the resizable_widget library to create the three panel structure of Google Chat."}, {"post_id": 3570, "ts": "2026-06-06T15:51:32Z", "hours": 1.2675, "text": "Load archive from flutter app In this session, I finished the basic button UI to trigger loading the archive. The first thing I did was to setup a MVVM structure for the app. I chose this as the flutter tutorial uses it for http requests, and I figured that file io posed a similar problem. I first tried to use async/await to prevent the UI from freezing while it loads the quite large zip file; I spent quite a while messing with Future, async, and await trying to get it to work. After my attempts failed, I turned to research, and learned that I actually needed to use compute to run the file processing in a background thread. this works quite well, as is demonstrated in the attached video. I also uploaded the Google Sans and Robot fonts to the project, selecting Google Sans as the default font. I want the project to look a bit like Google Chat in the end, so Google fonts are necessary"}, {"post_id": 3233, "ts": "2026-06-06T01:58:41Z", "hours": 0.7061, "text": "Linking archive loading to GUI I first setup the archive_handeler package to be importable. I made a mistake and accidentally setup the package imports in the chronovisor folder, not in archive_handeler. It was fortunately not a difficult fix, but it makes the git history a bit of a mess. After fixing the import, I added file_picker as a dependency, and added a basic button to use to create a file picker popup and linked the popup with the function to load the selected archive. This involved reading the file_picker docs; fortunately, the picker selection works on the web, as I am currently testing the app in a web environment."}, {"post_id": 3042, "ts": "2026-06-05T22:56:01Z", "hours": 1.3586, "text": "Finish basic archive parsing I finished the basic parsing of archives to retrieve the chat archives. Using the archive library, the archive is loaded, the entries in the Google Chat folder are located, and then they are split into each separate chat. I struggled a lot trying to figure out the most efficient way to sort the files. Each file has a name that includes the entire path inside, and I needed to extract the files from the folder for each specific space/dm, separate the two data json files from other file data, and wrap the data and files in a MessageGroup object. My problem was that the easiest way to get all the files is to use an enhanced for loop, which makes it hard to sort out the files they way I set up the object. Eventually, I settled on storing the files for each dm or space in a List, which could be looped through later; I want to get the program up and running, and it can be optimised later. I also spent a lot of time seeing what methods I had, especially on the ArchiveFile class that the extracted files are stored in, and just looking up APIs as I am new to Dart. At the moment there is not much to see here, as i am doing the basic message parsing before starting the actual gui app."}, {"post_id": 2914, "ts": "2026-06-05T20:22:10Z", "hours": 0.3117, "text": "Setup Chronovisor project I setup the basic project structure; I have a folder for the flutter app, and one for the package to handle all the archive details. This did not take that long, so I also prepared the archive_handeling package to have archive as a dependency, and setup basic loading of a zip file. Note: the time for this devlog is too high; I didn’t see the project show up of Hackatime at first, and so spent some time messing with the files trying to get it to show up, which inflated the time quite a bit. Oops."}]...
|
Title: Chronovisor by @greywolfcode | Stardance
D Title: Chronovisor by @greywolfcode | Stardance
Devlogs:
Setup Chronovisor project I setup the basic project structure; I have a folder for the flutter app, and one for the package to handle all the archive details. This did not take that long, so I also prepared the archive_handeling package to have archive as a dependency, and setup basic loading of a zip file. Note: the time for this devlog is too high; I didn’t see the project show up of Hackatime at first, and so spent some time messing with the files trying to get it to show up, which inflated the time quite a bit. Oops.
Finish basic archive parsing I finished the basic parsing of archives to retrieve the chat archives. Using the archive library, the archive is loaded, the entries in the Google Chat folder are located, and then they are split into each separate chat. I struggled a lot trying to figure out the most efficient way to sort the files. Each file has a name that includes the entire path inside, and I needed to extract the files from the folder for each specific space/dm, separate the two data json files from other file data, and wrap the data and files in a MessageGroup object. My problem was that the easiest way to get all the files is to use an enhanced for loop, which makes it hard to sort out the files they way I set up the object. Eventually, I settled on storing the files for each dm or space in a List, which could be looped through later; I want to get the program up and running, and it can be optimised later. I also spent a lot of time seeing what methods I had, especially on the ArchiveFile class that the extracted files are stored in, and just looking up APIs as I am new to Dart. At the moment there is not much to see here, as i am doing the basic message parsing before starting the actual gui app.
Linking archive loading to GUI I first setup the archive_handeler package to be importable. I made a mistake and accidentally setup the package imports in the chronovisor folder, not in archive_handeler. It was fortunately not a difficult fix, but it makes the git history a bit of a mess. After fixing the import, I added file_picker as a dependency, and added a basic button to use to create a file picker popup and linked the popup with the function to load the selected archive. This involved reading the file_picker docs; fortunately, the picker selection works on the web, as I am currently testing the app in a web environment.
Load archive from flutter app In this session, I finished the basic button UI to trigger loading the archive. The first thing I did was to setup a MVVM structure for the app. I chose this as the flutter tutorial uses it for http requests, and I figured that file io posed a similar problem. I first tried to use async/await to prevent the UI from freezing while it loads the quite large zip file; I spent quite a while messing with Future, async, and await trying to get it to work. After my attempts failed, I turned to research, and learned that I actually needed to use compute to run the file processing in a background thread. this works quite well, as is demonstrated in the attached video. I also uploaded the Google Sans and Robot fonts to the project, selecting Google Sans as the default font. I want the project to look a bit like Google Chat in the end, so Google fonts are necessary
Setting up basic GUI I made a mistake in my initial plan for this project. I wanted to get all the data loading parts done first as I have done json handling in other languages. This would have worked were I familiar with flutter. However, as I am still very new to flutter, I have been spending time trying to figure out how to process the data so it can easily be converted to flutter GUI; I am not sure what tools I have available as I just don’t have a lot of experience. Therefore, I decided I need to work on the UI along with the data processing. I created an app bar that moves the upload button onto it with an IconButton, and I am using the resizable_widget library to create the three panel structure of Google Chat.
Starting the Archive Generator I started making an application for generating archives that will be able to be used with the main app. This solves two problems: I will want to show the app working further into development without showing my personal chats It will allow people who don’t use Google Chat to test the app Design I am using the textual library for Python to create this helper app. I am using Python not Dart as I am faster with it, and the main focus of this project should be a Dart app. I am using textual not just a simple console app because I saw textual and thought it looked like a really cool library Progress In this session I made the first two menus- the main menu and the create player menu. I was doing some experimenting with how I wanted to design the app, and eventually settled on separate Screens for each menu. When implementing the Screens, however, I didn’t fully understand the api, and couldn’t figure out why my buttons were not rendering. It turned out, I just had to install and switch to the screen, not just push to it or switch to it.
Add Create User Menu UI I added all the required elements for the CreateUsemenu, the menu for creating the “user” who owns the chats in the archive. The menu has: A side bar that contains buttons for selecting to edit the email or name, or selecting to create the user A input box for the actual editing ContentSwitcher I spent most of my time on using a ContentSwitcher to switch which editor can be used based on which button was clicked. I completely missed how their demonstration worked the first time I tried to implement it in my app. Input Boxes My second problem was in saving the input boxes’ values so they can be used later. I was reading the API and completely missed how you were supposed to implement the on_input_changed method. They listed the variables (and their types) which were available when receiving the input. i thought that I had to create a method in my class with those inputs, then got very confused why I got so many argument errors. It turns out that you use event: Input.Changed parameter just like the button. Organisation The organisation is not entirely how I would like; the input widget is at the bottom not next to the buttons. I tried to wrap them in a Horizontal container, but this messed up using the self parameter. I might come back to this later after the rest of this app is done.
Attempting to fix layout issues In my last devlog, I created the Create User Menu, and stated that I wasn’t a fan of how the UI looked, but would work on it after the rest of the app was done. Then I thought I knew how to fix it, so I spent some time working on the UI. I also added a text label for the input box, and docked the sidebar on the left side so other widgets won’t affect it. Attempt 1 My idea was to take advantage of Python’s multiple inheritance and add the Horizontal container as a parent class; this would allow the Screen class to still be used for rendering, but everything would be aligned horizontally. This worked okay; it looked much like the attached screenshot. Unfortunate, the input was pushed over to the right. Attempt 2 I didn’t like having everything pushed over to the right, so I thought that “maybe I can wrap the label and input in a Vertical container to align the label and input box; maybe that would allow them to move over and align with the docked sidebar. While the input and label did line up, it had two problems: The input and label were still pushed over on the right side Wrapping with a Vertical container is done with the Python “with” statement. This makes the Vertical widget capture all the events the input boxes generate Attempt 3 Next I tried to use the grid layout. I have used the grid layout in Python Tkinter, so thought that it might be the way to go here. Using textual tcss, I setup a grid with one row and two columns, and didn’t dock the sidebar on the left. I attempted to make one column fill more of the screen than the other with grid-columns: 1fr 2fr; , unfortunately this didn’t work; this maybe could be a problem with how I read the docs; but I thought I understood how this works. They have an example of how to use the grid layout after all. Conclusion At this point, I have spent 49 minutes failing to make the layout look better. I was feeling frustrated, and realised that I was wasting my time. My goal is to make a Google Chat archive time machine in Dart Flutter, this is only a side part to make it easier to use and demonstrate. I fixed it up so it works, it is functional, and I am going to leave it. For real this time. Once the app is functional I might have a better idea of how to use textual to make a good UI, but my main focus should be making the main viewer.
DM Creation Menu Base It is time to start on the menus for actually creating archives. Spaces and DMs have the same basic elements, so I am planning to create a few custom widgets that I can reuse for both of them. I decided that I would start with creating the DM creation, as DMs are a bit simpler than spaces. Hub Menu The first thing I created was a hub menu that will be used to create new DMs, Spaces, or Export the archive. It is fairly similar to the Main Menu, except that I used tcss instead of a container to centre the buttons. i think that this makes the code cleaner, and makes more sense with how textual works. I had a small problem where nothing was happening after I added content-align to my tcss file for the Hub Menu; as it turns out I needed normal align to align buttons. DM Creation Menu This menu has a basic docked sidebar just like the User Creation Menu. I am hoping that the plan of using single widgets that “plug in” will prevent UI issues like the user Creation Menu has. ID and Archive Handling I added a scripts folder with two small scripts: id_handeler to create the IDs for each message archive archive_handeling for classes to store archive, and, in the future, handle exporting The ID handler contains a single Enum with values for DMs and Spaces. It also has a single static function to create a custom ID using Python’s uuid and base64 libraries, adding the correct DM or Space value based on the passed type. The archive handler currently contains two stub classes, one for storing archives, and another for storing messages. As more features are added, these classes will grow as well.
Adding Licence Information The main feature of this session was to add a popup box bound to “l” that displays the licence info. I opted to use the short licence info text that was designed to be at the top of source files, as I won’t have a popup on startup, and having multiple commands is not very user friendly. To go along with another bound command, I added the Footer widget to all menus, as it lists avaliabel commands. I also added licence comments to the top of my source files, including the dart files on the main branch. The Licence Box The texutal docs explain that the ModalScreen class can be used as the base of a popup message. Some tcss is used to centre and align everything; this took multiple attempts to figure out where in the container hierarchy to put the align properties. You can see that the button on the bottom is still not centred, but after failing for a bit, I decided that it looks fine. Another UI element to revisit later? Binding the Box I bound the box to “l”, as it makes sense for “licence”. i spent a little bit trying to figure out why the hotkey didn’t do anyhting; turns out it was a typo. It appears textual does not throw an error if you bind a function that does not exist to a key, maybe because you pass a string to it not ana actual function. Another feature I added was to make “l” close the popup box; whatever screen is on top takes priority.
Adding the People Viewer The purpose of this session was to add a way to view the people currently in the Space/DM. Part 1: Person Storage Up to this point, there was no way to actually store the created people. To fix this, I created a Person class, a simple object to store the name and email. I then added a list to the Archive class to store the people. Part 2: Dynamically Adding Widgets I am using a more complicated system than with User Creation to dynamically switch what you can edit; I am hoping that this will give me more control, and hopefully make it easier to make the UI look good. The new method is to dynamically add a container with the right widgets inside when a button is pressed. To this end, I created a class based on the VerticalScrollBar to control the person editing. I didn’t fully understand how to dynamically add widgets at first; my first idea was to have a instance variable containing a container that would be yielded in the compose method; I could then update the variable to update the UI. As it turns out, this is not how texual works; I did try a bunch of methods to refresh the screen, but then I learned that you can call a mount method to add new widgets dynamically. Part 3: The DataTable The final step to getting this to work was to add a DataTable. I originally tried to generate the table in the compose method, but I switched to generating it upon creation of the container, as that appears to be the intended way to do it. I am a bit disappointed that the data table is rendered far to the right; Another UI detail for later (actually for later this time) I guess.
People Editing! In this session, I implemented the menus for adding, removing, and editing people from a DM or Space. I decided to make the editing menus popup menus to avoid having to redo the sidebar with more menu switches. To make the popups work better, I separated the styling for popups into their own tcss file from the licence popup styles. Part 1: Editing Buttons The first order of business was to add the buttons for adding, editing, and removing people. They are setup so that the first person cannot be edited or removed, as they are the main user in all DMs and Spaces. A maximum number of people is passed to the people editing widget- two people for DMs, and 400 people for Spaces. 400 is the limit for personal use, and I don’t think any more than that is really required. Part 2: Adding People The people adding popup has two labels, one for the name and one for the email. The default values are generated based on the number of items so there are hopefully no duplicates. The UI design has each input labelled, and buttons to cancel or save the new person. Designing this was really interesting, as I found that nesting containers using their constructor, not a with statement, allows event bindings to bind correctly to the main container. This nested structure is surprisingly quite a bit like the Flutter child/children structure. Horizontal( Label("Name: "), Input(placeholder=self._name, id = "name") ), In order to make the table reload, I just switch back to the menu with a switch command; this works, although that fact that there is no default menu so it returns to the void menu means it isn’t a perfect solution. Part 3: Removing People This was fairly simple; the index for the person is passed into the constructor, so the person’s name can be referenced in the confirmation message. An option to remove the person or cancel is provided. Part 4: Editing People This popup is in part a modification of adding people and removing people. The person’s index is passed into the constructor so they can be found, and text boxes are provided to modify the person’s name. Part 5: Adding people to Spaces I was originally going to stop at adding people to DMs, but I thought that the larger number of people in Spaces would make a better demo. I unfortunately hadn’t actually made it easy to add this to spaces; I just forgot I would need to make this work for the separate Spaces and DMs. Making this separate is required as I store DMs and Spaces in separate dictionaries, and the command to return to the correct menu needs to be different for spaces and menus. To fix this, I pass a type to the constructor, which is used to determine which dictionary to use, and which call to make when closing the popup
Group Editing I was going to start doing messages in this session, and realised that there was an important element that I was missing: group settings. This is important for one sort of sneaky reason: message history is dated. At the beginning of chat history, there is a small informational message that says when the chat was created, and then all messages must come afterwards. Without defining a set start time, there would be no way to validate the dates set when new messages are added. To go along with this, I also added the ability to set the name for spaces. I am also storing if the archive is a DM or Space in Archive objects so I know what fields to ignore when saving occurs. Part 1: Date and Time Entry Date and time entry is split into a box for each Month, Day, Year, Hour, Minute, Second. Each of these has a corresponding label to detail what each field is for. All of these are arranged in a Horizontal container so that they line up. I also experimented with data validation; I at first started to right my own validation classes, then realised that textual has a built in validator for numbers. There are a few potential bugs, the biggest is that days are not validated based on the selected month, so entering dates like February 31st is possible. Also, a year that is too late causes an error, it may be something to do with python’s datetime, but it isn’t a priority to experiment with at the moment. Part 2: Name Entry The type (DM or Space) is passed to the container; this is used to enable or disable editing the group. The was I have set this up, the name editing is below the save button name, but I really am trying to get features in and not waste my time on UI for the moment. It works, and for now that is what matters. And speaking of the Save button… Part 3: The Save Button The Save button is fairly simple; while editing, values are stored locally in the container. When the save button is pressed, these values are actually written to the correct archive. Part 4: Silly Mistakes I don’t know what I was thinking today; I made a bunch of silly mistakes. The worst was trying to pass an integer where a string was required. I was setting up the base for the date & time inputs, and as I was thinking about time, so I just tried to input numbers. I got back an error about translation; it took a bit to figure out what was causing the error.
Message Adding Menu In this session, I added the menu for adding messages. This went surprisingly smoothly; I had to learn how to use the Select widget, but most of the other parts of the UI I had already used, or could piece together from other menus. The biggest UI section, the data and time editing, can simply be pulled from the group editing menus. Overall, this session went smoothly, and I was able to focus on how to do make person selection work. Part 1: Person Selection Each message obviously needs a person who sent it; for this I use a Select widget, which is essentially a drop down menu. After my previous problems with passing the wrong type into a field, I wasn’t exactly sure how to make the selection box work; I really wanted to pass Person objects in, as it would remove needing to use a person’s name as a key to try to find the right Person object afterwords. I tried it and… it totally worked. After overriding the str method to return the person’s name and email, the Select widget was functional. Sometimes it is nice when code just works.
Producing Output Zips It is about time to wrap up the archive generator, so it is time to actually generate archives. The basic steps are as follow: Parse the Archives Write the archives to the zip file. Obviously the basics steps are not that complicated; Parsing the archives in the complicated part… Part 1: Parsing Dates I wrote a quick datetime parsing tool for converting to/from strings. The chat archives have a fairly standard format for dates, so it was pretty easy to implement. The only really weird thing is there is a single narrow no-break space character ( U+202F ) for some reason. All other spaces are normal spaces. Part 2: Parsing Archives parsing archives is split into creating group_info.json and creating messages.json. group_info.json contains the details for every person in the group. This includes their name, email, and if they are Human or a Bot. messages.json contains the data for every messages. For each message, there is a json object detailing who created the message (name, email, and user type), and further data of when it was created, the message text, and some ID values. Message IDs Each message in the main group gets its own topic ID; messages in threads use the topic ID of the message the thread is linked to. The message’s own ID is created using the Group ID, the Topic ID, and an ID unique to the message (if it is a top level message, this is the same as the topic ID). #Part 4: Writing to Archives This is a fairly simple step; the most interesting part is generating the archive name. I based it on the actual exported zip names by making the name chronovisor-generator_takeout- + the time it was exported.
Making Exporting Functional This will be the final session (for now) on the archive generator. There are still plenty of bugs, but after this session, it is at the point where it is actually functional, so I merged the generator into the main branch. According to GitHub, this is a Python project now. Part 1: object Message has no attribute “person” Most of the time in this session was spent trying to fix this attribute error. This is where using textual makes debugging slightly more difficult, as there is no terminal to print to. There is a debug terminal that can be attached, but it still is more complicated. I tried to make Labels pop up with debug information, but the never appeared or caused more errors. Part 2: Logging I got so frustrated with the attribute error that I added logging to the entire application. I started to write my own logging script, but then learned that Python comes with a logging library. I initalised it to use all logging level, although I only use Debug and Info. This helped fix a few other bugs, such as not actually writing messages to the output (once the attribute error was fixed). I used logging to print all the vars in the messages being processed; from this I found that none of the instance variables existed. Part 3: The solution I turned to Google to try to fix my problem. After Googling attribute errors, I saw a suggestion that passing the class, not an initalised instance, could be the cause. As it turns out, when adding a new message to the messages list, I had capitalised the “M”, adding the object not the instance. I spent quite a lot of time for a single character mistake. I’m glad I found it, but it took a frustratingly long time to find for how simple it was. Finished? The archive generator is not done. There are still bugs, it hasn’t been stress tested, the UI looks bad, and only basic messages can be created. But it is functional enough so that I can work on the actual application for the time being.
Extracting Archives Previously, I was planning to load the each archive into memory and process it there, but I have since realised that this is not a great option; Most of the archive is taken up with files other than the main message files (i.e. images), and most of those files will not need to be loaded into memory, as they will not be shown on screen. The down side is of course that this trades ram for storage, but that seems like an okay tradeoff. A fairly large archive I am testing with is only about 1.5 GB. It may be worth adding an in-memory only option later, however. Part 1: Splitting the current function The first thing I did was split the current function I had to load and process the archives into one for processing (which is now incomplete), and another to extract them. Based on the archive library’s docs, it was very simple to convert the in-memory code to extraction code. Part 2: Foldering At this point, the extraction code extracted just fine, but it recreated the folder structure from the zip archive, when all I wanted was the “Google Chat” folder. I decided to give each new loaded archive its own folder, numbered based on when it was added. (The archives do have a user number inside, but finding it would involve reading through the archive while it was in memory, and it didn’t appear to have any benefit.) This is the point where I got stuck for a while; I was attempting to get the number of folders in the directory, and the asynchronous function for getting that number never returned. I spent a while messing with print statements and try/catch statements trying to find a solution. Eventually, I found a synchronous version that solved the problem, as I am not trying to run this async, but I am still not sure what the real problem was. Either way, it is now extracting successfully. I need to make a note to look into this further. Part 3: Screenshot In writing this devlog, I really wasn’t sure what to put as a screenshot; nothing about the Ui has changed. Eventually I settled on showing the output save directory. to demonstrate how it properly extracts to a new folder. Part 4: Time I’m honestly not sure exactly what went on with he time on this devlog. When I started working on extraction, there were 10-15 minutes (it has been a while since I looked at the exact number) already listed‚ but i couldn’t’ figure out what I had been working on; I ended up reverting those changes and starting over. After that I got sick and went to a Bible school, so it has been a while since I made any meaningful progress. I am fairly certain that the time on this devlog is too high, but I am not exactly sure why (maybe the time I spent trying to work when I wasn’t feeling well?). Either way, my apologies, and I’m hoping to get this project back on track in the next devlog....
|
1793242318.2276962
|
1793847118.2276962
|
|
8688
|
RC Off-Roading Car by @Wyatt_ | Stardance
|
Wyatt_
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: RC Off-Roading Car by @Wyatt_ | Stardance
|
1793256461.4271462
|
1793861261.4271462
|
|
8689
|
My awesome macropad! by @iamvetruvian | Stardance
|
iamvetruvian
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: My awesome macropad! by @iamvetruvian | Sta Title: My awesome macropad! by @iamvetruvian | Stardance...
|
1793213284.0726976
|
1793818084.0726976
|
|
8690
|
Untitled project by @vargitom | Stardance
|
vargitom
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Untitled project by @vargitom | Stardance
|
1792734648.4633787
|
1793339448.4633787
|
|
8691
|
resea by @jithesh_sarvin | Stardance
|
|
1
|
17.27
|
165
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-06-09T13:28:04Z", "hours": 16.0, "multiplier": 10.69, "payout": 165, "blessing": "neutral", "text": "What did you make? Research Feed — a personalized research paper discovery app. FastAPI backend + Next.js frontend, lightweight crawler pipeline (arXiv + OpenAlex) that populates a SQLite DB, a recommender/feed engine, and a demo deployment so people can try the site. What was challenging? Making the whole pipeline robust end-to-end: deduplication, citation backfill, handling OpenAlex/semantic‑scholar rate limits, purifier rules so we don’t delete useful papers, and getting Next.js + API routing right behind nginx on the Nest host. Deployment constraints (Nest environment, no Docker initially) added friction too. What are you proud of? A working interactive demo with a live crawler and ≈570 papers, a usable frontend (feed, search, paper pages), an automated demo deploy flow (Docker/demo scripts + non‑Docker bootstrap), and fixes for tricky issues (routing, citation backfill, offsets, and safer purifier logic). What should people know so they can test your project? Open the demo: https://jithesh.hackclub.app Try these flows: browse Home feed, click Search, open a paper, open the PDF (external), save a paper to Library, and use the refresh button on the feed. API checks: /api/stats (paper count), /api/feed, /api/papers/:id return JSON. If the UI shows JSON, hard-refresh (Ctrl+Shift+R) — nginx was fixed so UI routes should render HTML. To repro crawler/DB behavior (server access): tail logs at /root/research-feed/logs and manage services with systemctl (research-feed-api, research-feed-frontend). Report any broken link, missing PDF, or pages that show raw JSON and I’ll fix it quickly. Want me to craft a one‑paragraph summary for the project page or the “What did you make” field?", "repo": "https://github.com/Lunarcolony/ReaSea", "demo": "https://jithesh.hackclub.app"}]...
|
[{"post_id": 12053, "ts": "202 [{"post_id": 12053, "ts": "2026-06-19T17:41:36Z", "hours": 0.3608, "text": "thank you for using this website. I updated the architecture for better recommendation"}, {"post_id": 6243, "ts": "2026-06-10T14:22:08Z", "hours": 0.905, "text": "added a new cashing system which helps save data locally in the browser so that each person gets a individualized feed in the demo website."}, {"post_id": 5575, "ts": "2026-06-09T12:41:30Z", "hours": 8.4961, "text": "devlog-4 • Crawlers: OpenAlex and arXiv jobs pull open-access papers (min 5 citations) while offsets are persisted in crawler_offsets.json so each topic resumes where it left off. • Canonical catalog: All crawled papers land in data/papers.db, ensuring every service references the same SQLite store even when multiple components run concurrently. • Enrichment: Each new row undergoes OpenAlex metadata backfill, DOI/arXiv linking, and topic/authorship tagging so everything downstream sees complete content. • Metrics + embeddings: classification.metrics computes trending/hybrid scores and embeddings.pipeline generates vector representations that let the feed rank novelty and relevance. • Purifier: db_purifier.py runs after enrichment, removing paywalled, duplicate, or incomplete papers while keeping PDF/ArXiv URLs up to date. • Feed cache: Once the catalog is clean, FeedCache stores session rows and shown IDs so the API can quickly respond without re-running heavy scoring on every request. • API: FastAPI’s /feed handler calls build_feed(db, refresh?, client_seen_ids) which builds a FeedContext containing seen IDs, soft/hard exclusions, and user-interest signals. • Feed logic: The engine pulls candidates via _select_from_query, applies feedback-weighted scores, injects high jitter on refresh, and enforces seen-paper penalties so every refresh reshuffles without repeating the exact same order. • Frontend: The YouTube-style carousel shows hero/trending/high-impact rows, pulls seen IDs from localStorage, calls api.getFeed(refresh, seenIds), and records events (click/save/dismiss) to teach the feed what must never reappear. • Feedback loop: User events plus the refresh button feed back into feed.build_feed() via soft/hard exclusions, ensuring fresh papers are promoted while clicked/saved items stay hidden forever."}, {"post_id": 5325, "ts": "2026-06-09T01:54:14Z", "hours": 2.1086, "text": "Smarter Fallback: If you have seen absolutely every paper in the catalog, the algorithm will now smoothly fall back to showing you the best papers you’ve already seen rather than giving you a completely blank screen. Clearer UI & Reset Button: I updated the frontend so if you ever somehow run out of papers again, it will proudly tell you “You’re all caught up!” and give you a button to Clear your read history so you can start over while the crawlers hunt for more."}, {"post_id": 5232, "ts": "2026-06-08T22:52:39Z", "hours": 2.0489, "text": "I have updated the program and intereface to look better, it now has custom themes better crawler and more"}, {"post_id": 3889, "ts": "2026-06-06T23:55:52Z", "hours": 2.9272, "text": "This is like Netflix, but for research papers. It crawls the internet to index research papers and classifies them by topic. Then, a feed-based ML algorithm filters and displays a personalized list based on the user’s activity."}]...
|
Title: resea by @jithesh_sarvin | Stardance
Devlo Title: resea by @jithesh_sarvin | Stardance
Devlogs:
This is like Netflix, but for research papers. It crawls the internet to index research papers and classifies them by topic. Then, a feed-based ML algorithm filters and displays a personalized list based on the user’s activity.
I have updated the program and intereface to look better, it now has custom themes better crawler and more
Smarter Fallback: If you have seen absolutely every paper in the catalog, the algorithm will now smoothly fall back to showing you the best papers you’ve already seen rather than giving you a completely blank screen. Clearer UI & Reset Button: I updated the frontend so if you ever somehow run out of papers again, it will proudly tell you “You’re all caught up!” and give you a button to Clear your read history so you can start over while the crawlers hunt for more.
devlog-4 • Crawlers: OpenAlex and arXiv jobs pull open-access papers (min 5 citations) while offsets are persisted in crawler_offsets.json so each topic resumes where it left off. • Canonical catalog: All crawled papers land in data/papers.db, ensuring every service references the same SQLite store even when multiple components run concurrently. • Enrichment: Each new row undergoes OpenAlex metadata backfill, DOI/arXiv linking, and topic/authorship tagging so everything downstream sees complete content. • Metrics + embeddings: classification.metrics computes trending/hybrid scores and embeddings.pipeline generates vector representations that let the feed rank novelty and relevance. • Purifier: db_purifier.py runs after enrichment, removing paywalled, duplicate, or incomplete papers while keeping PDF/ArXiv URLs up to date. • Feed cache: Once the catalog is clean, FeedCache stores session rows and shown IDs so the API can quickly respond without re-running heavy scoring on every request. • API: FastAPI’s /feed handler calls build_feed(db, refresh?, client_seen_ids) which builds a FeedContext containing seen IDs, soft/hard exclusions, and user-interest signals. • Feed logic: The engine pulls candidates via _select_from_query, applies feedback-weighted scores, injects high jitter on refresh, and enforces seen-paper penalties so every refresh reshuffles without repeating the exact same order. • Frontend: The YouTube-style carousel shows hero/trending/high-impact rows, pulls seen IDs from localStorage, calls api.getFeed(refresh, seenIds), and records events (click/save/dismiss) to teach the feed what must never reappear. • Feedback loop: User events plus the refresh button feed back into feed.build_feed() via soft/hard exclusions, ensuring fresh papers are promoted while clicked/saved items stay hidden forever.
added a new cashing system which helps save data locally in the browser so that each person gets a individualized feed in the demo website.
thank you for using this website. I updated the architecture for better recommendation
Ships:
What did you make? Research Feed — a personalized research paper discovery app. FastAPI backend + Next.js frontend, lightweight crawler pipeline (arXiv + OpenAlex) that populates a SQLite DB, a recommender/feed engine, and a demo deployment so people can try the site. What was challenging? Making the whole pipeline robust end-to-end: deduplication, citation backfill, handling OpenAlex/semantic‑scholar rate limits, purifier rules so we don’t delete useful papers, and getting Next.js + API routing right behind nginx on the Nest host. Deployment constraints (Nest environment, no Docker initially) added friction too. What are you proud of? A working interactive demo with a live crawler and ≈570 papers, a usable frontend (feed, search, paper pages), an automated demo deploy flow (Docker/demo scripts + non‑Docker bootstrap), and fixes for tricky issues (routing, citation backfill, offsets, and safer purifier logic). What should people know so they can test your project? Open the demo: https://jithesh.hackclub.app Try these flows: browse Home feed, click Search, open a paper, open the PDF (external), save a paper to Library, and use the refresh button on the feed. API checks: /api/stats (paper count), /api/feed, /api/papers/:id return JSON. If the UI shows JSON, hard-refresh (Ctrl+Shift+R) — nginx was fixed so UI routes should render HTML. To repro crawler/DB behavior (server access): tail logs at /root/research-feed/logs and manage services with systemctl (research-feed-api, research-feed-frontend). Report any broken link, missing PDF, or pages that show raw JSON and I’ll fix it quickly. Want me to craft a one‑paragraph summary for the project page or the “What did you make” field?...
|
1793256557.575552
|
1793342957.575552
|
|
8692
|
RC plane with Pi pico as FC by @Amik | Stardance
|
Amik
|
0
|
1.02
|
0
|
[]
|
[{"post_id": 2940, "ts": "2026 [{"post_id": 2940, "ts": "2026-06-05T20:57:28Z", "hours": 1.0244, "text": "I am continuing modelling of the plane in CAD, the rough outer model is almost done, only a part of tail section remains"}]...
|
Title: RC plane with Pi pico as FC by @Amik | Star Title: RC plane with Pi pico as FC by @Amik | Stardance
Devlogs:
I am continuing modelling of the plane in CAD, the rough outer model is almost done, only a part of tail section remains...
|
1793220918.5941193
|
1793825718.5941193
|
|
8693
|
My first slack bot! by @Dwwda | Stardance
|
Dwwda
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: My first slack bot! by @Dwwda | Stardance
|
1793386441.6218023
|
1793991241.6218023
|
|
8694
|
Untitled project by @vurigekarel | Stardance
|
vurigekarel
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Untitled project by @vurigekarel | Stardanc Title: Untitled project by @vurigekarel | Stardance...
|
1792741843.990543
|
1793346643.990543
|
|
8695
|
NULL
|
NULL
|
0
|
0.0
|
0
|
[]
|
[]
|
|
1792943477.2030451
|
1793807477.2030451
|
|
8696
|
Untitled project by @kingofpixel1887 | Stardance
|
kingofpixel1887
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: Untitled project by @kingofpixel1887 | Star Title: Untitled project by @kingofpixel1887 | Stardance...
|
1792759764.8810441
|
1793364564.8810441
|
|
8697
|
My awesome macropad! by @danish1 | Stardance
|
danish1
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: My awesome macropad! by @danish1 | Stardanc Title: My awesome macropad! by @danish1 | Stardance...
|
1792759876.8297553
|
1793364676.8297553
|
|
8698
|
OrbitBot by @LilSaadt | Stardance
|
LilSaadt
|
1
|
1.0
|
3
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-06-07T18:58:53Z", "hours": 1.0, "multiplier": 2.94, "payout": 3, "blessing": "neutral", "text": "I built Orbit Bot, a Slack bot that pulls live NASA APOD images and adds space-themed features like facts, an oracle, and a launch animation. The main challenge was handling Slack Block Kit formatting and making sure external APIs like NASA didn’t break the bot when responses failed or were rate-limited. I’m most proud of the design and pushing through to complete the project, aswell as doing some extra error handling. To test it, just run the slash commands like /nasapic, /orbitfact, and /orbitlaunch inside Slack.", "repo": "https://github.com/FrenchV/OrbitBot", "demo": "https://hackclub.enterprise.slack.com/archives/C0B8TB0CZ34"}]...
|
[{"post_id": 3896, "ts": "2026 [{"post_id": 3896, "ts": "2026-06-07T00:08:57Z", "hours": 0.7211, "text": "Orbit Bot — Dev Log Built a simple Slack bot that pulls in space-related APIs and adds a bit of personality on top of them. What’s in so far The bot runs on Slack Bolt (Socket Mode) and is wired up with a handful of slash commands and one mention handler. /nasapic pulls the NASA Astronomy Picture of the Day and displays it with a formatted Slack block (image or video depending on the response type) /orbitfact returns a random space fact from a small built-in list /orbitoracle gives a randomized “cosmic answer” for fun decision making /orbitcatfact pulls live data from the Cat Fact API /orbitlaunch simulates a multi-stage launch sequence using timed message updates /orbitping checks responsiveness and returns latency /orbithelp lists all available commands in a clean help panel APIs used NASA APOD API (Astronomy Picture of the Day) Cat Fact API Both are pulled using Axios and handled with basic error fallback messages. Structure Everything is currently in a single file for simplicity. Commands are separated clearly and use Slack Block Kit for formatting. Environment variables handle tokens and API keys: Slack bot token Slack app token (Socket Mode) NASA API key What I learned / ran into Slack Block Kit formatting matters a lot more than expected, small mistakes can break whole messages Socket Mode makes local testing way easier than dealing with public endpoints Keeping responses short is important"}, {"post_id": 3705, "ts": "2026-06-06T19:15:53Z", "hours": 0.3392, "text": "Dev Log — Orbit Bot 🚀 So I am currently trying to build a small Slack bot called Orbit Bot that shows cool NASA space images and simple space commands, just a fun cozy vibe. I set it up in Slack, connected it to Node.js, and got it responding to basic test commands like /ping to check if it’s alive and learned how to call API’s like for cat images. I learned how bots listen to messages, how APIs send data, and how to fix things when nothing shows up (which happened a lot at first). Next steps are adding more space commands like planets, random facts, and maybe daily space images so the bot feels more alive and fun to use. (Also designing the bot aswell)"}]...
|
Title: OrbitBot by @LilSaadt | Stardance
Devlogs: Title: OrbitBot by @LilSaadt | Stardance
Devlogs:
Dev Log — Orbit Bot 🚀 So I am currently trying to build a small Slack bot called Orbit Bot that shows cool NASA space images and simple space commands, just a fun cozy vibe. I set it up in Slack, connected it to Node.js, and got it responding to basic test commands like /ping to check if it’s alive and learned how to call API’s like for cat images. I learned how bots listen to messages, how APIs send data, and how to fix things when nothing shows up (which happened a lot at first). Next steps are adding more space commands like planets, random facts, and maybe daily space images so the bot feels more alive and fun to use. (Also designing the bot aswell)
Orbit Bot — Dev Log Built a simple Slack bot that pulls in space-related APIs and adds a bit of personality on top of them. What’s in so far The bot runs on Slack Bolt (Socket Mode) and is wired up with a handful of slash commands and one mention handler. /nasapic pulls the NASA Astronomy Picture of the Day and displays it with a formatted Slack block (image or video depending on the response type) /orbitfact returns a random space fact from a small built-in list /orbitoracle gives a randomized “cosmic answer” for fun decision making /orbitcatfact pulls live data from the Cat Fact API /orbitlaunch simulates a multi-stage launch sequence using timed message updates /orbitping checks responsiveness and returns latency /orbithelp lists all available commands in a clean help panel APIs used NASA APOD API (Astronomy Picture of the Day) Cat Fact API Both are pulled using Axios and handled with basic error fallback messages. Structure Everything is currently in a single file for simplicity. Commands are separated clearly and use Slack Block Kit for formatting. Environment variables handle tokens and API keys: Slack bot token Slack app token (Socket Mode) NASA API key What I learned / ran into Slack Block Kit formatting matters a lot more than expected, small mistakes can break whole messages Socket Mode makes local testing way easier than dealing with public endpoints Keeping responses short is important
Ships:
I built Orbit Bot, a Slack bot that pulls live NASA APOD images and adds space-themed features like facts, an oracle, and a launch animation. The main challenge was handling Slack Block Kit formatting and making sure external APIs like NASA didn’t break the bot when responses failed or were rate-limited. I’m most proud of the design and pushing through to complete the project, aswell as doing some extra error handling. To test it, just run the slash commands like /nasapic, /orbitfact, and /orbitlaunch inside Slack....
|
1793264035.7971005
|
1793350435.7971005
|
|
8699
|
Whisper-API by @myroslavrepin | Stardance
|
myroslavrepin
|
1
|
13.82
|
0
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-07-01T02:55:16Z", "hours": 13.0, "multiplier": null, "payout": 0, "blessing": "neutral", "text": "I wanted to transcribe my audio meetings into text, but everything I found was either paid or behind a subscription. So I thought, why should I pay for something I can build myself? So I did. The real challenge was getting the model to actually run on a Raspberry Pi 5. With only 4GB of RAM it choked badly at first. I fixed it by splitting the audio into chunks and transcribing them one at a time instead of loading the whole file into memory at once. Drop your audio, get your text back, fully self-hosted, running on a Pi.", "repo": "https://github.com/MyroslavRepin/whisper-api", "demo": "https://whisper.myroslavrepin.com/"}]...
|
[{"post_id": 37382, "ts": "202 [{"post_id": 37382, "ts": "2026-07-30T02:01:50Z", "hours": 0.8217, "text": "minor fixes and deployment"}, {"post_id": 18331, "ts": "2026-06-29T13:11:06Z", "hours": 3.6767, "text": "I was working on deployment and creating Vue.js dashboard"}, {"post_id": 9289, "ts": "2026-06-15T16:23:01Z", "hours": 0.7839, "text": "I was working on deploying to my PI 5 and testing its abilities (it did ended well). Also I added a to_email field."}, {"post_id": 8503, "ts": "2026-06-14T04:16:14Z", "hours": 1.0808, "text": "I finally stopped chasing perfections and shipped MVP. I mean like real MVP. I have almost no UI, but fully working transcription"}, {"post_id": 7812, "ts": "2026-06-13T01:14:46Z", "hours": 6.875, "text": "Just finished building MVP backend"}, {"post_id": 3290, "ts": "2026-06-06T04:36:46Z", "hours": 0.5197, "text": "Created base project portfolio"}]...
|
Title: Whisper-API by @myroslavrepin | Stardance
Title: Whisper-API by @myroslavrepin | Stardance
Devlogs:
Created base project portfolio
Just finished building MVP backend
I finally stopped chasing perfections and shipped MVP. I mean like real MVP. I have almost no UI, but fully working transcription
I was working on deploying to my PI 5 and testing its abilities (it did ended well). Also I added a to_email field.
I was working on deployment and creating Vue.js dashboard
minor fixes and deployment
Ships:
I wanted to transcribe my audio meetings into text, but everything I found was either paid or behind a subscription. So I thought, why should I pay for something I can build myself? So I did. The real challenge was getting the model to actually run on a Raspberry Pi 5. With only 4GB of RAM it choked badly at first. I fixed it by splitting the audio into chunks and transcribing them one at a time instead of loading the whole file into memory at once. Drop your audio, get your text back, fully self-hosted, running on a Pi....
|
1793278079.0815067
|
1793364479.0815067
|
|
8700
|
My first slack bot! by @aarni_heinonen | Stardance
|
aarni_heinonen
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: My first slack bot! by @aarni_heinonen | St Title: My first slack bot! by @aarni_heinonen | Stardance...
|
1792767197.7293913
|
1793371997.7293913
|