Fixing a bug with displaying the next recurrence of a weekly event with the help of automated testing.

On the morning of Sunday, November 9th, 2025, I deployed an update to dmvboardgames.com to show information about groups a user joined and dates of recurring events that a user had an RSVP to attend.

I navigated to my user page shortly afterwards in the morning and saw that it was showing Sunday, November 16th as the next event date for the two events I host on Sundays. Since the current date was November 9th, the next event dates should’ve been displayed on November 9th.

I narrowed down the root cause to the following line of code on the backend. It made the next event date 7 days in the future if the user member page was accessed on the day of an event, regardless of whether the current time was before or after the event start time.

LocalDate nextEventDate = LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.valueOf(eventDay.toUpperCase())));

The test also had the start date in the future. I added a pause if the test was being run at midnight and rewrote the test case with the following code.

LocalDate expectedDate = LocalDate.now();
LocalDate startDate = eventFromDb.getStartDate();
assertEquals(expectedDate, startDate);

Then I rewrote the next event date calculation.

LocalDate nextEventDate = LocalDate.now();
DayOfWeek eventDayValue = DayOfWeek.valueOf(eventDay.toUpperCase());
if(!eventDayValue.equals(nextEventDate.getDayOfWeek())){
nextEventDate = nextEventDate.with(TemporalAdjusters.next(eventDayValue));
}

Afterwards, the test passed.

I then added another test case for an event that was on a day that wasn’t the current one.

After fixing another test case that was incorrectly using the next day as a next event occurrence, all the tests passed.

I merged the changes to the main branch, then deployed to the release branch, and created a new release.

After the release build was complete, I started a production deployment.

After the deployment, my user page correctly showed the next occurrence of the two events I host on November 9th.

Thoughts about the cooperative card game The Gang!

On Wednesday night, I played a cooperative card game called The Gang for around 2 hours. The Gang is a cooperative adaptation of Texas Hold’em where players have to guess how good their hand it relative to other players.

A round of The Gang starts with players drawing 2 cards and then taking a chip to represent how strong they think their hand is. The chips have a number of stars ranging from 1 to the number of players in the game. Then there are 3 more rounds where cards are revealed, players reasses the relative strength of their hand, and then take a chip. During the 4 round, the chips players take have to correctly represent the

There are 1326 possible poker hands. A binary search to find a value in data that has 1326 items would take up to 11 iterations.

There are 6! ways that players can take chips. As a result, the way players represent chips can represent up to 9 bits of information. Players have 3 rounds to take chips before they have to take chips correctly representing the strength of their hand. This represents 27 bits of information, which is not enough to communicate the exact contents of people’s hands, even when considering the fact that the game is played with a standard 52 card deck without duplicates.

I think the fact that players have to communicate the relative strength of their hand over multiple rounds instead of simplify taking chips to communicate the exact cards in their hand is a brilliant design.

View models and JavaScript UI libraries

I was looking at two JavaScript libraries, reactive, and ripple.js that use view models to dynamically render html. This made me think about the design of my framework, places.js, which uses a state based approach like the following example.

import {BaseDynamicComponent} from "@bponnaluri/places-js";
import {IS_LOGGED_IN_KEY, LOGIN_STORE} from "../../data/auth/LoginStore.ts";

export class OpenCreateGroupComponent extends BaseDynamicComponent {
  constructor() {
    super([{
      componentReducer:(data:any)=>{
        return {
          [IS_LOGGED_IN_KEY]: data?.loggedIn
        }
      },
      dataStore:LOGIN_STORE
    }]);
  }

  override getTemplateStyle():string{
    return `  
      <link rel="stylesheet" type="text/css" href="/styles/sharedHtmlAndComponentStyles.css"/>
      <style>
        a {
          color: white;
          text-decoration: none;
        }    
      </style>`
  }

  render(data: any){
    const url = data[IS_LOGGED_IN_KEY] ?
      `html/groups/create.html` :
      `/html/users/createAccount.html?message=Register_an_account_and_log_in_to_create_a_group`

    return `
        <a href ="${url}">Create group</a>`

  }
}

The data passed into the render method is a regular JavaScript object without type checking. For example, in the component below, if I tried to render the url using the templated string <a href ="${data.url}">Create group</a>`, the component would render a link with an undefined value.

Adding a view model would be one way of adding type checking to this code. I think it would also provide additional value for more complex components. However, I don’t think I should add view models to places.js.

The additional code needed to create view models would mean increased costs with maintenance complexity. Having less code means reduced maintenance complexity.

Adding view models to places.js would mean bugs in the framework that would cost time to fix. The extra complexity would also make it more difficult to make necessary updates to places.js in the future.

I want to make the source code of dmvboardgames.com and places.js approachable to anyone with a basic understanding of CSS, HTML, and Javascript. Adding view models would make the code less approachable.

  • The additional code needed to create view models would mean increased costs with maintenance complexity. Having less code means reduced maintenance complexity.
  • Adding view models to places.js would mean bugs in the framework that would cost time to fix. The extra complexity would also make it more difficult to make necessary updates to places.js in the future.
  • I want to make the source code of dmvboardgames.com and places.js approachable to anyone with a basic understanding of CSS, HTML, and Javascript. Adding view models would make the code less approachable.
  • If a developer decides to use view models with places.js, they can extend the BaseDynamicComponent class in places.js to implement view models. As a general rule, I think it is better for other developers to extend the framework to meet the needs of the product they are building. If I implement view models, it is likely that I won’t implement them in a way that precisely meets the needs of other developers. Also, for developers who decide not to use view models, they will be unnecessary complexity. If I add support for the optional use of view models, I’ll have to spend more time maintaining places.js , which will mean I have less time to focus on improving the quality of places.js.

It has been around a month since I last made an update to places.js. Over the past month, I have been actively using places.js to develop new UI components for dmvboardgames.com, and there haven’t been any bugs on the website related to places.js. Modifying a code that has been working reliably for an update that may not be useful is an unnecessary risk.

I’m also making significant changes to the backend to support users joining groups, and I’ll likely be making additional changes. These changes are likely to have bugs, and making changes to places.js will obscure the source of bugs. It is normal to have bugs in software, and it’s impossible to consistently write 100% bug free code on the first try. On the other hand, steps can be taken to make sure the source of a bug can be quickly identified, so that a change can be made to fix it.

If a developer decides to use view models with places.js, they can extend the BaseDynamicComponent class in places.js to implement view models. As a general rule, I think it is better for other developers to extend the framework to meet the needs of the product they are building. If I implement view models, it is likely that I won’t implement them in a way that precisely meets the needs of other developers. Also, for developers who decide not to use view models, they will be unnecessary complexity. If I add support for the optional use of view models, I’ll have to spend more time maintaining places.js , which will mean I have less time to focus on improving the quality of places.js.


To add type safety in the future, I think an alternative solution could be to look into somehow leveraging TypeScript as places.js already uses TypeScript.

In the immediate future, I think updating the constructor to use an updated reducer would be a good way of reducing complexity.

 componentReducer:(data:any)=>{
   let url = "html/groups/create.html"
   if(data?.loggedIn){
     url = "/html/users/createAccount.html?message=Register_an_account_and_log_in_to_create_a_group"
   }
   return {openUrl:url}
 },

Then, the render method could be rewritten as follows.
render(data: any){
 return ` <a href ="${data.openUrl}">Create group</a>`
 }

Using a test driven approach for group membership: Part 3

I’m working on adding functionality to dmvboardgames.com which will allow users to join groups and see information about the groups they are a part of. After implementing backend logic as described in my previous post, I worked on end-to-end testing with the UI.


When doing end-to-end testing, I realized some adjustments needed to be made to the backend to make sure data showed up correctly on the UI. I also deleted some unused code and did some refactoring to improve readability.

As usual, I ran all the backend integration tests to make sure everything was working as expected.



Below is a preliminary version of the user member page with some test data. I plan to update this page to include the day and time of each event and improve the styling.

When working on the UI, I’m relying on the following design choices to help reduce complexity.

  • Using a multi-page web site instead of a SPA(Single Page Application). Since dmvboardgames.com consists of multiple independent web pages, changes are isolated. I don’t have to be concerned about breaking unrelated functionality when I make a change.
  • Isolating component CSS styles with shadow DOM. This allows me to have styles scoped to those components only using standard CSS. Also, if I’m looking for a component style to update, I can find it in the component’s style definition. Here is an example from the member data component.
  • Using a framework for common tasks. On the UI, I use a framework places.js I developed for state-based UIs, managing asynchronous data fetching, and setting up components to use shadow DOM. Here is the source code for places.js: https://github.com/gatherspiel/places-js

Using a test driven approach for group membership: Part 2

I’m working on adding functionality to dmvboardgames.com which will allow users to join groups and see information about the groups they are a part of. After implementing test cases as described in my previous post, I worked on implementing the logic to make sure the tests pass.

Here are a couple of notes about design decisions I made with the code:

  • The service layer logic does permission checking, and calls a UserRepository instance to get the data. While writing unit tests on UserMemberService.java wouldn’t be complicated if I mocked the dependencies, I don’t think they are worth writing. The value of the unit tests isn’t high. On the other hand, the unit tests will have ongoing costs due to making automated tests harder to run, and adding additional code that needs to be maintained. They will also be duplicating test coverage from the integration tests.
  • In the service layer, for the joinGroup and leaveGroup functions, I pass in a user object to the UserRepository instance which runs a database query. Although only the user id is necessary, I decide to pass the whole user object to avoid parameter confusion with the group id which is also an integer.



The code changes are here: https://github.com/gatherspiel/backend/pull/243. The next step will be adding API endpoints, creating a UI, and then testing end-to-end

Using a test driven approach for group membership

I’m working on adding functionality to dmvboardgames.com which will allow users to join groups and see information about the groups they are a part of.

I decided to start on the backend by adding tests and skeleton logic. Here are the tests I added. https://github.com/gatherspiel/backend/pull/243/commits/2954bbfac36ea82b5074a2c606e4f71747060ce1

I then ran the tests, and they all unsurprisingly failed. Later, I will work on finishing the backend code to ensure the tests pass, and I’ll likely make some updates to the tests. I’m finding the approach of specifying the test cases first with placeholder logic to be very useful for figuring out how the code should be structured at a high level. The approach has also helped me avoid writing incorrect code because of a test case I wasn’t thinking about.

Why I think there will always be a need for frontend web developers, regardless of how powerful AI becomes.

As AI models become more powerful, I’ve heard speculation that AI will soon replace frontend web developers. I don’t think this will be the case.

Currently, I don’t use AI at all for development, and think it should be completely avoided for frontend development.  Humans have a fundamental advantage when it comes to understanding how to build a website that fits the preferences of user. This advantage wouldn’t go away, even if we could magically conjure up an AI model that cost nothing to train or run. 

When developing a website, you are making something that users will look at and interact with  People have a variety of preferences for their user experience that can’t be precisely encoded in data, while an AI model needs data. There might also be specific circumstances that affect use of a website such as a college student spending less time on an online game site during final exam week. 

A human developer can talk to likely users to get an understanding of their preferences and circumstances. The developer can then understand how those circumstances will affect someone’s usage of a site and then incorporate that understanding when working on a site.

One example of how automation can be used to be more productive without LLMs.

I’m currently working on developing event RSVP functionality for dmvboardgames.com. Initially, I decided to have separate database tables for users with edit permissions on an event and people who have an RSVP to attend an event.

However, I ran into challenges because of the need to query both tables to display information about an event. Event hosts should have permission to edit an event, and they should also be automatically counted as an event RSVP. Also, event hosts should not have the ability to directly update their RSVP like a regular user, and they should have another UI to step down as event host or make someone else the host.

To simplify the code and the database, I decided to combine event rsvp and event editor permissions into one table. After updating the schema on my local environment, I ran the integration tests, which unsurprisingly had test failures. Within approximately 15 seconds, the test results identified which areas of the code needed to be updated. This will be especially useful for developers who haven’t been closely working with the codebase for months.

I also added a test case to verify that a standard user could not modify an event. Then I set the test to automatically fail as a placeholder until I created the test. This was a reminder that this test had to be run before a production deployment. When attempting to create a build for a production deployment, the build will fail if there are any test failures.

After updating queries and deleting some code, the tests passed. Aside from simplifying the code and database design, the performance was better because I was able to get rid of an extra database query when retrieving event RSVP data.



On the other hand, I maintain a copy of the database schema definition for the unit tests that is not automatically synced with the database migrations that were run in production. There are a large number of migrations that have run, and some of them involve schema changes to existing tables. Running these migrations to set up a test database would make the tests take significantly longer to finish. Each test file recreates the database schema and test data to help ensure that tests aren’t affected by other tests. Manually copying the relevant schema changes is also less complex than an automated copying process.

Also, the local development environment uses the same copy of the database schema definition. This allows for manual testing to help verify that the test schema is in sync with the production schema.

Task specific AI and work

I think AI is useful for specific tasks with inputs and outputs that can be represented as data. That being said, I there are two fundamental problems that make LLMs unsuitable for work outside the costs of creating or running them. First. most tasks cannot be represented by data. Second, LLMs will generate plausible sounding output about things they don’t understand, which makes it hard to tell when using an LLM is useful.

With a task specific AI for something such as spell check, there will be a clear boundary between what the AI can do and what it doesn’t understand. Trying to use a spell check AI for something such as writing legal briefs will result in output that is clearly unusable instead of something that appears to make sense.

This article gives a great overview of why using LLMs should not be used to write legal briefs.https://apnews.com/article/artificial-intelligence-tools-work-errors-skills-fddcd0a5c86c20a4748dc65ba38f77fa


Here are some great examples of task specific AI and when they were created.

Weather prediction(1950): https://www.vos.noaa.gov/MWL/dec_07/weatherprediction.shtml

Spell checker(1961): https://en.wikipedia.org/wiki/Spell_checker

Automated train operation on the DC Metro(1976): https://www.wmata.com/initiatives/plans/Automatic-Train-Operation-ATO/index.cfm

StarCraft:Brood War AI(1998): https://en.wikipedia.org/wiki/StarCraft:_Brood_War

TI-89 Titanium Integral Solver(2004): https://en.wikipedia.org/wiki/TI-89_series#TI-89_Titanium