On Oct 22, I read the word "Merged" twice. It was my first contribution to Forem (the platform that hosts dev.to). I don't write Ruby. A coding assistant wrote the code, and it still took 12 weeks.
To explain why that word mattered, I have to start in 2019.
I kinda discovered dev.to in around April 2019 and at the time I didn't really get into it. It was just one of the many resources I'd search when I wanted to find solutions to bugs or to create features. One time or two it'd show up in my Google search and then I'd click off it. In April 2021 I finally signed up for an account on dev.to.
I heard being social is an important aspect to make in this industry. I knew at the time blogging was the easiest for me. I'd never really built a network in the industry, I'd just started following people on LinkedIn and Twitter and my projects were all unknown. I wasn't really the kind of person who'd self-promote and ask for a job. It just wasn't right for me.
I'm also an introvert. I wasn't really comfortable meeting new people and I was also very conscious of what they think of me. The Nigerian dev communities didn't help either. They were just hostile to newbies like me. I didn't even have a mentor or friend to learn with.
I only ever used the platform to read articles, and I never took the time to write any posts or comments. I didn't even remember my account existed.
In November 2025, I created another account to boost my online visibility. I'd noticed that searching my name in Google only resulted in my LinkedIn and Facebook profiles. So here I am, with 62,275 followers and 109 posts. dev.to is the second most used app on my phone (after X), and I refresh it frequently. dev.to is how I met developers outside Nigeria.
I had been wanting to dig into Forem for months, but because Forem is built on Rails, and I don't write Ruby, I held off. However, when DEV announced the Summer Bug Smash, and Jess Lee's post came out, that was the final push I needed.
The bug fix was small. It took 12 weeks to merge, most of it waiting. Here is how it went.
Looking at the code was not the easiest thing to do. I could not tell which issues I could or couldn't fix, so I used an AI coding assistant to help me figure it out.
Back in July, the repo had 34 open issues. Some of them were not actually bugs. One was a question the maintainers had already answered. One was an account problem that went to support. And none of the issues were labeled "good first issue".
So I chose #23687, which was a small bug: the Posts count on the dashboard was different than the Posts tab. Since the issue description was short, I could follow it.
The coding assistant identified the issue and told me how to fix it: the badge was including the count of statuses and archived posts, while the tab was not. The coding assistant wrote the code fix and the tests, and explained what was going on. I recognized it as a typical JavaScript issue: the count didn't match the list after filtering.
Here is the fix that was merged. First, the sidebar action sets the count:
def sidebar
@user = current_user
@organizations = @user.admin_organizations
@action = params[:state]
+ @posts_count = posts_count_for(@user)
end
Then a helper counts the same posts the tab lists:
# The "Posts" nav item always links to the default (non-archived, full posts
# only) view of the user's own dashboard, so its indicator should reflect
# that same scope rather than the user's raw articles_count, which also
# includes statuses and archived posts that never show up in that list.
def posts_count_for(user)
user.articles.from_subforem.full_posts.where(archived: false).count
end
And the badge reads that number, not articles_count:
- <span class="c-indicator"><%= @user.articles_count %></span>
+ <span class="c-indicator"><%= @posts_count %></span>
The mobile menu got the same one-line change. The rest of the PR is the test.
Since I did not have Ruby installed on my machine, I couldn't run the tests. I created a PR and let the Forem CI run the tests. I was a bit weirded out that I submitted a PR to code I couldn't read line by line.
The CI failed the first time, but the issue was in the test, not the code. The GitHub status also showed "Merging is blocked", and I assumed a maintainer would have to merge it. That was correct. I then signed the CLA, and waited.
After a while, I forgot about it, until I got a notification. Then a reviewer approved the PR on Oct 20 and noticed I had added a line I should not have. So I asked the maintainers if they wanted me to remove it, and two days later Mikey (a maintainer) removed it and merged.
First, I felt relieved, then proud. That evening, DEV sent me the DEV Contributor badge. It was sent to my old account. I remembered about my old account because of the badge email.
Small fixes are still fixes, and now, someone using the dashboard will see the correct post count.
I entered the fix in the Summer Bug Smash on Aug 3 while it was still awaiting review. And about 11 weeks later, it got merged. I did not win the Bug Smash, but this counts as a win to me.
I learned most of what I know about software development from reading open source projects: the code, documentation, and GitHub issues. This is my first contribution to Forem, and nobody asked me where I was from or what my job title was. They just reviewed my change. That is part of why I love open source.
Forem was special, because it was an open source project powering a site I used on a daily basis. The AI coding assistant transformed my side of things. It helped me submit a fix that got merged by a maintainer. And AI changed the other side of things as well. The Forem README welcomes AI-assisted pull requests, and will reject "slop". The repo has an AGENTS.md that outlines how coding assistants should work.
The review on my PR says it was written by an AI agent, and the line I should not have added passed the CI. It only got caught by the reviewer. Both sides now have AI, but it is ultimately up to a human to make a decision. That's where the trust comes in. People still question the utility of AI. After the line that slipped past CI, I understand why. I still could not have sent this fix without it.
AI might take the ability to write code out of the equation entirely. For example, I know when building an API, I need to spin up a server, create a router, set up route paths to point to handlers, serialize and deserialize the body. When learning a new language, I am trying to figure out how to accomplish these tasks, rather than trying to understand what needs to be accomplished. Removing this limitation also means I can develop with the language more appropriate for the system. AI could change the mindset about hiring, from devs who understand their language to devs who understand systems.
If you want to contribute to Forem:
- Don't wait for a "good first issue". There were none available in July.
- Any issue labeled "bug" and "always open for contribution" is up for grabs.
- Verify the issue is a real bug. Some were not.
- Check if the issue has an assignee. Some were already taken.
- Sign the CLA. That's one comment on your PR.
- Add a test. The README says all PRs should include tests.
- If you're unable to run the tests locally, state it in your PR, and let CI run the tests. I did, and the failing tests in my first run turned out to be a good thing.
- Be patient. My PR took 12 weeks.
Special thanks to Jess for posting about the Bug Smash, and for encouraging me to finally dig into the repo; Ben for creating DEV and Forem; Mikey for reviewing my PR, pushing the cleanup, and merging it; and everyone who answered my comments.
Fix dashboard "Posts" count to match what the Posts tab shows
#23690
Fixes the count mismatch described in #23687.
The "Posts" indicator in the dashboard sidebar (_actions.html.erb / _actions_mobile.html.erb) renders @user.articles_count, which is a counter_culture cache on User that counts every Article row belonging to the user, regardless of type_of (full_post, status, fullscreen_embed) or archived state.
But the "Posts" nav link always opens the default dashboard view (DashboardsController#show with no params), which only lists non-archived, full_post-type articles — statuses and archived posts are filtered out.
So any user who has ever posted a status/boost update or archived a post sees a "Posts" badge that's higher than the number of posts actually listed under that tab, matching the report in #23687 ("Posts 2" shown after publishing only 1 post).
Add a posts_count_for(user) helper in DashboardsController that counts articles with the same scope as what's actually displayed (full_posts, archived: false, from_subforem), and use it via a new @posts_count ivar in both the full-page render (#show) and the AJAX sidebar partial (#sidebar) instead of the raw counter cache. User#articles_count itself is left untouched since it's used elsewhere (badges, spam heuristics, etc.) where the broader "all articles ever" semantics are correct.
Added regression specs in spec/requests/dashboard_spec.rb covering both the #show and #sidebar actions: a user with one full post, one status, and one archived post should see posts_count of 1.
Note: I wasn't able to run the Rails test suite locally (no Ruby/Postgres environment on this machine), so I'm relying on CI to confirm the specs pass — flagging that openly in case a maintainer wants to double check before merging.
Need help on this PR? Tag
@codesmith-bot with what you need. Autofix is disabled.
Top comments (1)
Nice little story describing how you went about this!
Very pragmatic approach - you didn't bother (at least that's what I assume) getting the whole Forem application running on your dev box ... :-)
Maybe you even didn't install Ruby or Rails (because you don't know Ruby anyway) ... :-)
For this (admittedly simple) issue, AI allowed you to bypass all of that!