What We Ask in Engineer Interviews to Assess "Product Development Skills"

Hi! I’m @fortkle, Tech Lead and Engineering Lead for the Avvy division at AnotherBall. Alongside product development, I also conduct first-round interviews for server-side engineers.

Recently, I made a small change to how I ask questions in those interviews. It was not a major redesign of the interview process. I simply split what had previously been a single design question into several stages.

Looking back at why I made this change, I realized that it reflects what we are trying to assess when we talk about an engineer’s “development capabilities.” In this post, I’ll introduce one of the questions we actually use and explain the thinking behind it.

Trying a Bit of Product Development During the Interview

In our first-round interviews for server-side engineers, we set aside time to discuss product development scenarios that could realistically arise in Avvy. I take the role of a Product Manager (PdM), bring up an idea whose details have not yet been worked out, and ask the candidate to think as a server-side engineer at AnotherBall.

During the interview, I tell them:

From here, I’ll speak to you in the role of an Avvy PdM.
Please think as an AnotherBall engineer and ask me anything you need to know.

I answer their questions in the role of the PdM. Based on those answers, the candidate digs deeper, organizes the underlying needs and assumptions, works out what we should build, and then moves on to technical design. For a short time during the interview, we try to work through product development together as we would at AnotherBall.

For example, we might use scenarios like these:

  • Show the gift ranking and each user’s current position in real time during an event
    • In Avvy, users can watch a streamer’s live stream and send gifts while the stream is live.
    • Avvy regularly holds events in which users compete in rankings based on points earned through support such as gifts.
  • Create an event that only users who meet certain conditions can join
    • For example, the conditions might include “started their first stream during a certain period” or “earned at least a certain number of coins.”
  • Build a mechanism that prevents League demotion on days when a streamer cannot go live
    • Avvy has a system called the League, which is similar to a ranking system for streamers. A streamer’s League goes up or down based on their daily streaming results.

We are not asking candidates to guess Avvy’s current implementation or the architecture we use. We want to understand what they ask when given an ambiguous request, how they interpret the PdM’s answers, and how they update their initial ideas through the conversation.

In this post, I’ll use the first scenario—showing a gift ranking and the user’s current position in real time during an event—as an example.

“When a Gift Is Sent, I Want the Ranking to Update in Real Time”

For this example, assume that users compete in a ranking based on gifts sent during the event. In the role of the PdM, I make the following request:

When a gift is sent, I want the ranking to update in real time.

The detailed specifications have not been decided yet. If you were a server-side engineer at AnotherBall, how would you proceed?

We Were Trying to Assess Too Many Things with One Question

Previously, after presenting this request, I would simply ask, “How would you proceed?” or “How would you design it?” Actual product development can also begin with an unstructured request raised in a casual conversation, so at first glance, this seemed like a realistic question.

However, with the previous approach, we had not clearly separated the information that the interviewer should provide upfront from the information we expected the candidate to uncover through questions. We were also trying to assess several things from a single response: the ability to understand and organize the underlying need, technical design ability, and the ability to consider failure scenarios.

As a result, when something was missing from a candidate’s answer, it was difficult to tell whether they had failed to ask for necessary context, whether the interviewer had not provided enough information, or whether there was an issue with the technical design itself.

Without separating these concerns, we were effectively asking candidates to solve an under-specified problem however they liked.

For example, suppose someone hears the request and immediately starts discussing a design using Redis or WebSockets. Does that mean they are not good at clarifying the underlying need? Or did they simply assume that the requirements had already been decided because the interviewer had presented it as a design question?

On the other hand, asking a large number of detailed questions is not necessarily better. If the PdM’s answers do not change the candidate’s next questions or their design, that is also different from the kind of conversation we have in actual product development.

Looking at the question again, we were trying to assess three broad capabilities:

  1. Can the candidate identify the underlying need and the necessary conditions from an ambiguous request?
  2. Can they produce a technical design based on clarified conditions?
  3. Can they design for recovery when failures such as missing or duplicate processing occur?

We therefore decided that it would be better to assess these in separate stages rather than trying to see everything through one large question.

So We Split the Question into Three Stages

We now use the same scenario but divide the discussion into three main stages.

1. What Would You Ask the PdM First?

We do not immediately ask for a technical design.

The PdM says, “When a gift is sent, I want the ranking to update in real time.”
What would you ask the PdM first? Please feel free to ask me anything while I act as the PdM.

At this stage, we look at whether the candidate tries to turn the still-ambiguous phrase “real-time ranking” into concrete conditions needed for the design. For example, the conversation might touch on questions like these:

  • Why do we want the ranking to update in real time?
  • Who should see it, and on which screen?
  • How much delay would still count as “real time”?
  • Are temporary inconsistencies in the displayed ranking acceptable?
  • Does the final ranking need the same level of immediacy?

However, we do not grade candidates using a checklist of whether they asked every one of these questions. We also look at whether they can dig deeper based on the PdM’s answers and update the hypothesis they initially had.

2. We Give Everyone the Same Conditions and Ask Them to Design the System

After discussing the underlying need and assumptions, the interviewer gives every candidate the same set of conditions. For example:

  • The ranking may be delayed by around 10–30 seconds.
  • The live-stream screen displays the current user’s position.
  • The event ranking page displays the top 100 users.
  • Gift traffic may increase significantly near the end of the event.
  • Temporary inconsistencies in the displayed ranking are acceptable.
  • The final ranking must be accurate because it is used to determine rewards.

We then ask:

Given these conditions, how would you design the server-side system?

We give all candidates the same conditions because we want to assess their ability to understand the need separately from their technical design ability. If candidates design the system using only the information they personally managed to uncover, differences in how much information they obtained would also change the difficulty of the design problem itself.

In the first stage, we observe the conversation with the PdM. In the second stage, we provide the same conditions and discuss how the candidate would design a system around them. We are not evaluating whether they can name Redis or a particular cloud service.

Where and how should the confirmed gift transaction be recorded? How should the ranking used for display be generated? Should the live display and final ranking calculation be separated? We ask why they chose a particular design under the given conditions.

3. Finally, We Introduce One Failure Scenario

After discussing the normal flow, we add a single failure scenario:

The gift transaction succeeds, but updating the ranking fails.
What should the design take into account to prepare for this situation?

Here, we look at whether the candidate separates the gift record from the ranking displayed on screen. Can the failed operation be retried? Can the same operation run multiple times without applying the result twice? Can the ranking be recalculated from the authoritative record?

There is much more I would like to write about this stage, including the separation of authoritative and derived data, retries, and idempotency. To keep this article focused, I’ll leave those topics for another time.

In a real system, every operation does not always complete as expected. Being able to preserve data through a failure and recover afterward is also an important part of a server-side engineer’s development capabilities.

Why Do We Start by Asking, “What Would You Ask the PdM First?”

Of these three stages, what I particularly want to discuss in this article is why we begin by asking, “What would you ask the PdM first?” Being able to produce a technical design from completed requirements is, of course, important. However, in actual product development, requirements such as “updates must appear within ten seconds,” “temporary inconsistencies are acceptable,” and “the final ranking must be accurate” are not always neatly defined at the beginning.

The starting point is often something like:

I want to update the ranking in real time.

This is still a request whose background and purpose have not been fully articulated. From there, the PdM and engineer talk, understand what they are trying to achieve, and turn it into concrete technical conditions. We consider that process to be part of an engineer’s development capabilities.

Requirements Are Made Concrete Through Conversation

I previously wrote a post titled “Understanding the Need, Not Just the Requirements.” As I discussed there, I distinguish between a “need” and a “requirement” as follows:

  • Need: The state or value we want to create for users or the business
  • Requirement: A concrete condition the system must satisfy to meet that need

The request, “When a gift is sent, I want the ranking to update in real time,” may look like a fairly concrete requirement. In particular, the phrase “real time” makes it easy to see the problem as a purely technical one: how should we implement a real-time ranking?

However, at this point, we do not know how many seconds “real time” means or why the ranking needs to update so quickly. What the PdM actually wants to achieve might be something like this:

We want users to feel the excitement of the event by seeing their gift immediately affect their position in the ranking.

Only after understanding this need can we discuss whether the experience fails unless the update appears within one second, whether a delay of ten or thirty seconds is acceptable, whether temporary inconsistencies are acceptable, and how accurate the final ranking needs to be.

In other words, the phrase “real-time ranking” already contains part of a proposed solution to the underlying need.

Is That Requirement Really Necessary?

One idea that stayed with me after reading The Agile Samurai, a popular introductory book on agile development, was that calling something a “requirement” can cause it to be treated as indispensable and non-negotiable from the outset.

For example, if we accept “real time” as a fixed requirement, the conversation immediately becomes about how to build a real-time system. On the other hand, if we go back and ask why real time is needed, we can identify which conditions are truly necessary to meet the underlying need.

The conversation may reveal that an update within one second really is important. If so, we should treat that as a requirement and design accordingly. But if a delay of ten to thirty seconds still delivers the intended experience, we gain more options for aggregation and client updates. We may be able to provide a sufficient experience while reducing load and cost.

Understanding the underlying need is not only about empathizing with users. Knowing what must be protected and what can be relaxed gives us more technical options and helps us make better trade-offs.

Between a Need and a Requirement, There Is Conversation

The flow in this example can be summarized as follows:

The important point is not that the engineer hears the need and then defines the requirements alone. The PdM understands the user experience and business outcome they want to create. The engineer understands how technical difficulty, system load, and cost change depending on the conditions. By bringing that knowledge into the conversation, they can define feasible requirements that still meet the underlying need.

One way we build this kind of shared understanding in Avvy development is through user story mapping. When considering a new feature or experience, we lay out how users move through Avvy and discuss which experiences matter most and how much we should build in the current scope, with the PdM, designer, and engineers participating together.

One thing I learned from Jeff Patton’s book User Story Mapping is that creating shared understanding through conversations around stories is more important than simply writing a polished specification and handing it off. The completed map is valuable, but personally, I find even greater value in the conversations that happen while creating it and the shared understanding the team develops through that process.

The interaction in an interview cannot reproduce actual product development exactly. Even so, rather than having the PdM hand over completed requirements, starting from an ambiguous request lets us briefly experience a process that matters in our day-to-day development.

Looking back, this conversation was exactly what we wanted to see by beginning the interview question with, “What would you ask the PdM first?”

Conclusion

When people talk about an engineer’s development capabilities, they often think of programming, databases, infrastructure, and architecture. These are all important, of course. In the question introduced here, after clarifying the need and assumptions, we also discuss the technical design and how the system should behave when something fails.

At the same time, actual product development does not always begin with completed requirements. It also involves starting from an ambiguous request, talking with PdMs and designers, understanding the value we want to deliver to users, and making what we should build concrete while communicating technical constraints and options. We consider that process to be part of the development capabilities we want to assess in interviews.

This does not mean that engineers should make every decision in place of the PdM. It means bringing the PdM’s understanding of users and the business together with the engineer’s technical knowledge through conversation.

You are absolutely welcome to read this article before interviewing with us. The scenario changes from interview to interview, and what we want to understand is not whether you already know the correct answer, but how you change your questions and design based on the PdM’s responses. If you think through these ideas beforehand, I believe we can have an even deeper conversation.

Of course, this format has its own issues. It favors people who can put their thoughts into words on the spot, and it may be tough for those who think best when given time to reflect. We plan to keep improving the format — for example, by letting candidates share their thinking with diagrams and charts rather than words alone.

For a short time during the interview, we try developing a product together as if we were working at AnotherBall. We will continue using actual interviews to make this a better opportunity for both sides to understand each other.

We’re Hiring!

AnotherBall is looking for engineers who can understand the underlying product need, communicate across roles, and work with the team to turn ideas into reality. If you are interested in product development at Avvy, please take a look at our open positions!

AnotherBall Careers

References

  • Jonathan Rasmusson, The Agile Samurai: How Agile Masters Deliver Great Software
  • Jeff Patton, User Story Mapping: Discover the Whole Story, Build the Right Product

AnotherBall Is a Gold Sponsor of DroidKaigi 2026!

Hi there! We’re the mobile engineering team at AnotherBall.

AnotherBall is a gold sponsor of DroidKaigi 2026!

We’re bringing a booth too! You can build an avatar in Avvy and walk away with an instant photo of it. And on September 3 at 12:20, RIO (@rioX432) from our mobile team takes the stage in Meerkat.

Event Details

  • Official site: DroidKaigi 2026
  • Dates: Tuesday, September 1 – Thursday, September 3, 2026
  • Venue: Bellesalle Shibuya Garden (Sumitomo Fudosan Shibuya Garden Tower 1F / B1, 16-17 Nanpeidaicho, Shibuya-ku, Tokyo 150-0036)
  • AnotherBall booth: September 2 (Wed) and 3 (Thu)

We Build a Live Streaming App Called Avvy

We build Avvy, a VTuber avatar and live streaming app: build a 2D avatar on your phone alone, then start streaming with no camera and no gear. You combine 2D illustration parts into an original avatar, and the phone camera tracks your face and expressions to drive it. Viewers send gifts during a stream to cheer the streamer on.

Avvy's avatar builder and streaming screen (from the official Avvy site)

Here’s what the Android side runs on. Business logic is shared with iOS through Kotlin Multiplatform (KMP), the UI is Jetpack Compose, avatar rendering runs on Unity as a Library (UaaL), and face tracking goes through MediaPipe. A game engine and native UI share one app, so we hit walls a typical app never runs into.

Our last conference sponsorship was Kotlin Fest 2025, where we came in at the silver tier.

September 3, 12:20, Meerkat

RIO is talking about a wall we hit in Android face tracking.

Face tracking accuracy shows up directly in the quality of the avatar’s expression. When the streamer smiles, does the avatar on screen smile the same way? The user experience turns on that, so once we knew the accuracy wasn’t there, turning back wasn’t an option.

The session is aimed at:

  • Engineers already using MediaPipe in an Android app, or weighing it up
  • Anyone hitting ML inference or sensor data whose quality varies from device to device
  • Anyone curious about face tracking or motion capture
  • Anyone after patterns for abstracting hardware-dependent features in KMP

Your Avatar, Printed on the Spot

The centerpiece of the booth is building your avatar in Avvy.

Pick a hairstyle, then eyes, then an outfit, and you have your own avatar. Switch to camera mode, make a face, and take one shot — the avatar makes the same face. That photo gets printed right there as an instant photo in a DroidKaigi 2026 original design, in a sleeve you can attach to your name card.

The instant photo and holder you take home, plus the AnotherBall sticker we hand out (sample)

Start to finish it takes about five to six minutes. If you’re short on time, grabbing a sticker and moving on is completely fine.

Every staff member is wearing an instant photo of their own avatar on their name plate. Engineers will be there too!

Come Talk Shop

Posts the mobile team has written so far.

If any of them catch your eye, come find us at the booth. How we embed Unity in a native app, how we build the real-time side of live streaming — getting into that with people who find it interesting is the best part of showing up at a conference.

Come build your avatar, come talk to us at the booth. The mobile engineering team will be at Bellesalle Shibuya Garden on September 2 and 3 — see you there!

We’re Hiring

AnotherBall is looking for mobile engineers who want to build apps with Kotlin, Unity, and AI. We’re picking up KMP and Compose Multiplatform as we go, and we’d love to grow the product together.

Grab a staff member at the booth, or apply through the links below.

An Airtight Team Is Built Beyond Boundaries ── What to Do With the Time AI Frees Up

An Airtight Team Is Built Beyond Boundaries

Hi! I’m Tsuji, a server-side engineer at AnotherBall.

Most days I’m writing backend code, or chasing an infrastructure alert down a rabbit hole and shaving a yak somewhere along the way.

What to Do With the Room AI Frees Up

How is AI working out for you these days?

AnotherBall is no exception — we use AI company-wide, for development and for plenty of things that aren’t development.

How AI Put Our Company Into BAKUSOKU Mode

Once AI is part of how you work, a question shows up that everyone seems to be asking: what should humans be doing? The answers you hear back tend to sound like “contribute to the bottom line” or “create real value.”

For years, engineering teams have measured themselves with Four Keys[1], which came out of Google’s DORA research.

DORA is a research program run by Google Cloud that keeps asking the same question year after year: what does a software team need in order to deliver value quickly and reliably?

Held up against that question, I’m not sure Four Keys still works as a yardstick in the AI era.

DORA has changed the metrics several times since 2023.

  • 2023: MTTR was renamed and redefined as failed deployment recovery time
  • 2024: a fifth metric, deployment rework rate, was added, taking the set from four to five. The groupings were reorganized into Throughput and Instability
  • 2025: the report was renamed, from Accelerate State of DevOps Report to State of AI-assisted Software Development Report
  • Sources: DORA’s software delivery performance metrics[2] / A history of DORA’s software delivery metrics[3]

You can read that as a shift in emphasis, from how fast you move to how little you break. Which makes it hard to argue that the right thing to do with the freed-up time is pour it straight back into development.

Does Faster Development Mean Faster Value?

Let me answer my own question.

The short version: development getting faster doesn’t make value arrive faster.

Have you read The Goal by Eliyahu M. Goldratt? Great book, and worth asking your company to expense.

The idea at its center is the Theory of Constraints: unless you deal with the constraint in the flow of work, what you do never shows up in the result.

So when development speeds up, or an individual does, and that wasn’t the constraint, the output of the organization stays where it was.

Widening development alone doesn't change total flow

Back to DORA for a moment, because the reports show the same pattern.

  • 2024: for every 25% increase in AI adoption, delivery throughput fell 1.5% and delivery stability fell 7.2%
  • The same survey found individual-level measures rising across the board: productivity +2.1%, flow +2.6%, and so on
  • Individuals got faster. Delivery as a whole did not. That is the same shape as the argument in The Goal
  • 2025: the relationship with throughput flipped from negative to positive, while the relationship with instability stayed negative
  • Sources: Accelerate State of DevOps Report 2024[4] / State of AI-assisted Software Development 2025[5] / Announcing the 2025 DORA Report[6]

These are correlations drawn from a survey, not causation. The direction still lines up with what the Theory of Constraints predicts: individual speed and delivery speed come apart.

Which is why the Avvy team runs Scrum, uses Linear, and put a custom kanban on top of it so bottlenecks are visible.

Using AI Coding to Detect Team Stagnation: Building a Custom Chrome Extension Kanban

A ring on each assignee icon shows how long a card has been sitting

Bottlenecks move around, so pointing at one and calling it permanent doesn’t work. On our team, though, the hard and high-priority work had a habit of piling up on the PdM.

The Weight of the PdM Role

Job titles mean different things at different companies.

On the Avvy team, the PdM holds the company vision, the product’s philosophy, the goals for the quarter, and a pile of other variables in their head at once, then turns abstract strategy into something concrete. It’s a hard job.

If working across levels of abstraction interests you, this book covers it well (in Japanese).

賢さをつくる 頭はよくなる。よくなりたければ。

The obvious move is to spend the freed-up time helping out. Given how hard the role is, it isn’t that simple.

Even where help is possible, you don’t want to end up with the PdM, who owns the outcome, losing track of the product and the people.

Crossing Boundaries Without Leaving Your Post

Long before AI showed up, I’d landed on this as the thing that matters on a cross-functional team: keep your responsibility clear, and cross function lines.

Crossing function lines is what people usually mean when they talk about crossing boundaries.[7]

For the reasons above, though, crossing one isn’t easy. So here’s a case where it happened naturally on the Avvy team.

It started when someone who uses our internal admin screen came to me. They’d written up a set of improvements and wanted a second opinion.

Internal tools like an admin screen aren’t so much low priority as never written down at all. Someone has to shape the work into an Epic before it can even be ranked against anything else. Until that person shows up, the work never starts.

The kanban I mentioned earlier surfaces cards that are sitting still. What was never on the board stays invisible.

This had been bothering me for a while, so a PdM and two engineers sat down and went through the list.

Looking at it, the requirements and the design needed a bit more work, but the cost looked low and the value to the people using it looked high. That let me say: we’ll gather the requirements on the engineering side and come back with a proposal for what goes into the sprint.

Structurally, engineers picked up the context, shaped an Epic, handed the conclusion back to the PdM, and then carried it out. It starts with the PdM and returns to the PdM, which is the same shape as a SubAgent in AI terms.

The parts that matter:

  1. The final call stayed with the PdM
  2. The PdM knew what the engineers were doing while they did it
  3. I kept doing my own job

The third one is the one to protect. It’s what respect for the person whose territory you stepped into actually looks like.

I know nothing about American football, but there’s a post I’ve kept coming back to for years.

The post is in Japanese, so here’s the gist. American football has an anti-pattern called over-pursuit: leaving the role you were assigned to go stop the ball yourself. What’s embarrassing about doing it as an adult player, the author says, isn’t the deviation itself. It’s that the deviation says you don’t trust your teammates.

When something you could fix is sitting right there in front of you, it’s hard not to reach for it, and reaching for it feels like the right call. That post is what made me see that it isn’t always.

The difference between over-pursuit and crossing a boundary

On Beyond Boundaries

I like this phrase, which is AnotherBall’s vision, enough that I once told our CTO @tatsushim we should be using it in more places. The boundaries aren’t only the national kind. They sit between teams and between people too.

This post has been about crossing them, and about what’s on the other side.

It’s never easy. Boundaries vary in height, and some can’t be crossed, some don’t need to be, and some shouldn’t be.

Getting past that difficulty is where the trust, the results, and the sense that the work was worth doing come from.

Stack up enough of those crossings and the gaps in a team start to close. Responsibility is clear, so nobody wonders whose job something is. Functions overlap, so when someone lets go, somebody else can catch it. An airtight team is probably just that state.

Keep your responsibility clear, cross function lines. If that’s the one line you take away from this, I’ll be glad.

Responsibility and function are different axes

We’re Hiring

AnotherBall is full of people who want to build something good and want the team around them to be good too. The door is open, so come talk to us.

AnotherBall Careers

References

  1. Four Keys
  2. DORA’s software delivery performance metrics
  3. A history of DORA’s software delivery metrics
  4. Accelerate State of DevOps Report 2024
  5. State of AI-assisted Software Development 2025
  6. Announcing the 2025 DORA Report
  7. How to Cross Boundaries (in Japanese)

WWDC26 In-Person Report ── Three Passion-Filled Days!

WWDC26 In-Person Report

Introduction

Hi! This is the iOS team at AnotherBall.

From June 7 to 9, 2026, one of our iOS engineers attended WWDC26 in person in the US, flying in from Tokyo. Below is their first-hand report!

About WWDC and the Special Event

WWDC (Worldwide Developers Conference) is Apple’s annual developer conference. If you’re in the Apple Developer Program, you can enter a lottery, and the developers who win are invited to the Special Event on site.

The acceptance email. I could hardly believe it when it arrived...

Every session is streamed online, but on site you can talk directly with Apple engineers at the In-person Labs and mingle with developers from around the world. That’s value you can’t get from the stream!

By the way, at AnotherBall, attending tech conferences counts as work, so I got to go to WWDC on company time. I’m grateful to work somewhere that supports these chances to learn.

Schedule

Here’s the list of events I attended. I’ll walk through the details along this timeline.

All times are Pacific.

Sun, June 7 (Day 0)

  • 4:00 PM~ Welcome Reception @ Apple Infinite Loop Campus

Mon, June 8 (Day 1)

  • 10:00–11:30 AM Keynote @ Apple Park
  • 1:00–2:00 PM Platforms State of the Union @ Apple Park
  • 2:15–4:00 PM In-person Labs @ Apple Park
    • 2:45–3:15 PM Design Lab
    • 3:15–3:30 PM App Review Lab

Tue, June 9 (Day 2)

  • 10:00–11:30 AM Developer Session @ Steve Jobs Theater
  • 11:30 AM–3:00 PM Mixer @ Apple Developer Center Cupertino
  • 4:00–6:00 PM What’s new in iOS 27? by Paul Hudson @ Residence Inn by Marriott San Jose Cupertino
  • 8:00–10:30 PM The Mandalorian and Grogu @ Steve Jobs Theater

Day 0 ── Welcome Reception

The day before the Keynote, there was a welcome event. The venue was Apple’s old headquarters, the Apple Infinite Loop Campus.

Entrance of the Apple Infinite Loop Campus

By the time I arrived around 3:30 PM there was already a long line, and the place was buzzing. It took about 30 minutes to get in.

The registration line at the Welcome Reception

What personally surprised me was the “hospitality” of the Apple staff. When I scanned my badge QR code at registration and walked in, they clapped loudly, gave high-fives, and cheerfully shouted “Welcome.” In an instant I fell in love with this place. On the way out it was the same. As you can see in the video below, they gave me a grand send-off 😂.

At the Reception, Apple served food and drinks in the courtyard, and I could sit on a bench and chat with other developers at a relaxed pace. The grass felt way too good…

Reception in the courtyard

What stuck with me most was a conversation with one of the Swift Student Challenge winners. He’d built a video app that always records in the right orientation, even when you switch between the front and back camera on a walk or rotate the phone. He solved an everyday problem beautifully with an app — a lovely example.

A big tree gave us shade, which was nice. The California sun was really strong.

There was also a world map of where attendees came from, with everyone pinning their hometown. As you’d expect, people came from all over the world. California is multicultural to begin with, with people of every background, so even visiting as a foreigner I never felt out of place.

The world map showing where attendees came from

Day 1 ── Keynote and Platforms State of the Union

Finally, Keynote day! And my first visit to Apple Park. This was the day I was most excited about.

Apple Park

I arrived at the Apple Park Visitor Center around 8:30 AM, finished registration, and entered Apple Park.

My first impression: “This is huge!” The four-story ring-shaped campus building was bigger than I imagined, and the curves were beautiful.

Apple Park under a blue sky

A short walk later, the Keynote venue came into view!

The outdoor stage of the Keynote venue

The outdoor stage felt very open, and the impression was completely different from the conference-room presentations I’m used to. It was refreshing.

Looking back at the Apple Park ring building, overwhelmed by its scale

Breakfast was served at Caffè Macs (the staff cafeteria), and it was delicious.

A top-class free breakfast

I heard Caffè Macs serves over 12,000 meals a day to employees from around the world, which gives you a direct sense of the scale.

Keynote

Before the 10:00 AM live stream, Craig Federighi and Tim Cook came out and got the crowd going. For Tim Cook in particular, this being his last WWDC before stepping down as CEO, there was huge applause and cheering.

Tim Cook appears. "I've never seen this many iPhones in my life," he greeted us

After Tim Cook’s greeting, the video began and the whole venue watched it live.

This year’s updates focused on AI integration, and the ties with the iOS ecosystem felt stronger. Honestly, though, the reaction in the venue wasn’t great. In my opinion, it mostly brings things other AIs can already do into iOS. A solid, evolutionary step, you could say.

Watching the live stream together with the whole venue

Lunch Break and App Icons

After breakfast, lunch was provided for free too.

After eating, I sat down in the grass area I couldn’t get to during the Keynote. When I glanced up at the screen, panels of all kinds of app icons were showing.

The icons flipped one by one, and the overall color gradually shifted to the next theme color.

Could it be… is Avvy’s icon in there? I watched closely for the moment it turned orange…

Look closely at the bottom right of the orange panel...

And there it was — the “Avvy” app icon appeared!

The Avvy icon appears!!

I jumped out of my chair to take a selfie at that moment 😂

This was the exact moment Avvy was seen by people around the world. I felt that Apple really recognizes each and every participant as a contributor, and I came to like Apple even more ❤️

Platforms State of the Union

The afternoon session on Day 1 was developer-facing, walking through the new features and APIs in concrete detail.

Summary of Platforms State of the Union

App Intents, Core AI, Swift / SwiftUI improvements, the AI-agent-driven Xcode 27 — they’ve all evolved. I especially like that Xcode 27 supports AI agents. Being able to finish work entirely inside Xcode is a huge help. I tried the beta, and with no more switching between Xcode and the CLI, the context-switching cost dropped a lot!

In-person Labs (Design Lab / App Review Lab)

In this session, Apple engineers are split up by topic, and for two hours you can ask anyone whatever you like. Personally, this lab is the highlight of WWDC. There’s nowhere else you get to talk directly with engineers you’d otherwise never connect with.

Venue map

Of the various labs, the Design Lab and App Review Lab require advance booking. I managed to get reservations, so I headed over when the time came.

At the Design Lab, I had a 30-minute 1-on-1 with an Apple designer and got to talk through a specific new feature — a really valuable time. Rather than handing me advice on how to build the feature, they thought it through with me from a user-story angle: who feels the value, how to present it to the target users, and so on.

As an engineer, I tend to focus on how to build something, but I was reminded that I need to think clearly about the “Why” first.

The Design Lab was on the 3rd floor, overlooking the venue The courtyard and the Rainbow Stage

At the App Review Lab, I asked about TestFlight External Testing and how to manage TestFlight apps, and with other Apple engineers I talked through Xcode Previews issues and Foundation Models. The Apple engineers were all easygoing and high on hospitality, which left a strong impression.

Reception in the Inner Ring

After the sessions, the grass inside Apple Park opened up, and I got to take photos with the famous Rainbow Stage.

Rainbow Stage!

The grass area was so wide that I lay flat on it once and felt the sheer power of that vast ground. It felt great (lol).

Of course there was food too 😋

Day 2 ── Developer Sessions & Mingling

Day 2 had sessions even more focused on developers. You can watch the announcements live at Steve Jobs Theater, but I overslept a bit and arrived at 9:30, by which point the seats were already full, so I was guided to the theater room at the neighboring Apple Developer Center.

Watching on the big screen in the theater room

Developer Session @ Steve Jobs Theater

I watched the 1.5-hour live session. Here, Apple engineers presented the new features of iOS 27 in a relay format. Seeing the actual coding made the implementation much easier to picture than on Day 1.

An Apple engineer explaining with real code

Mixer @ Apple Developer Center Cupertino

From 11:30 AM to 3:00 PM, the Apple Developer Center was opened to WWDC attendees for free mingling. Over good food from the food trucks, I talked with developers from China and Colorado. The chances to connect never stopped coming.

All kinds of food trucks Soft serve in the blazing sun tasted great 😋

There was also the Swift Group Lab watch party, the Tools Lounge, and the Reality Composer Lounge, where I could freely watch Apple engineers present on individual topics.

The Q&A about Xcode 27's AI really got going

What’s new in iOS 27? by Paul Hudson @ Residence Inn by Marriott San Jose Cupertino

A little after 3:00 PM, I joined a community event held at a nearby hotel. This time the speaker was Paul Hudson (@twostraws) of Hacking with Swift, who live-coded the features added in Xcode 27. The fact that he’d tried all of them overnight is just incredible…

Paul Hudson's talk. How many times have his articles saved my life...

This event was actually part of one run by a group called CommunityKit, which apparently ran from June 7 to 12. A whole week of events for iOS developers is a scale unthinkable in Japan.

CommunityKit event schedule

The Mandalorian and Grogu Screening @ Steve Jobs Theater

The night of Day 2, capping off the final event, was a movie screening at what may be one of the best theaters in the world. I finally got into the long-awaited Steve Jobs Theater that I couldn’t enter that morning!

Inside the theater

The building was simply beautiful — stunning. I took a photo with the WWDC26 symbol here and, reluctantly, left Apple Park behind.

WWDC26 symbol & Apple Park

Closing

As someone who loves Apple, getting to visit Apple Park through WWDC26 was a real honor, and it felt like a long-held dream coming true.

Everything announced on site can be caught up on in real time through the videos. But the atmosphere, the excitement, and the kindness of the Apple staff are things I don’t think I’d have understood without being there. Talking with developers from around the world broadened my view, of course, and feeling firsthand that everyone is solving some interesting problem pushed my motivation higher than ever before!

To close, one last takeaway. On Day 2’s Developer Session, one of the presenters was an engineer I’d actually talked to the day before. The moment they stepped on stage, I felt, “They aren’t some untouchable, larger-than-life people. They’re the same individual engineers as me.” That was the moment I felt closer to Apple and came to like it even more.

Steve Jobs Theater at night was beautiful

We’re Hiring

AnotherBall is looking for engineers who want to take on the latest Apple technologies. We’re searching for teammates to build our product standing shoulder to shoulder with developers from around the world.

If this sounds interesting, let’s have a chat!

AnotherBall Careers

From Game Dev to Native ── The Upside of Mixed Backgrounds

Hello there!

I’m Davide from the AnotherBall Mobile Team. At AnotherBall, many client engineers (myself included) had years of experience in the video game industry before joining the native client team.

Today I want to talk about what a team with mixed backgrounds brings to the table — the friction, the upsides, and how AI can help smooth things out.

Different problems to solve

At a fundamental level, engineers are people who love solving problems. But when you put many engineers together, people naturally gravitate toward the problems they care about most.
This is especially true when engineers come from different backgrounds, such as game development. Games and mobile apps may both run on the same device, but they tend to pull engineers in different directions:

  • Traditionally, games are about creating an experience, while apps are about completing tasks efficiently.
  • Many games — especially story-driven or competitive ones — are designed to hold your full attention; most apps live alongside notifications, backgrounding, deep links, and interruptions.
  • Classic console and PC titles ship once or in big drops; mobile apps — and live-service games — ship continuously.

The lines blur in practice, but at a glance there are still a lot of differences!

Avvy is a hybrid

We are building Avvy, a mobile application that lets anyone become a VTuber in one minute. As described in a previous post, this application sits at the seam between a game and a community app. That means users expect the polished experience of a social network, alongside the engagement and performance of a game.

In Avvy, game and social features live side by side

Building something that lives on both sides means we can’t pick one mindset and stick with it. The streaming experience needs to feel as alive as a game; the rest of the app needs to feel as familiar as any other social product. Neither half can be ignored.

The upside of mixed backgrounds

Mixed backgrounds bring different instincts to the same problem. A game developer and a mobile engineer can look at the same screen and notice completely different things — and on a hybrid product like Avvy, that’s exactly what we want.

A good example is the pipeline behind Avvy’s real-time face tracking — the cornerstone of the Avvy experience. The device camera captures your facial motion in real time and feeds it to Unity, which animates the avatar. Since the camera and Unity live in two different frameworks, communication between them can be slow; but for the avatar to feel alive, data has to reach Unity at a steady 60 frames per second.

On Android, the standard native-to-Unity bridge cost 3 ms per frame — nearly 20% of the 16.6 ms frame budget, enough to break the illusion. Coming from a game development background, my instinct was to reach for memory-mapped files on Android and raw pointers on iOS. The results were solid:

Strategy iOS Android
Parameters (PInvoke / AndroidJavaProxy) 0.01 ms 3 ms
Memory-mapped files 0.007 ms 0.11 ms
Direct pointer access 0.001 ms not supported

By the way, this pipeline really deserves its own post — we'll write one!

AI bridges the gap

What makes this work today, more than it would have a few years ago, is AI. Crossing from games to native used to mean a steep ramp: new languages, new frameworks, new conventions. But AI lets you focus on architecture and best practices while it handles the mechanical translation.

For example, when we were implementing Avvy’s in-stream points system, we had a clear idea in mind: a number that rolls like an odometer and squashes-and-stretches in response to server events. This is the prompt I gave Claude Code:

Animate a Text to get an odometer effect using the Disney 12 principles of animation (squash and stretch).

That vocabulary is the bread and butter of game development — and Disney’s 12 principles of animation (the classic rules of motion that include squash-and-stretch, anticipation, follow-through, and a few others) are something game developers are familiar with.

Claude Code translated it into SwiftUI in basically one shot. The squash-and-stretch came out as a bottom-anchored vertical scale, animated with a bouncy spring:

1
2
3
4
5
6
7
8
9
10
Text(String(character))
.offset(y: offsetY)
.scaleEffect(x: 1, y: scaleY, anchor: .bottom)
.opacity(opacity)

withAnimation(.spring(duration: 0.6, bounce: 0.5).delay(delay)) {
offsetY = 0
scaleY = 1.0
opacity = 1.0
}

The odometer feel came from sequencing each character with a small stagger, so digits pop in one after another:

1
2
let delay = Double(index) * staggerDelay
withAnimation(.spring(duration: 0.6, bounce: 0.5).delay(delay)) { ... }

A couple of hours of polish and parameter tuning later, the animation was ready. Without AI, I’d have likely spent the better part of a day on SwiftUI animation docs first.

Animation in action!

Conclusion

Different backgrounds create unique dynamics in a team, but ultimately they let the team cover more ground, especially on a product like Avvy. And in practice, AI is not a replacement for what each of us brings — it acts as a multiplier, and lets us move across domains with less friction. What’s left is the part AI can’t do for you: human taste, judgment, attention to detail, and the willingness to ask why something feels wrong.

For me personally, the move from games to native came down to two things: I wanted to ship more often and iterate with real users — something that’s hard to do in games, where years long release cycles are not uncommon — and I wanted a new challenge (though I still love making games!). What surprised me is how much carried over: solid principles tend to matter more than the specifics of any one stack, and when used with care, AI is able to fill the gaps. If you’ve been on the fence about broadening your experience, now is probably a great time to do it.

We’re Hiring

AnotherBall is looking for client engineers interested in app development using Swift, Kotlin, Unity, and AI. We believe that, besides technical skills, attention to detail, a curious mind, and strong ownership are becoming increasingly important skills for engineers in the age of AI. If that resonates with you, let’s have a chat!

AnotherBall Careers

KMP × Kotlin 2.3 ── How Android Got Slower While iOS Builds Improved by 47%

Hi there!
I’m RIO (@rioX432) from the AnotherBall Mobile Team.

I recently gave a talk at Mobile Study Group: Wantedly × teamLab × Sansan #24 about the build performance impact of upgrading Kotlin from 2.0.20 to 2.3.20 in our KMP project for the live streaming app Avvy.

Slides

Summary

We share domain logic across iOS and Android through our KMP module persona-domain-kmm (25 repositories, 33 UseCases). Before the upgrade, the XCFramework build for iOS averaged 14.3 minutes (max 28 min), and we were stuck on Kotlin 2.0.20 — blocking dependency updates and the path to AGP 9.

After upgrading to Kotlin 2.3.20 (along with Ktor 3.4, AGP 8.13, and Gradle 8.13), we measured the results across 87 GitHub Actions runs:

  • iOS: 47% faster — average build time dropped from 14.3 min to 7.7 min. The key factor was linkReleaseFramework, which sped up by 46% (7m38s → 4m07s) and accounts for ~90% of the total build.
  • Android: ~30s slower (+30%) — compileKotlin increased by 43%, caused by a K2 JVM regression (KT-81883, unresolved). Configuration phase also grew by 26s (+32%).

The iOS improvement far outweighs the Android trade-off. For KMP projects, Kotlin 2.3 is a must-have — and it positions you ahead of the upcoming AGP 9 migration.

Keeping Dependencies Fresh with Claude Code

One lesson from being stuck on Kotlin 2.0.20 is that falling behind on dependencies creates compounding problems. To prevent this, we built a custom Claude Code skill (/update-deps) that automates the dependency update workflow — from checking for new versions and researching changelogs, to applying updates, running build verification, and creating PRs with Kotlin compatibility checks. This lets us keep dependencies up to date with minimal friction.

We’re Hiring

At AnotherBall, we strive to develop in an environment with minimal technical debt — staying on top of dependency updates and keeping our codebase healthy. If that kind of mindset resonates with you, we’d love to talk!

AnotherBall Careers

Embedding UaaL in a SwiftUI App: Managing the View Lifecycle

Hello! We’re the iOS team at AnotherBall.

In a previous post, our mobile team introduced the multi-repository architecture behind Avvy — covering how Kotlin Multiplatform (KMP) and Unity as a Library (UaaL) are built, distributed, and integrated across five repositories.

What makes Avvy technically interesting is how naturally UaaL and native code work together. In this post, we’ll zoom into the iOS side and explain how we manage UaaL views within a SwiftUI app.

Background: The Roles of Unity and Native

A core design principle in Avvy is that Unity is responsible only for 2D avatar functionality. Unity handles avatar rendering and the avatar customization (dress-up) UI. Everything else — all other features and UI — is implemented natively, so we can take full advantage of native capabilities and deliver an experience that feels like a proper streaming app.

UaaL’s Constraint: Only One Instance at a Time

There’s a major constraint when working with UaaL: loading more than one instance of the Unity runtime isn’t supported, so only one UaaL view can be displayed on screen at a time. If you try to display two simultaneously, one of them won’t render.

In Avvy, we show avatars across multiple screens — the streaming view, avatar home, gacha, and more. This means we need to swap the UaaL view between screens on every navigation. But having each screen manage this lifecycle individually is cumbersome and increases the risk of unexpected bugs.

To solve this, we created a dedicated SwiftUI component called UnityView that centralizes this management, allowing each screen to use it just like any other view.

Anatomy of the Streaming Screen

As an example, let’s look at the streaming screen. Unity sits at the bottom layer handling only avatar rendering, with native UI overlaid on top.

The gray area is Unity’s avatar rendering region, and the yellow areas are native overlays. In Avvy’s iOS app, we call the Unity region UnityView. From SwiftUI’s perspective, it works just like any other view:

1
2
3
4
5
6
7
8
9
10
UnityView(displayType: .liveStream) // Specify which Unity scene to load
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea() // Render edge-to-edge including safe areas
.overlay {
if viewModel.isSceneLoading {
LoadingOverlay() // Loading indicator
} else {
overlayContent // Native buttons, comment list, etc.
}
}

Developers working on each feature screen don’t need to think about Unity’s lifecycle at all — just open and close the screen, and the avatar display toggles automatically.

Implementing UnityView

UnityView is a UIViewControllerRepresentable that wraps a UnityViewController internally. We need a UIKit view controller because the rendering view provided by UnityFramework is a UIKit UIView.

For example, when presenting the streaming screen as a modal from the avatar home screen, the Unity view needs to automatically move to the frontmost screen. UnityViewController achieves this using the viewWillAppear/viewWillDisappear lifecycle:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public struct UnityView: UIViewControllerRepresentable {
public let displayType: DisplayType

public func makeUIViewController(context: Context) -> UnityViewController {
return UnityViewController(displayType: displayType)
}
}

public final class UnityViewController: UIViewController {
// The Unity view provided by UaaL. Simplified for this article.
// It's a singleton, so the same instance is reused across all screens.
private var unityView: UIView = UnityFramework.shared.rootView

public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
addUnityView() // Add the view
}

public override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
removeUnityView() // Remove the view
}

private func addUnityView() {
view.insertSubview(unityView, at: 0) // Add Unity's view at the bottom layer
unityView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
unityView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
unityView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
unityView.topAnchor.constraint(equalTo: view.topAnchor),
unityView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}

private func removeUnityView() {
unityView.removeFromSuperview()
}
}

When the screen appears (viewWillAppear), we add the Unity view; when it disappears (viewWillDisappear), we remove it. It’s an extremely simple implementation, but this alone is enough to guarantee that only one Unity UIView exists on screen at any given time.

Additionally, alongside the view swapping, the Avvy app also pauses and resumes Unity to reduce battery consumption and resource usage.

Wrap-up

When embedding UaaL in a native app, there’s a constraint that only one view can be displayed at a time. By using UIViewController‘s lifecycle to automate view swapping and resource management, and wrapping it as a SwiftUI UnityView, we made it possible to display avatars without worrying about any of that. This architecture allows Avvy to maintain a native app experience while being an avatar-centric app.

In the next post, we’d like to cover how we use the DisplayType introduced in this article to load specific scenes in Unity, and more broadly, how native and Unity communicate with each other.

We’re Hiring

At AnotherBall, we care about building testable, maintainable architecture — and we’re always looking for engineers who share that mindset. If this kind of work excites you, we’d love to talk!

AnotherBall Careers

Using AI Coding to Detect Team Stagnation: Building a Custom Chrome Extension Kanban

Hi, I’m @fortkle, a Tech Lead and Head of Engineering for the Avvy team at AnotherBall.

In this post, I’ll share how I built a custom Chrome extension that overlays a kanban view on top of Linear — making it easier to spot stagnation and priority misalignment in our Scrum workflow.

The Physical Whiteboard Kanban That Just Worked

Our Avvy development team uses Scrum, with a kanban board at the center of our daily transparency and inspection process.

When I think about kanban, what still comes to mind is the physical whiteboard and sticky notes we used at a previous job.

The setup was simple:

  • Draw lanes on the whiteboard with a marker (Todo / In Progress / In Review / Done)
  • Arrange cards from top to bottom in priority order
  • Place Issues (parent) on the left, with Sub-issues (children) expanding to the right

That alone made it clear what we were working on and in what order across the sprint.

We started with these simple rules, but our kanban evolved as we progressed through sprints.

For example, we printed members’ Slack icons and attached them to magnets to place on cards as assignment indicators. We also wrote WIP limits directly on the whiteboard with markers.

The Kanban Guide states that “how flow transparency is achieved is limited only by the imagination of the Kanban system members,” and the physical whiteboard was exactly that kind of tool.

If you’re interested in creative uses of physical kanban, The Agile Coach’s Toolbox – Visualization Examples is a great read.

Moving to Linear — Recreating “That Feeling”

Our current Avvy team includes remote members working from different regions and countries, so a physical whiteboard isn’t practical. We use Linear for our kanban instead.

Linear has a Board View for card management, but after using it for a while, I noticed several gaps from the whiteboard experience.

Example of grouping cards by assignee (all data shown is fictional)

  • The Issue/Sub-issue parent-child structure can’t be laid out ideally on the board (possible with grouping/sub-grouping, but not ideal)
  • The default UI limits how much you can see in one screen, making it hard to grasp the full sprint
  • Stagnating cards aren’t easy to spot at a glance
  • WIP limits can’t be enforced visually

We kept feeling like, “It works, but it doesn’t have that feeling.”

Overlaying a Custom View with a Chrome Extension

The solution I tried was to overlay a custom view on top of Linear using a Chrome extension.

Since the Linear API provides Issue and Sub-issue data along with status change history, I used that to recreate the whiteboard-era layout in the browser.

Custom view recreating the physical kanban (all data shown is fictional)

Key features include:

Expanded Issue/Sub-issue parent-child structure
Sub-issues expand from their parent Issue card on the board, making the full picture easy to grasp. Being able to see the entire sprint in one screen is a major plus.

Vertical ordering by priority
Tasks within the sprint are displayed from top to bottom in priority order. As the sprint progresses, cards move toward the upper right in a diagonal pattern — making it easy to notice when lower-priority cards are moving ahead of higher-priority ones.

Handling unexpected tasks
With priority made visible, when an unexpected task comes in, it’s easier to decide: “Let’s drop this lower-priority item and fit in the new task.”

AI Coding Makes “I Just Want to Tweak This” Instant

Building a custom view like this was technically possible before, but AI coding takes it to another level. The key point is that with AI coding, small changes can be made in minutes. Here are a few examples of improvements I made.

Stagnation Indicator

There was a problem: “Even if a member is stuck in In Progress, it’s hard to notice just by looking at the board.” With the physical kanban, we’d draw tally marks on sticky notes to visualize elapsed days.

I told the AI: “I want to visualize elapsed time in each lane. Add an indicator like a 5-segment gauge on the assignee’s avatar — fill one segment per day, up to a maximum of 5 days of stagnation.” The UI came together in just a few minutes.

A 5-segment ring indicator appears on the assignee’s avatar in the upper right of each card, filling one segment per day since the card moved to In Progress. Now it’s easy to see at a glance when a card has been stuck for several days.

Since the Linear API provides timestamps for status changes, this was all achievable on the client side — no server-side additions needed.

Filtering and Card Focus

During our Daily Scrums, each member shares what they’re working on that day. But it wasn’t always clear at a glance where each person’s cards were on the board. With the physical kanban, you could just point at the card directly.

I told the AI — along with a screenshot — “When filtering by assignee, I sometimes lose track of where their cards are. Only when filtering is active, add ←→ buttons in the attached screenshot’s position to focus on that person’s cards one by one — like a browser’s in-page search (Ctrl+F).” The result looked like this:

With minor UI tweaks, the implementation matched my vision almost immediately. Also, instead of hiding non-selected cards (the typical filter behavior), I chose to highlight the selected ones. That way, you can still see the context of other tasks even while filtering — which turned out to be very useful.

How the Team Changed After We Started Using It

Here are some changes we saw after adopting this board:

  • “This task seems higher priority than what we’re currently working on!” conversations increased
    • When tasks are ordered by priority, misalignment becomes easy to spot.
  • We started catching stagnation earlier
    • Cards stuck in In Review for several days became more visible, leading to earlier “I can help if you’re stuck” offers.
  • Making trade-offs within a sprint became easier
    • When unexpected tasks came in, reaching consensus on “let’s drop this one this sprint” got smoother.

On the downside, since this is a Chrome extension, it’s not accessible from smartphones or dedicated desktop apps. Also, even for internal-only distribution, the Chrome Web Store review process takes 2–3 days, which slowed down our rollout to the team.

We’ve only just started using it, but this made me realize the potential of building ideal kanban tools with AI.

Distributing the Chrome Extension Within the Team

You can publish a Chrome extension to the Chrome Web Store while restricting access to company members only. This article was helpful for the specific steps:

Distribute to Google Group members - Google

We also automated the submission process using GitHub Actions, so merging a PR automatically triggers a review submission.

Wrap-up

I had half given up on the idea that “the flexibility of physical kanban can’t be replicated in digital tools” — but AI coding has changed that for us.

Describe what you want to visualize in plain language, and a working UI appears in minutes. Free from the constraints of existing tools, teams can now build the visualizations they actually need.

We’ll keep evolving our kanban as our team grows and its needs change.

We’re Hiring

At AnotherBall, we believe in giving teams the freedom to identify and solve their own problems — just like the kanban we built here. If that kind of culture sounds appealing, we’d love to talk.

AnotherBall Careers

From Zendesk to Chatwoot ── How We Rebuilt Our CS Flow Around AI

Hello! I’m Francis, in charge of AIOps at AnotherBall, where I work on applying AI to internal operations. This post is about how we rebuilt our CS flow around AI — bringing first reply time from 1,186 minutes down to under a minute and ending up with more insight into our users than ever.

Moving to Chatwoot

We were originally using Zendesk for customer support. Users would submit a form from the “Contact Us” section in the app, and CS would handle it through Zendesk’s ticket interface. We wanted to use AI to improve CS efficiency, but the more we invested in AI-assisted responses, the more we ran up against the platform’s limitations. Zendesk’s architecture wasn’t really built for a high level of AI customization — customizing the response flow required paid add-ons and workarounds. We wanted granular control over tones, template usage, and escalation rules per inquiry category, but Zendesk made that difficult.

Chatwoot was able to give us this control — it let us plug our own automation layer into the workflow while providing fine control over the AI responses. We’re also keeping operating costs low with the hosted plan, and we have the option to move to the open-source/self-hosted edition if we ever need to. Having an open-source foundation also makes the platform’s features overall much more transparent and easy to troubleshoot.

The Problem: Chatwoot is Chat-First, We’re Form-First

Chatwoot is a chat-based CS tool, but when we were using Zendesk, we handled inquiries through forms. Most issues don’t require real-time support, and we actually want users to include as much detail as possible in their first message. Chat makes it too easy to fire off incomplete messages, leading to unnecessary back-and-forth, so we didn’t want to switch our inquiry flow to chat.

To respond to inquiries, we also needed information such as the user’s device model, OS version, and app version. Our existing form lets us pre-populate device information automatically when users submit from within the app, so they don’t have to type it themselves. Doing that reliably with a chat widget wasn’t straightforward, so I needed a way to keep the form-based intake while still converting each inquiry into a native Chatwoot conversation.

The Solution: Google Forms + Sheets + Apps Script

I went with a stack I could fully control:

  1. Google Forms — the user-facing inquiry form
  2. Google Spreadsheet — form responses land here automatically
  3. Google Apps Script — handles connection to Chatwoot and Slack

When a user submits the form, an Apps Script trigger formats the submission (including all the fields) and sends it as an email to our Google support inbox. Chatwoot is connected to that mailbox, so each email is automatically ingested as a new conversation with the full context preserved. Once the conversation exists, I use Chatwoot’s API to apply the inquiry-category label and set a few contact attributes.

I also have a script that functions as a webhook handler. Chatwoot fires a message_created event for every new message, which triggers the script to send the message to Slack: customer messages open a new thread, agent and AI replies post as threaded responses.

CS inquiry flow: Google Form → Chatwoot → Slack

Every inquiry lands in the Google Sheet, which is the most valuable part of the architecture. With all the raw data there, I built an automated weekly CS digest: every Friday, an LLM classifies the week’s inquiries (including feature requests), and a script then posts a bilingual JP/EN summary to Slack.

As a result, the team reacted quickly, and it’s sparked ideas beyond CS — applying a similar approach to our social media and getting a better sense of how users experience the app. This feels like the beginning of something bigger: not just responding to our community, but actually knowing them.

Prompt Engineering for Chatwoot

Here are a few prompt-engineering choices that made our AI replies more effective:

  • Explicit template structure. I include the exact headers, tone guidelines, and sign-off patterns our human agents use. The model follows them reliably when the structure is explicit.

    1
    2
    3
    4
    5
    6
    Role: You write professional emails on behalf of [App] Support.
    Use a greeting, structured paragraphs, and a courteous closing.
    Insert TWO line breaks between paragraphs.

    Greeting: Address the user by username (e.g. "user_12345").
    If no identifier is available, use a neutral greeting.
  • Inquiry type routing. Different inquiry types get different system prompts. We have the bot identify the inquiry type using the message content and select the appropriate prompt — no manual tagging required.

  • Escalation logic. The model escalates to a human when it’s not confident.

Results

We switched to Chatwoot on Feb 16:

Metric Zendesk avg (Jan 15–Feb 15) Chatwoot avg (Feb 16–Feb 26)
First reply time 1,186 min (19.8 hrs) ~1 min
Resolution time 289.7 hrs (12.1 days) 30 hrs (1.25 days)

Zendesk’s averages are skewed by outlier tickets — the median was 299 min / 172.8 hrs, still well above Chatwoot’s averages. Ten days is a short window though, so I’ll definitely be revisit the data once we have a full month of data.

The ~1 min first reply is the AI auto-responding the moment a conversation is created, regardless of time zone. Human agent follow-ups average 6 hr 46 min.

With everything in Google Sheets, I can easily analyze our data. I can track inquiry volume, spot emerging issues, and run a weekly bilingual CS digest automatically — no dashboard, no analytics add-ons needed.

The setup was a real investment, but I’m in a better position now: a stack I control, AI integration that works, and data I can act on. The broader lesson: own your data and your integration layer. Whether this tradeoff makes sense depends on your team, but for me, wanting to invest heavily in AI and iterate quickly, a more open, controllable stack was the right call.

We’re Hiring

AnotherBall sits at the intersection of entertainment and technology. We build products that connect people to the content and communities they love — and we use AI across the full stack to do it better and faster.

If that sounds like the kind of environment you want to be part of, we’d love to hear from you.

AnotherBall Careers

Mobile App Development with KMP × Unity UaaL ── Multi-Repo Setup and Automation

Hi! We’re the AnotherBall Mobile Engineering Team.

Our app “Avvy” has a somewhat complex architecture: we use KMP (Kotlin Multiplatform) to share business logic across iOS and Android, and Unity as a Library (UaaL) to embed Unity’s 2D avatar rendering into our native apps.

In this article, we’ll share how we coordinate five repositories and how much we’ve automated with GitHub Actions.

Repository Structure

To avoid build complexity and inter-team dependencies, we split our codebase by function into separate repositories.

Repository Role Artifacts
shared-kmm Business logic AAR / XCFramework (KMP library)
unity-module 2D avatar rendering UaaL libraries for Android / iOS
android-app Android app APK / AAB
ios-app iOS app IPA
unity-spm SPM distribution for Unity XCFramework Swift Package

Each repository communicates through pre-built libraries—AAR for Android and XCFramework for iOS. This allows each team to work independently.

What the Unity Module Does

The Unity module handles avatar display and real-time control.

  • Avatar display: Renders avatars with 2D animation
  • Face tracking: Detects facial movements via camera and reflects them on the avatar
  • Customization: Outfit and accessory changes

Communication with native apps requires special handling. Face tracking sends data 60 times per second, so standard bridges would cause latency. On iOS, we use direct pointer access; on Android, we use memory-mapped files for fast data exchange.

How We Automated It

We use GitHub Actions to automate most of the cross-repository coordination. About 1,200 PRs are processed automatically each month (December 2025 figures).

Server API Changes

When the server’s API definition file (OpenAPI) is updated, an update PR is automatically created in the KMP repository.

KMP Library Updates

From KMP library release to update PR creation in each app repository—everything is automated.

The flow is simple: Publish → Trigger → Update.

  1. When a release is triggered in shared-kmm, it publishes to GitHub Packages
  2. On success, gh workflow run triggers the update workflow in each app repository
  3. Each app gets an auto-generated PR with the version update
1
2
3
4
5
6
7
8
9
# On KMP release (excerpt)
- name: Publish to GitHub Packages
run: ./gradlew publish

- name: Trigger Android update
run: |
gh workflow run update-kmm-version.yml \
--repo AnotherBall/android-app \
--field version=${{ env.VERSION }}
1
2
3
4
5
6
7
8
9
10
11
# Android app update workflow (excerpt)
- name: Update version in libs.versions.toml
run: |
sed -i "s/kmm = \".*\"/kmm = \"${{ inputs.version }}\"/" \
gradle/libs.versions.toml

- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
with:
title: "Update KMP to ${{ inputs.version }}"
branch: "auto/kmm-${{ inputs.version }}"

Unity Library Distribution

To use Unity modules in the iOS app, we distribute them via SPM (Swift Package Manager).

  1. Build the Unity XCFramework and upload it to GitHub Releases
  2. Calculate a hash value for the file
  3. Auto-generate Package.swift (embedding the download URL and hash)
  4. Create a PR in the distribution repository (unity-spm)

While we could distribute directly from the unity-module repository, Xcode downloads the entire repository when resolving SPM packages. By creating a separate unity-spm repository that contains only Package.swift, we significantly speed up the download process.

The hash value lets the iOS app verify the file wasn’t corrupted during download.

Auto-Generated Release Branch PRs

When changes are pushed to a release/* branch, multiple merge PRs are automatically created:

  • release/2.10.0main (for production release)
  • release/2.10.0release/2.11.0 (to propagate bug fixes to the next version)

Version numbers are compared to determine the appropriate merge targets, preventing missed merges.

Package.resolved Conflict Resolution (iOS)

When multiple KMM/UaaL update PRs exist simultaneously, Package.resolved file conflicts occur. In the iOS repository, we have a workflow that automatically resolves these conflicts.

Trigger: Push to release/* branch

How it works:

  1. Fetch open PRs targeting the release branch via GitHub API
  2. Filter PRs with titles starting with chore: update KMM or chore: update UaaL
  3. Check if each PR can be merged, and identify those with conflicts
  4. Resolve conflicts for each PR
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Attempt to merge base branch
if git merge "origin/$BASE_REF" --no-edit; then
echo "Merge succeeded"
else
# On conflict: temporarily adopt PR's Package.resolved
git checkout --ours Package.resolved
git add Package.resolved
git merge --continue
fi

# Re-resolve SPM dependencies (incorporates base branch changes)
make resolve-package-dependencies

git commit -m "chore: resolve Package.resolved conflict"
git push

The key is make resolve-package-dependencies (which runs xcodebuild -resolvePackageDependencies internally), re-resolving dependencies including target branch changes. When multiple PRs have conflicts, they’re processed in parallel.

Remaining Challenges

  • CI/CD execution time: Gradle/Xcodebuild can take 40+ minutes; we’re looking into better caching strategies
  • Workflow duplication: Similar logic exists in multiple workflow files; we want to extract it into reusable components
  • Auto-merging update PRs: Currently we only auto-create PRs; we’d like to auto-merge when tests pass

Conclusion

Even with a complex setup combining KMP and Unity UaaL, separating repositories and communicating through artifacts lets each team work independently. We’ve learned that automation isn’t a one-time setup—it requires ongoing improvement.

We’re Hiring

AnotherBall is looking for mobile engineers interested in app development using Kotlin/Swift/Unity/AI!
We’re seeking teammates to grow our product together while adopting new technologies like KMP. If you’re interested, we’d love to hear from you!