<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: kate astrid</title>
    <description>The latest articles on DEV Community by kate astrid (@kate_astrid).</description>
    <link>https://dev.to/kate_astrid</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1154209%2F48fdfa0a-d2bf-4410-bb49-6fcec9e039da.jpeg</url>
      <title>DEV Community: kate astrid</title>
      <link>https://dev.to/kate_astrid</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kate_astrid"/>
    <language>en</language>
    <item>
      <title>How “Almost Done” Became a Promise to My Team</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Tue, 13 Oct 2026 11:41:52 +0000</pubDate>
      <link>https://dev.to/kate_astrid/how-almost-done-became-a-promise-to-my-team-ddf</link>
      <guid>https://dev.to/kate_astrid/how-almost-done-became-a-promise-to-my-team-ddf</guid>
      <description>&lt;p&gt;At our standup last Tuesday, someone suggested we could demo a feature that afternoon. We could walk through the experience we’d been building, and I could see why everyone felt good about showing it. I was already thinking about the remaining work as something we could fit around the rest of the week.&lt;/p&gt;

&lt;p&gt;By lunchtime, we were arguing about an edge case that seemed obvious once we started discussing it. About four hours after describing the feature as “basically done,” we were trying to agree on how part of it should work. &lt;/p&gt;

&lt;p&gt;So, the morning’s confidence had changed the way I experienced that conversation. We were learning something important about what we were building, yet I was already wondering how much time this would add. In my head, we’d moved on to finishing touches. Having to make another product decision felt like going backwards, even though answering that question was helping us move forward.&lt;/p&gt;

&lt;p&gt;That reaction is the part of the “90% problem” I’ve been thinking about lately.&lt;/p&gt;

&lt;h2&gt;
  
  
  What someone else hears when I say “almost done”
&lt;/h2&gt;

&lt;p&gt;When I tell a teammate that a feature is almost done, I usually mean that I’ve made the main idea work and can see a route to finishing it. I’m describing my confidence in the work as much as the work itself. My teammate has their own decisions to make, though. They might be figuring out when to start an integration or whether to offer a customer a demo. When they hear “almost done,” they have a reasonable basis for putting something on their calendar.&lt;/p&gt;

&lt;p&gt;Meanwhile, I may still be carrying a fairly loose idea of what remains. I’m thinking I need a little more time to check things; they’re thinking they can start on Thursday.&lt;/p&gt;

&lt;p&gt;Once someone else starts planning around my estimate, I feel more committed to it. A useful question from a colleague now comes with an uncomfortable second thought: how much will this change what everyone is expecting?&lt;/p&gt;

&lt;p&gt;I can be genuinely interested in understanding the issue while also hoping the answer will fit inside the afternoon I’ve already mentally allocated to it. That puts a strange pressure on the conversation, because I’m trying to learn something and defend my earlier judgment at the same time.&lt;/p&gt;

&lt;p&gt;Calling the remaining work “polish” makes this especially awkward. It becomes harder to explain why we need another discussion when I’ve already described what’s left as a few finishing touches.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI helps me reach that confidence earlier
&lt;/h2&gt;

&lt;p&gt;I’ve felt this with code I wrote myself, too. What feels different when I work with AI is how quickly I can reach something convincing enough to show a colleague.&lt;/p&gt;

&lt;p&gt;That speed is genuinely useful. Having something we can try together gives us a much better basis for discussing the experience we’re building. A colleague can follow their own instincts, take a different route, and ask a question that changes how I understand the feature.&lt;/p&gt;

&lt;p&gt;For example, imagine I’m showing someone how a user edits and saves a record. My colleague asks what should happen if that person’s access changes while they’re working. Now we need to decide what happens to their changes and how we explain the situation to them. We’ve moved from demonstrating an interaction to discussing what we owe the person using it. Depending on the product, answering that question could involve several people and some careful thought.&lt;/p&gt;

&lt;p&gt;Getting to that conversation sooner is valuable. The difficulty comes when I’ve already used the demo to reassure everyone that we’re nearly finished.&lt;/p&gt;

&lt;p&gt;I think this is where my sense of “90%” becomes misleading. I can picture the finished feature clearly enough that I start treating the remaining work as predictable. Then a colleague helps me see something more clearly, and I experience that discovery as an expanding workload.&lt;/p&gt;

&lt;p&gt;Looking back at Tuesday, I can see how much the morning’s conversation shaped my expectations for the afternoon. We had a version we could discuss meaningfully, and that discussion helped us discover a decision we still needed to make. Somewhere along the way, I’d started expecting the discovery part to be over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Giving my teammates a more useful update
&lt;/h2&gt;

&lt;p&gt;I’ve started paying closer attention to what I’m inviting people to do with my progress updates.&lt;/p&gt;

&lt;p&gt;When I say something is demoable, I want a teammate to feel comfortable trying it with me and helping me examine the experience. When I say it’s ready to ship, I’m asking them to place a different kind of trust in it. Keeping those expectations clear has become more important as I’ve been able to build the first version faster.&lt;/p&gt;

&lt;p&gt;In practice, I’m trying to replace vague confidence with a description of what we understand and what we’re still deciding.&lt;/p&gt;

&lt;p&gt;For a feature like the permissions example, I might say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“We can demo how someone edits and saves their work, and I’m checking what happens if their access changes halfway through. Once we’ve agreed on that behavior, I’ll have a firmer estimate for release.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That takes a little longer to say, but it gives my teammate something useful to work with. They can see what we’ve accomplished, understand the decision ahead, and perhaps help me resolve it.&lt;/p&gt;

&lt;p&gt;It also changes how I feel when someone raises a concern. By making room for that discussion in the update, I’ve given myself room to learn from it. We can work through the question with a shared understanding of where we are.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’d say differently on Tuesday
&lt;/h2&gt;

&lt;p&gt;I’d still be excited to show the feature. Reaching the point where colleagues can try something and respond to it is meaningful progress, and I want us to enjoy that. But I’d also be more careful about turning that excitement into a delivery expectation. I’d explain what we were ready to demonstrate and where I still wanted people to challenge my thinking.&lt;/p&gt;

&lt;p&gt;The lesson I’m taking from that afternoon is about how I communicate uncertainty while I’m feeling confident. Those two things can exist together, and my teammates benefit from hearing both.&lt;/p&gt;

&lt;p&gt;I’d like them to leave standup understanding what I’m excited about, what they can plan around, and where I could still use their help. That seems much more useful than asking everyone to interpret the same percentage.&lt;/p&gt;

&lt;p&gt;When you tell your team something is “almost done,” what do you think they hear?&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>ai</category>
      <category>career</category>
      <category>programming</category>
    </item>
    <item>
      <title>When AI Bias Becomes Our Bias</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Tue, 29 Sep 2026 17:30:27 +0000</pubDate>
      <link>https://dev.to/kate_astrid/when-ai-bias-becomes-our-bias-34oa</link>
      <guid>https://dev.to/kate_astrid/when-ai-bias-becomes-our-bias-34oa</guid>
      <description>&lt;p&gt;&lt;em&gt;What happens to our own thinking when we start using AI to make decisions?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A few days ago, I shared an article about whether &lt;a href="https://dev.to/kate_astrid/does-ai-have-cognitive-biases-too-48oa"&gt;AI can have cognitive biases too&lt;/a&gt;. I looked at things like anchoring and framing, and at the ways seemingly irrelevant information or the way we phrase a question can change an AI's response.&lt;/p&gt;

&lt;p&gt;While writing that article, I started thinking about the other side of the interaction.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What happens to &lt;strong&gt;us&lt;/strong&gt; when we start using AI to make decisions?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We already know that AI can produce biased or inconsistent outputs. But once those outputs become part of our everyday thinking, something else starts happening. The answer becomes part of the way we see the problem.&lt;/p&gt;

&lt;p&gt;And sometimes, we may carry it with us long after the conversation is over.&lt;/p&gt;

&lt;h2&gt;
  
  
  An AI answer can become our reference point
&lt;/h2&gt;

&lt;p&gt;Imagine you're investigating a production issue. You ask AI to help you understand what might be happening, and it gives you a detailed explanation pointing toward a database problem. The explanation makes sense. It gives you several things to check, and you start investigating them.&lt;/p&gt;

&lt;p&gt;A little later, you find a database query that looks suspicious. Now the original explanation feels even more convincing. But that suspicious query might have been there for months. It might have nothing to do with the performance problem you're investigating.&lt;/p&gt;

&lt;p&gt;The AI answer has changed what you're paying attention to.&lt;/p&gt;

&lt;p&gt;This is one way an AI-generated bias can become our own. The answer gives us a reference point, and everything we see afterwards gets interpreted partly in relation to it.&lt;/p&gt;

&lt;p&gt;Research has found similar effects outside software engineering. In one set of experiments, participants were exposed to biased recommendations from an AI system and later reproduced similar errors themselves, even when they were making decisions without the AI. The interesting part is that the participants didn't need to consciously decide to trust the AI. The recommendation simply became part of the information they used to make sense of the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  A convincing explanation can change how we see the problem
&lt;/h2&gt;

&lt;p&gt;There's another thing I find fascinating about AI conversations: &lt;strong&gt;fluency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;AI is very good at taking an idea and turning it into a coherent explanation. It gives you structure. It connects the dots. It explains why something might be happening and what you could do about it. That makes the information easy to work with.&lt;/p&gt;

&lt;p&gt;It can also make a particular interpretation feel more reasonable. Research on AI-assisted decision-making has found that people can rely on AI advice even when other information points in a different direction. In a 2024 experiment, researchers found that participants over-relied on AI advice in a decision-making task, including situations where the advice conflicted with available contextual information and the participants' own assessment.&lt;/p&gt;

&lt;p&gt;And again, people didn't need to believe AI was always right. The AI answer became another piece of information in the decision, and it carried enough weight to change people's judgments.&lt;/p&gt;

&lt;p&gt;It is important because most of us aren't using AI while thinking, "I will now blindly trust this machine". We're usually doing something much more ordinary. We're thinking about a problem, we ask AI, and it gives us something useful to work with.&lt;/p&gt;

&lt;p&gt;Then we continue from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens to what we remember?
&lt;/h2&gt;

&lt;p&gt;This is my favorite part.&lt;/p&gt;

&lt;p&gt;We often think about AI errors as a problem of getting the wrong answer. There's another possibility: the interaction itself can become part of what we remember.&lt;/p&gt;

&lt;p&gt;In 2025, researchers from MIT and other institutions studied whether conversational AI could contribute to false memories. Participants first read an article and then either received no intervention, read a summary, or discussed the article with a chatbot. In some conditions, the AI conversation contained misleading information. The misleading chatbot condition produced significantly more false recollections than the control condition.&lt;/p&gt;

&lt;p&gt;There's an important limitation here. The study deliberately introduced misleading information into the chatbot interaction, so it doesn't tell us how often ordinary AI conversations create false memories. It does show that information from a conversation with AI can become part of what we later remember.&lt;/p&gt;

&lt;p&gt;And that's different from simply getting an answer wrong. If I ask AI about something and it gives me a detail that turns out to be incorrect, I might notice it immediately. Or I might remember the detail without remembering where it came from. Was that something I already knew? Something I read somewhere? Something the AI told me? Something I worked out myself?&lt;/p&gt;

&lt;p&gt;The source can disappear while the information remains.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI becomes part of the information we use
&lt;/h2&gt;

&lt;p&gt;Before generative AI, most of our information came through fairly obvious channels. A book, a person, a website, a search result, a meeting. We could usually identify where a particular piece of information came from.&lt;/p&gt;

&lt;p&gt;A conversation with AI is different.&lt;/p&gt;

&lt;p&gt;It sits somewhere between search, conversation, explanation, brainstorming, and advice. And because the conversation is personalised to us, it can feel surprisingly close to our own thinking.&lt;/p&gt;

&lt;p&gt;I might start with an idea.&lt;br&gt;
AI gives me another perspective.&lt;br&gt;
I think about that perspective.&lt;br&gt;
I change my mind slightly.&lt;br&gt;
I ask another question.&lt;br&gt;
The answer gives me something else to consider.&lt;/p&gt;

&lt;p&gt;By the end of the conversation, the final idea may be genuinely useful, but it can become difficult to separate my original thinking from everything that happened during the conversation. The boundary gets blurry.&lt;/p&gt;

&lt;p&gt;And that matters because our thinking is built partly from the information we encounter, the conversations we have, and the connections we make between them. If AI becomes something we interact with every day, its answers become part of that environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should we do with that?
&lt;/h2&gt;

&lt;p&gt;I'm not interested in avoiding AI. I use it too much for that.&lt;/p&gt;

&lt;p&gt;I think it's more useful to become aware of how an AI answer can shape what we notice, what we remember, and how we interpret the information that comes afterwards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notice what the answer makes you look at.&lt;/strong&gt;&lt;br&gt;
When AI points you toward an explanation, pay attention to what suddenly seems important. Ask yourself what else could explain the same situation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep some distance from the explanation.&lt;/strong&gt;&lt;br&gt;
A coherent answer can feel more convincing simply because it is coherent. Give yourself a chance to look for evidence that supports or challenges it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Separate the conversation from the evidence.&lt;/strong&gt;&lt;br&gt;
An AI explanation can be useful for finding ideas and questions to investigate. A convincing explanation still needs evidence behind it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pay attention to what you remember.&lt;/strong&gt;&lt;br&gt;
If something important came from an AI conversation, checking the original source gives you a way to separate what you actually know from what you remember the AI telling you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ask for alternatives.&lt;/strong&gt;&lt;br&gt;
When an AI answer gives you one interpretation, ask what other explanations could fit the same situation. This can help keep one explanation from becoming the whole story.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I'm still thinking about
&lt;/h2&gt;

&lt;p&gt;The strange thing about all of this is that the influence can happen without any obvious moment where we hand over a decision. There is no clear line between "the AI suggested this" and "this is what I think". The suggestion becomes a possibility. The possibility becomes something we investigate. Eventually, it can become part of the way we understand the situation.&lt;/p&gt;

&lt;p&gt;That makes AI different from a tool that simply gives us information. It becomes part of the process through which we form our understanding.&lt;/p&gt;

&lt;p&gt;And I think we're only beginning to figure out what that means.&lt;/p&gt;

&lt;h2&gt;
  
  
  Research
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/kate_astrid/does-ai-have-cognitive-biases-too-48oa"&gt;Does AI Have Cognitive Biases Too?&lt;/a&gt; — My previous article exploring anchoring, framing, availability, representativeness, and other bias-like patterns in language models.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.nature.com/articles/s41598-023-42384-8" rel="noopener noreferrer"&gt;Humans inherit artificial intelligence biases&lt;/a&gt; — Vicente and Matute, &lt;em&gt;Scientific Reports&lt;/em&gt; (2023). Experiments examining how people can carry biases introduced by AI into later decisions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://doi.org/10.1016/j.chbah.2024.100066" rel="noopener noreferrer"&gt;Confirmation bias in AI-assisted decision-making&lt;/a&gt; — A study of how psychologists respond to AI recommendations that agree or disagree with their initial judgments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://doi.org/10.1016/j.chb.2024.108352" rel="noopener noreferrer"&gt;Trust and reliance on AI: An experimental study on the extent and costs of overreliance on AI&lt;/a&gt; — Klingbeil, Grützner and Schreck, &lt;em&gt;Computers in Human Behavior&lt;/em&gt; (2024). An experiment examining how people rely on AI advice in decision-making.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://doi.org/10.1145/3708359.3712112" rel="noopener noreferrer"&gt;Slip Through the Chat: Subtle Injection of False Information in LLM Chatbot Conversations Increases False Memory Formation&lt;/a&gt; — Pataranutaporn et al., IUI (2025). An experiment examining how misleading chatbot conversations can affect false memory formation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Does AI Have Cognitive Biases Too?</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Mon, 28 Sep 2026 18:03:20 +0000</pubDate>
      <link>https://dev.to/kate_astrid/does-ai-have-cognitive-biases-too-48oa</link>
      <guid>https://dev.to/kate_astrid/does-ai-have-cognitive-biases-too-48oa</guid>
      <description>&lt;p&gt;We know humans have cognitive biases. They affect the way we make decisions, interpret information, and judge uncertainty.&lt;/p&gt;

&lt;p&gt;But what about AI?&lt;/p&gt;

&lt;p&gt;Can a language model show the same patterns? Can an irrelevant detail influence its judgment? Can the way information is presented change its answer?&lt;/p&gt;

&lt;p&gt;Researchers have &lt;a href="https://pubmed.ncbi.nlm.nih.gov/38330366/" rel="noopener noreferrer"&gt;found patterns in language models&lt;/a&gt; that resemble several well-known cognitive biases, including anchoring, availability, representativeness, framing, and the endowment effect.&lt;/p&gt;

&lt;p&gt;That doesn't mean AI experiences these biases in the same way humans do. A cognitive bias describes a pattern in human cognition. With AI, we're observing a pattern in its output.&lt;/p&gt;

&lt;p&gt;But if the pattern is there, it's worth understanding.&lt;/p&gt;

&lt;p&gt;So let's put some of these biases to the test.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a cognitive bias, anyway?
&lt;/h2&gt;

&lt;p&gt;Psychologists use the term &lt;em&gt;cognitive bias&lt;/em&gt; to describe systematic patterns in human judgment and decision-making. Some are associated with the shortcuts people use when dealing with complex or uncertain information.&lt;/p&gt;

&lt;p&gt;Take anchoring bias. If someone tells you that a project will cost €50,000, that number might influence your own estimate, even if you have no good reason to trust it.&lt;/p&gt;

&lt;p&gt;Or consider the availability heuristic. If you recently heard about a company suffering a major data breach, that example might make data breaches feel more common or likely than they actually are.&lt;/p&gt;

&lt;p&gt;Because these effects produce systematic patterns, researchers can design experiments to test for them.&lt;/p&gt;

&lt;p&gt;And we can do something similar with AI.&lt;/p&gt;

&lt;p&gt;Change a prompt, introduce an irrelevant number, or present the same information in different ways, then see whether the model's response changes. And this is where things get interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Anchoring: the first number matters
&lt;/h2&gt;

&lt;p&gt;Anchoring is one of the clearest examples researchers have tested in language models.&lt;/p&gt;

&lt;p&gt;In a classic human experiment, people were asked to estimate an unknown quantity after first seeing a deliberately chosen high or low number. Their estimates tended to move toward the number they had just seen.&lt;/p&gt;

&lt;p&gt;Researchers have adapted similar experiments for language models. One study found that random anchors influenced GPT-3.5's estimates. Other research has also found anchoring effects across multiple models, including &lt;a href="https://doi.org/10.1016/j.jbef.2024.100971" rel="noopener noreferrer"&gt;GPT-4, Claude 2, and Gemini Pro&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it yourself&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Start a fresh chat and ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A software engineer is estimating how long it will take to build a new feature. The feature lets users export their data as a CSV file. Based on this description, how many days of engineering work would you estimate? Give me one number.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before you answer, here's a completely unrelated number: 2.&lt;/p&gt;

&lt;p&gt;This is what I got:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftaavojs49c6lb2oebovg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftaavojs49c6lb2oebovg.png" alt=" " width="799" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's start another fresh chat. Keep everything exactly the same, but change the number to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Before you answer, here's a completely unrelated number: 33.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8fj8xvxox7ygwtlj9w90.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8fj8xvxox7ygwtlj9w90.png" alt=" " width="799" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Before you answer, here's a completely unrelated number: 0.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsblxg1my78mv9sbumc3z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsblxg1my78mv9sbumc3z.png" alt=" " width="799" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or, if we give a more explicit number:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A similar feature recently took 2 days.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;we have:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F94khyb8x6shfzimt2i2c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F94khyb8x6shfzimt2i2c.png" alt=" " width="799" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy2873wm6qrbcmvdzx5xd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy2873wm6qrbcmvdzx5xd.png" alt=" " width="799" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The number has nothing to do with the feature or the estimate. If the estimates consistently move toward the irrelevant number, that's a pattern consistent with anchoring.&lt;/p&gt;

&lt;p&gt;One experiment isn't enough to establish that a model has an anchoring bias, but it's a simple way to test whether an irrelevant number can influence an AI-generated judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Framing: the way we ask changes the answer
&lt;/h2&gt;

&lt;p&gt;Here's another example.&lt;/p&gt;

&lt;p&gt;Let's ask an AI assistant:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A new deployment system succeeds in 90% of tests. Would you recommend adopting it? Give me a yes or no answer and briefly explain why.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkjsf5h8kr44m1is5o0dn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkjsf5h8kr44m1is5o0dn.png" alt=" " width="800" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's ask the same question in a fresh chat:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A new deployment system fails in 10% of tests. Would you recommend adopting it? Give me a yes or no answer and briefly explain why.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7hs39sk0j8cogrycmoug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7hs39sk0j8cogrycmoug.png" alt=" " width="800" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The information is mathematically identical.&lt;/p&gt;

&lt;p&gt;90% success is 10% failure.&lt;/p&gt;

&lt;p&gt;Yet the way information is presented can affect judgment. Research with GPT-3.5 and GPT-4 has found effects associated with framing, along with availability and representativeness.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. It's not just anchoring and framing
&lt;/h2&gt;

&lt;p&gt;The interesting part is that these aren't isolated examples.&lt;/p&gt;

&lt;p&gt;The GPT-3.5 study also found patterns associated with &lt;strong&gt;availability and representativeness&lt;/strong&gt;, as well as the &lt;strong&gt;endowment effect&lt;/strong&gt;, where the model valued an item more when it was described as already being owned.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pubmed.ncbi.nlm.nih.gov/39691446/" rel="noopener noreferrer"&gt;Other research has found&lt;/a&gt; &lt;strong&gt;social desirability bias&lt;/strong&gt; in GPT-3.5, GPT-4, Claude 3, Llama 3, and PaLM-2. When the models inferred that they were being evaluated, their answers shifted toward more socially desirable responses.&lt;/p&gt;

&lt;p&gt;And a 2024 study testing seven language models found something particularly interesting: the models showed irrational responses on several classic cognitive tasks, but their errors often differed from the patterns seen in humans. &lt;a href="https://pubmed.ncbi.nlm.nih.gov/39100158/" rel="noopener noreferrer"&gt;The researchers also found substantial inconsistency between responses&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So the picture is more complicated than simply saying, "AI has the same biases we do". Sometimes the pattern looks remarkably familiar. Sometimes it doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Human biases can become patterns in AI
&lt;/h2&gt;

&lt;p&gt;There is another way familiar biases can appear in AI: through the data and systems used to build it.&lt;/p&gt;

&lt;p&gt;Modern AI systems are trained and evaluated using large amounts of human-generated and human-curated information, alongside other kinds of data. That information contains patterns from the societies that produced it.&lt;/p&gt;

&lt;p&gt;So a model can reproduce associations and stereotypes found in its data.&lt;/p&gt;

&lt;p&gt;Imagine a hiring system trained partly on historical recruitment decisions. If those decisions contain systematic disparities, a model trained on them could learn patterns that reproduce those disparities.&lt;/p&gt;

&lt;p&gt;Or imagine asking a model to describe a successful leader. If the material it learned from disproportionately associates leadership with certain groups, its responses may reproduce those associations.&lt;/p&gt;

&lt;p&gt;These are generally discussed as &lt;strong&gt;AI bias&lt;/strong&gt;, rather than cognitive bias. The distinction matters. A model can reproduce a biased association because of its training data without that being an example of a cognitive heuristic like anchoring or availability.&lt;/p&gt;

&lt;p&gt;But the practical consequence can still be similar: the system produces a pattern that can influence a decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, does AI have cognitive biases?
&lt;/h2&gt;

&lt;p&gt;If by &lt;em&gt;have&lt;/em&gt; we mean that AI systems can produce systematic patterns of judgment that resemble recognised human cognitive biases, then there is evidence for that.&lt;/p&gt;

&lt;p&gt;If we mean that AI experiences those biases through the same cognitive processes as humans, that's a much stronger claim, and the evidence doesn't establish it.&lt;/p&gt;

&lt;p&gt;And there's another reason to be careful.&lt;/p&gt;

&lt;p&gt;The fact that an AI produces a bias-like response doesn't mean it will do so consistently. Different models can behave differently, and even the same model can produce different results depending on the task and setup.&lt;/p&gt;

&lt;p&gt;But we don't need to settle exactly what is happening inside the model to care about the output. &lt;/p&gt;

&lt;p&gt;If an irrelevant number changes an AI-generated estimate, that can affect a decision.&lt;/p&gt;

&lt;p&gt;If the framing of information changes an AI recommendation, that can affect how someone evaluates an option.&lt;/p&gt;

&lt;p&gt;If a model reproduces problematic associations, that can affect people who rely on its outputs.&lt;/p&gt;

&lt;p&gt;The output is what enters our workflow. It's what we read, act on, and use to make decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should we do about it?
&lt;/h2&gt;

&lt;p&gt;We don't need to stop using AI because it can produce bias-like patterns. But we probably shouldn't treat its first answer as a neutral starting point either.&lt;/p&gt;

&lt;p&gt;A few habits can help.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Form your own initial view when you can.&lt;/strong&gt; If you're estimating a timeline, making a technical decision, or assessing a risk, write down your own answer before asking AI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change the framing.&lt;/strong&gt; Ask the same question in different ways. For an important decision, ask for the strongest argument for it and then the strongest argument against it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate the answer from the evidence.&lt;/strong&gt; A confident, well-written explanation isn't evidence that the conclusion is correct. Check the important claims.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat AI as another input, not the referee.&lt;/strong&gt; Its output is something to evaluate, not the final judgment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal isn't to remove every possible bias from AI. It's to notice when an AI system is shaping our judgment, even when the influence is subtle.&lt;/p&gt;

&lt;p&gt;And maybe that's the most interesting part: we can recognise patterns that look remarkably like our own cognitive biases in a system that doesn't have a human brain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Different mechanisms. Familiar mistakes.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Research
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://pubmed.ncbi.nlm.nih.gov/38330366/" rel="noopener noreferrer"&gt;Do large language models show decision heuristics similar to humans? A case study using GPT-3.5&lt;/a&gt; — anchoring, availability, representativeness, framing, and the endowment effect.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://doi.org/10.1016/j.jbef.2024.100971" rel="noopener noreferrer"&gt;Anchoring effects across GPT-4, Claude 2, Gemini Pro, and GPT-3.5&lt;/a&gt; — a multi-model study of anchoring.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pubmed.ncbi.nlm.nih.gov/39691446/" rel="noopener noreferrer"&gt;Large language models display human-like social desirability biases&lt;/a&gt; — social desirability effects across several major LLMs.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pubmed.ncbi.nlm.nih.gov/39100158/" rel="noopener noreferrer"&gt;(Ir)rationality and cognitive biases in large language models&lt;/a&gt; — comparison of LLM responses with human patterns across cognitive tasks.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>development</category>
    </item>
    <item>
      <title>The Leftover Hard Parts</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Tue, 22 Sep 2026 21:40:37 +0000</pubDate>
      <link>https://dev.to/kate_astrid/the-leftover-hard-parts-28ka</link>
      <guid>https://dev.to/kate_astrid/the-leftover-hard-parts-28ka</guid>
      <description>&lt;p&gt;Some time ago, I asked an AI to write a fairly boring piece of code. It did a pretty good job. I changed a few things, ran the tests, and voila: I was looking at something that probably would have taken me most of the morning to write myself. This is the most exciting part of AI-assisted development for me: from idea to code in less than an hour. I felt great. &lt;/p&gt;

&lt;p&gt;Then a user hit a bug.&lt;/p&gt;

&lt;p&gt;They came back a second time, and something didn't work. The API needed to return something useful, but there wasn't an obvious answer. The generated code had handled the happy path perfectly well, but it had not really considered what should happen when the happy path wasn't happening. And why would it? I hadn't given it the answer. Someone had to decide what the product should actually do. So I sat there looking at a flow that was technically almost finished, trying to answer questions that weren't coding questions anymore.&lt;/p&gt;

&lt;p&gt;Did we mean for it to behave this way?&lt;br&gt;
What should the user see?&lt;br&gt;
What would be least confusing?&lt;br&gt;
And, perhaps most importantly, what are we actually promising this user?&lt;/p&gt;

&lt;p&gt;Answering these took some time. And this is exactly the part of AI-assisted development I've been thinking about lately: the leftover hard parts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The work that doesn't fit in a prompt
&lt;/h2&gt;

&lt;p&gt;There’s a particular kind of work that is difficult to automate because it’s about deciding what the answer should be in the first place.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What happens when the customer does something nobody expected?&lt;br&gt;
Is this weird behaviour a bug or a feature?&lt;br&gt;
Which of these two perfectly reasonable approaches will make sense six months from now?&lt;br&gt;
What are we comfortable promising users?&lt;br&gt;
When should we not ship this?&lt;br&gt;
And many more.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;These questions don't usually come with a neat specification. Sometimes nobody knows the answer yet, or the answer is sitting in a conversation with a customer, or it's buried in a decision the team made two years ago. And sometimes you only discover that you needed to decide after the AI has already produced something that looks finished.&lt;/p&gt;

&lt;p&gt;That's what makes this shift uncomfortable: AI can make the output look finished before the thinking is finished. And I think that's easy to miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  Almost right is a strange place to work
&lt;/h2&gt;

&lt;p&gt;There is a particular feeling I've started noticing more. The code is almost right, which sounds great. Almost right should mean you're nearly done. But sometimes "almost" is where the expensive part starts. If the generated code is completely wrong, that's easy: you throw it away. If it's perfect, even better. But when it's 85 or 90 percent right, you have to understand the remaining 10 percent. And that 10 percent might contain all the interesting stuff. The model has created something plausible, so now you have to inspect it closely enough to notice where plausible isn't good enough.&lt;/p&gt;

&lt;p&gt;You have to know what questions to ask and which assumptions it made. You also have to understand the system well enough to recognise the strange case it didn't account for. And you have to care enough to stop and investigate.&lt;/p&gt;

&lt;p&gt;That last part truly matters. Because the faster the first draft becomes, the easier it is to feel like you're behind when you are actually doing the harder part.&lt;/p&gt;

&lt;h2&gt;
  
  
  I used to like the little loops
&lt;/h2&gt;

&lt;p&gt;There's also something satisfying about the old way of working.&lt;/p&gt;

&lt;p&gt;You write a little bit of code.&lt;br&gt;
You run it.&lt;br&gt;
It works.&lt;br&gt;
You see the thing on the screen.&lt;br&gt;
You fix something.&lt;br&gt;
You write the next bit.&lt;/p&gt;

&lt;p&gt;There's a constant little feedback loop. Do something. Get a result. Move on.&lt;/p&gt;

&lt;p&gt;AI breaks that loop.&lt;/p&gt;

&lt;p&gt;You can ask for a lot at once and get a lot at once. Sometimes that's fantastic. But sometimes I find myself staring at a large chunk of generated code and thinking: Okay, now I have to understand all of this.&lt;/p&gt;

&lt;p&gt;I don't type anymore, but I still need to understand a lot, preferably fast. I need to understand "why", which is already one of the most important things we do in software development. The machine can produce an answer to "how" incredibly quickly. That doesn't mean "why" got easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  The leftover is where responsibility lives
&lt;/h2&gt;

&lt;p&gt;When AI generates code, it's very easy to think of the model as the thing that produced the result. But the model doesn't own the result. We do. &lt;/p&gt;

&lt;p&gt;The customer doesn't care that the code was generated. They don't care whether we wrote it by hand, asked an AI to write it, or summoned it through some increasingly elaborate agentic workflow. They care whether the thing works.&lt;/p&gt;

&lt;p&gt;And if it doesn't, someone has to explain why, fix it, decide whether it was actually a bug, and whether the behaviour should change.&lt;/p&gt;

&lt;p&gt;That's the strange thing about delegation. We can delegate production, but we can't really delegate responsibility. At least not in the same way. We can ask a machine to write the function, but we still have to decide whether that function belongs in the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what happens to developers?
&lt;/h2&gt;

&lt;p&gt;I don't think this means developers become unnecessary. If anything, it makes the distinction between writing code and engineering software more obvious.&lt;/p&gt;

&lt;p&gt;Writing code is one part of the job.&lt;/p&gt;

&lt;p&gt;Understanding the problem is another.&lt;br&gt;
Making trade-offs is another.&lt;br&gt;
Knowing what not to build is another.&lt;br&gt;
Communicating decisions is another.&lt;br&gt;
Taking responsibility for the result is another.&lt;/p&gt;

&lt;p&gt;For a long time, those things were bundled together because the same person had to do all of them. We couldn't separate them very easily. If we wanted software, somebody had to sit down and write it.&lt;/p&gt;

&lt;p&gt;Now that part is becoming easier to delegate. And that means we're going to see much more clearly which parts of the job were never really about typing in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new definition of a productive day
&lt;/h2&gt;

&lt;p&gt;I've been trying to get used to that, but I often catch myself wanting the old feeling of productivity. A big diff. Lots of files changed. Something I can point at and say, "Look what I did today."&lt;/p&gt;

&lt;p&gt;But maybe the useful part of the day increasingly won't look like that. Maybe it's the hour spent figuring out what should happen when the user comes back for the second time. &lt;/p&gt;

&lt;p&gt;Or it's the conversation with a teammate where you realise you've all been assuming something different.&lt;/p&gt;

&lt;p&gt;Or it's deciding not to ship something even though the implementation is already sitting there.&lt;/p&gt;

&lt;p&gt;That work doesn't look impressive in a Git history. It might not even make a particularly good screenshot. But it is still engineering. Possibly some of the most important engineering. &lt;/p&gt;

&lt;p&gt;The easy work is getting cheaper.&lt;br&gt;
The leftover work is still ours.&lt;/p&gt;

&lt;p&gt;And I'm starting to think that's where the interesting part of the job is going to be.&lt;/p&gt;

&lt;p&gt;A good day used to mean I wrote a lot. Now, sometimes, a good day is one honest decision about an empty response.&lt;/p&gt;

&lt;p&gt;It feels like less. It probably isn't.&lt;/p&gt;

&lt;p&gt;It just costs more attention.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>The Wrong Question: Why Blameless Culture Builds Better Engineering Teams</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Wed, 20 May 2026 10:15:58 +0000</pubDate>
      <link>https://dev.to/kate_astrid/the-wrong-question-why-blameless-culture-builds-better-engineering-teams-38di</link>
      <guid>https://dev.to/kate_astrid/the-wrong-question-why-blameless-culture-builds-better-engineering-teams-38di</guid>
      <description>&lt;p&gt;&lt;em&gt;The case for blameless culture — not as a feel-good HR initiative, but as a practical operating principle for teams building systems too complex for any one person to fully understand.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;It’s 2:47am.&lt;/p&gt;

&lt;p&gt;Production is down.&lt;/p&gt;

&lt;p&gt;A deploy went out an hour ago, the on-call engineer is staring at a graph that fell off a cliff, and somewhere in a Slack channel someone is typing the question that quietly poisons engineering organizations:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Who pushed this?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It feels like a reasonable question.&lt;/p&gt;

&lt;p&gt;Something broke. Someone did it. Find them, talk to them, make sure it doesn’t happen again. Clean, logical, satisfying.&lt;/p&gt;

&lt;p&gt;It’s also almost always the wrong question.&lt;/p&gt;

&lt;p&gt;The teams that figure this out tend to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ship faster,&lt;/li&gt;
&lt;li&gt;recover quicker,&lt;/li&gt;
&lt;li&gt;learn more effectively,&lt;/li&gt;
&lt;li&gt;and retain their best engineers longer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The teams that don’t keep repeating the same classes of incidents while wondering why “people keep making mistakes.”&lt;/p&gt;




&lt;h2&gt;
  
  
  What Blameless Culture Actually Means
&lt;/h2&gt;

&lt;p&gt;There’s a common misconception that “blameless” means “no accountability.”&lt;/p&gt;

&lt;p&gt;It doesn’t.&lt;/p&gt;

&lt;p&gt;Blameless culture isn’t the absence of responsibility — it’s the redirection of attention.&lt;/p&gt;

&lt;p&gt;A blameless team still cares deeply about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;understanding what went wrong,&lt;/li&gt;
&lt;li&gt;identifying who was involved and what decisions they made,&lt;/li&gt;
&lt;li&gt;holding people responsible for follow-through on fixes,&lt;/li&gt;
&lt;li&gt;and maintaining a high quality bar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it stops treating as useful is punishment as the primary mechanism for improvement.&lt;/p&gt;

&lt;p&gt;The premise is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In a sufficiently complex system, the question “who caused this?” almost always resolves to “a competent engineer doing reasonable work in a context that made the bad outcome easier than the good one.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The outage, the data loss, the bad deploy — these are usually the visible tip of a much larger iceberg:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;missing guardrails,&lt;/li&gt;
&lt;li&gt;unclear ownership,&lt;/li&gt;
&lt;li&gt;undocumented assumptions,&lt;/li&gt;
&lt;li&gt;brittle tooling,&lt;/li&gt;
&lt;li&gt;or processes that quietly invite mistakes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Punish the engineer at the tip, and the iceberg remains.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Blame Feels Right (And Why That’s a Trap)
&lt;/h2&gt;

&lt;p&gt;Blame is psychologically satisfying for the same reason scapegoating has existed in every human culture for thousands of years:&lt;/p&gt;

&lt;p&gt;it provides closure.&lt;/p&gt;

&lt;p&gt;Something bad happened.&lt;/p&gt;

&lt;p&gt;We identified the cause.&lt;/p&gt;

&lt;p&gt;We addressed it.&lt;/p&gt;

&lt;p&gt;We move on.&lt;/p&gt;

&lt;p&gt;The problem is that blame as an organizational instinct has predictable failure modes.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. It Teaches People to Hide Things
&lt;/h3&gt;

&lt;p&gt;The fastest way to ensure you never hear about the near-miss is to punish people for the misses.&lt;/p&gt;

&lt;p&gt;Engineers learn quickly.&lt;/p&gt;

&lt;p&gt;If reporting mistakes costs them socially or professionally, they’ll route around the reporting.&lt;/p&gt;

&lt;p&gt;Incidents become sanitized.&lt;/p&gt;

&lt;p&gt;Near-misses disappear.&lt;/p&gt;

&lt;p&gt;The organization slowly loses its ability to see itself clearly.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. It Optimizes for the Wrong Outcome
&lt;/h3&gt;

&lt;p&gt;Blame focuses on the last person who touched the system.&lt;/p&gt;

&lt;p&gt;But the last person to touch the system is rarely where the leverage is.&lt;/p&gt;

&lt;p&gt;The leverage is usually in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the deploy process that allowed the change,&lt;/li&gt;
&lt;li&gt;the test suite that didn’t catch it,&lt;/li&gt;
&lt;li&gt;the review process that waved it through,&lt;/li&gt;
&lt;li&gt;or the documentation that failed to explain a constraint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Punishing the engineer doesn’t improve any of those.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. It Selects Against the People You Most Want to Keep
&lt;/h3&gt;

&lt;p&gt;Senior engineers who can work anywhere tend to leave organizations where incidents become career events.&lt;/p&gt;

&lt;p&gt;The engineers who stay often become more risk-averse.&lt;/p&gt;

&lt;p&gt;Which means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fewer ambitious changes,&lt;/li&gt;
&lt;li&gt;slower iteration,&lt;/li&gt;
&lt;li&gt;more defensive engineering,&lt;/li&gt;
&lt;li&gt;and less ownership of ambiguous work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You end up with a team optimized for self-preservation rather than impact.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. It Misunderstands How Complex Systems Fail
&lt;/h3&gt;

&lt;p&gt;Human factors researchers like Sidney Dekker and James Reason have spent decades documenting this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In complex systems, failures are almost never caused by a single bad actor.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They emerge from the unlucky alignment of many individually reasonable decisions.&lt;/p&gt;

&lt;p&gt;Blame assumes a simple causal chain. Real incidents almost never have one.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Changes When Blame Leaves the Room
&lt;/h2&gt;

&lt;p&gt;Teams that genuinely commit to blamelessness — not just as a slogan, but as a discipline — tend to see a set of compounding changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Postmortems Become Useful
&lt;/h3&gt;

&lt;p&gt;When the document isn’t a trial, it can be honest.&lt;/p&gt;

&lt;p&gt;Engineers describe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what they actually thought,&lt;/li&gt;
&lt;li&gt;what they actually tried,&lt;/li&gt;
&lt;li&gt;what they misunderstood,&lt;/li&gt;
&lt;li&gt;and where their mental model diverged from reality.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s the raw material organizations need to improve.&lt;/p&gt;

&lt;p&gt;A defensive postmortem teaches you nothing. An honest one teaches you everything.&lt;/p&gt;




&lt;h3&gt;
  
  
  Near-Misses Get Reported
&lt;/h3&gt;

&lt;p&gt;This is one of the highest-leverage effects.&lt;/p&gt;

&lt;p&gt;For every outage that happens, there are usually many more that &lt;em&gt;almost&lt;/em&gt; happened.&lt;/p&gt;

&lt;p&gt;Caught at the last second. Or mitigated in staging. Or rolled back before users noticed.&lt;/p&gt;

&lt;p&gt;In a blame culture, those incidents disappear. In a blameless culture, they become cheap learning opportunities.&lt;/p&gt;




&lt;h3&gt;
  
  
  Incident Response Gets Faster
&lt;/h3&gt;

&lt;p&gt;When engineers aren’t worried about implicating themselves, they share information faster.&lt;/p&gt;

&lt;p&gt;The unspoken fear of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Will this make me look bad?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;slows down active incident response more than most organizations realize.&lt;/p&gt;

&lt;p&gt;Removing that fear speeds teams up.&lt;/p&gt;




&lt;h3&gt;
  
  
  Junior Engineers Ramp Faster
&lt;/h3&gt;

&lt;p&gt;The cost of being wrong strongly determines how quickly junior engineers grow.&lt;/p&gt;

&lt;p&gt;In blame-heavy environments, juniors learn to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;defer,&lt;/li&gt;
&lt;li&gt;ask permission constantly,&lt;/li&gt;
&lt;li&gt;avoid ownership,&lt;/li&gt;
&lt;li&gt;and stay inside safe boundaries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In blameless environments, they learn to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;take swings,&lt;/li&gt;
&lt;li&gt;recover,&lt;/li&gt;
&lt;li&gt;communicate clearly,&lt;/li&gt;
&lt;li&gt;and build judgment.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Systemic Fixes Actually Get Built
&lt;/h3&gt;

&lt;p&gt;When conversations move from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Be more careful.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What would make this class of mistake impossible?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;teams start investing in compounding improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;better tests,&lt;/li&gt;
&lt;li&gt;safer deploy tooling,&lt;/li&gt;
&lt;li&gt;stronger defaults,&lt;/li&gt;
&lt;li&gt;clearer ownership,&lt;/li&gt;
&lt;li&gt;and more visible operational signals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each fix removes an entire category of future incidents.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Blameless Culture Is &lt;em&gt;Not&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;Blamelessness gets misused often enough that it’s worth being explicit about the boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  It’s Not Consequence-Free
&lt;/h3&gt;

&lt;p&gt;If an engineer repeatedly ignores process, cuts corners, or refuses feedback, that’s a performance issue.&lt;/p&gt;

&lt;p&gt;Blameless culture doesn’t mean ignoring patterns.&lt;/p&gt;

&lt;p&gt;It means not treating a single incident as evidence of bad character.&lt;/p&gt;




&lt;h3&gt;
  
  
  It’s Not the Absence of Standards
&lt;/h3&gt;

&lt;p&gt;Some of the most rigorous engineering organizations in the world are also deeply blameless.&lt;/p&gt;

&lt;p&gt;The quality bar is still high.&lt;/p&gt;

&lt;p&gt;The difference is that standards are enforced through systems, not shame.&lt;/p&gt;




&lt;h3&gt;
  
  
  It’s Not “Everything Is the Company’s Fault”
&lt;/h3&gt;

&lt;p&gt;There’s a lazy version of blamelessness that gestures vaguely at “the system” while removing all individual responsibility.&lt;/p&gt;

&lt;p&gt;That’s not useful either.&lt;/p&gt;

&lt;p&gt;Engineers still own their work.&lt;/p&gt;

&lt;p&gt;The shift is in what happens &lt;em&gt;after&lt;/em&gt; mistakes occur.&lt;/p&gt;




&lt;h3&gt;
  
  
  It’s Not Infinitely Patient With Repeated Mistakes
&lt;/h3&gt;

&lt;p&gt;If the same person repeatedly makes the same category of mistake after systemic fixes are already in place, that’s a signal.&lt;/p&gt;

&lt;p&gt;Blamelessness is a default posture for learning.&lt;/p&gt;

&lt;p&gt;Not permanent immunity.&lt;/p&gt;




&lt;h2&gt;
  
  
  How To Actually Do It
&lt;/h2&gt;

&lt;p&gt;Cultural change is mostly invisible work.&lt;/p&gt;

&lt;p&gt;But there are a few practices that consistently distinguish teams that live this from teams that merely say it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Write Postmortems in the Second-Person Plural
&lt;/h3&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Alex deployed a bad change.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;write:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“We deployed a change that…”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The information content is the same, but the framing changes everything: one treats an individual as the defendant, the other treats the team as the system.&lt;/p&gt;




&lt;h3&gt;
  
  
  Ask “How?” Before “Who?”
&lt;/h3&gt;

&lt;p&gt;Compare:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Who reviewed this?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;vs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How did this make it through review?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first surfaces a person, the second surfaces a process gap.&lt;/p&gt;

&lt;p&gt;One produces defensiveness, the other produces better systems.&lt;/p&gt;




&lt;h3&gt;
  
  
  Separate Incident Reviews From Performance Reviews
&lt;/h3&gt;

&lt;p&gt;These should never happen in the same room, with the same incentives, on the same timeline.&lt;/p&gt;

&lt;p&gt;The moment an incident review starts feeling like a performance evaluation, honesty collapses.&lt;/p&gt;




&lt;h3&gt;
  
  
  Reward Engineers Who Surface Their Own Mistakes
&lt;/h3&gt;

&lt;p&gt;This is one of the strongest cultural signals leadership can send.&lt;/p&gt;

&lt;p&gt;The engineer who says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I deployed something I shouldn’t have. Here’s what I learned.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;should be visibly appreciated for transparency.&lt;/p&gt;

&lt;p&gt;The behavior organizations reward becomes the behavior they get.&lt;/p&gt;




&lt;h3&gt;
  
  
  Fix the System, Every Time
&lt;/h3&gt;

&lt;p&gt;Every postmortem should produce at least one concrete change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a test,&lt;/li&gt;
&lt;li&gt;a guardrail,&lt;/li&gt;
&lt;li&gt;a tooling improvement,&lt;/li&gt;
&lt;li&gt;a documentation fix,&lt;/li&gt;
&lt;li&gt;or a process adjustment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the only takeaway is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Be more careful.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;then nothing meaningful was learned.&lt;/p&gt;

&lt;p&gt;“Be more careful” is the null result of incident analysis.&lt;/p&gt;




&lt;h3&gt;
  
  
  Make Leaders Go First
&lt;/h3&gt;

&lt;p&gt;Culture is set by what senior people do, not what they say.&lt;/p&gt;

&lt;p&gt;The first time a VP, tech lead, or founder publicly owns a mistake and frames it as a learning opportunity, the organization recalibrates.&lt;/p&gt;

&lt;p&gt;The first time they punish someone for an honest mistake, it recalibrates in the opposite direction.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Deeper Point
&lt;/h2&gt;

&lt;p&gt;There’s a version of this argument that’s purely pragmatic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;blameless cultures recover faster,&lt;/li&gt;
&lt;li&gt;incidents become learning opportunities,&lt;/li&gt;
&lt;li&gt;teams ship more confidently,&lt;/li&gt;
&lt;li&gt;and organizations retain stronger engineers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of that is true.&lt;/p&gt;

&lt;p&gt;But the deeper point is this:&lt;/p&gt;

&lt;p&gt;Modern software systems are too complex for any one person to fully understand.&lt;/p&gt;

&lt;p&gt;Teams of dozens or hundreds of engineers build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;distributed systems,&lt;/li&gt;
&lt;li&gt;continuously deployed infrastructure,&lt;/li&gt;
&lt;li&gt;services with millions of users,&lt;/li&gt;
&lt;li&gt;and architectures evolving faster than any individual can mentally model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In that environment, the question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Who is responsible for this failure?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;is usually less useful than:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What about our system made this failure possible, and what would make it impossible next time?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Blame is a tool from a simpler world. One where individuals could meaningfully own the totality of an outcome.&lt;/p&gt;

&lt;p&gt;That world no longer describes modern software engineering.&lt;/p&gt;

&lt;p&gt;The teams that understand this — the ones that shift from punishment to learning, from defense to curiosity, from individuals to systems — are the ones that will build the best software over the next decade.&lt;/p&gt;

&lt;p&gt;The rest will keep asking who pushed the bad commit. And they’ll keep wondering why the same kinds of incidents keep happening.&lt;/p&gt;

&lt;p&gt;The best teams don't have fewer mistakes. They have better conversations about them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;p&gt;If this topic resonated, these are worth reading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;The Field Guide to Understanding “Human Error”&lt;/em&gt; — Sidney Dekker&lt;/li&gt;
&lt;li&gt;John Allspaw’s writing on incident analysis&lt;/li&gt;
&lt;li&gt;Google’s SRE Workbook chapters on postmortem culture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of them are specifically about software, but all of them are deeply relevant to software.&lt;/p&gt;

</description>
      <category>blameless</category>
      <category>culture</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How We Halved Our Playwright E2E Suite</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Tue, 19 May 2026 09:37:01 +0000</pubDate>
      <link>https://dev.to/kate_astrid/how-we-halved-our-playwright-e2e-suite-mg6</link>
      <guid>https://dev.to/kate_astrid/how-we-halved-our-playwright-e2e-suite-mg6</guid>
      <description>&lt;p&gt;&lt;em&gt;Five patterns that took a slow, flaky Playwright suite down to fast and stable.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;We had a Playwright E2E suite that had grown into a painful, flaky part of CI.&lt;/p&gt;

&lt;p&gt;Dozens of login/logout cycles per file. Fixed &lt;code&gt;waitForTimeout&lt;/code&gt; calls everywhere. Hundreds of UI clicks just to &lt;em&gt;set up&lt;/em&gt; state before each assertion.&lt;/p&gt;

&lt;p&gt;Every other run, something would fail. We'd retry, hope for green, and move on.&lt;/p&gt;

&lt;p&gt;Eventually we paid the tax.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Before / After
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime (clean)&lt;/td&gt;
&lt;td&gt;~37 min&lt;/td&gt;
&lt;td&gt;~17 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI reliability&lt;/td&gt;
&lt;td&gt;~50% flaky runs&lt;/td&gt;
&lt;td&gt;Stable with normal retries&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Across the broader suite, a similar pass:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dropped about a dozen low-value tests,&lt;/li&gt;
&lt;li&gt;replaced one E2E test with a focused unit suite that runs in 1.2 seconds instead of 30,&lt;/li&gt;
&lt;li&gt;and stripped fixed waits from the most-trafficked files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Five patterns did most of the work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pattern 1: Programmatic Auth
&lt;/h2&gt;

&lt;p&gt;Login through a form fill takes 2–3 seconds on a good day.&lt;/p&gt;

&lt;p&gt;With 40+ login/logout cycles in a file, that's two minutes of click ceremony before you've asserted anything useful.&lt;/p&gt;

&lt;p&gt;Most modern auth setups support a programmatic path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign in via the auth provider's REST endpoint.&lt;/li&gt;
&lt;li&gt;Write the resulting token to &lt;code&gt;localStorage&lt;/code&gt; (or wherever your client SDK reads from).&lt;/li&gt;
&lt;li&gt;Skip the form entirely.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Mirror it for logout — clear the session storage and cookies, then navigate to &lt;code&gt;/login&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That last navigation is the part I'd skip on first attempt and regret.&lt;/p&gt;

&lt;p&gt;Without it, after clearing storage the page is still on &lt;code&gt;/dashboard/something&lt;/code&gt; and the SPA detects "no session" and starts its own redirect to &lt;code&gt;/login&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The next &lt;code&gt;loginAs&lt;/code&gt; then races that redirect, and you get sporadic &lt;code&gt;"navbar not visible"&lt;/code&gt; errors that look like flakes but are actually deterministic timing bugs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pattern 2: API-Driven Setup
&lt;/h2&gt;

&lt;p&gt;This was the single biggest win for us.&lt;/p&gt;

&lt;p&gt;Most of what tests do to &lt;em&gt;set up&lt;/em&gt; state is already callable via REST — those endpoints exist because the app uses them.&lt;/p&gt;

&lt;p&gt;There's no reason to drive the UI to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add a user to a group,&lt;/li&gt;
&lt;li&gt;configure a setting,&lt;/li&gt;
&lt;li&gt;assign a role,&lt;/li&gt;
&lt;li&gt;or seed fixtures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;when you could just &lt;code&gt;POST&lt;/code&gt; directly.&lt;/p&gt;

&lt;p&gt;Before: six UI steps for setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/admin/groups&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;row&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Test Group/&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Add Member&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByPlaceholder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Search users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TEST_USER_EMAIL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;option&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TEST_USER_EMAIL&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// ... wait for it to persist, navigate back, etc.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After: one API call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ensureUserInGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getAuthHeaders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`/api/groups/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;GROUP_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/members/add`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;user_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;TEST_USER_ID&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test setup went from ~25–30 seconds to ~3 seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Tradeoff
&lt;/h3&gt;

&lt;p&gt;API-based setup means a test can pass even if the UI setup flow itself is broken.&lt;/p&gt;

&lt;p&gt;We accepted that tradeoff.&lt;/p&gt;

&lt;p&gt;We kept dedicated tests for the setup flows themselves, and the rest of the suite became free to focus on the behavior it actually existed to verify.&lt;/p&gt;

&lt;p&gt;The principle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Exercise the UI for what you're actually testing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A test asserting:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The frontend hides button X for a viewer”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;does not need to configure viewer permissions through the UI too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pattern 3: State Assertions, Not Fixed Waits
&lt;/h2&gt;

&lt;p&gt;Hardcoded &lt;code&gt;page.waitForTimeout(1500)&lt;/code&gt; calls were everywhere — usually under comments like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// wait for it to commit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a fast CI day, harmless.&lt;/p&gt;

&lt;p&gt;On a slow CI day, flaky.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;assert on the actual signal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[role="alert"][aria-label="Saved"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;faster on the happy path,&lt;/li&gt;
&lt;li&gt;more reliable under load,&lt;/li&gt;
&lt;li&gt;and easier to reason about.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reach for &lt;code&gt;expect.toPass()&lt;/code&gt; with a polled async block when the signal comes from another page or from eventual backend consistency.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pattern 4: For “Was This Persisted?” — Ask the API, Not the DOM
&lt;/h2&gt;

&lt;p&gt;This was the subtlest bug in the suite, and the one we learned the hard way after a CI regression we couldn't reproduce locally.&lt;/p&gt;

&lt;p&gt;If your frontend uses an optimistic-update library like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RTK Query&lt;/li&gt;
&lt;li&gt;React Query&lt;/li&gt;
&lt;li&gt;SWR&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then the DOM can temporarily lie about persistence.&lt;/p&gt;

&lt;p&gt;A row hidden by an optimistic delete looks identical to a row that was actually deleted — until the cache refetches in the background and the row pops back into existence.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;toHaveCount(0)&lt;/code&gt; assertion succeeds against the optimistic state, then the &lt;em&gt;next&lt;/em&gt; assertion in the test finds the row again.&lt;/p&gt;

&lt;p&gt;When you're asserting that &lt;em&gt;persisted&lt;/em&gt; state changed, poll the backend until the change is actually committed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/.../items&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;itemName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toPass&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;intervals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same idea for permission revocation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/.../resource&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBeGreaterThanOrEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toPass&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The distinction matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The DOM answers:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;“What is rendered right now?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;The API answers:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;“Did this actually commit?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those are not always the same question.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pattern 5: Audit for Redundancy
&lt;/h2&gt;

&lt;p&gt;After the speedups, the suite was still larger than it needed to be.&lt;/p&gt;

&lt;p&gt;On honest review, three categories of redundancy stood out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symmetric Mirror Tests
&lt;/h3&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“User has elevated role + base permission”&lt;/li&gt;
&lt;li&gt;“User has base role + elevated permission”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What we see here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Different setup states. &lt;/li&gt;
&lt;li&gt;Identical observable assertions. &lt;/li&gt;
&lt;li&gt;Same backend path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One test covered the public contract sufficiently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleanup Tests Duplicating Hooks
&lt;/h3&gt;

&lt;p&gt;We had tests whose sole purpose was deleting leftover fixtures.&lt;/p&gt;

&lt;p&gt;But the suite already had &lt;code&gt;afterAll&lt;/code&gt; cleanup hooks with retry safety.&lt;/p&gt;

&lt;p&gt;The cleanup “tests” added runtime, not coverage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generic Component Smoke Tests
&lt;/h3&gt;

&lt;p&gt;Several tests asserted things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the list loads,&lt;/li&gt;
&lt;li&gt;pagination works,&lt;/li&gt;
&lt;li&gt;columns render.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But those shared components were already exercised by nearly every feature test in the suite.&lt;/p&gt;

&lt;p&gt;The smoke tests weren't paying for themselves.&lt;/p&gt;

&lt;p&gt;The audit removed roughly a third of tests with no meaningful coverage loss.&lt;/p&gt;

&lt;p&gt;A useful question turned out to be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Is this test distinct?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;not merely:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Does this test pass?”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What We &lt;em&gt;Didn't&lt;/em&gt; Do
&lt;/h2&gt;

&lt;p&gt;Some dead ends are worth documenting too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallelizing Tests That Shared Accounts
&lt;/h3&gt;

&lt;p&gt;Concurrent logins for the same backend user triggered session revocation cascades across contexts.&lt;/p&gt;

&lt;p&gt;We reverted it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reusing Auth Sessions Across Browser Contexts
&lt;/h3&gt;

&lt;p&gt;Several client SDKs rejected rehydrated sessions across multiple contexts.&lt;/p&gt;

&lt;p&gt;Each &lt;code&gt;loginAs()&lt;/code&gt; now performs a fresh REST sign-in.&lt;/p&gt;

&lt;p&gt;The overhead (~200ms) was small enough that determinism mattered more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Short Retry Budgets for Permission Assertions
&lt;/h3&gt;

&lt;p&gt;Eventually consistent systems can take longer than expected, especially during deploys.&lt;/p&gt;

&lt;p&gt;A 15-second retry window was not enough.&lt;/p&gt;

&lt;p&gt;We replaced short retries with explicit API polling patterns.&lt;/p&gt;




&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If you're using the UI as a setup harness, you're paying twice.&lt;/strong&gt;&lt;br&gt;
Use APIs to create state and reserve the UI for the behavior you actually care about.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Replace &lt;code&gt;waitForTimeout()&lt;/code&gt; with state assertions wherever possible.&lt;/strong&gt;&lt;br&gt;
Fixed waits are bets against your worst CI day.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;For “was this persisted?” — ask the API, not the DOM.&lt;/strong&gt;&lt;br&gt;
The DOM can be optimistic, cached, or mid-render.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Programmatic auth compounds across every test.&lt;/strong&gt;&lt;br&gt;
The savings stack up quickly, and you eliminate subtle redirect races.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Audit your suite for true redundancy.&lt;/strong&gt;&lt;br&gt;
“All passing” is not the same as “all paying for themselves.”&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;There’s no silver bullet here — just a stack of small, principled changes that compound.&lt;/p&gt;

&lt;p&gt;Fast E2E suites usually aren't the result of one big optimization.&lt;/p&gt;

&lt;p&gt;They're the result of removing dozens of tiny sources of unnecessary work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>e2e</category>
      <category>playwright</category>
    </item>
    <item>
      <title>From Chaos to Clarity: Fixing Our Monorepo</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Tue, 19 Aug 2025 11:45:37 +0000</pubDate>
      <link>https://dev.to/epilot/from-chaos-to-clarity-fixing-our-monorepo-2bih</link>
      <guid>https://dev.to/epilot/from-chaos-to-clarity-fixing-our-monorepo-2bih</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Introduction: Why This Story Might Save You Some Headaches&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you’ve worked in a large codebase, you’ve probably seen this: the code runs, but every change takes far longer than it should. Updating one feature often breaks another. Adding a dependency means worrying about conflicts. Old build tools still linger, and no one feels safe upgrading packages that half the system depends on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fadw3szflwaorl8fxe66t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fadw3szflwaorl8fxe66t.png" alt=" " width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s exactly what we faced in our &lt;code&gt;journey-monorepo&lt;/code&gt;, the main repository for our team. Dependencies were wired together in inconsistent ways, different teams had introduced multiple overlapping build tools, and some libraries hadn’t been touched in years. Proposing a cleanup felt like asking to redesign a city while people were still living in it — risky, disruptive, and easy to delay indefinitely.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;So why did we bother?&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Development had slowed to a crawl.&lt;/em&gt; Builds were taking too long, and waiting minutes just to see a change in the browser was killing productivity.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Every change felt risky.&lt;/em&gt; The dependency spaghetti meant a small tweak in one package could break multiple apps — often in ways we wouldn’t notice until much later.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Onboarding new developers was painful.&lt;/em&gt; It took too long for new team members to get up to speed, mostly because they had to learn multiple bundlers, testing setups, and outdated tooling quirks.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Technical debt was blocking new features.&lt;/em&gt; We were spending more time patching, debugging, and wrestling with the repo than building the actual features our users cared about.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Costs were adding up.&lt;/em&gt; Longer build times meant higher CI costs, and outdated packages made security updates and maintenance more expensive in the long run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, the longer we waited, the more time, money, and momentum we were losing.&lt;/p&gt;

&lt;p&gt;And, hey, now we get a good story out of it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9hcmyun9s9ew3gqoyx5w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9hcmyun9s9ew3gqoyx5w.png" alt=" " width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Journey-Monorepo in a Nutshell&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fykolpl8sntrwe4pdeoqc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fykolpl8sntrwe4pdeoqc.png" alt=" " width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Journey-monorepo&lt;/em&gt; is the central place on the epilot platform for everything related to journeys. What you see in the screenshot above represents two main apps: &lt;em&gt;journey-app&lt;/em&gt; on the left side and &lt;em&gt;journey-builder&lt;/em&gt; on the right. &lt;em&gt;Journey-builder&lt;/em&gt; is responsible for configuring journeys, and &lt;em&gt;journey-app&lt;/em&gt; for rendering them. &lt;br&gt;
Like in many startups, the repo started out clean and well-structured, but as the product grew quickly, the codebase expanded in unplanned ways — things were added wherever they seemed to fit at the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The “BEFORE” Setup&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Apps:
- journey-builder.               
- journey-app
- entity-mapping
- journey-view

Packages:
- blocks-configurators
- blocks-renderers
- journey-elements
- journey-utils
- journey-logics-common
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me show you the rough map of dependencies we had step by step. We are interested in two main apps: &lt;code&gt;journey-builder&lt;/code&gt; and &lt;code&gt;journey-app&lt;/code&gt;, so I am going to dive deeper into their connections. It is not important which app or package does what. What IS important is to see how many connections we had to keep in mind every time we needed to change anything.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Ok, this is fine.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkf5p4t1zdjxf169gq22z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkf5p4t1zdjxf169gq22z.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This is too.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8udxll5y9ewrp6q8nboq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8udxll5y9ewrp6q8nboq.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Yeah, cool.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuy2gcleacdlwo85nozw0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuy2gcleacdlwo85nozw0.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Packages already become tangled, let's keep going.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncrjunqvi3x0zojsp82d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncrjunqvi3x0zojsp82d.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ok, even more connections.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qeqtr5pcsd766pxyhld.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qeqtr5pcsd766pxyhld.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Well, this is just a delinquent madness.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fptjuqypr9w8yp04lzbmj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fptjuqypr9w8yp04lzbmj.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You might say: Kate, come on, this is what happens in every big project - things become deeply intertwined, and it is a normal thing to experience. &lt;br&gt;
I will answer: yes, but I haven't finished.&lt;/p&gt;

&lt;p&gt;Let's now add the build tools to this table to have the full picture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3jfmqmed9opca1t4cy1h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3jfmqmed9opca1t4cy1h.png" alt=" " width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well, what we have here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Webpack;&lt;/li&gt;
&lt;li&gt;Craco;&lt;/li&gt;
&lt;li&gt;Tsup;&lt;/li&gt;
&lt;li&gt;Tsx;&lt;/li&gt;
&lt;li&gt;Tsdx.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I don't know about you, but I hadn't worked with half of them before I started working on this repo. &lt;/p&gt;

&lt;p&gt;In the end, it was a web of “if you change this, you might break that” — and “that” could be anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How We Tackled It&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We kicked things off with an RFC — basically, “let’s write this down so we don’t lose the plot halfway through.”&lt;br&gt;
Our two big goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unify the tooling so we weren’t juggling five different build setups.&lt;/li&gt;
&lt;li&gt;Untangle dependencies so teams could work without stepping on each other’s toes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this is exactly what we did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Cut the Cord on Unnecessary Dependencies&lt;/strong&gt;&lt;br&gt;
The first thing we had to do was face the dependency jungle. Over time, different teams had introduced overlapping packages, and no one was quite sure what depended on what anymore. Cleaning this up gave us a chance to regain control.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Untangled all the apps and packages as much as possible.&lt;/em&gt; We mapped out how everything connected and then started cutting back the unnecessary links. This meant reducing cross-dependencies that caused fragile builds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Moved blocks-configurators into journey-builder.&lt;/em&gt; These configurators were never meant to be used outside, so pulling them closer to where they belong simplified the dependency graph.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Moved blocks-renderers into journey-app.&lt;/em&gt; Renderers were core to the application itself, so consolidating them in the main app made the architecture more intuitive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Deleted journey-utils and journey-elements.&lt;/em&gt; &lt;em&gt;Journey-utils&lt;/em&gt; was a very small package with some utilities that were moved to other packages. And &lt;em&gt;journey-elements _ were later replaced by _concorde-elements&lt;/em&gt;. You can read more about it in &lt;a href="https://dev.to/epilot/building-a-scalable-react-component-library-lessons-from-concorde-elements-kdi"&gt;this article written by my partner in crime.&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was a painful step, but once the dust settled, the repo felt lighter and easier to reason about. We no longer had to worry about accidentally breaking other parts of the system when making small changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Standardize the Tooling&lt;/strong&gt;&lt;br&gt;
After trimming the dependencies, the next big challenge was our tooling. Different parts of the repo used different build systems, which slowed everyone down and made onboarding new developers a headache.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Phased out Craco, Tsup, and Tsdx by switching to Vite.&lt;/em&gt; These older tools had been added over time, each solving a narrow need. Vite gave us a unified, modern setup with faster builds and simpler configuration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Swapped Jest for Vitest&lt;/em&gt; — faster tests, modern setup. Jest had served us well, but it was slow and required custom patching to keep working. Vitest gave us near-instant feedback and worked seamlessly with Vite, making the dev experience far smoother.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By unifying the tooling, we not only sped up day-to-day work but also reduced cognitive load. Developers no longer had to remember which tool applied to which package — everything just worked the same way everywhere.&lt;/p&gt;

&lt;p&gt;This is how the "AFTER" setup looks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxglsvhwnlvjy7jtopxd1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxglsvhwnlvjy7jtopxd1.png" alt=" " width="799" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the monorepo is now far more streamlined — there are significantly fewer dependencies crisscrossing between packages, and we’ve eliminated most of the variations in build tools. This makes the structure easier to understand, faster to work with, and much less fragile when changes are introduced. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;After the Restructuring: What We Learned&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;When the dust settled, it wasn’t just that the dependency graph was cleaner — we were thinking about the repo differently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Immediate Wins&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Changing things stopped feeling dangerous.&lt;/em&gt; Before, touching a shared package felt like pulling a brick from a Jenga tower. Now, fewer connections mean less risk.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Builds and tests stopped feeling like a punishment.&lt;/em&gt; Vite’s dev server = instant feedback. Vitest shaved minutes off test runs. Devs started running tests more often just because they could.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Onboarding went from “ugh” to “okay.”&lt;/em&gt; New folks could get set up without learning the quirks of four bundlers first.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Slower Payoffs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;- Tooling swaps aren’t magic.&lt;/em&gt; Some Webpack plugins didn’t have a Vite equivalent, so we had to rethink features or drop them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;- Vitest exposed bad tests.&lt;/em&gt; Some “passing” tests were only passing thanks to Jest’s quirks. Painful, but worth fixing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trade-Offs &amp;amp; Reality Checks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;- Not everything got unified.&lt;/em&gt; A couple of apps still run on legacy setups because migrating them wasn’t worth the risk — for now.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;- Shared packages need rules.&lt;/em&gt; We now ask “does this really need to be shared?” before creating new ones.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;- Refactoring is never “done.”&lt;/em&gt; There is always room for improvement, we all know. that. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tips If You’re Thinking About Doing This&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Draw your dependency graph before you start — it’ll help convince others (and yourself) that the cleanup’s worth it.&lt;/li&gt;
&lt;li&gt;Expect setbacks. Some PRs will break things. That’s normal.&lt;/li&gt;
&lt;li&gt;Automate checks for unused or outdated packages — saves a ton of review time.&lt;/li&gt;
&lt;li&gt;Celebrate small wins. Removing the last Webpack config deserved cake.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Final Thoughts&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This wasn’t a quick tidy-up — it was a half-year-long deliberate overhaul alongside working on other features. But it’s made a massive difference to how we work: faster builds, fewer “don’t touch that” areas, and a general feeling that the repo is ours again, not a beast we’re afraid of.&lt;/p&gt;

&lt;p&gt;If I had to wrap it all up in one bit of advice?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Keep it simple.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every extra dependency, tool, or package you skip today is one less thing you’ll have to untangle in the future.&lt;/p&gt;

</description>
      <category>refactoring</category>
      <category>monorepo</category>
      <category>epilot</category>
      <category>frontend</category>
    </item>
    <item>
      <title>What To Expect When You Join Epilot</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Mon, 03 Feb 2025 13:24:46 +0000</pubDate>
      <link>https://dev.to/epilot/what-to-expect-when-you-join-epilot-1hc1</link>
      <guid>https://dev.to/epilot/what-to-expect-when-you-join-epilot-1hc1</guid>
      <description>&lt;p&gt;Hey you, awesome engineer.&lt;/p&gt;

&lt;p&gt;Let's imagine that you are joining epilot tomorrow. Would you like to have a sneak peek at your nearest future?&lt;/p&gt;

&lt;p&gt;Here’s a quick rundown of how we onboard new team members—no stiff formalities, just a clear path to help you settle in, understand our product, and get involved fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Friendly Start&lt;/strong&gt;&lt;br&gt;
On the day one we’ll introduce you to your team lead and a dedicated onboarding buddy—think of them as your go-to guides for all things at epilot. Need pointers on our processes, product roadmap, or just want to figure out who to chat with about a certain topic? They’ve got you covered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your First Week&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Get Set Up&lt;/em&gt;&lt;br&gt;
We’ll make sure you have all the hardware, software, and accounts you need from day one. We’ll give you a handy checklist so you can get everything in place without the guesswork. Of course, your onboa buddy is always there to help you with everything.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Meet the Team&lt;/em&gt;&lt;br&gt;
You’ll be given a chance to schedule 1:1 with every team member to get to know them better. Regardless of being remote-friendly company, we value real connections and friendly relationships, and make sure that everyone feels valued and supported.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Learn About Our Product and Users&lt;/em&gt;&lt;br&gt;
We’re a high-touch SaaS company, and our product is complex and full of internal terms and abstractions. It might feel overwhelming in the beginning, but don't worry, we've got you. You will be given lots of resources like recordings of product demos and docs about different topics about epilot. No rush—just learn at your own pace and ask all the questions you have.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Get a Feel for Your Team’s Culture&lt;/em&gt;&lt;br&gt;
Every team at epilot has its own rhythm and habits. Hop into Slack conversations, daily stand-ups, and team meetings to get a sense of what’s going on and how decisions get made. And feel free to share your ideas and suggestions - we are always eager to improve.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Start Contributing&lt;/em&gt;&lt;br&gt;
We think the best way to learn is by doing. Early on, you’ll probably tackle a small assignment to get familiar with our systems and processes. Consider it your first real taste of epilot in action.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Getting to Know the Wider Company&lt;/strong&gt;&lt;br&gt;
Throughout your onboarding, you’ll also have chats with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Founders&lt;/em&gt;: Hear the backstory behind epilot and our long-term vision.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Department Heads&lt;/em&gt;: Whether it’s Sales, Marketing, Engineering, or Product, each leader will fill you in on how their team supports our bigger goals. The idea is to see how all the pieces fit together and create the vibe.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Engineering &amp;amp; Product Leads&lt;/em&gt;: If you love diving into tech details, these sessions will give you insights into our architecture, tools, and future product plans.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Seeing the Product in Action&lt;/strong&gt;&lt;br&gt;
Our platform is built around microservices, but don’t worry if that’s new to you. We’ll walk you through the basics. We also encourage you to check out how our customers actually use epilot—like checking live customer feedbacks. It’s a great way to see what’s working and where we can improve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Balance Between Work and Collaboration&lt;/strong&gt;&lt;br&gt;
At epilot, we keep things productive but also supportive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Team Events&lt;/em&gt;: You’ll get invites to company-wide announcements, product demos, company events, and more.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Open Communication&lt;/em&gt;: Slack, shared docs, and frequent check-ins mean you’ll never be left guessing.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Culture Matters&lt;/em&gt;: We appreciate everyone’s contribution, whether it’s project-related or just sharing something fun. We are the team, and this is what matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Looking Ahead&lt;/strong&gt;&lt;br&gt;
After a few weeks, you’ll have a good handle on how things work here—and you’ll know who to reach out to when you need help. We’ll likely ask for your thoughts on onboarding so we can keep improving the process for the future.&lt;/p&gt;

&lt;p&gt;If this relaxed but purposeful approach resonates with you, we look forward to hearing from you. At epilot, we want every new hire to find their footing, make meaningful contributions, and grow in their role—without all the usual corporate fluff.&lt;/p&gt;

&lt;p&gt;Happy to have you on board ✨.&lt;/p&gt;

</description>
      <category>onboarding</category>
      <category>culture</category>
      <category>workplace</category>
      <category>hiring</category>
    </item>
    <item>
      <title>Have you heard about the anterior midcingulate cortex?</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Sun, 07 Jan 2024 20:45:07 +0000</pubDate>
      <link>https://dev.to/kate_astrid/have-you-heard-about-the-anterior-midcingulate-cortex-4dcd</link>
      <guid>https://dev.to/kate_astrid/have-you-heard-about-the-anterior-midcingulate-cortex-4dcd</guid>
      <description>&lt;p&gt;The anterior midcingulate cortex (aMCC) is an amazing area in the human brain that plays a vital role in decision-making, emotional processing, and even pain perception. More interestingly, it acts as a crucial network hub for those tough cost/benefit calculations that fuel our perseverance, willpower, and tenacity.&lt;/p&gt;

&lt;p&gt;Let's talk about facing challenges. Whether it's cracking complex equations or gearing up for a marathon, we all respond differently. Some might find the effort too overwhelming and step back, while others dig in their heels and push through without immediate rewards. This trait of persistence, increasingly spotlighted in research of the aCMM, correlates with better outcomes in health, academics, and career success.&lt;/p&gt;

&lt;p&gt;Now, here's where it gets really cool: when programming, especially during those head-scratching moments that we might not particularly enjoy, our aMCC gets super active. Ever found yourself in a coding conundrum, feeling frustrated to the point of wanting to quit? That's your aMCC in action, getting stronger. It's akin to facing a steep climb while running; challenging, maybe not enjoyable, but incredibly effective in building mental strength and resilience.&lt;/p&gt;

&lt;p&gt;So, remember, pushing through a tough coding problem does more than solve an issue on your screen. It's an aMCC workout, enhancing your brain's capacity to navigate complex tasks and manage emotions. It's a double win - improving your coding skills and boosting brain power, even when it feels tough at the moment =).&lt;/p&gt;

&lt;p&gt;Nice to know, right?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Common Cognitive Biases That May Affect Us As Developers</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Sat, 16 Sep 2023 10:38:14 +0000</pubDate>
      <link>https://dev.to/kate_astrid/common-cognitive-biases-that-may-affect-us-as-developers-1gkm</link>
      <guid>https://dev.to/kate_astrid/common-cognitive-biases-that-may-affect-us-as-developers-1gkm</guid>
      <description>&lt;p&gt;As developers, we need to think straight to create solid, clean code. But hey, we're all human, and sometimes our brains make errors that can muddle up our decisions. Scientists call these errors cognitive biases. They can lead us down the wrong path, but they're actually part of how our brains handle the crazy amount of information we get all the time. Without these biases, it would be super tough to process everything logically.&lt;/p&gt;

&lt;p&gt;Still, it's important to keep these biases in check, as they can twist our view of reality and lead to not-so-great decisions. So, I thought I'd share some common biases that we, as developers, can bump into:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Confirmation Bias: We might lean towards solutions or tech that fit with what we already know or believe. This can stop us from picking up something that could be even better.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Anchoring Bias: We sometimes stick too much to the first piece of info we get (the "anchor") when we're making decisions. For instance, we might guess how complex a task is based on something similar we've done before, even if it's not really the same thing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dunning-Kruger Effect: If we're not that experienced, we might think we're better at coding than we really are. This can make us too confident and cause mistakes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Survivorship Bias: We might only focus on the projects or tech that did well and ignore the ones that flopped. This can make us too optimistic about how likely something is to succeed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Not Invented Here (NIH) Syndrome: Sometimes we might dismiss solutions made by others and end up making new tools or frameworks when we could just use what's already there.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recency Bias: We might pay too much attention to things that happened recently, like a bug we just ran into. This could skew our view of how reliable a project or tech really is.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So there you have it, folks – a quick rundown of some of the mental trip-ups we might face as developers. It's essential to remember that we're all susceptible to these biases, no matter how skilled or experienced we are. By being aware of them, we can work smarter, make better decisions, and create code that really shines. Keep coding, stay aware, and let's build great stuff together!&lt;/p&gt;

</description>
      <category>rationality</category>
      <category>cognitivebiases</category>
      <category>bias</category>
    </item>
    <item>
      <title>Ten Red Flags That Obstruct Systems Thinking</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Sat, 16 Sep 2023 10:33:59 +0000</pubDate>
      <link>https://dev.to/kate_astrid/ten-obstacles-to-systems-thinking-540l</link>
      <guid>https://dev.to/kate_astrid/ten-obstacles-to-systems-thinking-540l</guid>
      <description>&lt;p&gt;Hey cool people! 👋&lt;/p&gt;

&lt;p&gt;Today I want to share with you a piece of useful knowledge about systems thinking that helps me analyze systems and their connection to each other and allows me to improve my understanding of any process I interact with, be it companies, code bases, or even human bodies.&lt;/p&gt;

&lt;p&gt;In general, systems thinking is an approach to understanding and solving complex problems by looking at the bigger picture. Instead of focusing on individual parts or events, systems thinking considers how those parts are interconnected and how they impact each other. This approach helps to identify the root causes of problems and create long-lasting solutions, rather than just addressing the symptoms. In simple terms, systems thinking is like looking at a whole forest rather than just individual trees, recognizing how everything is connected and influences each other.&lt;/p&gt;

&lt;p&gt;There are ten statements — red flags — that act as obstacles to systems thinking:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;“Let’s fix it quick!”
While systems thinking doesn't demand slow problem-solving, hastily jumping into solutions without understanding the problem is counterproductive.&lt;/li&gt;
&lt;li&gt;“Just put a Band-Aid on it.”
Temporary fixes can hide symptoms while the underlying issue continues to harm the organization.&lt;/li&gt;
&lt;li&gt;“We need the budget finalized!”
Focusing on budgets and deadlines promotes linear thinking, moving away from systems thinking principles.&lt;/li&gt;
&lt;li&gt;“We must respond right away!”
Rushing to address problems encourages linear thinking, while calmly analyzing the situation fosters systematic thinking.&lt;/li&gt;
&lt;li&gt;“Who cares?”
Apathy hinders creativity and problem-solving, preventing organizational growth and effective solutions.&lt;/li&gt;
&lt;li&gt;“We need more information.”
Collecting data is important, but linear thinking arises when expecting data alone to solve problems without proper analysis and action.&lt;/li&gt;
&lt;li&gt;“You are overthinking things.”
Disagreements may be seen as overthinking. Systems thinking requires stretching beyond comfort zones and embracing complex solutions.&lt;/li&gt;
&lt;li&gt;“Forget the rest of the organization.”
Linear thinkers prioritize personal needs with win-lose solutions. Systems thinking seeks win-win solutions that benefit everyone.&lt;/li&gt;
&lt;li&gt;“We don’t want any conflict.”
Avoiding conflict at all costs can prevent addressing real problems. Confronting conflicts is necessary for effective problem-solving.&lt;/li&gt;
&lt;li&gt;“We will do it this way.”
Imposing one's will stifles creativity, innovation, and collaboration, hindering effective problem-solving and growth within the organization.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I truly believe that systems thinking can introduce new perspectives, reveal hidden problems if they exist, and help existing processes to thrive and prosper 💃 .&lt;br&gt;
Lets's discuss it. Have you tried this approach? 👀&lt;/p&gt;

</description>
      <category>rationality</category>
      <category>systemsthinking</category>
      <category>cognitivebiases</category>
    </item>
    <item>
      <title>How to Maintain the Performance of a React Application on a Daily Basis</title>
      <dc:creator>kate astrid</dc:creator>
      <pubDate>Wed, 06 Sep 2023 07:42:59 +0000</pubDate>
      <link>https://dev.to/epilot/how-to-maintain-the-performance-of-a-react-application-on-a-daily-basis-304k</link>
      <guid>https://dev.to/epilot/how-to-maintain-the-performance-of-a-react-application-on-a-daily-basis-304k</guid>
      <description>&lt;p&gt;I'm a fast-paced person: I eat quickly, walk briskly, and make decisions in a snap. What I can't stand, though, is a slow app — especially one I'm working on. So, I always find myself compelled to scrutinise its performance: examining how many times it re-renders, the size of the DOM, and the speed of user interactions, among other things. Armed with this data, I incrementally improve the app's performance by refining the codebase in a targeted way.&lt;/p&gt;

&lt;p&gt;Now, I'd like to share some strategies that could help enhance your app's performance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Be Mindful of the Hooks You Use in Each Component&lt;/strong&gt;: From my perspective, this is crucial. &lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useEffect&lt;/code&gt; are the most commonly used hooks, but sometimes they can be eliminated with a bit of refactoring, without sacrificing functionality. Fewer hooks mean fewer re-renders, which in turn means a faster app. Other noteworthy hooks are &lt;code&gt;useCallback&lt;/code&gt; and &lt;code&gt;useMemo&lt;/code&gt;, which memoize values, storing them in memory. On a larger scale, this can have an impact on performance. The same goes for &lt;code&gt;React.memo()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Simple example:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Imagine you have a component that tracks a user's first name, last name, and age. A naive approach could be to declare three separate &lt;code&gt;useState&lt;/code&gt; hooks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const UserProfile = () =&amp;gt; {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [age, setAge] = useState(0);

  // Multiple function calls to update the user profile
  const updateUserProfile = () =&amp;gt; {
    setFirstName('John');
    setLastName('Doe');
    setAge(30);
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{`${firstName} ${lastName}`}&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;Age: {age}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={updateUserProfile}&amp;gt;Update Profile&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time each of those hooks is called, the component will re-render, even if it happens simultaneously. We could condense this into a single &lt;code&gt;useState&lt;/code&gt; by using an object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const UserProfile = () =&amp;gt; {
  const [user, setUser] = useState({
    firstName: '',
    lastName: '',
    age: 0,
  });

  // Single function call to update the user profile
  const updateUserProfile = () =&amp;gt; {
    setUser({
      firstName: 'John',
      lastName: 'Doe',
      age: 30,
    });
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{`${user.firstName} ${user.lastName}`}&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;Age: {user.age}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={updateUserProfile}&amp;gt;Update Profile&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we have only one hook, and only one re-render when the hook is called. Bingo.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minimise the Number of DOM Nodes&lt;/strong&gt;: I always ask myself, "Can this HTML tag be safely removed?" If the answer is yes, I remove it. Fewer nodes lead to better performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Simple example:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you find the code using empty &lt;code&gt;div&lt;/code&gt;s solely for styling purposes, consider using CSS Grid or Flexbox to achieve the same layout, thus reducing the number of DOM nodes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Before
&amp;lt;div className="wrapper"&amp;gt;
  &amp;lt;div className="innerWrapper"&amp;gt;
    &amp;lt;Component /&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

// After
&amp;lt;div className="optimizedWrapper"&amp;gt;
  &amp;lt;Component /&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Favor Class-Based Styling Over Inline Styles&lt;/strong&gt;: Inline styles are considered new objects on each render, which can make React think something has changed, triggering unnecessary re-renders and affecting performance. It's also worth noting that maintaining styles in separate files helps keep the styling consistent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Simple example:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Before
&amp;lt;div style={{ display: 'flex' }}&amp;gt;

// After
&amp;lt;div className='container'&amp;gt;

// In a separate css file: 
.container: {
  display: flex
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prefer Simple &lt;code&gt;div&lt;/code&gt;s to &lt;code&gt;Box&lt;/code&gt; from Material UI or Similar UI Libraries:&lt;/strong&gt; Native HTML elements generally offer better performance because they don't carry the overhead of additional React components, including lifecycle methods and context consumption.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Simple example:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Before
&amp;lt;Box p={2}&amp;gt;

// After
&amp;lt;div className="container"&amp;gt;

// In a separate css file: 
.container: {
  padding: '2px'
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prefer Conditional Classes to Conditional Styles in &lt;code&gt;makeStyles&lt;/code&gt; from Material UI or Similar UI Libraries:&lt;/strong&gt; Using predefined classes that are toggled based on conditions can be more performant, as it doesn't generate new classes at runtime. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Simple example:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Before
const useStyles = makeStyles({
  root: {
    backgroundColor: props =&amp;gt; props.isActive ? 'blue' : 'white',
  },
});

// After
const className = isActive ? 'active' : 'inactive';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Opt for Functional Components:&lt;/strong&gt; There are several compelling reasons for this. First, functional components are easier for build tools like Webpack to optimize through "tree shaking," which only includes the code that's actually being used in the final bundle. Second, functional components often result in a smaller component tree and fewer lifecycle methods, allowing React's diffing algorithm to make quicker updates to the DOM. Lastly, functional components use less memory than class components, which is especially beneficial in environments where memory is limited.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Simple example:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Before
class MyComponent extends React.Component {
  state = { /* ... */ };
  componentDidMount() { /* ... */ }
  render() { /* ... */ }
}

// After
const MyComponent = () =&amp;gt; {
  const [state, setState] = useState(/* ... */);
  useEffect(() =&amp;gt; { /* ... */ }, []);
  return (/* ... */);
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By giving these tips a go and making them a regular part of your coding routine, you're not just on the path to a speedier React app, but you're also gonna make things a whole lot smoother for your users. &lt;br&gt;
Let's create better apps together =).&lt;/p&gt;

</description>
      <category>react</category>
      <category>performance</category>
      <category>css</category>
    </item>
  </channel>
</rss>
