You are browsing as a guest. Sign up (or log in) to start making projects!

F3rro_32

@F3rro_32

Joined June 12th, 2026

  • 36Devlogs
  • 7Projects
  • 4Ships
  • 30Votes
Open comments for this post

6h 47m 25s logged

Hey guys, how’s going? Soon school is going to start for me, but luckily it’s my last year in high school, so I’m really happy about this.
Lately I had much time to code, so I continued working on this project.
Last devlog was all backend; I said the engine was there and the screens weren’t. This time it’s quite the opposite. I didn’t really touch the backend, I spent the last 5-6 hours writing the frontend.

Here’s the details of what I did:

  • Document lines the grid is finally in the record detail. You can add lines, set quantity, price, discount and VAT, additionaly with the subtotal / VAT / total block, that updates while you type. When you save, the whole set of lines is sent at once.

  • Tags I added the tag picker on the record detail, so you can attach and remove tags from a record as you wish. I also made the page that shows every record with a given tag, from all the modules together.

  • Duplicates The duplicate finder now has its own screen: you choose a module, you get the pairs it thinks are duplicates, and the you see the two records side by side and pick field by field which value to keep before merging them.

  • Reports There’s a page to build a report with a wizard (rows, columns, metrics, filters) and a preview that shows you the result before saving it.

  • Dashboard widgets This is the main thing of this cycle. I wrote all six widget types, which are:
    - KPI tile
    - split KPI tile
    - chart
    - table
    - pivot
    - record list
    A widget is just a saved report query plus the type of rendering you want, so it runs on the report engine.

  • Dashboards with tabs You can create a dashboard, add tabs to it and put widgets inside, so you can keep for example sales and support separated from each other.

  • Sidebar and topbar The sidebar now has the routes for all the new pages, and the topbar has a quick-create button to make a record from anywhere without opening its module first.

Two things are still not nice and I’m aware: in some places the relation fields show the raw id instead of the name of the record, and the totals of the document lines are not formatted with the user preferences yet, so you read 9054.00 instead of 9.054,00 EUR. Both are small and both are on the list to be improved.

Now I’m working on the PDF generator

See you, I hope you enjoy this project

0
0
18
Open comments for this post

8h 5m 42s logged

Hey guys, how it’s going? These days I’ve got quite a lot of work to do, since we’re in the middle of harvest season here in Italy, so I don’t have much time to work on the project.
Unfortunately, today it rains, so I’m home, and I can spend some time on the PC.

Lately I’ve been working a lot on the backend.
The first two things I did are documents and document numbering: I created the model for document rows (DocumentLine), with its engine to calculate totals, including subtotals, discounts and VAT. I also added unit tests for the calculator.

I also implemented automatic document numbering, so documents like quotes, invoices and tickets are automatically assigned a number when they’re created.

Then I worked on quote revisions, with a new QuoteRevisionService to keep track of different versions of the same quote.

Another thing I started working on is document conversion. The idea is to be able to convert a quote into an invoice, but this part is still a work in progress.

I also spent quite a bit of time on reports and dashboards:

  • added the Report model and the ReportEngine / ’ReportService`
  • added default reports through a ReportSeeder
  • created the Dashboard model, with tabs and widgets
  • added the related controllers, services and repositories

On the frontend side I’ve scaffolded the pieces for dashboards and widgets — the widgets/ and dashboard-tabs/ components exist and the dashboard and report services are written — but the components themselves are still empty shells. The data layer is ready, the screens aren’t written yet. That’s what I’ll pick up next.

Another big thing I worked on is tags and data quality.

I added the tag model and APIs, together with the services and repositories. I also started working on a duplicate finder and record merging system, so duplicate contacts/companies can be detected and merged instead of leaving messy data around.

For PostgreSQL, I also enabled pg_trgm to improve fuzzy searches for duplicate detection.

Finally, I added user preferences for number and date formats, including a live preview so users can immediately see how their chosen formats will look in the app.

I’ve also added icons for the new modules.

As you can probably tell, this was a very backend-heavy cycle. Almost everything I built this time: document lines, numbering, quote revisions, reports, dashboards, tags and duplicate detection lives in the models and the services, and the UI for most of it isn’t there yet. So this time there isn’t much to show in screenshots: the engine is in place, the screens come next. I’d rather get the model right first and build the interface on top of something solid than the other way around.

Hopefully I’ll be able to find some more time to work on it after harvest, so see you in a while.

0
0
13
Open comments for this post

1h 1m 59s logged

Hey guys, how you doing?
Today I finished the 3 services, geo, weather and forecast.

After that I proceeded filling the widget components, as you can now see in the screen.

I also imported everything in app.ts and displayed in app.html. This app has no other routes rather than /, so I don’t need the file app.routes.ts.

I’ll now improve the signal and fix some small details, along with the README, but we’re quite there.

0
0
9
Open comments for this post

1h 8m 22s logged

Hey guys, how are you? I proceeded creating the services and components I need for the project.

I then focused on the main feature of the app, the wave.

I made a component called waveform, that basically creates the form, and with the scope component, it manages all the things related to the wave.

When typing, the wave will do some things, you’ll see.

See you in the next devlog

0
0
11
Open comments for this post

26m 23s logged

Hey guys, how it’s going? I’m currently working on the Give your website a pulse mission. I decided to make a simple page, but at the same time very minimal and useful.
I started by creating the angular app (I explained in IMPLEMENTATION-CHOISES every single choice I made, mostly the one discording with the guide provided) and dockerizing the application.

I then focused on installing TailwindCSS and started the CSS, with this basic grid, which will be the background. Stay tuned for further devlogs

0
0
5
Open comments for this post

4h 59m 46s logged

Hey guys, back on this project. Last time I said the next round was going to be document lines, my quote was “the only part of this CRM that doesn’t fit the dynamic JSON engine”. That’s what this round was, plus the report engine on top of it.

Document Lines: One single table for all four documents: quote, sales order, delivery note, invoice. The line is exactly the samein all of them, the only thing that changes is the document holding it, and a document here is just a Record like any other, so there’s no type constraint on the line at all.
A line can be a product line, a free text line (no amounts, just something to say in the middle of the document) or a subtotal.
Two things get copied at save time: the description and the vat percentage.

The math is its own class, DocumentLineCalculator, and it’s the one piece of this project I wanted testable completely on its own, because it’s the only place where a wrong rounding produces a wrong tax document.
The order is fixed:

  1. gross = quantity × unit price
  2. discount = a percentage of the gross, or a flat amount
  3. net = gross - discount, rounded to 2 decimals here
  4. vat = net × rate, rounded
  5. line total = net + vat

Rounding happens after the discount and after the vat, never on the unit price: round the unit price and the error gets multiplied by the quantity. Rounding mode is HALF_UP, which is the commercial rounding used here, not HALF_EVEN. And a flat discount bigger than the line itself gets clamped instead of producing a negative amount, since that’s a typo 100% of the time.

Lines are always saved all together. The endpoint is a PUT that replaces the whole set. It deletes and rewrites instead of diffing row by row, because a document has a few dozen lines at most and the diff would cost more code than it’s worth. The reason for this rule is the totals: they depend on every line, so a partial save would leave the header and the lines disagreeing, even if only for a moment.

The totals (subtotal, vatTotal, total) get written back into the document’s own JSON, plus the header level discount and shipping cost. They live there so they can be list columns you filter and sort on, which is the whole point of having them.

Now I’m working on the dashboard graphs, widget and this kind of things.I hope you like the project.

I’m sorry if I don’t have any screenshot of the working website, it’s because I focused only on the backend, the frontend of this part still needs to be written.

0
0
15
Open comments for this post

6h 25m 25s logged

Hey guys, still on this project. This round was all about the stuff that sits on top of the modules, instead of new modules.

  • Per-user formatting preferences. There’s a new “My preferences” page in the user menu. Every user picks how they want to see numbers and dates: thousands separator, decimal separator, currency (any ISO code, the symbol gets resolved automatically), how many decimals, whether the symbol goes before or after the number, and whether to cut trailing zeros. Same thing for the date format. There’s a live preview box that updates while you touch the selects, so you see 1.234,50 € turn into $ 1,234.5 as you change this. The setting applies everywhere, lists, record details and so on.

  • Tags. These are cross-module, which is the whole point; a tag can sit on a lead and on a ticket. There are two kinds: shared ones, visible to the whole workspace, and private ones, which only you see. Each one has a color. You can attach and detach them from any record, and there’s a “all records with this tag” view that goes across modules, filtered by your permissions, so if you can see tickets but not opportunities, you only get the tickets.I did not build this as a field type, even though I already have a TAGS one. That one belongs to a single ObjectType with its own fixed options, so it gives you neither the shared catalog nor the cross-module search.

  • Duplicate merge. You pick a module and it gives you back the candidate pairs, ranked by how similar their titles are (there’s a threshold you can move, default 0.6). Then you get the two records side by side and choose, field by field, which value survives. When you confirm:

    • the winner keeps everything you picked,
    • every relation that pointed at the loser now points at the winner,
    • ids of the loser mentioned inside other records get rewritten too, including the ones stored in lists,
    • notes and tags move over (and if the winner already had that tag, the duplicate link is dropped),
    • the loser goes to the trash, not deleted. A wrong merge has to be recoverable, and I already have a bin, so I used it.
  • Document numbering. Documents now number themselves. It’s configurable per module: prefix, whether to include the year, padding, separator.

Right now it’s on for three of them:
- quotes → 2025-47
- invoices → FT2025-0001
- tickets → TT643

The counter is per module and per year, and it’s locked while it’s being read, so two people creating a quote at the same time can’t get the same number. If you type a number by hand, yours wins, that matters when you’re entering an old document with its original number.
On top of that, quotes have revisions. Revising 2025-13 gives you 2025-13_2, a brand new document that links back to its parent, while the parent moves to the “revised” stage. And you can revise a revision, so 2025-13_2_2 works too, the counter is progressive per parent, not global, which took a bit more care than I expected.

Next up is the big one: document lines. Quote → order → delivery note → invoice, with quantities, discounts and VAT per line. It’s the only part of this CRM that doesn’t fit the dynamic JSON engine everything else is built on, so it gets its own real tables.

See ya in the next devlog, I hope you like the project.

P.S. you’ll see the tag in the next devlog, I still have to implement the frontend

0
0
17
Open comments for this post

5h 34m 30s logged

Hey guys, I’m still working on this project, cause there are lots of things to do.Lately I focused on improving and adding things to the ObjectTypes.
As you can see in the two images of the sidebar, I added like 10 fields.
Among them, the most important are

  • goals, basically contains the possibility of creating a goal, setting an owner, which is responsible for this goal (clearly depends on the situation), the actual goal and the period. Along with that you can set a metric, so if the goal is revenue, opportunities, leads, custom
  • project_task, basically it contains the instruction for each member, on a certain project
  • service_contract, the support, it contains the subject, the company which is referred to, start and end date, the actual status, so active, expiring, expired, terminated, hours spent and so on.

Along with these and the other I didn’t cite here, I added 3 object for the sales, where this two are the most important:

  1. The first one is the supplier, it contains all the specs, so name, vat, email, phone, website, address, and eventual notes.
  2. The second one, the most important, is sales_order, which contains the subject, the number of the order, a relation with the company, and a relation with the contact, a relation with the quote (I don’t know if it’s right, in italian it’s called preventivo), the possibility of setting a status, so created, confirmed, shipped, closed, cancelled, the order date, a relation with the payment Method object, and all the financial part, so the subtotal, with maybe the discount, vat, the total and the infos for the shipping, billing and shipping address.

I hope to finish this project asap, since it’s taking me so looong.See ya in the next devlogs

0
0
1
Open comments for this post

5h 15m 31s logged

Hey guys, I have stopped this project for a bit, but here I am again. I’m currently improving the CRM, mostly the security, which, for me, is one of the most important parts, and adding functionalities.

Lately I focused on the mails. I added my smtp server, and in the backend, I managed all mails. Here is an example, with the invitation email. It’s in italian, but don’t worry, basically it invites you to the CRM, and then you can create your own account, with password. As you can see in the third screenshot, I can manage the invitation link, whether I want to invite an admin, or a simple employee.

1
0
51
Open comments for this post

1h 7m 3s logged

Just fixed a little bug, the same songs kept coming back whenever you played the same artist twice. That’s because the artist track pool was too small, so every game used it up entirely. I fixed this and also added a nice thing, suggested by @stef. I improved the hints, now it’s easier to guess a song after hints 2 and 3.
I hope you enjoy playing

0
0
4
Open comments for this post

5h 55m 14s logged

Hi guys, here we go again with another devlog.
Lately I worked a lot, so this devlog will be pretty long.
Let’s start with the first piece, the IDT. I made three files: idt.h with the structs and the prototypes, idt.c with the actual table, and idt_flush.s to load it. The table has 256 entries, one for each interrupt vector.

Then I worked on the exceptions raised by the CPU. I created 32 stubs in isr_stubs.s, one for each exception.

I also had to work on the hardware interrupts. I worked in the file io.h, with I/O ports. I used outb and inb, which are inline assembly, for managing ports in C, since C doesn’t have a way for managing them.

Another big part is the PIC 8259 with the remap. When booted, there’s IRQ 0-7 on vectors 8-15. In protected mode there’s already double fault and page fault, so those vectors are already taken. But without remap, when you type a key the kernel sees it as “coprocessor segment overrun”. A problem is that the chip is really old, so it’s older than the numbering it collides with.
There’s one more thing about the PIC, and it’s the end of interrupt. After it sends you an interrupt, it doesn’t send the next ones until you tell it you’re done, by writing 0x20 to its command port. So I have to do that at the end of every IRQ handler. If you forget it you get exactly one interrupt and then nothing, forever, and the annoying part is that nothing looks broken: no error, no crash, the first interrupt works perfectly.

The last piece is how the handler goes back, and it’s probably the most interesting one.
I would have used ret, like in a normal function, but it doesn’t work here. That’s because ret only restores eip, while an interrupt also pushed cs and eflags. Eflags is where the IF flag lives (the one that says if interrupts are enabled or not) and the CPU turns it off by itself when it gives you the interrupt, so that your handler doesn’t get interrupted in the middle.. So with ret interrupts stay off forever and you never get another one.

The solution is iret. And right before it I have to do add esp, 8, because the vector number and the error code were pushed by me, not by the CPU, and iret knows nothing about them.

To test all of this I unmasked IRQ 0, the timer, and I didn’t have to configure the timer at all: the BIOS had already programmed the PIT, so it was ticking the whole time and the interrupts started coming as soon as I enabled them. The screen fills up with IRQ raised: 00. That screenshot proves three things at the same time: the remap worked, because the interrupt arrives on vector 32 and not on 8; the EOI is getting through, otherwise there would be one single line; and iret puts the stack back exactly as it found it, a few hundred times in a row without missing once.

I’m so happy for this work. Look forward for the next steps

0
0
7
Open comments for this post

5h 2m 41s logged

First devlog where the screen looks exactly the same before and after.
Turns out that’s the interesting part: the GDT is the table the CPU reads on every single memory access, and doing it right means nothing visible happens.

What I built

  • A real VGA text driver (vga.c): putchar, write, line wrap, \n, and scrolling. The surprise: there is no hardware scroll in VGA text mode, scrolling means copying all 2000 cells up one row yourself and blanking the last one. I’d always assumed the terminal did that for you, it’s a for loop.

  • The GDT (gdt.c, gdt.h): three entries: a null one (mandatory, so an uninitialized segment register faults immediately instead of doing something random), one for code, one for data. Base 0 and limit 4GB on both, the flat model. You can’t turn segmentation off on x86, so you make it irrelevant instead.

  • Replacing GRUB’s table.: GRUB leaves you a working GDT, otherwise the kernel wouldn’t run at all, but it lives in GRUB’s memory and GRUB is gone. My stack grows, .bss expands, an allocator shows up eventually, and one day something writes over it. From that moment the CPU reads garbage on every memory access.

  • Shift and mask code for a 40 year old compatibility hack.: A descriptor is 8 bytes with the base address split into three non-contiguous chunks and the limit split in two. The 286 had 24 bit addresses in 1982, the 386 needed 8 more bits of base and 4 more of limit and couldn’t move any existing field without breaking every OS already written, so the new bits got wedged into the spare bytes at the end. __attribute__((packed)) isn’t optional here, one byte of padding and the CPU misreads everything.

  • The assembly loader (gdt_flush.s): lgdt plus reloading all five data segment registers, because each one caches the descriptor internally and changing the table doesn’t touch registers already loaded.

  • A jump that goes nowhere.: mov cs, ax does not exist: cs says which segment your code is in and eip says where in it, two halves of one address, so changing only cs lands the next instruction fetch at the old offset inside the new segment. They have to change together, and the only instruction that does that is a far jump. The idiom is jumping to the very next line. I disassembled it to convince myself: the jump sits at 0x15, is 7 bytes long, ends at 0x1c, and its destination is 0x1c. The jump is the vehicle, loading cs is the cargo.

  • Proof that something invisible happened. Nothing changed on screen so I asked the CPU through the QEMU monitor: GDT= 001042f4 00000017, and nm says my table is at 001042f4 with sizeof(gdt) - 1 = 23 = 0x17. CS reads [-R-], readable not writable, DS reads [-WA], writable not executable. I set the data access byte to 0x92 and the CPU reports 93: that’s the accessed bit, hardware turns it on by itself the first time the segment is used.

Look forward for the next steps

0
0
1
Open comments for this post

3h 22m 28s logged

Booted my first kernel today. It’s just a black screen with a white “F” in the corner, but it’s my black screen with my white “F”, printed by code I wrote, with no OS underneath.

What I built

  • A cross-compiler from source. i686-elf-gcc doesn’t exist on a normal Linux install, you build it yourself from binutils + gcc source, targeting a “fake” freestanding platform (no OS, no libc assumed).
    I wrote the whole build script in bash, which I’d genuinely never written before this project.

  • A Multiboot2 header (boot.s, NASM assembly) the exact magic number, checksum, and end tag GRUB looks for to recognize a bootable kernel. Also set up a stack (the CPU doesn’t give you one for free) and the _start entry point that hands off from assembly into C.

  • A linker script (linker.ld) that tells the linker to load the kernel at 1MB — the first chunk of physical memory not already claimed by legacy BIOS/VGA stuff.

  • kernel.c — writes directly to the VGA text buffer at physical address 0xB8000 (2 bytes per character: ASCII + color attribute). No printf, no stdlib, just a raw pointer and a cast.

Look forward for the next steps

0
0
3
Ship

This project contains three microprojects, all related.
There’s the slack_bot, already shipped for the mission.
There’ s the ds_bot, improved from it’s first version in flavortown.
And there’s a newborn, a website, containing everything necessary for the bot, with also docs, for hosting the bot locally.
For making the design of the website, since I’m not that great with graphics, I used v0, which generated mockups for me. That’s why the website Ui seems so AI.

  • 1 devlog
  • 7h
  • 14.66x multiplier
  • 98 Stardust
Try project → See source code →
Open comments for this post

6h 42m 57s logged

Spent today hardening VinylBot for production: fixed a crash bug where an unhandled ffmpeg/stream error in the audio player could take down the entire bot for every server at once, added global uncaughtException/unhandledRejection handlers to both bots, fixed a race condition in the Slack round timer, and cleaned up leaked temp mp3 files on failed downloads.

Also dockerized the Angular website (nginx + multi-stage build) and got all three services (Discord bot, Slack bot, website) live on their own subdomains behind the VPS firewall.

For the problem of the streaming on server or vps I decided to add two methods for gathering audio mp3s. The first time it will try with spotify, searching for the preview_url. After that it will try deezer and Itunes, that represents a valid alternative.

0
0
3
Open comments for this post

47m 54s logged

I don’t know why, but the db crashed, so the backend wasn’t working and many of you couldn’t test out the app.
I just fixed this problem, and added in docker compose the restart clause, so things like this won’t happen again.
Sorry for the problem

0
0
4
Open comments for this post

1h 58m 28s logged

Hey guys, today I’m working on fixing the ds bot. First of all I had to review the method of streaming the song.
I was first using yt-dlp for everything, but I noted that putting it on a server was making it not working anymore. I discovered that is because yt blocks certain ips and certain requests, so this was an impossible choice. The solution was spotify preview, which are basically like 20 seconds of a song. That’s perfect for me, since I don’t have to stream the whole song, but instead just a little part.

The approach is this. I created a variable in the env, called DEPLOY_ENV. This specifies if we’re running the bot locally, or on a server or vps.

If the variable is vps, first the bot is gonna try finding the song on spotify. If it’s not present, it’s gonna search for local files, as alternative.

If the variable is local, the first alternative remains spotify, but as a second there’s also yt-dlp, since locally it works fine.

I also adjusted the cards of some commands, as you can see from the screens, along with testing out the bot.

I still have some more work to do, but I think soon the ds bot will be ready.

0
0
5
Loading more…

Followers

Loading…