<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:series="https://publishpress.com/"
	>

<channel>
	<title>Atomic Spin</title>
	<atom:link href="http://spin.atomicobject.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://spin.atomicobject.com/</link>
	<description>Atomic Object’s blog on everything we find fascinating.</description>
	<lastBuildDate>Mon, 08 Jun 2026 20:08:49 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://spin.atomicobject.com/wp-content/uploads/cropped-AO-Favicon-512-32x32.png</url>
	<title>Atomic Spin</title>
	<link>https://spin.atomicobject.com/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>The Value of In-Person Backlog Management Tools</title>
		<link>https://spin.atomicobject.com/in-person-backlog-management-tools/</link>
					<comments>https://spin.atomicobject.com/in-person-backlog-management-tools/#respond</comments>
		
		<dc:creator><![CDATA[Valerie Nielson]]></dc:creator>
		<pubDate>Mon, 08 Jun 2026 12:00:20 +0000</pubDate>
				<category><![CDATA[Development Practices]]></category>
		<category><![CDATA[backlog]]></category>
		<guid isPermaLink="false">https://spin.atomicobject.com/?p=3673854</guid>

					<description><![CDATA[<p>As a Junior Developer who is just starting out, I never really got to witness the world of Software design without virtual backlog management. I often hear about the boards filled with sticky notes as the backlog stand-in, but I always just assumed that since we don’t do it anymore, it must be inefficient and [&#8230;]</p>
<p>The post <a href="https://spin.atomicobject.com/in-person-backlog-management-tools/">The Value of In-Person Backlog Management Tools</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>As a Junior Developer who is just starting out, I never really got to witness the world of Software design without virtual backlog management. I often hear about the boards filled with sticky notes as the backlog stand-in, but I always just assumed that since we don’t do it anymore, it must be inefficient and costly. However, when the project I was working on, Gripfusion, had a huge app redesign and refactor, the team wondered about the best way to help us get so much done in two months effectively.</p>
<h2>Context</h2>
<p>The app designs were approved by the client and were ready to be mocked out. However, with a huge firmware change coming, we had to redo a lot of how the app worked, meaning there would have to be multiple streams of work, one for just design and one for just restoring the Bluetooth functionality to the app. It became clear that it was going to be hard to keep track of what in the app would be usable, was being worked on, had only visual designs, or only had functionality.</p>
<p>While the backlog worked, it took time to sift through what was in there quickly, let alone explain to someone who wasn’t deep in the weeds. The Team came up with the idea to go old school to help keep track of the app refactor. We printed out all the Figma designs and taped them to a whiteboard. We utilized different colored and labeled sticky notes to keep track of the progress of that designed page. By being able to see the Figma designs and to see their current progress, it provided the team a lot more value than expected. It was able to keep track of which pages had the functionality behind them, while some pages were only designed in Storybook.</p>
<p>It was nice to take part of the old software world and use an actual whiteboard to track our work. It&#8217;s true that the <a href="https://linear.app/">Linear</a> backlog we utilized was still extremely vital to the project, and none of it could have been done without Figma. But having a physical copy of what was inside Linear and inside Figma allowed us to make sure stories weren&#8217;t being missed.</p>
<h2>Value #1: Improved Understanding</h2>
<p><figure id="attachment_3673922" aria-describedby="caption-attachment-3673922" style="width: 352px" class="wp-caption alignleft"><img fetchpriority="high" decoding="async" class="wp-image-3673922" src="https://spin.atomicobject.com/wp-content/uploads/WhiteboardInProgress-590x443.jpg" alt="The same whiteboard as before with the designs but now with 7 green sticky notes and 9 pink sticky notes" width="352" height="264" srcset="https://spin.atomicobject.com/wp-content/uploads/WhiteboardInProgress-590x443.jpg 590w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardInProgress-1024x768.jpg 1024w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardInProgress-150x113.jpg 150w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardInProgress-768x576.jpg 768w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardInProgress-1536x1152.jpg 1536w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardInProgress-2048x1536.jpg 2048w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardInProgress-600x450.jpg 600w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardInProgress-1200x900.jpg 1200w" sizes="(max-width: 352px) 100vw, 352px" /><figcaption id="caption-attachment-3673922" class="wp-caption-text">&#8220;In progress.&#8221; Some done stories (green sticky notes) &amp; a few done only in storybook (pink sticky notes).</figcaption></figure></p>
<p>Sometimes a backlog can be overwhelming, especially when there are hundreds of stories. Having the visuals on the whiteboard made it easier to understand where a story was coming from or what was being worked on.</p>
<p>It was also a sanity check that you understood what a story meant. Oftentimes, when putting up the sticky note, you’d have to recall where it belonged. Sometimes you’d realize it’s not just in one place, or that the thing you are making could be used somewhere else, and it helped make clear what should be abstracted out versus specialized. It also helped others know where we were at and what a story meant as we would write the name of the story on the sticky note.</p>
<blockquote><p>“The whiteboard made it easy to have conversations about progress on the roadmap in a tangible way. Sometimes, looking at pictures is much easier to wrap your head around than looking at a backlog&#8217;s list of stories.”</p>
<p>&#8211; Renee Liken, Delivery Lead</p></blockquote>
<p>&nbsp;</p>
<h2>Value #2: Constant Visibility</h2>
<p><figure id="attachment_3673925" aria-describedby="caption-attachment-3673925" style="width: 309px" class="wp-caption alignright"><img decoding="async" class="wp-image-3673925" src="https://spin.atomicobject.com/wp-content/uploads/WhiteboardWONotYets-590x443.jpg" alt="The same whiteboard as before however now mostly covered in green with a few blue, and orange sticky notes" width="309" height="232" srcset="https://spin.atomicobject.com/wp-content/uploads/WhiteboardWONotYets-590x443.jpg 590w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardWONotYets-1024x768.jpg 1024w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardWONotYets-150x113.jpg 150w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardWONotYets-768x576.jpg 768w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardWONotYets-1536x1152.jpg 1536w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardWONotYets-2048x1536.jpg 2048w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardWONotYets-600x450.jpg 600w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardWONotYets-1200x900.jpg 1200w" sizes="(max-width: 309px) 100vw, 309px" /><figcaption id="caption-attachment-3673925" class="wp-caption-text">In-progress stories (blue sticky notes), a few in review (orange sticky notes), and a lot done (green sticky notes).</figcaption></figure></p>
<p>Something about virtual tools that physical tools will always have the upper hand on is that they are there. This whiteboard allowed the team to gain context even when they weren’t looking at it.</p>
<p>You’d come in and be able to see out of the corner of your eye that there was more green, orange, or blue, and by that, you’d be able to just know that there was progress being made or that you should check the PR list.</p>
<blockquote><p>“For me the biggest value is it’s something that’s in your face all the time so whenever you got to the office, you were able to glance at it and take a look at our progress and see where we’re at was nice to have a non-digital version available for the team to unite around”</p>
<p>&#8211; Bryan Elkus, Designer</p></blockquote>
<p>&nbsp;</p>
<h2>Value #3: Improved Team Morale</h2>
<p><figure id="attachment_3673926" aria-describedby="caption-attachment-3673926" style="width: 363px" class="wp-caption alignleft"><img decoding="async" class="wp-image-3673926" src="https://spin.atomicobject.com/wp-content/uploads/WhiteboardDone-590x444.jpg" alt="The same whiteboard as before with multiple yellow, orange, blue and pink sticky notes however every design photo has a green sticky note on it" width="363" height="273" srcset="https://spin.atomicobject.com/wp-content/uploads/WhiteboardDone-590x444.jpg 590w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardDone-1024x771.jpg 1024w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardDone-150x113.jpg 150w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardDone-768x578.jpg 768w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardDone-1536x1157.jpg 1536w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardDone-2048x1542.jpg 2048w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardDone-600x452.jpg 600w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardDone-1200x904.jpg 1200w" sizes="(max-width: 363px) 100vw, 363px" /><figcaption id="caption-attachment-3673926" class="wp-caption-text">Toward the end. Pink sticky notes to indicate bug or feature requests stories, and things still in review (orange), in progress(blue), or not yet completed (yellow).</figcaption></figure></p>
<p>One of the values the whiteboard provided was that it made it easier to see the wins the team was achieving. Being able to visibly see each state and page of the app made it easy to note and celebrate even the small stories getting done. That&#8217;s because they, too, got a green sticky note when they were done.</p>
<p>One of my favorite things when we were using the whiteboard was that when you finished a frustrating story, you got to take a beat to write DONE on a sticky note and take the In Review one down, crumple it up, and proudly display the done sticky note. It was a great way to take a second to appreciate the work being done before moving on to the next story that needed to be done.</p>
<p>&nbsp;</p>
<h2>Value #4: Effortless Updates</h2>
<p><figure id="attachment_3673927" aria-describedby="caption-attachment-3673927" style="width: 361px" class="wp-caption alignright"><img loading="lazy" decoding="async" class="wp-image-3673927" src="https://spin.atomicobject.com/wp-content/uploads/WhiteboardInReview-590x444.jpg" alt="The same whiteboard but with a a few green sticky notes but the board mainly has pink and orange sticky notes in the mid section of the board on some of the designs." width="361" height="272" srcset="https://spin.atomicobject.com/wp-content/uploads/WhiteboardInReview-590x444.jpg 590w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardInReview-1024x771.jpg 1024w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardInReview-150x113.jpg 150w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardInReview-768x578.jpg 768w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardInReview-1536x1157.jpg 1536w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardInReview-2048x1542.jpg 2048w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardInReview-600x452.jpg 600w, https://spin.atomicobject.com/wp-content/uploads/WhiteboardInReview-1200x904.jpg 1200w" sizes="auto, (max-width: 361px) 100vw, 361px" /><figcaption id="caption-attachment-3673927" class="wp-caption-text">Many things in review (orange) or in storybook (pink), and a few things done (green).</figcaption></figure></p>
<p>On our team, both our designer and delivery lead were on other projects in our office. That meant they were often balancing multiple plans, stories, and meetings, and often didn’t have time to do a full dive into our backlog.</p>
<p>Not only that, our client was balancing efforts with a firmware team and a hardware team. So, while he was co-located in our office, he often didn’t have the time to fully dive into our process unless we were in a meeting where that was relevant. The whiteboard provided those updates or check-ins without interrupting dev time or without the client having to ask as many questions. Being able to see what was in progress, in review, or done quickly was very valuable as the team grew busier.</p>
<blockquote><p>“In our case, the physical board gave me peace of mind that our co-located clients could easily see our tracked progress at a glance and on a regular basis. Before it was set up, I would have to worry about whether they would actually keep up with our backlog.”</p>
<p>&#8211; Patrick Pale, Tech Lead</p></blockquote>
<h2>Value #5: Public Facing Accountability</h2>
<p><figure id="attachment_3673924" aria-describedby="caption-attachment-3673924" style="width: 380px" class="wp-caption alignleft"><img loading="lazy" decoding="async" class="wp-image-3673924" src="https://spin.atomicobject.com/wp-content/uploads/whiteboardWNotYets-590x444.jpg" alt="The same whiteboard but with a lot of yellow sticky notes and green covering the board. Some pink and blue sticky notes here and there. " width="380" height="286" srcset="https://spin.atomicobject.com/wp-content/uploads/whiteboardWNotYets-590x444.jpg 590w, https://spin.atomicobject.com/wp-content/uploads/whiteboardWNotYets-1024x771.jpg 1024w, https://spin.atomicobject.com/wp-content/uploads/whiteboardWNotYets-150x113.jpg 150w, https://spin.atomicobject.com/wp-content/uploads/whiteboardWNotYets-768x578.jpg 768w, https://spin.atomicobject.com/wp-content/uploads/whiteboardWNotYets-1536x1157.jpg 1536w, https://spin.atomicobject.com/wp-content/uploads/whiteboardWNotYets-2048x1542.jpg 2048w, https://spin.atomicobject.com/wp-content/uploads/whiteboardWNotYets-600x452.jpg 600w, https://spin.atomicobject.com/wp-content/uploads/whiteboardWNotYets-1200x904.jpg 1200w" sizes="auto, (max-width: 380px) 100vw, 380px" /><figcaption id="caption-attachment-3673924" class="wp-caption-text">Toward the end of the refactor, with a new &#8220;not yet&#8221; yellow sticky notes, blue in-progress sticky notes, pink bug or feature request sticky notes, and green done sticky notes.</figcaption></figure></p>
<p>As the Gripfusion team grew and we gained more people on the client side, the whiteboard made it easier to catch people up on what was happening with the project, where we were in the project, what you should have soon, what should be tested, etc. Having such a prominent tool gave the developer team clear accountability because we could point at an image and say, “This will be done this week” without getting lost in technical jargon, making the goal clear.</p>
<p>Moreso, it made it easier for others to ask about progress or issues with the app, being able to say, “I see here that this is marked as done. However it’s not behaving as I expected.” That makes it easier than having to open the backlog, find the developer’s story, check the progress, and then bring it to the developers.</p>
<blockquote><p>“I liked it because it makes the concepts the teams are talking about visible in a way that’s public and open that gives accountability to everyone and to see progress in a tangible way.”</p>
<p>&#8211; Mason Ferlic, Gripfusion Co-founder + CEO</p></blockquote>
<p>&nbsp;</p>
<h2>Wrapping Up</h2>
<p>While in-person tools may not work for every project, the whiteboard proved vital for our project. It got the whole team involved, excited, and on the same page. While we also would not have been successful without well-defined stories and processes with our virtual backlog tool, I think combining the two proved to be surprisingly useful to our team.</p>
<p>I will concede that it was sometimes hard to remember to update, and that we didn’t use it every day. However, the fact that the goal that we were working toward was so clearly and proudly displayed helped with Visibility, Accountability, Morale, Understanding, and updates. I would highly recommend that any team that gets the chance to create a physical backlog tool for the team to use should do it.</p>
<p>The post <a href="https://spin.atomicobject.com/in-person-backlog-management-tools/">The Value of In-Person Backlog Management Tools</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://spin.atomicobject.com/in-person-backlog-management-tools/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>My Encyclopedia is Human</title>
		<link>https://spin.atomicobject.com/my-encyclopedia-is-human/</link>
					<comments>https://spin.atomicobject.com/my-encyclopedia-is-human/#respond</comments>
		
		<dc:creator><![CDATA[Maria Irimie]]></dc:creator>
		<pubDate>Sun, 07 Jun 2026 12:00:00 +0000</pubDate>
				<category><![CDATA[The Software Life]]></category>
		<category><![CDATA[knowledge sharing]]></category>
		<guid isPermaLink="false">https://spin.atomicobject.com/?p=3673940</guid>

					<description><![CDATA[<p>A few weeks ago, I asked my younger brother a very random question: “What would the consequences be of putting higher performance shock absorbers in my car?” Question One He led me down an interesting conversation about the costs, the considerations, the damage it could do to my very simple Mazda CX-30. We chatted about [&#8230;]</p>
<p>The post <a href="https://spin.atomicobject.com/my-encyclopedia-is-human/">My Encyclopedia is Human</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>A few weeks ago, I asked my younger brother a very random question:</p>
<blockquote><p>“What would the consequences be of putting <a href="https://dobinsons.com/suspension/shock-absorbers?srsltid=AfmBOor2JbWFmN9upaaoI_CTLl1MBeVHJNZl6nENz8v00ucAlTyDbjqI">higher performance shock absorbers</a> in my car?”</p></blockquote>
<h2>Question One</h2>
<p>He led me down an interesting conversation about the costs, the considerations, the damage it could do to my very simple Mazda CX-30. We chatted about it for about 5 minutes, realistically, with follow-ups and back-and-forth. In the end, I learned why I should probably just get the recommended shocks for my car until it dies, but also what it takes for other cars to get that smooth, uninterrupted ride on a pot-holey Michigan road.</p>
<p>If you’re curious, not only would it make it easier for me to roll over my car, but it could also cause wear on surrounding parts and be more costly than it’s worth. (It messes up all the physics… or something. I should ask him again.) I got to peek into one of my brother’s knowledge bases for a while and add it to mine.</p>
<h2>More Questions</h2>
<p>On a different day, I asked my friend Nicole, a medical assistant at a podiatrist&#8217;s office, what kinds of exercises I could do to strengthen my ankles, since they’d been hurting. She walked me through what they recommend at her office. My vacuum was purchased on the recommendation of a friend. I know what plants work best for my house from my mother. And, when I’m curious about something in urban planning, I know I can always ask my cousin, who’s in that field.</p>
<p>My encyclopedia is human. All the entries are from my friends! Everyone in my life holds things I can reference, and I know that, for many others, I do the same.</p>
<p>As I move through my career in both software and consulting, the lessons that stick the most with me are all the ones I’ve learned from other human beings. It’s always easier to try something new or see a new perspective when I outsource to a new set of hands, ears, and eyes. But also, seeing others for the knowledge they hold frees up space in my mind for the knowledge I must hold, because I know I can consult them.</p>
<h2>Why and How</h2>
<p>I think a few things allow me to enable this. For one, I love conversation. That base level joy surely adds to what I get out of any one experience. Another factor is that I love the people I have in my life. It makes me remember more easily what I’ve learned because I&#8217;ve attached it to a person I’m learning from.</p>
<p>In having a conversation, a person can also tailor how they deliver that knowledge to what they know about me and my use case, which helps make the knowledge more immediately practical. Google’s AI summary may be able to tell me what changing the shocks could do to any car, but may not tell me as well what could happen with my car specifically. My brother, on the other hand, has knowledge of vehicles in my car’s class and size, giving him an upper hand to apply that knowledge.</p>
<p>I love learning, getting my hands into things, and turning concepts into tangible objects. <a href="https://spin.atomicobject.com/tag/learning/">I love learning</a> what others have learned. And, I love sharing my knowledge. As AI changes the landscape of information, I think that core feeling will remain. Even as it gives us more access to knowledge and patterns than ever, I still believe it is important to take human reasoning, past experience, and intuition to apply that knowledge correctly.</p>
<p>My encyclopedia is human.</p>
<p>The post <a href="https://spin.atomicobject.com/my-encyclopedia-is-human/">My Encyclopedia is Human</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://spin.atomicobject.com/my-encyclopedia-is-human/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to Ask Better Questions</title>
		<link>https://spin.atomicobject.com/asking-better-questions/</link>
					<comments>https://spin.atomicobject.com/asking-better-questions/#respond</comments>
		
		<dc:creator><![CDATA[Bo French]]></dc:creator>
		<pubDate>Sat, 06 Jun 2026 12:00:59 +0000</pubDate>
				<category><![CDATA[Effective communication]]></category>
		<category><![CDATA[effective communication]]></category>
		<guid isPermaLink="false">https://spin.atomicobject.com/?p=3673918</guid>

					<description><![CDATA[<p>Not all questions are created equal. Recently, some discourse has been happening in my early career program around finding our voice, participating in meetings, and asking questions. This tends to be harder for early-career people for a multitude of reasons. I think a common denominator for a lot of the reasons comes down to self-limiting [&#8230;]</p>
<p>The post <a href="https://spin.atomicobject.com/asking-better-questions/">How to Ask Better Questions</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><i>Not all questions are created equal.</i><br />
<img loading="lazy" decoding="async" class="size-full wp-image-3673933 aligncenter" src="https://spin.atomicobject.com/wp-content/uploads/questions.gif" alt="" width="320" height="180" /><br />
Recently, some discourse has been happening in my early career program around finding our voice, participating in meetings, and asking questions. This tends to be harder for early-career people for a multitude of reasons. I think a common denominator for a lot of the reasons comes down to self-limiting beliefs and fear.</p>
<p>Separately, I&#8217;ve been subscribed to a couple of developer-focused newsletters, and there&#8217;s been a recurring theme around &#8216;just asking&#8217; and all the benefits that come with it. While I agree, and there are innumerable benefits to asking more questions, I think there is an important caveat worth considering. That is the concept that not all questions are created equal.</p>
<p>Understanding when, where, what, and to whom to direct questions is an important skill to develop. It is powerful for your own growth and being a solid contributor on a team. I will go into a few simple tips I have learned along the way, with some gems from teammates who have a lot more experience than me.</p>
<h2><b>Do the work before you ask.</b></h2>
<p>Before bringing a question to a teammate or into a meeting, I first try to find the answer myself and exhaust my resources. This is especially true for technical questions or general curiosity like what is this? How does this work? These tend to be thinner questions that are worth trying to resolve on your own first, as there is usually a black-and-white answer out there. It&#8217;s absolutely okay to escalate them to a teammate if you hit a wall or it isn&#8217;t clicking. Building those exploratory skills before offloading the question is part of growing as a developer.</p>
<p>A related tool is that if you know what someone <i>may</i> respond with to your question, you should look into that before asking. For example, say they likely would say, &#8220;Have you looked at <i>x</i> yet?&#8221; You should look into x before asking! It will show that you are trying to solve your own problems before asking. It will go a long way with your team.</p>
<h2><b>Know your audience and your setting.</b></h2>
<p>Tied closely to the above is understanding who you&#8217;re asking and whether the question makes sense for that person. Knowing your audience is paramount so that you don&#8217;t waste other people&#8217;s time, but also to get an accurate answer.</p>
<p>This becomes even more critical in client or stakeholder-facing meetings, where impressions matter more. I personally do a quick review with a teammate of any question I&#8217;m thinking of raising in those settings beforehand as a sanity check. This makes sure the answer isn&#8217;t already available to me somewhere, or I am not missing something obvious, and it&#8217;s worth surfacing in one of those meetings.</p>
<p>The same concept applies to where and when you ask. Does the setting warrant the question? Asking the team how the new sprint ceremonies feel in a refinement meeting obviously would not be the place. Asking in the right environment increases your odds of getting a good answer, and being respectful to the purpose of the meeting. Being attuned to these nuances is a part of developing good instincts.</p>
<h2><b>Write it down first.</b></h2>
<p>Before asking a question, I write down the key context and points I want to hit in a bulleted list. These keep me on track, make sure I don&#8217;t forget anything important mid-conversation and give me a reference point. I&#8217;m a big fan of a good artifact, and this is one of the simplest ones you can make. I like to take notes during conversations and I can cross reference the list to make sure I got all the questions answered.</p>
<h2>Decide on meeting-appropriate questions.</h2>
<p>Bring thick questions into meetings. These are the questions that are genuinely valuable for the group and spark deep discussion. Think &#8220;why&#8221; questions. Or, &#8220;Have we considered X?&#8221; or, &#8220;Is Y a concern?&#8221; These kinds of questions open and encourage discussions instead of closing them. Bonus points if you have follow-up questions to dive deeper and expand upon ideas.</p>
<p>Does the team benefit from having this discussion? This is a crucial idea to consider for bringing up a question in a meeting setting. For most things development related absolutely the team should be in on it! Things like a new idea for a way forward or a bug to bring up would be great. Bringing up that I left a comment on someone&#8217;s PR to remove some import would not be right for a group setting.</p>
<h2>Try the Diamond Strategy.</h2>
<p>The basic idea is to open up with an easier thinner introductory question before diving into the deep stuff. It can be a powerful segue into deep conversation and makes gradual progress into good collaboration. It&#8217;s a simple tool, but it helps conversations flow smoothly. Here is an example of this strategy at work:</p>
<p>Thin opening question: How are we handling error states in this service?</p>
<p>Thick continuation question: Why are we catching every error? Should they bubble up? Do we really need an error state for that?</p>
<p>Thin closing question: So it&#8217;s better to let errors bubble up in this service so we preserve the full stack trace, is that right?</p>
<p>This is a pretty simple, clear-cut example of how a conversation with this framework in mind could work.</p>
<p>The goal of this post was not to make asking questions harder, but rather to give you tools to make you more effective at this. Asking more questions in general is good, but going in prepared with an artifact for reference, knowing your audience/setting, and letting the conversation flow progressively make these questions better for all involved. Like anything, these skills take time to improve at, and don&#8217;t let the fear of asking a bad question stop you from asking at all! I have asked many silly questions in my early career. Below is a paragraph of all the negative things I have experienced from asking non-perfect questions:</p>
<p>&nbsp;</p>
<p>The post <a href="https://spin.atomicobject.com/asking-better-questions/">How to Ask Better Questions</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://spin.atomicobject.com/asking-better-questions/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>From AI Hype to AI Discipline at Merge 2026</title>
		<link>https://spin.atomicobject.com/ai-merge-2026/</link>
					<comments>https://spin.atomicobject.com/ai-merge-2026/#respond</comments>
		
		<dc:creator><![CDATA[Kendra Haan]]></dc:creator>
		<pubDate>Fri, 05 Jun 2026 12:00:14 +0000</pubDate>
				<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[artificial intelligence]]></category>
		<category><![CDATA[conference]]></category>
		<guid isPermaLink="false">https://spin.atomicobject.com/?p=3673976</guid>

					<description><![CDATA[<p>Every year, the Merge conference in Grand Rapids brings together software developers, designers, product leaders, and technology professionals from across West Michigan to discuss how our industry is changing. Organized by Software GR, the conference has become a space for practical conversations about software development, collaboration, leadership, and emerging technology trends. For the past few [&#8230;]</p>
<p>The post <a href="https://spin.atomicobject.com/ai-merge-2026/">From AI Hype to AI Discipline at Merge 2026</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Every year, the <a href="https://merge.softwaregr.org/">Merge conference</a> in Grand Rapids brings together software developers, designers, product leaders, and technology professionals from across West Michigan to discuss how our industry is changing. Organized by <a href="https://softwaregr.org/">Software GR</a>, the conference has become a space for practical conversations about software development, collaboration, leadership, and emerging technology trends.</p>
<p>For the past few years, I have had the opportunity to serve on the planning committee for Merge, with Atomic Object participating as a conference sponsor. Helping plan and execute the conference gave me a chance to spend more time thinking not only about individual talks, but also about the broader themes emerging across the schedule. This year, I spent some time reflecting on the changes in the <a href="https://spin.atomicobject.com/tag/artificial-intelligence/">artificial intelligence landscape</a> over the past year.</p>
<p>Last year, many conversations about AI in software development felt exploratory. Teams were experimenting with new tools, testing workflows, and trying to understand what generative AI might mean for the future of product development. The questions were broad: Should we adopt these tools? Where do they fit? Are we all becoming &#8220;vibe coders&#8221; now?</p>
<p>At Merge 2026, the tone felt noticeably different. AI wasn’t presented as a novelty or a future possibility. It was treated as a reality of the way we work now. The conversation has shifted from whether teams should use AI to how teams use it effectively. In my mind, that shift says a lot about where the industry is heading.</p>
<h2>2025&#8217;s take on AI</h2>
<p>Looking back at Merge 2025, many AI-related talks focused on helping teams approach AI thoughtfully amid the growing hype and lingering uncertainty. Talks had topics like “Smart Adoption, Not Blind FOMO,” framing AI as a trendy tool that still needed integration into existing workflows. The emphasis was on avoiding too much disruption, protecting team dynamics, and considering the tradeoffs that came with rapidly changing tooling and not falling into the trap of maximizing output at all costs.</p>
<p>Other talks approached AI through the lens of integration and collaboration. One talk described AI as a “universal translator” that could help bridge communication gaps between systems and teams. Another warned about the risks of runaway code generation and increasingly fragmented systems. Across these talks, a common thread emerged: teams were trying to figure out how AI fit into existing practices without destabilizing them.</p>
<p>The underlying question in 2025 seemed to be: How do we adopt AI responsibly?</p>
<p>That question reflected the broader feeling of the industry at the time. AI tools were rapidly improving, but many organizations were still experimenting at the edges. There was excitement, but also caution and uncertainty.</p>
<h2>The AI Landscape of 2026</h2>
<p>A year later, the conversation has changed quite a bit. At Merge 2026, speakers were no longer encouraging teams to adopt AI. Instead, they assumed AI was already integrated into day-to-day work and focused on the experiences and consequences of that reality.</p>
<p>The questions became more concrete:</p>
<ul>
<li>How do we maintain quality in AI-assisted workflows?</li>
<li>How do we supervise increasingly autonomous systems?</li>
<li>What happens to engineering discipline when code generation becomes cheap?</li>
<li>How do teams preserve trust and shared understanding?</li>
</ul>
<p>Several talks reflected on this transition directly. Drew Colthorp’s session on moving from “vibe coding” to “agentic engineering” focused on keeping humans in control of increasingly capable AI systems. Rather than treating AI as a replacement for engineering rigor, this talk emphasized the need for structure, oversight, and intentional workflows.</p>
<p>Another talk, provocatively titled “TDD is Dead! Quality Code in the Age of AI” argued that specifications and clear expectations become even more important when AI is involved. Ambiguity that might once have resulted in a small misunderstanding between developers can now generate entire layers of incorrect implementation faster than ever before.</p>
<p>The most interesting shift between 2025 and 2026 was not that AI became more capable. It was that the conversation became more disciplined.</p>
<h2>The New Anxiety Is Trust</h2>
<p>One theme that appeared repeatedly across this year’s talks was trust.<br />
As AI systems become more integrated into software delivery, teams are wrestling with a new set of concerns:</p>
<ul>
<li>Can we trust generated code?</li>
<li>Can we maintain architectural clarity?</li>
<li>Can we identify hallucinations quickly enough?</li>
<li>Who is accountable for mistakes?</li>
<li>How do we avoid accumulating invisible complexity?</li>
</ul>
<p>These concerns are different from the hype-driven conversations that dominated the industry a year ago. They reflect practical experience. Teams have now spent enough time using AI-assisted workflows to understand both the productivity gains and the hidden costs. AI can accelerate output, but it does not necessarily improve judgment.</p>
<p>That may explain why several Merge talks emphasized practices that experienced software teams already value:</p>
<ul>
<li>Clear communication</li>
<li>Iterative feedback loops</li>
<li>Testing</li>
<li>Observability</li>
<li>Architectural simplicity</li>
<li>Collaborative review</li>
<li>Shared understanding across disciplines</li>
</ul>
<p>None of those practices became obsolete because of AI; instead, they became more important. As implementation becomes faster and cheaper, cross-team collaboration, clarity, and feedback cannot be undervalued.</p>
<h2>AI Is Reshaping Collaboration More Than It Is Replacing People</h2>
<p>Another interesting pattern across the Merge 2026 schedule was the new take on cross-functional collaboration. Several talks explored how AI is changing the boundaries between design, development, and product work. Developers can prototype interfaces more quickly. Designers can explore technical ideas more independently. Product teams can generate artifacts and synthesize information faster than before.</p>
<p>AI lowers the friction involved in crossing disciplines. But lowering friction is not the same thing as eliminating expertise. In practice, these tools seem to increase the importance of context, alignment, and decision-making. Teams still need shared understanding around user needs, product goals, architectural tradeoffs, and quality standards. One of the more compelling ideas in this year’s conference was that AI brings the disciplines of product, design and developement closer together.</p>
<h2>What AI Adoption Looks Like in 2026</h2>
<p>If there was a consistent message across Merge 2026, it was that the current state of AI adoption is less about maximizing automation and more about building disciplined systems around it. The teams most likely to succeed with AI probably are not the ones generating the most code or adopting the newest tools first. They are the ones creating strong feedback loops, maintaining accountability, and preserving space for human judgment.</p>
<p>Mature AI teams:</p>
<ul>
<li>Treat AI as augmentation rather than autonomy</li>
<li> Prioritize clarity and specification</li>
<li>Preserve review and verification practices (aka testing)</li>
<li>Invest in collaboration</li>
<li>Recognize that velocity without validation creates fragility</li>
</ul>
<p>That perspective felt noticeably more grounded than many AI conversations, even from just a year ago.</p>
<p>What encouraged me most about this year’s conference was not the excitement around AI itself, but the growing focus on collaboration, accountability, and craftsmanship. Conferences like Merge are valuable not because they predict the future perfectly, but because they help us compare notes as an industry while the future is still unfolding. I’m curious to see where the conversation goes next.</p>
<p>The post <a href="https://spin.atomicobject.com/ai-merge-2026/">From AI Hype to AI Discipline at Merge 2026</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://spin.atomicobject.com/ai-merge-2026/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CQRS and Event Sourcing in TypeScript: A Production Walkthrough</title>
		<link>https://spin.atomicobject.com/cqrs-event-sourcing-typescript/</link>
					<comments>https://spin.atomicobject.com/cqrs-event-sourcing-typescript/#respond</comments>
		
		<dc:creator><![CDATA[Vlad Surganov]]></dc:creator>
		<pubDate>Thu, 04 Jun 2026 12:00:27 +0000</pubDate>
				<category><![CDATA[TypeScript]]></category>
		<category><![CDATA[typescript]]></category>
		<category><![CDATA[event-based app]]></category>
		<guid isPermaLink="false">https://spin.atomicobject.com/?p=3673832</guid>

					<description><![CDATA[<p>Below, you&#8217;ll find a tour of CQRS, Event Sourcing, and Projections as they actually appear in a real production NestJS + KurrentDB + PostgreSQL + Drizzle codebase. It&#8217;s roughly a 15-minute read. The goal is not just to discuss what these patterns are, but why they are shaped the way they are, and where they [&#8230;]</p>
<p>The post <a href="https://spin.atomicobject.com/cqrs-event-sourcing-typescript/">CQRS and Event Sourcing in TypeScript: A Production Walkthrough</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><em>Below, you&#8217;ll find a tour of CQRS, Event Sourcing, and Projections as they actually appear in a real production NestJS + KurrentDB + PostgreSQL + Drizzle codebase. It&#8217;s roughly a 15-minute read. The goal is not just to discuss what these patterns are, but why they are shaped the way they are, and where they earn their keep.</em></p>
<p>Most CQRS explainers reach for a bank account aggregate. If you have read one more bank account example in your life, you are owed a refund.</p>
<p>This post uses an actual production system — the kind with a dozen-plus event types per aggregate, schema evolution, and side effects that cannot be allowed to block the UI — as its running example. No toy code.</p>
<h2>A Motivating Puzzle</h2>
<p>Here is a question worth holding onto for the whole post.</p>
<p>You ship a feature that lets an administrator approve a pending organization. The implementation is the natural one: <code>UPDATE organizations SET status = 'active' WHERE id = ?</code>.</p>
<p>Two weeks later, your product manager asks you a perfectly reasonable question: <em>“Who approved this organization, and when, and why did we reject that other one last month — can you pull up the reasoning?”</em></p>
<p>You cannot. The row tells you <em>what state we are in</em>. It says nothing about <em>how we arrived there</em>. You saved the destination and bulldozed the road behind you.</p>
<p>That puzzle — and the fact that you cannot solve it by adding a few audit columns without making your life worse — is the itch that Event Sourcing was invented to scratch.</p>
<p>Pair it with CQRS, then sprinkle projections on top, and a number of other problems you did not realize were problems begin dissolving: temporal queries, retroactive bug fixes, new read models added years after the fact.</p>
<p>I am not here to sell you anything. These patterns have real costs, which we will discuss honestly. But when the fit is right, the fit is glorious.</p>
<h2>The Production Stack</h2>
<p>Our example system’s stack, for the record: <strong>NestJS</strong> as the framework, <strong>@nestjs/cqrs</strong> for command-handler plumbing, <strong>KurrentDB</strong> (the database formerly known as EventStoreDB) as the event log, <strong>PostgreSQL</strong> with <strong>Drizzle ORM</strong> on the read side, and a small fleet of <strong>projections</strong> that translate between the two.</p>
<p>This is for teams whose domain has a memory: approvals, rejections, assignments, migrations, corrections, compliance questions, and a business habit of asking <em>why</em> something is in its current state.</p>
<p>If your application is mostly simple profile edits and preference toggles, the honest recommendation is to enjoy your <code>UPDATE</code> statements and spend the complexity budget somewhere else.</p>
<h2>The Thesis, in One Paragraph</h2>
<p>It&#8217;s worth memorizing, because we will come back to it:</p>
<p><strong>CQRS</strong> observes that reading data and writing data are different problems, and it is a form of violence to force one model to do both. <strong>Event Sourcing</strong> proposes that we store not the current state, but the sequence of facts that produced it. <strong>Projections</strong> are the bridge: they translate that stream of facts into whatever shapes the read side wants.</p>
<p>That is the entire idea. Everything else we do today is plumbing, and plumbing, as any homeowner will tell you, is where the interesting failures live.</p>
<h2>Commands Describe Intent, not Data</h2>
<p><img loading="lazy" decoding="async" class="size-medium wp-image-3673836 aligncenter" src="https://spin.atomicobject.com/wp-content/uploads/Screenshot-2026-05-13-at-3.58.34-PM-558x800.png" alt="" width="558" height="800" srcset="https://spin.atomicobject.com/wp-content/uploads/Screenshot-2026-05-13-at-3.58.34-PM-558x800.png 558w, https://spin.atomicobject.com/wp-content/uploads/Screenshot-2026-05-13-at-3.58.34-PM-715x1024.png 715w, https://spin.atomicobject.com/wp-content/uploads/Screenshot-2026-05-13-at-3.58.34-PM-105x150.png 105w, https://spin.atomicobject.com/wp-content/uploads/Screenshot-2026-05-13-at-3.58.34-PM-768x1101.png 768w, https://spin.atomicobject.com/wp-content/uploads/Screenshot-2026-05-13-at-3.58.34-PM-1072x1536.png 1072w, https://spin.atomicobject.com/wp-content/uploads/Screenshot-2026-05-13-at-3.58.34-PM-600x860.png 600w, https://spin.atomicobject.com/wp-content/uploads/Screenshot-2026-05-13-at-3.58.34-PM-1200x1720.png 1200w, https://spin.atomicobject.com/wp-content/uploads/Screenshot-2026-05-13-at-3.58.34-PM.png 1270w" sizes="auto, (max-width: 558px) 100vw, 558px" /></p>
<p>On the write side of a CQRS system, the API does not accept “please make this row look like that.” It accepts <strong>commands</strong> — named, intentful requests like <code>ApproveOrganization</code> or <code>RejectOrganization</code>.</p>
<p>A command is a verb. It is a thing someone, or something, is trying to do.</p>
<p>In our codebase, every command extends a common base class carrying three fields: the <strong>entity ID</strong> it targets, the <strong>issuer</strong> (a user? the system? a migration?), and a <strong>timestamp</strong>. Specific subclasses add the rest:</p>
<pre><code class="language-typescript">export class CreateOrganizationCommand extends Command {
  constructor(
    id: string,
    issuer: Issuer,
    public readonly fields: OrganizationData,
  ) {
    super(id, issuer);
  }
}

export class RejectOrganizationCommand extends Command {
  constructor(
    id: string,
    issuer: Issuer,
    public readonly rejectReason: OrganizationRejectReason,
  ) {
    super(id, issuer);
  }
}</code></pre>
<p>Notice what is present and what is absent. There is no <code>status: 'approved'</code> parameter anywhere. The command does not tell the system what the new row should look like.</p>
<p>Instead, it tells the system what the user is <em>trying to accomplish</em>. The system then decides whether that is a legal thing to do, and what state changes it implies.</p>
<p>This distinction is more important than it appears. When your write API speaks in verbs — <em>approve</em>, <em>reject</em>, <em>invite</em> — instead of nouns-with-mutations — <em>PATCH the status field to &#8220;approved&#8221;</em> — the code begins to speak the same dialect as the business.</p>
<p>Your product manager can read your command names. That is not cosmetic. It is a substantial reduction in translation cost between the people who decide what the software should do and the people who make it do it.</p>
<p>A well-named command set is, in effect, an executable glossary of your domain. If a business analyst cannot read your commands, your commands are poorly named.</p>
<h2>The Aggregate is a State Machine</h2>
<p>A command bus routes each command to a <strong>command handler</strong>. The handler’s job is to load the right <strong>aggregate</strong> — a domain entity with its full history rehydrated into memory — and hand the command to it.</p>
<p>In our system, the aggregate extends a class called <code>PersistentEntity</code>. It is, at heart, a state machine with one responsibility: given its current state and an incoming command, decide what events should fire.</p>
<pre><code class="language-typescript">export class Organization extends PersistentEntity&lt;
  OrganizationCommand,
  OrganizationData,
  OrganizationEvent
&gt; {
  static readonly eventMap = {
    [OrganizationCreatedEvent.TYPE]: OrganizationCreatedEvent,
    [OrganizationUpdatedEvent.TYPE]: OrganizationUpdatedEvent,
    [OrganizationApprovedEvent.TYPE]: OrganizationApprovedEvent,
    [OrganizationRejectedEvent.TYPE]: OrganizationRejectedEvent,
    [OrganizationDocumentAddedEvent.TYPE]: OrganizationDocumentAddedEvent,
    // ...more domain events
  } satisfies EventMapFor&lt;OrganizationEvent&gt;;

  static readonly stateMap = {
    [UninitializedOrganizationState.STATE_NAME]: UninitializedOrganizationState,
    [PendingOrganizationState.STATE_NAME]:       PendingOrganizationState,
    [ActiveOrganizationState.STATE_NAME]:        ActiveOrganizationState,
    [RejectedOrganizationState.STATE_NAME]:      RejectedOrganizationState,
    [InactiveOrganizationState.STATE_NAME]:      InactiveOrganizationState,
    [ClosedOrganizationState.STATE_NAME]:        ClosedOrganizationState,
  } satisfies StateMapFor&lt;OrganizationState&gt;;

  constructor() {
    super(new UninitializedOrganizationState());
  }
}</code></pre>
<h3>States are real objects.</h3>
<p>An organization in <em>Pending</em> state is a genuinely different thing from one in <em>Active</em> state, and it accepts different commands. You cannot approve an already-approved organization. You cannot update a rejected one.</p>
<p>That rule does not live in an <code>if</code> statement buried in a service. It lives in the state class, where it cannot be forgotten. Invalid transitions become impossible, not merely improbable.</p>
<h3>Exhaustiveness is enforced by TypeScript.</h3>
<p>The phrase <code>satisfies EventMapFor&lt;OrganizationEvent&gt;</code> instructs TypeScript: “this object must cover every event type in the <code>OrganizationEvent</code> union.” Add another event to the union and forget to register it here? The compiler refuses to build.</p>
<p>This is our first encounter with a recurring theme: <em>exhaustiveness checking</em>. In an event-sourced system, the type system is the first, last, and best line of defense.</p>
<p>The pattern to internalize: use your language’s type system to make impossible states impossible. “Approve a rejected organization” should not be a bug you catch in code review. It should be a bug the compiler already caught while you were off doing something else.</p>
<h2>Events are What Actually Get Saved</h2>
<p>Here is where we diverge most sharply from the world of CRUD. The aggregate does not persist itself. It emits <strong>events</strong>, and the events are appended to a stream.</p>
<p>The aggregate’s in-memory state is merely a cached fold over the event history.</p>
<pre><code class="language-typescript">export class OrganizationApprovedEvent extends EntityEvent {
  static readonly TYPE = 'OrganizationApprovedEvent' as const;
  readonly type = OrganizationApprovedEvent.TYPE;

  constructor(
    entityId: string,
    issuer: Issuer,
    timestamp: Date = new Date(),
  ) {
    super(entityId, issuer, timestamp);
  }
}</code></pre>
<p>An event is a <strong>fact about the past</strong>. It is immutable. It carries the minimum information needed to describe what happened — not a full snapshot of the world, just a delta.</p>
<p><code>OrganizationApprovedEvent</code> does not serialize the entire organization. It says: <em>this ID was approved, by this issuer, at the event timestamp.</em></p>
<p>When a command arrives, the aggregate validates it, constructs the appropriate events, and applies them to its own state. The framework then appends those events to the entity’s stream in KurrentDB with <strong>optimistic concurrency control</strong>.</p>
<p>In plain English: “I expect this stream to be at revision 42 when I write. If someone else got there first, fail loudly.”</p>
<pre><code class="language-typescript">async writeEvent&lt;E&gt;(
  streamName: string,
  eventType: string,
  eventData: E,
  expectedVersion?: number,
): Promise&lt;void&gt; {
  const event = jsonEvent({ id: uuid(), type: eventType, data: eventData as JSONType });

  expectedVersion !== undefined
    ? await this.client.appendToStream(streamName, event, { streamState: BigInt(expectedVersion) })
    : await this.client.appendToStream(streamName, event, { streamState: ANY });
}</code></pre>
<p>Streams are named by entity: <code>Organization-{uuid}</code>, <code>User-{uuid}</code>, and so on. KurrentDB also provides virtual category streams.</p>
<p>For example, <code>$ce-Organization</code> contains every organization event across every organization, which is precisely what projections will want to subscribe to in a moment.</p>
<p>A reflective pause. The first time it lands that your database contains no rows describing organizations — only an append-only log of things that have happened to organizations — produces a mild vertigo.</p>
<p>You have written code that has never, not once in its life, issued an <code>UPDATE</code>. The word “update” does not appear in your write path. You may want to sit down.</p>
<h2>Reconstructing the Present from the Past</h2>
<p>How, then, does the aggregate know its current state when the next command arrives? By rereading its history.</p>
<p>When a command targets organization <code>abc-123</code>, the framework loads stream <code>Organization-abc-123</code>, instantiates a fresh <code>Organization</code> aggregate, and feeds every event through <code>apply()</code> in order.</p>
<p>After the final event, the aggregate’s state reflects the current state of the organization. <em>Only then</em> is the new command dispatched.</p>
<h3>Snapshots keep replay cheap.</h3>
<p>The natural objection: “You replay the entire history for every command? Every time?”</p>
<p>Yes. For a while, that is fine. Events are small, streams are short, and event stores are astoundingly fast at reading them.</p>
<p>Eventually, though, an aggregate gets chatty. An organization with 40,000 document-added events will let you know. The answer is <strong>snapshots</strong>.</p>
<p>A snapshot is a serialized copy of an aggregate’s state at a particular event revision. In our system, snapshots live in a PostgreSQL <code>snapshots</code> table, not in KurrentDB itself. That keeps the write path clean.</p>
<p>Every hundred events, the framework persists a snapshot; we retain the last five per entity. On rehydration we load the most recent snapshot and replay only the events <em>after</em> that revision. Two hundred thousand events collapse to forty.</p>
<p>Here is the subtle, critical point: snapshots are a <strong>performance optimization, not a source of truth</strong>. If a snapshot is corrupt, outdated, or written under an older schema, you discard it and replay from the events.</p>
<p>The log is always authoritative. This is one of Event Sourcing’s quiet superpowers: <em>your caches are permitted to lie</em>, because you always have something to fall back on.</p>
<h2>Making Events Queryable</h2>
<p>The question that always arises at this point: the UI needs to show a list of organizations, sorted by name, filtered by status, paginated. You cannot possibly be replaying every event on every page load.</p>
<p>So how does anyone actually query this thing?</p>
<p>Correct. We do not. Meet the <strong>projection</strong>.</p>
<p>A projection is a long-running subscriber that reads events off a category stream — for us, <code>$ce-Organization</code> — and translates them into whatever read model the UI needs.</p>
<p>Most of ours write to PostgreSQL via Drizzle. The read side of the system is, by design, a completely boring, indexed, joinable relational database. It is exactly the shape your frontend wants, because your frontend was born wanting rows.</p>
<pre><code class="language-typescript">export const organizations = pgTable(
  'organizations',
  {
    id: uuid('id').primaryKey(),
    subscriptionType: text('subscription_type').notNull(),
    name: text('name').notNull(),
    addressLine1: text('address_line_1').notNull(),
    // ...more
    status: text('status').notNull().default(OrganizationStatus.Pending),
    rejectReason: jsonb('reject_reason'),
    approvedAt: timestamp('approved_at', { withTimezone: true }),
    rejectedAt: timestamp('rejected_at', { withTimezone: true }),
    createdAt: timestamp('created_at', { withTimezone: true }).notNull(),
    updatedAt: timestamp('updated_at', { withTimezone: true }).notNull(),
  },
  (table) =&gt; ({ idIdx: index('id_idx').on(table.id) }),
);</code></pre>
<p>This table is not where the organization’s data lives. This table is where a <em>read-shaped reflection</em> of that data lives, maintained by a projection that listens for organization events and keeps everything in sync.</p>
<h3>A projection is a translator.</h3>
<p>And the projection itself:</p>
<pre><code class="language-typescript">@Injectable()
export class OrganizationDbProjection
  extends AbstractDbProjection&lt;OrganizationEventEnvelope&gt; {

  protected readonly entityClass = Organization;
  protected readonly config: PersistentSubscriptionConfig = { startFrom: 'start' };

  protected async handleEvent(event: OrganizationEventEnvelope): Promise&lt;void&gt; {
    await match(event)
      .with({ type: OrganizationCreatedEvent.TYPE },  (e) =&gt; this.handleOrganizationCreated(e))
      .with({ type: OrganizationUpdatedEvent.TYPE },  (e) =&gt; this.handleOrganizationUpdated(e))
      .with({ type: OrganizationApprovedEvent.TYPE }, (e) =&gt; this.handleOrganizationApproved(e))
      // ...more
      .exhaustive();
  }
}</code></pre>
<p>Four things to notice here, each worth sitting with for a moment.</p>
<ul>
<li><strong>This is just pattern matching.</strong> We use <code>ts-pattern</code>’s <code>.exhaustive()</code>. Add a new event type to the union, forget to handle it here, and the build fails. Same exhaustiveness trick as in the aggregate.</li>
<li><strong>Each handler is a small SQL writer.</strong> <code>handleOrganizationApproved</code> sets <code>status = 'approved'</code> and <code>approved_at = event.timestamp</code>. That is all. There is no domain logic here; all of that already ran on the write side.</li>
<li><strong><code>startFrom: 'start'</code> is a superpower.</strong> If we want to change the read model — add a column, split a table, index differently, <em>add a completely new read model</em> — we can replay every event from the beginning of time.</li>
<li><strong>The offset update is atomic with the write.</strong> The abstract base class wraps <code>handleEvent()</code> and the offset bookkeeping in a single PostgreSQL transaction. Either both succeed or both roll back.</li>
</ul>
<h3>Read models are rebuildable.</h3>
<p>This is worth saying plainly: <em>the read side is rebuildable</em>. That changes the risk profile of database migrations more than any sentence of mine will convince you of until you experience it.</p>
<p>You cannot end up in a state where the event was projected but the offset was not saved. You also cannot advance the offset without applying the projection. This sounds obvious. It is not obvious. People get this wrong all the time, and then they wonder why production is weird.</p>
<h2>The Read Side is Mercifully Boring</h2>
<p>A small epiphany waits here. Once you have a projection maintaining a relational read model, the read side of your CQRS application stops being exotic.</p>
<p>Controllers inject an <code>OrganizationRepository</code>, the repository issues a Drizzle query, JSON comes out. There&#8217;s no <code>QueryBus</code>. No query handlers. No ceremony.</p>
<p>Some CQRS purists will insist that reads, too, should go through a query bus. I have built it both ways. A bus on the read side adds indirection without adding much value when your reads are mostly “give me the thing.”</p>
<p>A bus on the write side earns its keep, because commands have <em>behavior</em>: validation, state transitions, event emission. You want one place where all of that lives.</p>
<p>Asymmetry in architecture is a feature, not a bug. Do not make your system more symmetrical than your requirements.</p>
<h2>Production Details that Tutorials Skip</h2>
<p>Everything up to this point is standard CQRS and Event Sourcing — the stuff the textbooks describe. What follows is the set of pieces that tutorials gloss over.</p>
<p>In my experience, these are the difference between a proof-of-concept and something you can run a business on.</p>
<h3>KurrentDB offers optimistic concurrency, plus a belt.</h3>
<p>KurrentDB offers optimistic concurrency on writes: “I expect this stream to be at revision N.” If it is not, you receive a <code>WrongExpectedVersionError</code>, reload, and retry.</p>
<p>In practice, if two commands for the same entity arrive simultaneously, both threads load the aggregate, both build events at revision N+1, one wins, the other retries, reloads, and tries again. Under load, this gets ugly in a hurry.</p>
<p>So, for each entity, we acquire a <strong>PostgreSQL advisory lock</strong> keyed on the entity ID before executing the command. It is a FIFO serialization for that entity; other entities proceed in parallel, unaffected.</p>
<pre><code class="language-typescript">return this.lockService.withEntityLock(entityId, async (_tx: DrizzleTransaction) =&gt; {
  const entity = new entityClass();
  entity.receiveCommand(command);
  // ...persist events
});</code></pre>
<p>This is cheap, database-local, and already installed. No Redis, no ZooKeeper, no distributed-lock fairy dust.</p>
<p>Entity-level serialization turns out to be a pleasant sweet spot: commands for the same entity queue up; commands for different entities run concurrently. Write throughput scales with entity count, which is almost always what you actually want.</p>
<h3>Events evolve; upcasters help you survive.</h3>
<p>Your events live forever. Your code does not. Eventually, you will need to add a field to an event, rename one, or quietly concede that the original shape was a mistake.</p>
<p>You cannot <code>UPDATE</code> the event log. That would defeat the entire point of the log.</p>
<p>The answer is the <strong>upcaster</strong>: a small function that reads an older event version, transforms it to the current shape, and passes it up the stack.</p>
<p>Each event class carries a <code>SCHEMA_VERSION</code>. When the stored version is less than the current one, the upcasters run in order until the payload is current.</p>
<pre><code class="language-typescript">export function upcastUserCreatedEventV1(data: Record&lt;string, unknown&gt;) {
  // v2 added a required `status` field; default old events to 'active'
  if ((data.schemaVersion ?? 1) &gt;= 2) return data;
  return { ...data, status: 'active', schemaVersion: 2 };
}</code></pre>
<p>This is a lightweight pattern, but one you must build in <em>on day one</em>. Retrofitting versioning onto a log of a million events is not a sprint you want on your calendar.</p>
<p>Put the ceremony in place before you need it. The first upcaster can simply return its input. The skeleton is what matters.</p>
<h3>Idempotency is not optional</h3>
<p>KurrentDB’s persistent subscriptions deliver events <strong>at least once</strong>. On a crash, redeploy, or deliberate retry, the same event can arrive twice. Your projection must not care.</p>
<p>We achieve this with a per-projection offset stored in PostgreSQL and an in-memory LRU cache of recently-processed event IDs. The cache catches the hot path at about a 99% hit rate when the projection is caught up; the offset table catches the cold path.</p>
<p>In either case, applying an event twice is a no-op. <em>Assume duplicates. Design for duplicates. Duplicates are not an edge case; they are Tuesday.</em></p>
<p>The failure mode is painfully ordinary. A projection handles <code>OrganizationApprovedEvent</code>, updates the read model, and the process dies before the offset is committed.</p>
<p>On restart, the subscription redelivers the same event. If the projection increments counters, sends emails, or inserts child rows without an idempotency guard, congratulations: one approval just became two side effects.</p>
<p>This is why the offset write and read-model write share a transaction, and why side-effect projections need their own deduplication story.</p>
<h3>There are two flavors of projection.</h3>
<p>Some projections are the <em>read model</em>. They must be consistent enough that the UI can show the right thing after a write. Those run transactionally, as described above.</p>
<p>Many things, however, are not read model updates. Sending a welcome email. Auto-assigning a default profile when a user joins. Posting to a webhook. Notifying Slack.</p>
<p>These are <strong>side effects</strong>, and they should not share a transaction with your read model. If your email provider is having a day, your UI should not suffer.</p>
<p>So we run a second kind of projection: an <strong>async projection</strong>. It subscribes to the same category stream independently and handles side effects.</p>
<p>It can fail, retry, lag, and restart without touching the write side or the read model. Same machinery — a persistent subscriber with idempotency — different responsibilities.</p>
<p>This pattern is worth dwelling on, because it is the one newcomers to CQRS tend to underestimate. Once your side effects are modeled as projections over the event log, they stop being tangled into command handlers.</p>
<p>Adding a new one stops feeling dangerous. Want to notify Slack when an organization is approved? Write a projection. The rest of the system does not change. Nothing is refactored. Nothing is threaded.</p>
<p>You add new behaviors without modifying existing ones, which, as a gentleman named Meyer observed, is the whole ballgame.</p>
<h3>Correlation IDs give you an audit trail.</h3>
<p>When a command fires, we stamp a correlation ID onto the logging context. That ID rides along into every event the command produces.</p>
<p>Which means: when you are staring at a strange read-model state six months from now, you can find the events that caused it, trace back to the HTTP request that issued the command, and read every log line from that request.</p>
<p>The audit trail was not a feature we built. It fell out of the architecture.</p>
<p>Free features are the best features, and if you architect well, many of the features you pride yourself on shipping were never actually separate features at all.</p>
<h2>An honest Accounting of the Costs</h2>
<p>I like this architecture. I use it on real projects. That said, anyone who sells you a pattern without telling you what it costs is trying to sell you something.</p>
<h3>It is more code.</h3>
<p>For a trivial CRUD feature — “admins can edit their profile bio” — you do not want a command, an event, a state transition, and a projection handler.</p>
<p>You want a <code>PUT</code> endpoint and an <code>UPDATE</code>. If the majority of your application is features of that flavor, event sourcing will feel like formalwear at a backyard barbecue.</p>
<h3>Eventual consistency is real.</h3>
<p>After a write, the read model takes some milliseconds to catch up. Most of the time the user never sees it.</p>
<p>Sometimes they do, and you must design the UX to handle the gap: optimistic UI, explicit loading states, or, in rare cases, waiting for a projection checkpoint.</p>
<p>Pretending this problem does not exist is how you accumulate angry support tickets that say “I approved it but it still shows as pending.”</p>
<h3>Cross-aggregate queries are harder.</h3>
<p>“Show me all organizations whose primary contact has not logged in for 90 days” crosses two aggregates.</p>
<p>Projections can answer it — you build a joined read model for precisely that question — but that is another moving piece to design, populate, and maintain.</p>
<p>You trade the freedom of arbitrary joins for the discipline of purposeful read models.</p>
<h3>The onboarding ramp is real.</h3>
<p>New engineers need to internalize the write/read split, the immutability of events, the idempotency requirements, and the versioning story.</p>
<p>It is not difficult material, but it is unfamiliar, and unfamiliar costs you calendar days. Budget for them.</p>
<h3>Here&#8217;s when the cost is worth it.</h3>
<p>So when is formalwear the right call? When your domain is genuinely <em>eventful</em> — when “why is this thing in this state?” is a question the business asks, not just a question you ask at 2 a.m. during an incident.</p>
<p>Finance. Compliance. Healthcare. Logistics. Anything with a regulator or an auditor in its life.</p>
<p>Also: anywhere you suspect you will need read models you have not yet imagined. The ability to rebuild projections from the complete history is, frankly, a cheat code, and it is worth a lot of architectural weight.</p>
<h2>7 Things Worth Taing Away</h2>
<p>If nothing else from this post sticks, let these:</p>
<ul>
<li><strong>Commands are intents, not payloads.</strong> <code>ApproveOrganization</code>, not <code>UpdateOrganizationStatus</code>. Your codebase will begin to read like your product spec, and this is a wonderful thing.</li>
<li><strong>Keep aggregates small.</strong> An aggregate is a consistency boundary. If a command handler is reaching across to other aggregates, you have drawn the boundary in the wrong place. Solve it with async projections (“when X happens, dispatch Y”), not bigger aggregates.</li>
<li><strong>Use the type system for exhaustiveness everywhere.</strong> Event maps, state maps, projection handlers. <code>satisfies</code> and <code>.exhaustive()</code> are load-bearing. The compiler catching a missed case is worth any ten tests you could write.</li>
<li><strong>Install event versioning on day one.</strong> The first upcaster can be a no-op. The machinery is what matters.</li>
<li><strong>Treat the event log as sacred.</strong> No deletions. No mutations, outside explicit redaction or privacy-retention policies. Everything else — snapshots, projections, caches — is rebuildable, and therefore disposable.</li>
<li><strong>Prefer projections over sagas.</strong> If “when X, do Y” can be a projection subscribing to X’s events, make it one. One fewer concept in the codebase. One more boring, idempotent, restartable piece of code.</li>
<li><strong>Advisory locks are almost always enough.</strong> You probably do not need a distributed lock service. You almost certainly have PostgreSQL. PostgreSQL already has what you need.</li>
</ul>
<h2>A Closing Thought</h2>
<p>Here is the line I keep returning to: In a CRUD system, your database tells you <em>what</em>. In an event-sourced system, your database tells you <em>what happened</em>.</p>
<p>The difference sounds academic until the first time a stakeholder walks to your desk and asks why something is the way it is. With an event log, you can produce a perfectly ordered record of every decision, every actor, and every timestamp that conspired to produce this exact present.</p>
<p>That first time resembles magic. Once you have seen it, it becomes difficult to un-see.</p>
<p>CQRS and Event Sourcing are not right for every application. When they fit, though, they fit exactly. The more complex your domain becomes, the more precisely they fit.</p>
<p>If your business has a memory, your database should have one too.</p>
<p>Go read some event logs. Break something. Come back with better questions than the ones you started with.</p>
<p>The post <a href="https://spin.atomicobject.com/cqrs-event-sourcing-typescript/">CQRS and Event Sourcing in TypeScript: A Production Walkthrough</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://spin.atomicobject.com/cqrs-event-sourcing-typescript/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Your Smart Home Shouldn’t Need Its Manufacturer to Stay Alive</title>
		<link>https://spin.atomicobject.com/smart-home/</link>
					<comments>https://spin.atomicobject.com/smart-home/#respond</comments>
		
		<dc:creator><![CDATA[Travis Henderson]]></dc:creator>
		<pubDate>Wed, 03 Jun 2026 12:00:46 +0000</pubDate>
				<category><![CDATA[Extracurricular Activities]]></category>
		<category><![CDATA[cloud]]></category>
		<category><![CDATA[app]]></category>
		<guid isPermaLink="false">https://spin.atomicobject.com/?p=3673802</guid>

					<description><![CDATA[<p>Smart home devices are usually sold on convenience. Control the lights from your phone. Turn down the thermostat from bed. Get a camera notification when someone walks up to the porch. Automate the boring stuff and feel like you are living slightly closer to the future. That future gets a lot less fun when the [&#8230;]</p>
<p>The post <a href="https://spin.atomicobject.com/smart-home/">Your Smart Home Shouldn’t Need Its Manufacturer to Stay Alive</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Smart home devices are usually sold on convenience. Control the lights from your phone. Turn down the thermostat from bed. Get a camera notification when someone walks up to the porch. Automate the boring stuff and feel like you are living slightly closer to the future.</p>
<p>That future gets a lot less fun when the device only works because a company’s cloud service is still around.</p>
<p>My rule for buying new smart home gear has become pretty simple: if the core functionality depends on a proprietary cloud service, I’m probably not interested. The manufacturer’s app can be useful. Cloud features can be useful. But the device should support an open, local-first protocol out of the box.</p>
<p>For most smart home devices, that means looking for <a class="decorated-link" href="https://csa-iot.org/all-solutions/matter/?utm_source=chatgpt.com" target="_new" rel="noopener" data-start="1085" data-end="1136">Matter</a>. For cameras, that usually means looking for RTSP and/or ONVIF support. There are also good exceptions outside of Matter, like Zigbee, Z-Wave, MQTT, ESPHome, and other local integrations. The important part is not that every device has to use the same protocol. The important part is that the device should not become useless just because the manufacturer gets bored, pivots, gets acquired, changes its subscription model, or shuts down a server.</p>
<h2>Not Bricked, But Definitely Worse</h2>
<p>Google’s support changes for <a class="decorated-link" href="https://support.google.com/googlenest/answer/16233096?hl=en&amp;utm_source=chatgpt.com" target="_new" rel="noopener" data-start="1652" data-end="1746">older Nest Learning Thermostats</a> are a good example of the problem. Starting October 25, 2025, Google says 1st- and 2nd-generation Nest Learning Thermostats no longer connect to or work in the Nest app or Google Home app. Those thermostats can still be controlled directly on the device, and existing schedules can continue to run.</p>
<p>So, no, they were not turned into wall-mounted paperweights. That part matters.</p>
<p>But a lot of what made them “smart” went away: remote control from the app, notifications, phone-based settings changes, third-party assistants, Home/Away Assist, multi-device Eco mode control, and software/security updates. That is not nothing. If you bought the device because it was connected, programmable, and integrated with the rest of your home, losing those features is a meaningful downgrade.</p>
<p>This is the smart home trap. The physical device can still function, while the product you thought you bought quietly stops existing.</p>
<h2>The App Should Be a Bonus, Not the Foundation</h2>
<p>I do not think every smart home device needs to be fully open source, and I do not think every manufacturer app is bad. Sometimes the app is the easiest way to set up the device. Sometimes it exposes extra features. Sometimes it handles firmware updates in a clean way.</p>
<p>That is fine.</p>
<p>The problem starts when the app and cloud service are the only way to use the device. At that point, you are not just buying hardware. You are buying hardware plus an ongoing promise that the company will continue to care about that hardware.</p>
<p>That promise has a shelf life.</p>
<p>A better model is “cloud optional.” Use the manufacturer’s app if it helps. Use the cloud feature if it gives you something valuable. But the basic control path should still exist locally. A light switch should still switch lights. A thermostat should still integrate with your home. A camera should still expose a video stream. Your house should not need to ask a remote server for permission to behave like your house.</p>
<h2>Why Matter Helps</h2>
<p>Matter is not magic, but it is a useful step in the right direction. It gives smart home devices a common language that can work across ecosystems. Instead of buying something that only really belongs to Apple Home, Google Home, Amazon Alexa, SmartThings, or some vendor-specific app, you can buy something designed to be understood by multiple controllers.</p>
<p>That flexibility matters.</p>
<p>Maybe today you use Apple Home. Maybe next year you want to move more automations into Home Assistant. Maybe you have family members who prefer Alexa. Maybe you just do not want to make a 10-year device purchase based on your current phone brand. Matter makes that less painful.</p>
<p>This is where <a class="decorated-link" href="https://spin.atomicobject.com/home-assistant-via-linuxserver-ios/?utm_source=chatgpt.com" target="_new" rel="noopener" data-start="4402" data-end="4485">Home Assistant</a> is especially useful. It is very good at acting as the central place where different devices, protocols, and automations meet. If your devices support local standards, Home Assistant can often become the hub that ties them together without forcing everything through one company’s cloud.</p>
<p>Atomic Spin has covered this general direction before in <a class="decorated-link" href="https://spin.atomicobject.com/home-automation-2024/?utm_source=chatgpt.com" target="_new" rel="noopener" data-start="4832" data-end="4923">Home Automation in 2024: What’s New?</a>, especially around Matter, Thread, and the slow move away from piles of vendor-specific apps. That post also points out an important reality: vendor apps are still here, and Matter does not expose every possible feature for every device. Energy monitoring, RGB animations, and firmware updates may still require a manufacturer app.<br />
That is annoying. It is not a deal-breaker.</p>
<p>The goal is not perfection. The goal is an exit path.</p>
<h2>Cameras Are Their Own Special Case</h2>
<p data-start="5396" data-end="5664">Security cameras deserve separate treatment because they are often some of the most cloud-dependent devices in the house. Video storage, event detection, person recognition, package alerts, and remote viewing are all obvious places for companies to sell subscriptions.</p>
<p data-start="5666" data-end="5790">Again, cloud features are not automatically bad. But a camera that only works through a proprietary app is a risky purchase.</p>
<p data-start="5792" data-end="6200">For cameras, I want to see RTSP and/or ONVIF support. RTSP gives other systems a way to consume the camera’s video stream. ONVIF helps with discovery, camera control, and integration. Together, they make the camera much more likely to work with Home Assistant, a local NVR, Frigate, Blue Iris, Synology Surveillance Station, or tools like <a class="decorated-link" href="https://spin.atomicobject.com/scrypted-via-docker-image/?utm_source=chatgpt.com" target="_new" rel="noopener" data-start="6131" data-end="6199">Scrypted</a>.</p>
<p data-start="6202" data-end="6509">That last one is a good example of why open video paths matter. Scrypted can take camera feeds and make them available to other smart home ecosystems. But that only works if there is something useful to ingest in the first place. If the camera’s only output is “open our app,” your options are much thinner.</p>
<p data-start="6511" data-end="6594">A camera should be a camera, not just a viewing window into a subscription service.</p>
<h2>The Counterarguments Are Real</h2>
<p>There are fair arguments against being this picky.</p>
<p>The first is convenience. The manufacturer app is usually easier. You scan a QR code, tap through a few screens, and the device appears. No hub decisions. No protocol research. No fiddling with integrations. That is true, and I do not think convenience should be dismissed. But easy setup and local control are not opposites. A good smart home device should have both: a pleasant setup flow and a durable local control path underneath it.</p>
<p>The second argument is that cloud services often provide better features. Also true. A cloud-backed camera may have better object recognition. A thermostat may have better weather-aware scheduling. A vendor app may provide cleaner dashboards than a generic hub. But those should be enhancements, not load-bearing walls. If the cloud goes away, the device should degrade gracefully. Losing fancy object detection is one thing. Losing the ability to view your own camera feed locally is another.</p>
<p>The third argument is that Matter is still imperfect. Very true. Device support is still uneven. Some categories are better covered than others. Some manufacturers technically support Matter but still keep their best features in their own app. Thread networks can introduce their own troubleshooting fun. Anyone who has spent time with home automation knows that “standard” does not always mean “effortless.”</p>
<p>Still, imperfect interoperability is better than no interoperability. I would rather have a device that exposes 80% of its functionality through a local standard than one that exposes 100% through a cloud service that can disappear.</p>
<h2 data-section-id="nzhb1h" data-start="8263" data-end="8293">What to Look For on the Box</h2>
<p data-start="8295" data-end="8362">When buying new smart home gear, I’d treat these as positive signs:</p>
<ul data-start="8364" data-end="8899">
<li data-section-id="962yll" data-start="8364" data-end="8459">Matter support for lights, plugs, switches, sensors, locks, thermostats, and similar devices.</li>
<li data-section-id="1kd3yq1" data-start="8460" data-end="8536">Thread support for low-power devices, as long as Matter is also supported.</li>
<li data-section-id="7rjsjc" data-start="8537" data-end="8601">Zigbee or Z-Wave support if you already have a compatible hub.</li>
<li data-section-id="cddbjg" data-start="8602" data-end="8674">Clear Home Assistant compatibility, especially for local integrations.</li>
<li data-section-id="d5qshm" data-start="8675" data-end="8731">MQTT or ESPHome support for more DIY-friendly devices.</li>
<li data-section-id="1od0eei" data-start="8732" data-end="8781">RTSP and/or ONVIF support for security cameras.</li>
<li data-section-id="bbnib4" data-start="8782" data-end="8827">A local API or explicit local control mode.</li>
<li data-section-id="1jfkwvd" data-start="8828" data-end="8899">Setup codes, QR codes, or pairing flows that work with standard hubs.</li>
</ul>
<p data-start="8901" data-end="8938">And I’d treat these as warning signs:</p>
<ul data-start="8940" data-end="9322">
<li data-section-id="itosdw" data-start="8940" data-end="8994">“Works with our app” is the only integration listed.</li>
<li data-section-id="3ct77b" data-start="8995" data-end="9055">Remote control requires an account with no local fallback.</li>
<li data-section-id="1gqrzee" data-start="9056" data-end="9116">The camera feed cannot be accessed outside the vendor app.</li>
<li data-section-id="pyn9pb" data-start="9117" data-end="9160">Basic automations require a subscription.</li>
<li data-section-id="1bgkrcj" data-start="9161" data-end="9247">The product page talks a lot about AI features but says nothing about local control.</li>
<li data-section-id="1lon1ex" data-start="9248" data-end="9322">Reviews mention that the device stops working when the internet is down.</li>
</ul>
<p data-start="9324" data-end="9494">This does not mean you have to turn every purchase into a research project. Just add one more question before buying: “What happens if this company’s service shuts down?”</p>
<p data-start="9496" data-end="9572">If the answer is, “The device keeps doing its main job,” that is a good sign.</p>
<p data-start="9574" data-end="9635">If the answer is, “I guess I&#8217;ll buy a replacement,” keep walking.</p>
<h2 data-section-id="c0pc5p" data-start="9637" data-end="9679">Buy Devices That Can Outlive Their Apps</h2>
<p data-start="9681" data-end="9863">Smart home devices live in weird territory. They are physical objects installed in your home, but many of them behave like rented software. That mismatch is where the trouble starts.</p>
<p data-start="9865" data-end="10059">A light switch should last a long time. A thermostat should last a long time. A camera should not need to be replaced just because a product team decided the old app is inconvenient to maintain.</p>
<p data-start="10061" data-end="10338">Open, local-first protocols are not just a hobbyist preference. They are a practical way to protect yourself from product churn. They let you change hubs, mix ecosystems, move more control into Home Assistant, or keep basic functionality running when a cloud service goes away.</p>
<p data-start="10340" data-end="10562">The best smart home device is not the one with the flashiest app. It is the one that still makes sense five years from now, after you change phones, rebuild your automations, switch platforms, or the manufacturer moves on.</p>
<p data-start="10564" data-end="10637">Your smart home should belong to you. Not to a server you do not control.</p>
<p>The post <a href="https://spin.atomicobject.com/smart-home/">Your Smart Home Shouldn’t Need Its Manufacturer to Stay Alive</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://spin.atomicobject.com/smart-home/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to Auto-Sort Your Angular Imports</title>
		<link>https://spin.atomicobject.com/auto-sort-angular-imports/</link>
					<comments>https://spin.atomicobject.com/auto-sort-angular-imports/#respond</comments>
		
		<dc:creator><![CDATA[Michael Li]]></dc:creator>
		<pubDate>Tue, 02 Jun 2026 12:00:07 +0000</pubDate>
				<category><![CDATA[Development Practices]]></category>
		<category><![CDATA[ESLint]]></category>
		<category><![CDATA[angular]]></category>
		<guid isPermaLink="false">https://spin.atomicobject.com/?p=3673714</guid>

					<description><![CDATA[<p>Have you ever wondered if there was an easier way to sort imports automatically in your Angular project? Disorganized imports make code harder to read, slow down code reviews, and cause unnecessary merge conflicts. Sorting them manually requires a lot of effort — what if you could automate it? Introducing simple-import-sort simple-import-sort is an ESLint [&#8230;]</p>
<p>The post <a href="https://spin.atomicobject.com/auto-sort-angular-imports/">How to Auto-Sort Your Angular Imports</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Have you ever wondered if there was an easier way to sort imports automatically in your Angular project? Disorganized imports make code harder to read, slow down code reviews, and cause unnecessary merge conflicts. Sorting them manually requires a lot of effort — what if you could automate it?</p>
<h2>Introducing <a href="https://www.npmjs.com/package/eslint-plugin-simple-import-sort?activeTab=readme">simple-import-sort</a></h2>
<p><code>simple-import-sort</code> is an ESLint plugin that can be installed as an npm package. It allows ESLint to automatically sort and format your imports.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-3673805" src="https://spin.atomicobject.com/wp-content/uploads/Screen-Recording-2026-05-11-at-10.11.22-PM.gif" alt="" width="1590" height="528" /></p>
<p>As seen in this example above, <code>simple-import-sort</code> allows us to set up rules to automatically format our imports on save. This allows us to keep clean and organized imports throughout our entire repo.</p>
<h2>Installation</h2>
<p>Before we can install <code>simple-import-sort</code>, the plugin requires ESLint to be installed in your repository. If you haven&#8217;t installed ESLint yet, follow the setup instructions in the <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://eslint.org/docs/latest/use/getting-started">ESLint Docs</a>.</p>
<p>Once installed we can install the plugin by running the following command from your terminal:</p>
<p><code>npm install --save-dev eslint-plugin-simple-import-sort</code></p>
<h2>Setup</h2>
<p>After installing the plugin locate the <code>eslint.config</code> file in the root of your repository. Inside your config file, you will need to add the <code>simple-import-sort</code> config.</p>
<pre><code class="language-js">
import simpleImportSort from 'eslint-plugin-simple-import-sort';                                                                                                                                
                                                          
export default [
    {
        files: ['**/*.ts', '**/*.tsx', '**/*.js'],
        plugins: {                                                                                                                                                                              
            'simple-import-sort': simpleImportSort,
        },                                                                                                                                                                                      
        rules: {                                        
            'simple-import-sort/imports': 'warn',                                                                                                                                               
            'simple-import-sort/exports': 'warn',
        },                                                                                                                                                                                      
    },                                                  
];
</code></pre>
<p>This is the most basic config for <code>simple-import-sort</code> that will enable automatic sorting of your imports. By default the plugin groups side-effect imports, then external packages, then everything else, then relative imports — alphabetized within each group.</p>
<h2>Custom Grouping</h2>
<p>Beyond the default groupings, we can optionally customize the import grouping further. You can customize this further by adding additional rules to the ESLint config. Let&#8217;s add the <code>groups</code> object to the <code>'simple-import-sort/imports'</code> object in the config.</p>
<pre><code class="language-js">
'simple-import-sort/imports': [                                                                                                                                                                 
    'warn',                                             
    {
        groups: [                                                                                                                                                                               
            ['^@angular'],                    // 1. Framework
            ['^@?\\w'],                       // 2. External packages                                                                                                                           
            ['^@app', '^@shared'],            // 3. Internal aliases
            ['^\\.'],                         // 4. Relative imports                                                                                                                            
        ],                                              
    },                                                                                                                                                                                          
],   
</code></pre>
<p>This config groups your imports according to the rules you define. Adjust the groups to match your project&#8217;s import paths.</p>
<h2>Enabling Automatic Import Sorting</h2>
<p>The magic of this plugin happens when the imports can automatically fix themselves. Let&#8217;s look at a couple ways to do this.<br />
1. Run <code>eslint --fix</code> from the command line. This will automatically run import formatting on all of the files on your project.<br />
2. Setup autofix on save. Locate your .vscode/settings.json file and include this in the config.</p>
<pre><code class="language-js">
"editor.codeActionsOnSave": {                                                                                                                                                                   
    "source.fixAll.eslint": "explicit"
}  
</code></pre>
<p>Once you add this config, VSCode will automatically apply the formatting whenever you save the file. Other editors will have equivalent settings for formatting on save.</p>
<h2>A Small Note</h2>
<p>Keep in mind that this plugin doesn&#8217;t work alongside any other import sorting plugin. This will cause the two plugins to get stuck in a loop, each reformatting the other&#8217;s output.</p>
<p>This includes ESLint&#8217;s default import sorter <code>sort-imports</code>. If you see this included in your <code>plugins</code> array, then you will need to remove it when adding <code>simple-import-sort</code>.</p>
<h2>Enjoy Hassle-Free Import Sorting</h2>
<p>That&#8217;s it! Give this a try and enjoy never sorting imports manually again. Keep in mind that this may require some trial and error, as every project is different, but this is a basic guide on how to get started with <code>simple-import-sort</code>.</p>
<p>The post <a href="https://spin.atomicobject.com/auto-sort-angular-imports/">How to Auto-Sort Your Angular Imports</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://spin.atomicobject.com/auto-sort-angular-imports/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>When Code Becomes Cheap, What&#8217;s Left?</title>
		<link>https://spin.atomicobject.com/code-cheap-whats-left/</link>
					<comments>https://spin.atomicobject.com/code-cheap-whats-left/#comments</comments>
		
		<dc:creator><![CDATA[Kealy Williams]]></dc:creator>
		<pubDate>Mon, 01 Jun 2026 12:00:58 +0000</pubDate>
				<category><![CDATA[The Software Life]]></category>
		<guid isPermaLink="false">https://spin.atomicobject.com/?p=3673792</guid>

					<description><![CDATA[<p>A few months ago I watched a sprint board do something I&#8217;d never quite seen before. Story cards weren&#8217;t stacking up in In Progress. They were stacking up in Ready for Review. On every project I&#8217;d ever worked on, the bottleneck was writing the code. Suddenly, on this project, it wasn&#8217;t. So how&#8217;d we get [&#8230;]</p>
<p>The post <a href="https://spin.atomicobject.com/code-cheap-whats-left/">When Code Becomes Cheap, What&#8217;s Left?</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>A few months ago I watched a sprint board do something I&#8217;d never quite seen before. Story cards weren&#8217;t stacking up in In Progress. They were stacking up in Ready for Review. On every project I&#8217;d ever worked on, the bottleneck was writing the code. Suddenly, on this project, it wasn&#8217;t.</p>
<h2>So how&#8217;d we get here?</h2>
<p>We had a feature with a generous estimate and a runway that was definitely not generous. So we decided to try spec-driven development with <a href="https://github.com/gsd-build/get-shit-done">Get Shit Done (GSD)</a>, an open-source tool that turns research and context into structured, executable development plans. Pair that with Claude Opus 4.6 as the coding agent and a disciplined review process, and you get a workflow where the agent writes the first draft of an implementation, and the team takes it the rest of the way. What surprised me wasn&#8217;t the speedup. It was where the slow part went.</p>
<h2>The thing about getting faster</h2>
<p>When people talk about AI making software development faster, they usually mean writing the code. That&#8217;s the part you can put on a stopwatch. Our shortest time from pickup to pull request was 21 minutes. Great headline.</p>
<p>And it&#8217;s true. We saw it. The writing part got dramatically faster. So fast that work the team had estimated in months came together in weeks. We finished in a fraction of the time the original plan called for, and that wasn&#8217;t a rounding error. The time savings were real, and they were big.</p>
<p>Here&#8217;s the part no one warned me about. When writing gets that fast, it stops being the longest part of the work.</p>
<p>On our team, the median time from picking up a story card to having a pull request open dropped to about a day. And then? The PR sat for three days waiting on review. Then it sat for three more days in QA. Writing time collapsed. Review time didn&#8217;t. So even as the whole pipeline got faster, the shape of where time goes flipped completely.</p>
<p>The bottleneck didn&#8217;t go away. It moved.</p>
<h2>What it feels like from inside</h2>
<p>This is the part I didn&#8217;t expect to be a feeling thing, but it absolutely was.</p>
<p>I&#8217;m a developer. I&#8217;ve spent two decades writing software. I love writing software. When the agent started producing the first draft of every implementation, what was left for me was reviewing, redirecting, testing, and refactoring. All of those are skills. All of those are valuable. None of them feel the same as writing the code yourself.</p>
<p>The word that came up for me, once I sat with it, was grief. I was mourning the job I used to have. The craft I&#8217;d built over years of writing code. The skills I&#8217;d worked hardest to build, and was proudest of, suddenly weren&#8217;t the ones the work was asking for. Watching an agent do the part you used to be best at, faster than you can, is not nothing. It&#8217;s a real loss, even when you know the trade is worth making.</p>
<p>You can&#8217;t wave that off. What I&#8217;ll say is the work didn&#8217;t go away. It became different work. Reviewing agent-generated code well is its own craft. You have to know what good looks like, what your team&#8217;s standards actually are, and where the agent&#8217;s blind spots tend to be. Spoiler: it has plenty.</p>
<p>The hard parts didn&#8217;t disappear. They just stopped being the initial generation.</p>
<h2>When the bottleneck moves, look at it honestly</h2>
<p>Here&#8217;s the thing about a bottleneck. When you remove the one in front of you, you don&#8217;t get a frictionless world. You get a new bottleneck. And usually it&#8217;s one you&#8217;d never had reason to look at, because the old one was bigger.</p>
<p>For us, the new bottleneck was the review pipeline. Code review, QA, the back-and-forth between developers and reviewers. It was fine before, because writing the code took longer than reviewing it. Now? The review pipeline was the constraint.</p>
<p>That&#8217;s actually useful information. The writing speedup is real, and it showed up in our cycle times. What it also did was make the review pipeline the biggest chunk of time left. Not as a problem, but as the new place where humans add the most value.</p>
<h2>What stays the same</h2>
<p>The work changed shape. The principles underneath it didn&#8217;t.</p>
<p>Every line of code we shipped, a human signed off on. Reviewed it. Ran it. Tested it against the running application. Pushed back when something wasn&#8217;t right. That&#8217;s two agile principles holding their ground at once: individuals and interactions still matter more than the tools we hand them, and continuous attention to technical excellence still defines what&#8217;s worth shipping. If anything, both got more important.</p>
<p>Spec-driven development pushed the thinking earlier and the judgment later. The thinking part (what are we building, why, what does done look like) has to be done up front, because the agent is going to take you at your word. The judgment part (is this what we actually want, does it hold up, would I be proud to ship it) has to happen on every PR, because the agent will happily produce code that&#8217;s technically correct and contextually wrong.</p>
<p>It&#8217;s still humans, top to bottom. It&#8217;s just humans doing different things.</p>
<h2>The honest takeaway</h2>
<p>We delivered the feature in a fraction of the estimated time. That&#8217;s the headline, and I&#8217;m not going to pretend it isn&#8217;t.</p>
<p>The quieter thing we learned, and the thing I&#8217;m carrying forward, is what the work has become. The typing got cheap. The judgment didn&#8217;t. Knowing what good looks like, holding the bar, catching what the agent gets wrong. That&#8217;s the developer&#8217;s work now. The team that wins isn&#8217;t the one that writes the most code. It&#8217;s the one that knows what to do with it.</p>
<p>Speed was the prize, and we got it. What&#8217;s stuck with me is a clearer picture of where the work actually lives now.</p>
<p>The post <a href="https://spin.atomicobject.com/code-cheap-whats-left/">When Code Becomes Cheap, What&#8217;s Left?</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://spin.atomicobject.com/code-cheap-whats-left/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Hard-Won Lessons From a Year of Using AI</title>
		<link>https://spin.atomicobject.com/lessons-year-ai/</link>
					<comments>https://spin.atomicobject.com/lessons-year-ai/#respond</comments>
		
		<dc:creator><![CDATA[Ian Culver]]></dc:creator>
		<pubDate>Sun, 31 May 2026 12:00:07 +0000</pubDate>
				<category><![CDATA[AI for Developers]]></category>
		<category><![CDATA[artificial intelligence]]></category>
		<guid isPermaLink="false">https://spin.atomicobject.com/?p=3673817</guid>

					<description><![CDATA[<p>We are producing more with AI. What we&#8217;re producing less of, apparently, is honest reflection on what that actually means. The internet is full of frameworks, prompt guides, and tutorials promising you&#8217;ll &#8220;10x your productivity overnight.&#8221; The question nobody seems to want to answer is whether any of it is actually good. Most of it [&#8230;]</p>
<p>The post <a href="https://spin.atomicobject.com/lessons-year-ai/">Hard-Won Lessons From a Year of Using AI</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>We are producing more with AI. What we&#8217;re producing less of, apparently, is honest reflection on what that actually means. The internet is full of frameworks, prompt guides, and tutorials promising you&#8217;ll &#8220;10x your productivity overnight.&#8221; The question nobody seems to want to answer is whether any of it is actually good. Most of it is hype without a clear value proposition. This is my attempt at something more honest.</p>
<p>After a year of genuinely integrating AI into my daily work, not just experimenting with it, here&#8217;s what I&#8217;ve actually learned. These aren&#8217;t my original ideas. Some I picked up from people smarter than me, some I arrived at through trial and error. I can&#8217;t say I follow all of them perfectly, and it&#8217;s easy to slip when things get busy. But they&#8217;ve shaped how I try to work, and I keep coming back to them.</p>
<h2>Lesson 1: The First and Last 10% Are Yours</h2>
<p>I first came across this framing in <a href="https://www.linkedin.com/posts/mattprzegietka_i-stopped-using-ai-throughout-the-entire-activity-7446934486346424320-qQhx/">a LinkedIn post by Matt Przegietka</a>, and it clicked immediately. The idea: think of AI-assisted work in three unequal parts.</p>
<p>The first 10% is yours: the framing, the strategic thinking, the definition of what actually needs to happen. This is where you decide what problem you&#8217;re solving, why it matters, and what &#8220;done&#8221; looks like. AI can&#8217;t do this for you, and it&#8217;s tempting to think it can. But this is critical thinking, judgment, and human intelligence. It&#8217;s the stuff that sets everything else up for success or failure.</p>
<p>The middle 80% is where AI can do the heavy lifting. Research, drafting, synthesizing, iterating, generating options. All of it is on the table. This is where it genuinely earns its place in a workflow.</p>
<p>The last 10% is yours again: the review, the refinement, the final judgment call. Does this make sense? Is it true? Is it good? AI will produce confident, polished output that is sometimes subtly wrong, occasionally embarrassingly wrong, and sometimes genuinely excellent. You can&#8217;t tell the difference without showing up at the end.</p>
<p>This only works if you&#8217;re using tools that let you take outputs somewhere and do something with them. When you stay trapped in a chat window, handing everything back to the AI with each iteration, you end up in an endless loop, essentially like asking an intern to push pixels around while you hover over their shoulder. They fix one thing and break something else. You lose control of the work, and the last 10% never really happens. Getting that final stretch right means pulling the output out of the generative tool and finishing it yourself, in a space where you&#8217;re actually in charge.</p>
<p>The temptation is to skip the edges entirely: hand AI a vague goal, accept whatever it spits out, and call it done. I&#8217;ve done it. The results are rarely something I&#8217;m proud of, and often something I have to quietly fix later anyway.</p>
<h2>Lesson 2: Stop Chasing the 10x Hype</h2>
<p>I&#8217;m skeptical of the big productivity numbers I hear thrown around. Not because AI doesn&#8217;t make you faster. It does. But not in the way the hype would have you believe. A <a href="https://www.nber.org/papers/w31161">study out of Stanford and MIT</a> found that AI tools boosted productivity for knowledge workers by about 14% on average. Real, and worth celebrating. Not a revolution, but real.</p>
<p>In my experience, 2x is achievable and repeatable. With the right setup and a task that plays to AI&#8217;s strengths, I can genuinely get roughly twice as much done in the same amount of time. It compounds over weeks and months.</p>
<p>Three times is possible sometimes, usually on narrow, well-defined tasks where I&#8217;ve done the upstream work carefully and I&#8217;m reviewing just as carefully.</p>
<p>Beyond that, I get skeptical. Five times starts to feel like cutting corners and rushing past the parts of work that actually matter. And 10x? If someone tells you they&#8217;re 10x more productive with AI, they&#8217;ve either redefined what &#8220;done&#8221; means or they&#8217;re not reviewing their work closely enough. My hunch is it’s probably both.</p>
<p>The real risk isn&#8217;t underusing AI. It&#8217;s chasing a multiplier that sets you up to produce more output in less time without actually improving the quality of what you&#8217;re making. That&#8217;s a trap. More shit, faster, should not be the goal.</p>
<p>Set realistic expectations. Celebrate 2x. Build the habits that make it sustainable.</p>
<h2>Lesson 3: Avoid the Multitasking Trap</h2>
<p>This one surprised me, not because I didn&#8217;t know multitasking was bad, but because AI made the temptation so much worse.</p>
<p>When you have an agent running in the background, it&#8217;s easy to think: I&#8217;ll just kick off three things at once and get ahead. So you do. You&#8217;re half-reviewing one output, half-crafting another prompt, half-reading a Slack thread. And technically, technically, you can do all three. Nothing stops you. But here&#8217;s the thing: your brain is paying for all of it, and the work shows it.</p>
<p>Multitasking isn&#8217;t just inefficient, it&#8217;s neurologically impossible. The brain doesn&#8217;t run two cognitive tasks in parallel; it switches between them rapidly, and every switch carries a cost. The American Psychological Association cites research showing that <a href="https://www.apa.org/topics/research/multitasking">task-switching can eat up to 40% of productive time</a>. Psychologist Sophie Leroy&#8217;s work on &#8220;attention residue&#8221; shows that even after you&#8217;ve moved on to the next thing, part of your brain is still stuck on the previous one. You&#8217;re never fully present on what&#8217;s in front of you.</p>
<p>AI doesn&#8217;t fix this. It makes it worse because the faster you can generate output, the more pressure you feel to keep all the plates spinning. The result is three tasks done poorly instead of one done well, and then extra time spent fixing all three.</p>
<p>My colleague Rachael Hodder wrote <a href="https://spin.atomicobject.com/agent-wait-26-things-to-do/">a helpful post</a> on this recently. Her framing is useful—the “agent wait” is a real category of time now, and it deserves intentional use. But her most important point is buried in the last section: &#8220;The best thing you can do with the wait is stay one step ahead of the agent. Review the last output. Queue the next prompt. Plan the verification.&#8221;</p>
<p>That&#8217;s the key. When an agent is running, you don&#8217;t have to sit still. But if you decide to keep going, you should connect to the work at hand. Re-read what you&#8217;ve been prompting. Review the context you&#8217;ve gathered. Draft your next prompt. Prepare how you&#8217;ll verify what comes back. This keeps you in the work rather than escaping it, and it means you&#8217;ll be sharp and ready when the output lands instead of scrambling to reorient. It can also truly help to just step away and take a much-needed human break. AI is supposed to free up time in our lives, not keep us glued to a never-ending, artificial productivity cycle.</p>
<p>The alternative—pinging three Slack channels, opening a totally different project, catching up on email—isn&#8217;t rest, and it isn&#8217;t progress. It&#8217;s the worst of both.</p>
<h2>Good Human Habits, Better AI Outcomes</h2>
<p>I don&#8217;t follow these perfectly. Some days I catch myself running too many things at once, or accepting a draft I know deserves another pass, or jumping straight into prompting before I&#8217;ve actually thought through what I want. The craze around AI makes it easy to slip. There&#8217;s always pressure to move faster, do more, and squeeze another percentage point of efficiency out of everything. These lessons are my pushback against that pressure, even when I don&#8217;t manage to live up to them.</p>
<p>The tools are only as useful as the habits around them. Without mindful, human judgment guiding the process, all we&#8217;re really doing is automating mediocrity and making more work for ourselves.</p>
<p>The post <a href="https://spin.atomicobject.com/lessons-year-ai/">Hard-Won Lessons From a Year of Using AI</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://spin.atomicobject.com/lessons-year-ai/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Liked Best/Next Time: A Lightweight Feedback Framework for Onboarding</title>
		<link>https://spin.atomicobject.com/liked-best-next-onboarding/</link>
					<comments>https://spin.atomicobject.com/liked-best-next-onboarding/#respond</comments>
		
		<dc:creator><![CDATA[Renee Liken]]></dc:creator>
		<pubDate>Sat, 30 May 2026 12:00:45 +0000</pubDate>
				<category><![CDATA[Effective communication]]></category>
		<category><![CDATA[onboarding]]></category>
		<guid isPermaLink="false">https://spin.atomicobject.com/?p=3673707</guid>

					<description><![CDATA[<p>How do you currently give feedback to new team members as they&#8217;re onboarding in their new role? How do you set clear expectations for them of what success looks like? When we onboarded two delivery leads to the Ann Arbor office last year, we experimented with a lightweight informal feedback framework. This framework allowed us [&#8230;]</p>
<p>The post <a href="https://spin.atomicobject.com/liked-best-next-onboarding/">Liked Best/Next Time: A Lightweight Feedback Framework for Onboarding</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>How do you currently give feedback to new team members as they&#8217;re onboarding in their new role? How do you set clear expectations for them of what success looks like?</p>
<p>When we onboarded two delivery leads to the Ann Arbor office last year, we experimented with a lightweight informal feedback framework. This framework allowed us to make space for conversations on what was going well, what support was needed, and where expectations might be unclear. This fits really well with our continuous improvement mindset at Atomic. We know there&#8217;s always room for everyone involved to <a href="https://spin.atomicobject.com/atomic-values-teach-and-learn/">learn and grow</a>!</p>
<p>Many feedback frameworks exist, but they felt overly complex for what we needed to check in regularly with our onboarding delivery leads. The Liked Best/Next Time framework is a tool we learned about at a <a href="https://www.zingtrain.com/in-person-seminars/" target="_blank" rel="noopener">ZingTrain seminar</a>. It&#8217;s similar to the mindsets we bring to a team retrospective conversation, but with a much smaller scope.</p>
<h2>Overview of Liked Best/Next Time</h2>
<p>Liked Best / Next Time is a lightweight feedback framework from ZingTrain that keeps feedback positive, specific, and future-focused. Instead of framing feedback as what someone did right or wrong, it asks people to reflect on what worked well and what they want to try differently the next time they face a similar situation. ZingTrain contrasts this with the &#8220;<a href="https://en.wikipedia.org/wiki/Compliment_sandwich">compliment sandwich</a>.&#8221; The useful feedback is not hidden between niceties, and the conversation stays focused on learning from the experience rather than judging the person.</p>
<h3>Breaking it Down</h3>
<p>In a one-on-one training context, the person being trained goes first:</p>
<ul>
<li>What did they like best about their own performance?</li>
<li>What did the trainer like best, including any additional positive observations?</li>
<li>What would the trainee do differently next time?</li>
<li>What specific suggestions or expectations does the trainer want them to focus on next time?</li>
</ul>
<p>That order matters because it gives the trainee space to notice and own both their strengths and their opportunities for improvement before the trainer adds more. The trainer&#8217;s role is to reinforce what went well, help prioritize the &#8220;next times&#8221; and keep the feedback concrete enough to act on. &#8220;Do better&#8221; is not a behavior; &#8220;smile before picking up the phone&#8221; or &#8220;practice using the hold button&#8221; is.</p>
<p>The same pattern can scale to groups, projects, events, or team changes. A facilitator asks the group what they liked best and what they want to make sure to repeat, then asks what they might do differently next time. A note-taker captures the discussion in a shared place, often with initials so people can clarify later. The goal in the first pass is brainstorming, not debate: get the feedback visible, look for themes, then decide who will review it and what actions or proposals should come out of it.</p>
<p>The bigger cultural idea is that feedback becomes less scary when it is frequent, expected, and tied to shared goals. LB/NT gives teams a common language for continuous improvement: acknowledge what is working, name what could work better, and turn that into practical next steps.</p>
<p>If you&#8217;d like to read more, ZingTrain offers <a href="https://www.zingtrain.com/webinar/creating-a-feedback-friendly-team-culture-with-liked-best-next-time/" target="_blank" rel="noopener">a free-to-access webinar</a> on the topic!</p>
<h2>How we implemented it</h2>
<p>Our incoming delivery leads were already paired with a fellow delivery lead onboarding buddy. So we tried out this framework as part of the scheduled time-based check-ins. You could use it at any time, but we targeted weekly for the first month, and then monthly for the rest of the new team members&#8217; first 90 days, at the end of the:</p>
<ul>
<li>First week</li>
<li>Second week</li>
<li>Third week</li>
<li>Fourth week</li>
<li>Second month</li>
<li>Third month</li>
</ul>
<p><figure id="attachment_3673709" aria-describedby="caption-attachment-3673709" style="width: 590px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class="size-medium wp-image-3673709" src="https://spin.atomicobject.com/wp-content/uploads/A2DL-Onboarding-LBNT-590x378.png" alt="Screenshot of A2DL onboarding Trello board" width="590" height="378" srcset="https://spin.atomicobject.com/wp-content/uploads/A2DL-Onboarding-LBNT-590x378.png 590w, https://spin.atomicobject.com/wp-content/uploads/A2DL-Onboarding-LBNT-1024x655.png 1024w, https://spin.atomicobject.com/wp-content/uploads/A2DL-Onboarding-LBNT-150x96.png 150w, https://spin.atomicobject.com/wp-content/uploads/A2DL-Onboarding-LBNT-768x491.png 768w, https://spin.atomicobject.com/wp-content/uploads/A2DL-Onboarding-LBNT-1536x983.png 1536w, https://spin.atomicobject.com/wp-content/uploads/A2DL-Onboarding-LBNT-2048x1311.png 2048w, https://spin.atomicobject.com/wp-content/uploads/A2DL-Onboarding-LBNT-600x384.png 600w, https://spin.atomicobject.com/wp-content/uploads/A2DL-Onboarding-LBNT-1200x768.png 1200w" sizes="auto, (max-width: 590px) 100vw, 590px" /><figcaption id="caption-attachment-3673709" class="wp-caption-text">Our A2DL onboarding Trello board, with LBNT-inspired check-ins highlighted</figcaption></figure></p>
<p><figure id="attachment_3673710" aria-describedby="caption-attachment-3673710" style="width: 590px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class="size-medium wp-image-3673710" src="https://spin.atomicobject.com/wp-content/uploads/lbnt-desc-590x546.jpg" alt="Screenshot of check-in Trello card description" width="590" height="546" srcset="https://spin.atomicobject.com/wp-content/uploads/lbnt-desc-590x546.jpg 590w, https://spin.atomicobject.com/wp-content/uploads/lbnt-desc-1024x947.jpg 1024w, https://spin.atomicobject.com/wp-content/uploads/lbnt-desc-150x139.jpg 150w, https://spin.atomicobject.com/wp-content/uploads/lbnt-desc-768x711.jpg 768w, https://spin.atomicobject.com/wp-content/uploads/lbnt-desc-600x555.jpg 600w, https://spin.atomicobject.com/wp-content/uploads/lbnt-desc.jpg 1122w" sizes="auto, (max-width: 590px) 100vw, 590px" /><figcaption id="caption-attachment-3673710" class="wp-caption-text">LBNT-inspired guidance on our feedback check-in Trello cards</figcaption></figure></p>
<p>We found it gave us useful insights not just for the new team members&#8217; progress, but also for our onboarding process in general. If your team runs onboarding similarly, we&#8217;d love to hear how you structure feedback check-ins!</p>
<p>The post <a href="https://spin.atomicobject.com/liked-best-next-onboarding/">Liked Best/Next Time: A Lightweight Feedback Framework for Onboarding</a> appeared first on <a href="https://spin.atomicobject.com">Atomic Spin</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://spin.atomicobject.com/liked-best-next-onboarding/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
