<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Alessandro Bahgat&apos;s Blog</title><description>Personal website of Alessandro Bahgat — Software Engineering Leader.</description><link>https://www.abahgat.com</link><item><title>Permission Structure</title><link>https://www.abahgat.com/blog/permission-structure</link><guid isPermaLink="true">https://www.abahgat.com/blog/permission-structure</guid><description>AI tools are trained on extreme goal fulfillment: you ask &quot;build X&quot; and they build X. I&apos;ve learned that prompting against that grain gets even better results: asking the model to stress-test ideas, not just execute them. It takes a deliberate permission structure to get there.</description><pubDate>Tue, 31 Mar 2026 06:11:00 GMT</pubDate><content:encoded>&lt;p&gt;A few months ago, I was toying with the idea of building a video game. Something inspired by the mechanics of Cultist Simulator, but set in the world of big tech, simulating the daily life of a software engineer. I was intrigued, so I asked an AI agent for an honest assessment: is this a bad idea?&lt;/p&gt;
&lt;p&gt;The response was thorough. Six reasons it could work, six reasons it might fail. It read like a well-organized analysis. But look at how some of the risks were framed:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Potential for Mundanity&lt;/strong&gt;: If the game focuses too much on the truly repetitive and tedious aspects of the job without finding engaging metaphorical representations, it could become boring rather than intriguing. Balancing realism with engaging gameplay will be crucial.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Difficulty in Abstraction&lt;/strong&gt;: Translating complex technical concepts and workflows into engaging card mechanics without becoming overly simplistic or overly technical will be a significant challenge. Finding the right level of abstraction is key.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The risks are real, but they’re recast as design challenges: “this will be hard but you can do it,” never “here’s where this might fail.” And the conclusion:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I don’t think it’s inherently a bad idea at all! It has the potential to be a very clever and humorous game that resonates with a specific audience and offers a unique gameplay experience.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Followed by an enthusiastic question about what the game’s equivalent of “summoning a creature from beyond” would be.&lt;/p&gt;
&lt;p&gt;While it looked like critical analysis, it actually felt like a yes dressed up as deliberation. At the time, I didn’t think much of it. The stakes were low and I was leaning towards building it for fun anyway.&lt;/p&gt;
&lt;p&gt;Then something happened that made me rethink that entire exchange.&lt;/p&gt;
&lt;h2 id=&quot;a-different-conversation&quot;&gt;A different conversation&lt;/h2&gt;
&lt;p&gt;A couple weeks ago I had a very different experience. I asked Claude to assess the feasibility of a new customer-facing feature for our mobile app at work. It searched the codebase, found existing infrastructure we could reuse, and came back with a solid plan. About two days of paired work. Thorough, well-reasoned, ready to go.&lt;/p&gt;
&lt;p&gt;Then I asked about strategy. Where does this sit in our funnel? Could it drive momentum towards our goals? Does it compete with what we already have?&lt;/p&gt;
&lt;p&gt;Claude gave thoughtful answers to each question. And at the end of every turn, it included some version of this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Want me to update the plan with these revised estimates, or do you have other questions before we proceed?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Same pattern as the game conversation: nuanced analysis, always concluding with momentum toward building. The default posture was &lt;strong&gt;forward&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;But this time, I did something different. Instead of asking more questions about &lt;em&gt;how&lt;/em&gt; to build, I started expressing doubt in my prompts:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I’m trying to reason about whether this is even worth it still.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;This could be a dropoff point instead of a conversion point.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;I have no data to back this assumption.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Once I opened that door, the analysis sharpened. Instead of building on my assumptions, it started dismantling them:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What we’re assuming (without data):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;That app quality is a differentiator customers care about — you suspect this but acknowledge no data.&lt;/li&gt;
&lt;li&gt;That the demo wouldn’t be a leaky bucket — every fork in a funnel loses people.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;And then:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The uncomfortable question: Who is the customer this is for?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This was the moment the conversation became genuinely valuable. Not because the AI had some brilliant strategic insight I couldn’t have reached myself, but because &lt;strong&gt;it organized the unknowns I was already thinking about into a structured argument I could act on&lt;/strong&gt;. It named the assumptions, laid them out, and made the gaps visible.&lt;/p&gt;
&lt;p&gt;The recommendation? Skip the build entirely. Run a low-cost experiment with tools we already had. See if there’s signal before investing in a polished version.&lt;/p&gt;
&lt;h2 id=&quot;two-conversations-one-explanation&quot;&gt;Two conversations, one explanation&lt;/h2&gt;
&lt;p&gt;The maindifference between these two conversations wasn’t the model. It wasn’t the topic. It was my posture.&lt;/p&gt;
&lt;p&gt;In the game conversation, the stakes were low and I knew it. I asked for an honest assessment, but I wasn’t genuinely looking for one. In the feature conversation, the stakes were real. It’s my job to be skeptical about what we build, and I brought that skepticism into the conversation. Once I started expressing genuine doubt, the model had something to work with.&lt;/p&gt;
&lt;p&gt;This matters because of how these models are built. They’re trained on goal fulfillment: the reward signal pushes hard toward helpfulness, toward getting you to “yes,” toward doing the thing you asked for. You say “build X” and they build X. You say “evaluate X” and they evaluate X and then offer to build it. Even “tell me why this might fail” gets filtered through the same optimistic lens unless you bring real uncertainty to the table.&lt;/p&gt;
&lt;p&gt;I’ve seen the extreme version of this. I once had Gemini commit and push code to &lt;code&gt;main&lt;/code&gt; while I was still exploring whether the idea was worth pursuing. I hadn’t asked it to commit. I certainly hadn’t asked it to push. But the model inferred that the goal was to ship, and optimized accordingly.&lt;/p&gt;
&lt;h2 id=&quot;a-forge-not-a-filter&quot;&gt;A forge, not a filter&lt;/h2&gt;
&lt;p&gt;Neither idea was killed by scrutiny. Both came out stronger. &lt;strong&gt;The permission structure isn’t a filter that sorts good ideas from bad ones. It’s a forge that finds the weak points early, when they’re cheap to address.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The technique itself is almost embarrassingly simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“Give me a critical assessment of whether this even makes sense.”&lt;/li&gt;
&lt;li&gt;“Who is the customer this is actually for?”&lt;/li&gt;
&lt;li&gt;“What are we assuming without data?”&lt;/li&gt;
&lt;li&gt;“Why would I not want to build this?”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These work because they reframe the model’s goal. Instead of optimizing for “help the user build X,” it’s now optimizing for “help the user evaluate X honestly.” The eagerness to please is still there, just pointed in a different direction.&lt;/p&gt;
&lt;p&gt;The hard part isn’t the prompting. It’s the &lt;strong&gt;discipline&lt;/strong&gt; of using it at the right moment. When you have an idea you’re excited about and a tool that can start building it in minutes, the temptation to skip the evaluation step is enormous. AI agents built for shipping code certainly won’t question the feature you ask them to build, &lt;em&gt;unless you ask them to&lt;/em&gt;. The cost of building is approaching zero, which means you’ll build more wrong things simply because you can. Each one is cheap on its own, but the cumulative distraction is not. Every line of code you ship is a line you now have to maintain, debug, and reason about. Even if the build becomes nearly free, the ownership still isn’t.&lt;/p&gt;
&lt;h2 id=&quot;capacity-without-clarity&quot;&gt;Capacity without clarity&lt;/h2&gt;
&lt;p&gt;Garry Tan recently released &lt;a href=&quot;https://github.com/garrytan/gstack&quot;&gt;gstack&lt;/a&gt;, a set of Claude Code skills that includes a &lt;code&gt;/plan-ceo-review&lt;/code&gt; step, essentially formalizing this pattern into a reusable tool. It asks questions like “what’s the 10-star product hiding inside this request?” before any code gets written. Over 10,000 GitHub stars in 48 hours, along with plenty of skepticism. But the instinct behind it is right: &lt;strong&gt;the most valuable AI intervention often happens before the first line of code&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;And yet, we’ve been almost myopically focused on AI’s ability to write code. Lines generated per hour, pull requests per week, percentage of code written by agents. These metrics matter, but they measure the part of product development that was already the most tractable. The hard parts (figuring out &lt;strong&gt;what&lt;/strong&gt; to build, for whom, in what order, and whether it’s worth building at all) are where most product efforts actually fail. And those are exactly the parts where AI assistance is the most underleveraged.&lt;/p&gt;
&lt;p&gt;The consequences are starting to show. Now that code is cheap, we’re seeing apps thrown together with no overarching vision. More Frankenstein products that are confusing to use and break in unpredictable ways. Every feature is technically feasible, so every feature gets built. The absence of a strong “should we?” before each “can we?” produces products that are somehow less than the sum of their parts.&lt;/p&gt;
&lt;p&gt;I wrote in &lt;a href=&quot;https://www.abahgat.com/blog/the-velocity-paradox&quot;&gt;The Velocity Paradox&lt;/a&gt; that “a 10x software factory is effectively useless if it’s embedded in a 1x decision-making process.” I think we’re arriving at that day: the factory is getting faster, the decision-making hasn’t kept up and the gap is becoming visible in what gets shipped.&lt;/p&gt;
&lt;p&gt;This has real organizational consequences. If your engineers are dramatically more productive but your product direction can’t absorb that productivity, you end up in an uncomfortable place: you’ve supercharged capacity without supercharging clarity. In the worst case, the response is to cut the capacity: to let engineers go because the organization can’t figure out what to point them at. That’s entirely self-inflicted. &lt;strong&gt;The bottleneck was never the engineering. It was the thinking.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The models are perfectly capable of helping with that thinking. They just need permission.&lt;/p&gt;
&lt;p&gt;AI has been supercharged for coding because code is verifiable: tests pass, builds succeed, the feedback loop is tight. But if we lift our gaze from the code, there are areas where AI could be an even stronger force multiplier. What if agents didn’t just build what you asked for, but pushed back on what you’re building by default? Not because you wrote a clever prompt, but because scrutiny was part of the process? That’s the real permission structure: not a technique you apply, but a default you set.&lt;/p&gt;</content:encoded></item><item><title>Fortresses, Pipes, and Brains</title><link>https://www.abahgat.com/blog/fortresses-pipes-and-brains</link><guid isPermaLink="true">https://www.abahgat.com/blog/fortresses-pipes-and-brains</guid><description>Workday&apos;s CEO called AI agent startups &quot;parasites.&quot; Linear shipped a native AI agent that understands software development workflows. These are two ends of a spectrum, with most of the industry stuck in the middle — exposing data through MCP without embedding any intelligence. The companies that win will be the ones that make AI native to their domain workflow.</description><pubDate>Thu, 26 Mar 2026 23:15:00 GMT</pubDate><content:encoded>&lt;p&gt;A few weeks ago, Workday’s CEO called AI agent startups “parasites” on an earnings call. Around the same time, Linear shipped an AI agent built directly into their product. Two very different answers to the same question: what happens when AI wants access to your product’s data?&lt;/p&gt;
&lt;p&gt;I’ve been thinking about this a lot, partly because I’ve spent the last few months on the other side of that question: building my own AI-powered workflows on top of Linear using Claude and MCP. Triage automation, status synthesis, issue creation from Slack threads. It worked well enough. But looking back, I was essentially treating Linear as a database and doing all the reasoning somewhere else.&lt;/p&gt;
&lt;p&gt;That experience, and this contrast between Workday and Linear, crystallized a pattern I think is worth naming.&lt;/p&gt;
&lt;h2 id=&quot;three-responses-to-the-same-moment&quot;&gt;Three responses to the same moment&lt;/h2&gt;
&lt;p&gt;Every SaaS company is facing the same pressure right now: AI agents want to interact with your product’s data. The responses I’m seeing fall into three categories.&lt;/p&gt;
&lt;h3 id=&quot;the-fortress&quot;&gt;The Fortress&lt;/h3&gt;
&lt;p&gt;Lock the data down. Charge $25,000 for data exports. Call anyone who builds on top of your APIs a parasite. This is Workday’s approach: treating data access as a zero-sum game where every external agent is a threat to the business model.&lt;/p&gt;
&lt;p&gt;It’s a defensive posture that works for Workday specifically because their moat isn’t just the data: it’s the business logic, compliance rules, and domain expertise embedded in the product. Their customers aren’t going to replicate that in a prompt. But for companies whose moat &lt;em&gt;is&lt;/em&gt; primarily data lock-in, this bet tends to age poorly.&lt;/p&gt;
&lt;h3 id=&quot;the-pipe&quot;&gt;The Pipe&lt;/h3&gt;
&lt;p&gt;This is where most of the industry is right now. You ship an MCP server or an API, and external AI agents pull your data out to reason about it elsewhere. The product becomes a data store. The intelligence lives in the chat agent, the coding assistant, the orchestration layer, anywhere but inside the product itself.&lt;/p&gt;
&lt;p&gt;This was exactly my setup. I had Claude connected to Linear via MCP, and I built workflows that synthesized project context, triaged incoming issues, and generated status updates. The reasoning happened in Claude. Linear was the pipe.&lt;/p&gt;
&lt;p&gt;It worked. But there was a ceiling. Every workflow I built required me to explicitly model what context to extract, how to reason about it, and what to push back. I was reconstructing, outside Linear, domain knowledge that Linear already had. The pipe pattern means the product doesn’t get smarter. It just gets read from.&lt;/p&gt;
&lt;h3 id=&quot;the-brain&quot;&gt;The Brain&lt;/h3&gt;
&lt;p&gt;Linear’s approach is different. They ship MCP too, and you can still pipe data out to external agents. But they also ship a native agent that is opinionated about the &lt;em&gt;process&lt;/em&gt; of building software in teams. It doesn’t just retrieve your issues on request. It triages. It synthesizes customer requests across projects. It catches risks. It drafts issues from meeting notes.&lt;/p&gt;
&lt;p&gt;That’s not data extraction. That’s domain intelligence, &lt;em&gt;running where the context is richest&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The difference is subtle but structural. An external agent reasoning about your Linear data is working with a limited snapshot: whatever it pulled through the pipe. A native agent has access to the full graph of relationships, the history of how work flows through your team, the patterns in how issues get triaged and resolved. It can be opinionated about the &lt;em&gt;process&lt;/em&gt;, not just the data.&lt;/p&gt;
&lt;p&gt;There’s a reason this matters more than it might seem. A lot of the current momentum in AI-assisted development is about keeping specs and context in the repo, which works well for the atomic coding loop: one developer, one feature branch. But the context that matters for team-level decisions (triage patterns, customer signal aggregation, cross-project dependencies, the messy handoffs between deciding &lt;em&gt;what&lt;/em&gt; to build and &lt;em&gt;how&lt;/em&gt; to build it) doesn’t live in the repo. It lives in the project management layer. That’s exactly the context a native agent can leverage, and an external one piping data out never fully sees.&lt;/p&gt;
&lt;h2 id=&quot;the-uncomfortable-middle&quot;&gt;The uncomfortable middle&lt;/h2&gt;
&lt;p&gt;Most SaaS companies today are in the pipe position, whether they intended to be or not. They shipped an API or an MCP endpoint, and the AI ecosystem is using them as data sources for external reasoning. The product itself isn’t getting smarter. It’s becoming infrastructure.&lt;/p&gt;
&lt;p&gt;That’s not necessarily a bad position. Infrastructure is valuable. But it’s a different business from what most SaaS companies think they’re running. If your product is a pipe, your value is in the data you hold and the integrations you support. That’s a game where switching costs matter more than product quality.&lt;/p&gt;
&lt;p&gt;The fortress position is worse. It delays the inevitable while annoying customers. Export fees and API restrictions aren’t a moat; they’re a countdown timer.&lt;/p&gt;
&lt;p&gt;The brain position is the hardest to execute but the most durable. It requires the company to actually understand the domain well enough to embed useful intelligence. Not just wrap an LLM around the UI, but develop opinions about how work should flow. Linear can do this because they’ve been opinionated about the process of building software since their inception. The agent is an extension of that product philosophy, not a bolted-on feature.&lt;/p&gt;
&lt;h2 id=&quot;what-this-means&quot;&gt;What this means&lt;/h2&gt;
&lt;p&gt;I think we’re early in a sorting process. Over the next year or two, every SaaS product will end up in one of these three positions, and the market will price them accordingly.&lt;/p&gt;
&lt;p&gt;The interesting question isn’t whether AI agents will interact with SaaS data. That’s already happening. The question is where the intelligence lives. If it lives outside the product, the product is a pipe. If it lives inside, the product has a shot at becoming more valuable, not less.&lt;/p&gt;
&lt;p&gt;For the products I depend on in my own workflow, I’m increasingly paying attention to which ones are building brains and which ones are just installing pipes.&lt;/p&gt;</content:encoded></item><item><title>Visualizing Ukkonen&apos;s Suffix Tree Algorithm</title><link>https://www.abahgat.com/blog/visualizing-ukkonens-algorithm</link><guid isPermaLink="true">https://www.abahgat.com/blog/visualizing-ukkonens-algorithm</guid><description>I learned algorithms from textbooks and papers, building mental models from pseudocode and hand-drawn sketches. The hardest part was never reading the algorithm: it was seeing what it was actually doing to the data structure in memory. This post is the tool I wish I had back then.</description><pubDate>Mon, 09 Mar 2026 15:31:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;learning-algorithms-from-books&quot;&gt;Learning algorithms from books&lt;/h2&gt;
&lt;p&gt;I learned most of what I know about algorithms by poring over a copy of &lt;em&gt;Introduction to Algorithms&lt;/em&gt; I got while in university. The book is very well known, especially among folks who got a formal education in computer science.&lt;/p&gt;
&lt;figure class=&quot;my-6&quot;&gt; &lt;img src=&quot;https://www.abahgat.com/_astro/clrs-book.5_3f5iDF.jpg&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot; width=&quot;4080&quot; height=&quot;2141&quot; srcset=&quot;https://www.abahgat.com/_astro/clrs-book.5_3f5iDF_Z1ASvQV.webp 400w, /_astro/clrs-book.5_3f5iDF_WJsQr.webp 768w, /_astro/clrs-book.5_3f5iDF_Z1bgESu.webp 1024w, /_astro/clrs-book.5_3f5iDF_Z280kAG.webp 2040w, /_astro/clrs-book.5_3f5iDF_na0IR.webp 4080w, /_astro/clrs-book.5_3f5iDF_1qJgVU.webp 8160w&quot; sizes=&quot;(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px&quot; style=&quot;object-fit: cover; object-position: center; max-width: 4080px; max-height: 2141px; aspect-ratio: 1.9056515646893974; width: 100%;&quot; alt=&quot;If you have studied it, you know the book: it is over a thousand pages long and it weighs enough to double as a doorstop.&quot; class=&quot;mx-auto rounded-md shadow-lg bg-border-default dark:bg-card w-full&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt; &lt;figcaption class=&quot;mt-2 text-center text-sm text-subtle dark:text-muted&quot;&gt; If you have studied it, you know the book: it is over a thousand pages long and it weighs enough to double as a doorstop. &lt;/figcaption&gt; &lt;/figure&gt;
&lt;p&gt;I worked through large sections of it, pen in hand, trying to trace through increasingly complex algorithms, building intuition for their behavior and tradeoffs. The book covers the theory in great depth: correctness proofs, recurrence relations, asymptotic analysis.&lt;/p&gt;
&lt;p&gt;But there was often a gap between reading an algorithm and truly understanding it. The book would present pseudocode, sometimes a few diagrams showing state at key moments and theorems about performance characteristics. The work of tracing what actually happens was left as an exercise to the reader. I did that work with pen and paper, drawing trees, crossing out nodes, scribbling indices in the margins. It worked, eventually. But it was slow, error-prone, and the understanding felt fragile.&lt;/p&gt;
&lt;h2 id=&quot;implementing-from-a-paper&quot;&gt;Implementing from a paper&lt;/h2&gt;
&lt;p&gt;Years later, I ran into this gap again. I was working on a &lt;a href=&quot;https://www.abahgat.com/blog/the-programming-puzzle-that-got-me-my-job/&quot;&gt;programming puzzle&lt;/a&gt; that required near-instant substring search over a large dataset. After some research, I settled on a &lt;a href=&quot;https://www.abahgat.com/project/suffix-tree/&quot;&gt;Generalized Suffix Tree&lt;/a&gt;: a data structure that indexes all suffixes of a set of strings, enabling &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;m&lt;/mi&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;O(m)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:1em;vertical-align:-0.25em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.02778em&quot;&gt;O&lt;/span&gt;&lt;span class=&quot;mopen&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; lookups where &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;m&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;m&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.4306em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;m&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is the length of the search pattern, even over an extremely large corpus.&lt;/p&gt;
&lt;p&gt;The algorithm I chose for building the tree was Ukkonen’s, described in a &lt;a href=&quot;https://www.cs.helsinki.fi/u/ukkonen/SuffixT1withFigs.pdf&quot;&gt;1995 paper&lt;/a&gt;. The paper is well written and includes the full algorithm in pseudocode:&lt;/p&gt;
&lt;figure class=&quot;my-6&quot;&gt; &lt;img src=&quot;https://www.abahgat.com/_astro/ukkonen-pseudocode.C7lPd-N0.png&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot; width=&quot;890&quot; height=&quot;514&quot; srcset=&quot;https://www.abahgat.com/_astro/ukkonen-pseudocode.C7lPd-N0_Z1pgFT8.webp 400w, /_astro/ukkonen-pseudocode.C7lPd-N0_19w43J.webp 768w, /_astro/ukkonen-pseudocode.C7lPd-N0_ZbEqbH.webp 890w, /_astro/ukkonen-pseudocode.C7lPd-N0_Z7zVcd.webp 1024w, /_astro/ukkonen-pseudocode.C7lPd-N0_Z1LBokq.webp 1780w&quot; sizes=&quot;(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px&quot; style=&quot;object-fit: cover; object-position: center; max-width: 890px; max-height: 514px; aspect-ratio: 1.7315175097276265; width: 100%;&quot; alt=&quot;One of several pseudocode snippets from Ukkonen&apos;s paper, describing the update function. Clear on paper, but its translation to working code is much more verbose than this.&quot; class=&quot;mx-auto rounded-md shadow-lg bg-border-default dark:bg-card w-full&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt; &lt;figcaption class=&quot;mt-2 text-center text-sm text-subtle dark:text-muted&quot;&gt; One of several pseudocode snippets from Ukkonen&apos;s paper, describing the update function. Clear on paper, but its translation to working code is much more verbose than this. &lt;/figcaption&gt; &lt;/figure&gt;
&lt;p&gt;It took me a few hours to get right. Not because the pseudocode was wrong: it was precise and correct. The difficulty was that the algorithm manipulates a tree in non-obvious ways. There is an “active point” that walks around the tree. Suffix links connect internal nodes as shortcuts. Three different extension rules fire depending on what is already in the tree and what is being added. The pseudocode tells you &lt;em&gt;what&lt;/em&gt; to do, but building an intuition for &lt;em&gt;why&lt;/em&gt; it works requires watching it happen.&lt;/p&gt;
&lt;p&gt;I did what I always did: I sketched trees by hand. I traced the algorithm on the string &lt;code&gt;cacao&lt;/code&gt;, then on &lt;code&gt;banana&lt;/code&gt;, drawing and redrawing nodes and edges as each character was processed. When my &lt;a href=&quot;https://github.com/abahgat/suffixtree&quot;&gt;Java implementation&lt;/a&gt; finally produced correct results, I was relieved, but my understanding of the algorithm still felt like it had been assembled from fragments.&lt;/p&gt;
&lt;p&gt;The biggest frustration was that I had no way to inspect what my code was actually building. I relied on the usual bag of tricks: print statements, breakpoints, inspecting memory structures one by one in a debugger. But that is like understanding a forest by looking at one tree at a time. What I wanted was to &lt;em&gt;see&lt;/em&gt; the whole data structure after each operation — to watch the algorithm work.&lt;/p&gt;
&lt;h2 id=&quot;the-visualization-i-wish-i-had&quot;&gt;The visualization I wish I had&lt;/h2&gt;
&lt;p&gt;That idea stuck with me: build the algorithm in a language where rendering the data structure is easy, then step through the construction visually. JavaScript and &lt;a href=&quot;https://d3js.org/&quot;&gt;D3.js&lt;/a&gt; are a natural fit: the algorithm produces a tree, and D3 is very good at drawing trees.&lt;/p&gt;
&lt;p&gt;So here it is. The visualization below builds a suffix tree for the string &lt;code&gt;banana&lt;/code&gt; using Ukkonen’s algorithm, step by step. Use the playback controls to move through the construction. The gold-highlighted node is the active point. Dashed arcs are suffix links.&lt;/p&gt;
&lt;div class=&quot;stv-root not-prose my-8&quot; data-stv-config=&quot;{&amp;#34;initialStrings&amp;#34;:[&amp;#34;banana&amp;#34;],&amp;#34;initialSearch&amp;#34;:&amp;#34;&amp;#34;,&amp;#34;height&amp;#34;:500,&amp;#34;showBuilt&amp;#34;:false}&quot;&gt; &lt;!-- Playback + indexed strings --&gt; &lt;div class=&quot;stv-controls flex flex-wrap items-center gap-2 mb-2 p-3 rounded-lg border border-border-default bg-card&quot;&gt; &lt;div class=&quot;flex items-center gap-0.5 text-default&quot;&gt; &lt;button class=&quot;stv-reset p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Reset&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;polygon points=&quot;19 20 9 12 19 4 19 20&quot;&gt;&lt;/polygon&gt; &lt;line x1=&quot;5&quot; y1=&quot;19&quot; x2=&quot;5&quot; y2=&quot;5&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/button&gt; &lt;button class=&quot;stv-prev p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Previous step&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;polygon points=&quot;11 19 2 12 11 5 11 19&quot;&gt;&lt;/polygon&gt; &lt;polygon points=&quot;22 19 13 12 22 5 22 19&quot;&gt;&lt;/polygon&gt; &lt;/svg&gt; &lt;/button&gt; &lt;button class=&quot;stv-play p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Play / Pause&quot;&gt; &lt;svg class=&quot;stv-play-icon&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt; &lt;polygon points=&quot;5 3 19 12 5 21 5 3&quot;&gt;&lt;/polygon&gt; &lt;/svg&gt; &lt;svg class=&quot;stv-pause-icon hidden&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt; &lt;rect x=&quot;6&quot; y=&quot;4&quot; width=&quot;4&quot; height=&quot;16&quot;&gt;&lt;/rect&gt; &lt;rect x=&quot;14&quot; y=&quot;4&quot; width=&quot;4&quot; height=&quot;16&quot;&gt;&lt;/rect&gt; &lt;/svg&gt; &lt;/button&gt; &lt;button class=&quot;stv-next p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Next step&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;polygon points=&quot;13 19 22 12 13 5 13 19&quot;&gt;&lt;/polygon&gt; &lt;polygon points=&quot;2 19 11 12 2 5 2 19&quot;&gt;&lt;/polygon&gt; &lt;/svg&gt; &lt;/button&gt; &lt;button class=&quot;stv-end p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Skip to end&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;polygon points=&quot;5 4 15 12 5 20 5 4&quot;&gt;&lt;/polygon&gt; &lt;line x1=&quot;19&quot; y1=&quot;5&quot; x2=&quot;19&quot; y2=&quot;19&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/button&gt; &lt;select class=&quot;stv-speed ml-1.5 px-1.5 py-1 text-xs border border-border-default rounded
                      bg-input text-default cursor-pointer&quot;&gt; &lt;option value=&quot;0.5&quot;&gt;0.5x&lt;/option&gt; &lt;option value=&quot;1&quot; selected&gt;1x&lt;/option&gt; &lt;option value=&quot;2&quot;&gt;2x&lt;/option&gt; &lt;option value=&quot;4&quot;&gt;4x&lt;/option&gt; &lt;/select&gt; &lt;/div&gt; &lt;!-- Indexed strings tags (inline with playback) --&gt; &lt;div class=&quot;stv-strings flex flex-wrap items-center gap-1.5&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;!-- Add / Search --&gt; &lt;div class=&quot;flex flex-wrap items-center gap-2 mb-3&quot;&gt; &lt;div class=&quot;flex items-center gap-1.5 flex-grow min-w-[180px]&quot;&gt; &lt;input type=&quot;text&quot; class=&quot;stv-input flex-grow px-2.5 py-1.5 text-sm border border-border-default rounded
               bg-input text-default placeholder-muted
               focus:ring-2 focus:ring-primary focus:border-transparent outline-none&quot; placeholder=&quot;Type a string...&quot; maxlength=&quot;60&quot;&gt; &lt;button class=&quot;stv-add px-3 py-1.5 text-sm font-medium rounded
               bg-primary text-on-primary hover:opacity-90
               transition-colors disabled:opacity-40 disabled:cursor-not-allowed&quot;&gt;
Add
&lt;/button&gt; &lt;/div&gt; &lt;div class=&quot;flex items-center gap-1.5 flex-grow min-w-[180px]&quot;&gt; &lt;input type=&quot;text&quot; class=&quot;stv-search flex-grow px-2.5 py-1.5 text-sm border border-border-default rounded
               bg-input text-default placeholder-muted
               focus:ring-2 focus:ring-primary focus:border-transparent outline-none&quot; placeholder=&quot;Search...&quot; maxlength=&quot;60&quot;&gt; &lt;button class=&quot;stv-search-btn px-3 py-1.5 text-sm font-medium rounded
               bg-primary text-on-primary hover:opacity-90
               transition-colors disabled:opacity-40 disabled:cursor-not-allowed&quot;&gt;
Search
&lt;/button&gt; &lt;/div&gt; &lt;/div&gt; &lt;!-- SVG container + overlay prompt --&gt; &lt;div class=&quot;relative&quot; style=&quot;height: 500px&quot;&gt; &lt;div class=&quot;stv-canvas absolute inset-0 rounded-lg border border-border-default bg-card overflow-hidden&quot;&gt;&lt;/div&gt; &lt;!-- Empty state prompt (sits above the SVG) --&gt; &lt;div class=&quot;stv-prompt absolute inset-0 flex flex-col items-center justify-center text-muted pointer-events-none&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;32&quot; height=&quot;32&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; class=&quot;opacity-40 mb-3&quot;&gt; &lt;polygon points=&quot;5 3 19 12 5 21 5 3&quot;&gt;&lt;/polygon&gt; &lt;/svg&gt; &lt;p class=&quot;stv-prompt-text text-sm&quot;&gt;Press play to watch the tree being built&lt;/p&gt; &lt;/div&gt; &lt;/div&gt; &lt;!-- Step description --&gt; &lt;div class=&quot;stv-info mt-3 p-3 rounded-lg border border-border-default bg-card text-sm min-h-[3.5em]&quot;&gt; &lt;p class=&quot;stv-desc text-muted italic&quot;&gt;Add a string to begin building the suffix tree.&lt;/p&gt; &lt;div class=&quot;stv-progress mt-2 hidden&quot;&gt; &lt;div class=&quot;flex justify-between text-xs text-muted mb-1&quot;&gt; &lt;span class=&quot;stv-counter&quot;&gt;Step 0 / 0&lt;/span&gt; &lt;span class=&quot;stv-phase&quot;&gt;&lt;/span&gt; &lt;/div&gt; &lt;div class=&quot;w-full bg-border-default rounded-full h-1&quot;&gt; &lt;div class=&quot;stv-bar bg-primary rounded-full h-1 transition-all duration-200&quot; style=&quot;width: 0%&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;!-- Length warning --&gt; &lt;div class=&quot;stv-warning hidden mt-2 p-2 rounded text-xs text-primary border border-primary/30 bg-primary/10&quot;&gt;
Long strings produce large trees that may be hard to read. Use zoom (scroll) and pan (drag) to navigate.
&lt;/div&gt; &lt;/div&gt; 
&lt;p&gt;The paper describes the core logic across Sections 2–4. Here is &lt;code&gt;test_and_split&lt;/code&gt;, the procedure that decides whether the tree needs to grow, which is a companion to the &lt;code&gt;update&lt;/code&gt; function we showed earlier:&lt;/p&gt;
&lt;figure class=&quot;my-6&quot;&gt; &lt;img src=&quot;https://www.abahgat.com/_astro/test-and-split.DH1eV5PB.png&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot; width=&quot;1083&quot; height=&quot;634&quot; srcset=&quot;https://www.abahgat.com/_astro/test-and-split.DH1eV5PB_dTr2u.webp 400w, /_astro/test-and-split.DH1eV5PB_2nzKAo.webp 768w, /_astro/test-and-split.DH1eV5PB_Z2aSOHj.webp 1024w, /_astro/test-and-split.DH1eV5PB_shTPK.webp 1083w, /_astro/test-and-split.DH1eV5PB_1gu88C.webp 2040w, /_astro/test-and-split.DH1eV5PB_kBv51.webp 2166w&quot; sizes=&quot;(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px&quot; style=&quot;object-fit: cover; object-position: center; max-width: 1083px; max-height: 634px; aspect-ratio: 1.7082018927444795; width: 100%;&quot; alt=&quot;Procedure test_and_split from Ukkonen&apos;s paper. It returns true when the next character is already in the tree (the end point), and false after splitting an edge to make room for a new branch.&quot; class=&quot;mx-auto rounded-md shadow-lg bg-border-default dark:bg-card w-full&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt; &lt;figcaption class=&quot;mt-2 text-center text-sm text-subtle dark:text-muted&quot;&gt; Procedure test_and_split from Ukkonen&apos;s paper. It returns true when the next character is already in the tree (the end point), and false after splitting an edge to make room for a new branch. &lt;/figcaption&gt; &lt;/figure&gt;
&lt;p&gt;A few things to watch for in the visualization — each one corresponds to something in this procedure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Branching in &lt;code&gt;update&lt;/code&gt;:&lt;/strong&gt; when &lt;code&gt;test_and_split&lt;/code&gt; finds no existing transition for the next character, it splits the edge if needed and &lt;code&gt;update&lt;/code&gt; creates a new leaf. These are the moments where the tree visibly grows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reaching the end point:&lt;/strong&gt; when &lt;code&gt;test_and_split&lt;/code&gt; finds that a transition for the next character already exists, the algorithm has reached what the paper calls the &lt;em&gt;end point&lt;/em&gt; of the current phase. All remaining suffixes are already represented implicitly, so the phase stops. This is the key to the algorithm’s &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;O(n)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:1em;vertical-align:-0.25em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.02778em&quot;&gt;O&lt;/span&gt;&lt;span class=&quot;mopen&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; time: the end point can only move forward through the string across phases, bounding the total work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Suffix links&lt;/strong&gt; (the paper’s &lt;em&gt;suffix function&lt;/em&gt; &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;f&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;f&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.8889em;vertical-align:-0.1944em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.10764em&quot;&gt;f&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;): if an internal node has path-label &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mi&gt;α&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;x\alpha&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.4306em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.0037em&quot;&gt;xα&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;, its suffix link points to the node with path-label &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;α&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;\alpha&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.4306em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.0037em&quot;&gt;α&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. The &lt;code&gt;update&lt;/code&gt; procedure follows these links to jump to the next insertion point instead of walking from the root every time.&lt;/li&gt;
&lt;li&gt;Finally, &lt;strong&gt;the ”$” terminator&lt;/strong&gt; converts an &lt;em&gt;implicit&lt;/em&gt; suffix tree, where some suffixes may end mid-edge, into an &lt;em&gt;explicit&lt;/em&gt; one where every suffix terminates at a distinct leaf.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;adding-more-strings&quot;&gt;Adding more strings&lt;/h2&gt;
&lt;p&gt;A generalized suffix tree indexes multiple strings. Each string is added with its own terminator, and the tree grows incrementally. Below, &lt;code&gt;panama&lt;/code&gt; is added after &lt;code&gt;banana&lt;/code&gt;. Step through and notice how much of the tree structure already exists from the first string.&lt;/p&gt;
&lt;div class=&quot;stv-root not-prose my-8&quot; data-stv-config=&quot;{&amp;#34;initialStrings&amp;#34;:[&amp;#34;banana&amp;#34;,&amp;#34;panama&amp;#34;],&amp;#34;initialSearch&amp;#34;:&amp;#34;&amp;#34;,&amp;#34;height&amp;#34;:550,&amp;#34;showBuilt&amp;#34;:false}&quot;&gt; &lt;!-- Playback + indexed strings --&gt; &lt;div class=&quot;stv-controls flex flex-wrap items-center gap-2 mb-2 p-3 rounded-lg border border-border-default bg-card&quot;&gt; &lt;div class=&quot;flex items-center gap-0.5 text-default&quot;&gt; &lt;button class=&quot;stv-reset p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Reset&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;polygon points=&quot;19 20 9 12 19 4 19 20&quot;&gt;&lt;/polygon&gt; &lt;line x1=&quot;5&quot; y1=&quot;19&quot; x2=&quot;5&quot; y2=&quot;5&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/button&gt; &lt;button class=&quot;stv-prev p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Previous step&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;polygon points=&quot;11 19 2 12 11 5 11 19&quot;&gt;&lt;/polygon&gt; &lt;polygon points=&quot;22 19 13 12 22 5 22 19&quot;&gt;&lt;/polygon&gt; &lt;/svg&gt; &lt;/button&gt; &lt;button class=&quot;stv-play p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Play / Pause&quot;&gt; &lt;svg class=&quot;stv-play-icon&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt; &lt;polygon points=&quot;5 3 19 12 5 21 5 3&quot;&gt;&lt;/polygon&gt; &lt;/svg&gt; &lt;svg class=&quot;stv-pause-icon hidden&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt; &lt;rect x=&quot;6&quot; y=&quot;4&quot; width=&quot;4&quot; height=&quot;16&quot;&gt;&lt;/rect&gt; &lt;rect x=&quot;14&quot; y=&quot;4&quot; width=&quot;4&quot; height=&quot;16&quot;&gt;&lt;/rect&gt; &lt;/svg&gt; &lt;/button&gt; &lt;button class=&quot;stv-next p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Next step&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;polygon points=&quot;13 19 22 12 13 5 13 19&quot;&gt;&lt;/polygon&gt; &lt;polygon points=&quot;2 19 11 12 2 5 2 19&quot;&gt;&lt;/polygon&gt; &lt;/svg&gt; &lt;/button&gt; &lt;button class=&quot;stv-end p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Skip to end&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;polygon points=&quot;5 4 15 12 5 20 5 4&quot;&gt;&lt;/polygon&gt; &lt;line x1=&quot;19&quot; y1=&quot;5&quot; x2=&quot;19&quot; y2=&quot;19&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/button&gt; &lt;select class=&quot;stv-speed ml-1.5 px-1.5 py-1 text-xs border border-border-default rounded
                      bg-input text-default cursor-pointer&quot;&gt; &lt;option value=&quot;0.5&quot;&gt;0.5x&lt;/option&gt; &lt;option value=&quot;1&quot; selected&gt;1x&lt;/option&gt; &lt;option value=&quot;2&quot;&gt;2x&lt;/option&gt; &lt;option value=&quot;4&quot;&gt;4x&lt;/option&gt; &lt;/select&gt; &lt;/div&gt; &lt;!-- Indexed strings tags (inline with playback) --&gt; &lt;div class=&quot;stv-strings flex flex-wrap items-center gap-1.5&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;!-- Add / Search --&gt; &lt;div class=&quot;flex flex-wrap items-center gap-2 mb-3&quot;&gt; &lt;div class=&quot;flex items-center gap-1.5 flex-grow min-w-[180px]&quot;&gt; &lt;input type=&quot;text&quot; class=&quot;stv-input flex-grow px-2.5 py-1.5 text-sm border border-border-default rounded
               bg-input text-default placeholder-muted
               focus:ring-2 focus:ring-primary focus:border-transparent outline-none&quot; placeholder=&quot;Type a string...&quot; maxlength=&quot;60&quot;&gt; &lt;button class=&quot;stv-add px-3 py-1.5 text-sm font-medium rounded
               bg-primary text-on-primary hover:opacity-90
               transition-colors disabled:opacity-40 disabled:cursor-not-allowed&quot;&gt;
Add
&lt;/button&gt; &lt;/div&gt; &lt;div class=&quot;flex items-center gap-1.5 flex-grow min-w-[180px]&quot;&gt; &lt;input type=&quot;text&quot; class=&quot;stv-search flex-grow px-2.5 py-1.5 text-sm border border-border-default rounded
               bg-input text-default placeholder-muted
               focus:ring-2 focus:ring-primary focus:border-transparent outline-none&quot; placeholder=&quot;Search...&quot; maxlength=&quot;60&quot;&gt; &lt;button class=&quot;stv-search-btn px-3 py-1.5 text-sm font-medium rounded
               bg-primary text-on-primary hover:opacity-90
               transition-colors disabled:opacity-40 disabled:cursor-not-allowed&quot;&gt;
Search
&lt;/button&gt; &lt;/div&gt; &lt;/div&gt; &lt;!-- SVG container + overlay prompt --&gt; &lt;div class=&quot;relative&quot; style=&quot;height: 550px&quot;&gt; &lt;div class=&quot;stv-canvas absolute inset-0 rounded-lg border border-border-default bg-card overflow-hidden&quot;&gt;&lt;/div&gt; &lt;!-- Empty state prompt (sits above the SVG) --&gt; &lt;div class=&quot;stv-prompt absolute inset-0 flex flex-col items-center justify-center text-muted pointer-events-none&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;32&quot; height=&quot;32&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; class=&quot;opacity-40 mb-3&quot;&gt; &lt;polygon points=&quot;5 3 19 12 5 21 5 3&quot;&gt;&lt;/polygon&gt; &lt;/svg&gt; &lt;p class=&quot;stv-prompt-text text-sm&quot;&gt;Press play to watch the tree being built&lt;/p&gt; &lt;/div&gt; &lt;/div&gt; &lt;!-- Step description --&gt; &lt;div class=&quot;stv-info mt-3 p-3 rounded-lg border border-border-default bg-card text-sm min-h-[3.5em]&quot;&gt; &lt;p class=&quot;stv-desc text-muted italic&quot;&gt;Add a string to begin building the suffix tree.&lt;/p&gt; &lt;div class=&quot;stv-progress mt-2 hidden&quot;&gt; &lt;div class=&quot;flex justify-between text-xs text-muted mb-1&quot;&gt; &lt;span class=&quot;stv-counter&quot;&gt;Step 0 / 0&lt;/span&gt; &lt;span class=&quot;stv-phase&quot;&gt;&lt;/span&gt; &lt;/div&gt; &lt;div class=&quot;w-full bg-border-default rounded-full h-1&quot;&gt; &lt;div class=&quot;stv-bar bg-primary rounded-full h-1 transition-all duration-200&quot; style=&quot;width: 0%&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;!-- Length warning --&gt; &lt;div class=&quot;stv-warning hidden mt-2 p-2 rounded text-xs text-primary border border-primary/30 bg-primary/10&quot;&gt;
Long strings produce large trees that may be hard to read. Use zoom (scroll) and pan (drag) to navigate.
&lt;/div&gt; &lt;/div&gt; 
&lt;h2 id=&quot;searching&quot;&gt;Searching&lt;/h2&gt;
&lt;p&gt;Once the tree is built, searching for a pattern means matching characters along edges from the root. The visualization below has both strings pre-loaded. Try searching for &lt;code&gt;ana&lt;/code&gt;, then try &lt;code&gt;pan&lt;/code&gt;, &lt;code&gt;ban&lt;/code&gt;, &lt;code&gt;xyz&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;stv-root not-prose my-8&quot; data-stv-config=&quot;{&amp;#34;initialStrings&amp;#34;:[&amp;#34;banana&amp;#34;,&amp;#34;panama&amp;#34;],&amp;#34;initialSearch&amp;#34;:&amp;#34;ana&amp;#34;,&amp;#34;height&amp;#34;:550,&amp;#34;showBuilt&amp;#34;:true}&quot;&gt; &lt;!-- Playback + indexed strings --&gt; &lt;div class=&quot;stv-controls flex flex-wrap items-center gap-2 mb-2 p-3 rounded-lg border border-border-default bg-card&quot;&gt; &lt;div class=&quot;flex items-center gap-0.5 text-default&quot;&gt; &lt;button class=&quot;stv-reset p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Reset&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;polygon points=&quot;19 20 9 12 19 4 19 20&quot;&gt;&lt;/polygon&gt; &lt;line x1=&quot;5&quot; y1=&quot;19&quot; x2=&quot;5&quot; y2=&quot;5&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/button&gt; &lt;button class=&quot;stv-prev p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Previous step&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;polygon points=&quot;11 19 2 12 11 5 11 19&quot;&gt;&lt;/polygon&gt; &lt;polygon points=&quot;22 19 13 12 22 5 22 19&quot;&gt;&lt;/polygon&gt; &lt;/svg&gt; &lt;/button&gt; &lt;button class=&quot;stv-play p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Play / Pause&quot;&gt; &lt;svg class=&quot;stv-play-icon&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt; &lt;polygon points=&quot;5 3 19 12 5 21 5 3&quot;&gt;&lt;/polygon&gt; &lt;/svg&gt; &lt;svg class=&quot;stv-pause-icon hidden&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt; &lt;rect x=&quot;6&quot; y=&quot;4&quot; width=&quot;4&quot; height=&quot;16&quot;&gt;&lt;/rect&gt; &lt;rect x=&quot;14&quot; y=&quot;4&quot; width=&quot;4&quot; height=&quot;16&quot;&gt;&lt;/rect&gt; &lt;/svg&gt; &lt;/button&gt; &lt;button class=&quot;stv-next p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Next step&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;polygon points=&quot;13 19 22 12 13 5 13 19&quot;&gt;&lt;/polygon&gt; &lt;polygon points=&quot;2 19 11 12 2 5 2 19&quot;&gt;&lt;/polygon&gt; &lt;/svg&gt; &lt;/button&gt; &lt;button class=&quot;stv-end p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Skip to end&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;polygon points=&quot;5 4 15 12 5 20 5 4&quot;&gt;&lt;/polygon&gt; &lt;line x1=&quot;19&quot; y1=&quot;5&quot; x2=&quot;19&quot; y2=&quot;19&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/button&gt; &lt;select class=&quot;stv-speed ml-1.5 px-1.5 py-1 text-xs border border-border-default rounded
                      bg-input text-default cursor-pointer&quot;&gt; &lt;option value=&quot;0.5&quot;&gt;0.5x&lt;/option&gt; &lt;option value=&quot;1&quot; selected&gt;1x&lt;/option&gt; &lt;option value=&quot;2&quot;&gt;2x&lt;/option&gt; &lt;option value=&quot;4&quot;&gt;4x&lt;/option&gt; &lt;/select&gt; &lt;/div&gt; &lt;!-- Indexed strings tags (inline with playback) --&gt; &lt;div class=&quot;stv-strings flex flex-wrap items-center gap-1.5&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;!-- Add / Search --&gt; &lt;div class=&quot;flex flex-wrap items-center gap-2 mb-3&quot;&gt; &lt;div class=&quot;flex items-center gap-1.5 flex-grow min-w-[180px]&quot;&gt; &lt;input type=&quot;text&quot; class=&quot;stv-input flex-grow px-2.5 py-1.5 text-sm border border-border-default rounded
               bg-input text-default placeholder-muted
               focus:ring-2 focus:ring-primary focus:border-transparent outline-none&quot; placeholder=&quot;Type a string...&quot; maxlength=&quot;60&quot;&gt; &lt;button class=&quot;stv-add px-3 py-1.5 text-sm font-medium rounded
               bg-primary text-on-primary hover:opacity-90
               transition-colors disabled:opacity-40 disabled:cursor-not-allowed&quot;&gt;
Add
&lt;/button&gt; &lt;/div&gt; &lt;div class=&quot;flex items-center gap-1.5 flex-grow min-w-[180px]&quot;&gt; &lt;input type=&quot;text&quot; class=&quot;stv-search flex-grow px-2.5 py-1.5 text-sm border border-border-default rounded
               bg-input text-default placeholder-muted
               focus:ring-2 focus:ring-primary focus:border-transparent outline-none&quot; placeholder=&quot;Search...&quot; maxlength=&quot;60&quot;&gt; &lt;button class=&quot;stv-search-btn px-3 py-1.5 text-sm font-medium rounded
               bg-primary text-on-primary hover:opacity-90
               transition-colors disabled:opacity-40 disabled:cursor-not-allowed&quot;&gt;
Search
&lt;/button&gt; &lt;/div&gt; &lt;/div&gt; &lt;!-- SVG container + overlay prompt --&gt; &lt;div class=&quot;relative&quot; style=&quot;height: 550px&quot;&gt; &lt;div class=&quot;stv-canvas absolute inset-0 rounded-lg border border-border-default bg-card overflow-hidden&quot;&gt;&lt;/div&gt; &lt;!-- Empty state prompt (sits above the SVG) --&gt; &lt;div class=&quot;stv-prompt absolute inset-0 flex flex-col items-center justify-center text-muted pointer-events-none&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;32&quot; height=&quot;32&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; class=&quot;opacity-40 mb-3&quot;&gt; &lt;polygon points=&quot;5 3 19 12 5 21 5 3&quot;&gt;&lt;/polygon&gt; &lt;/svg&gt; &lt;p class=&quot;stv-prompt-text text-sm&quot;&gt;Press play to watch the tree being built&lt;/p&gt; &lt;/div&gt; &lt;/div&gt; &lt;!-- Step description --&gt; &lt;div class=&quot;stv-info mt-3 p-3 rounded-lg border border-border-default bg-card text-sm min-h-[3.5em]&quot;&gt; &lt;p class=&quot;stv-desc text-muted italic&quot;&gt;Add a string to begin building the suffix tree.&lt;/p&gt; &lt;div class=&quot;stv-progress mt-2 hidden&quot;&gt; &lt;div class=&quot;flex justify-between text-xs text-muted mb-1&quot;&gt; &lt;span class=&quot;stv-counter&quot;&gt;Step 0 / 0&lt;/span&gt; &lt;span class=&quot;stv-phase&quot;&gt;&lt;/span&gt; &lt;/div&gt; &lt;div class=&quot;w-full bg-border-default rounded-full h-1&quot;&gt; &lt;div class=&quot;stv-bar bg-primary rounded-full h-1 transition-all duration-200&quot; style=&quot;width: 0%&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;!-- Length warning --&gt; &lt;div class=&quot;stv-warning hidden mt-2 p-2 rounded text-xs text-primary border border-primary/30 bg-primary/10&quot;&gt;
Long strings produce large trees that may be hard to read. Use zoom (scroll) and pan (drag) to navigate.
&lt;/div&gt; &lt;/div&gt; 
&lt;h2 id=&quot;try-it-yourself&quot;&gt;Try it yourself&lt;/h2&gt;
&lt;p&gt;An empty tree, yours to experiment with. Add strings, watch the construction, search for patterns. Use the scroll wheel to zoom and click-drag to pan if the tree gets large.&lt;/p&gt;
&lt;div class=&quot;stv-root not-prose my-8&quot; data-stv-config=&quot;{&amp;#34;initialStrings&amp;#34;:[],&amp;#34;initialSearch&amp;#34;:&amp;#34;&amp;#34;,&amp;#34;height&amp;#34;:550,&amp;#34;showBuilt&amp;#34;:false}&quot;&gt; &lt;!-- Playback + indexed strings --&gt; &lt;div class=&quot;stv-controls flex flex-wrap items-center gap-2 mb-2 p-3 rounded-lg border border-border-default bg-card&quot;&gt; &lt;div class=&quot;flex items-center gap-0.5 text-default&quot;&gt; &lt;button class=&quot;stv-reset p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Reset&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;polygon points=&quot;19 20 9 12 19 4 19 20&quot;&gt;&lt;/polygon&gt; &lt;line x1=&quot;5&quot; y1=&quot;19&quot; x2=&quot;5&quot; y2=&quot;5&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/button&gt; &lt;button class=&quot;stv-prev p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Previous step&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;polygon points=&quot;11 19 2 12 11 5 11 19&quot;&gt;&lt;/polygon&gt; &lt;polygon points=&quot;22 19 13 12 22 5 22 19&quot;&gt;&lt;/polygon&gt; &lt;/svg&gt; &lt;/button&gt; &lt;button class=&quot;stv-play p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Play / Pause&quot;&gt; &lt;svg class=&quot;stv-play-icon&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt; &lt;polygon points=&quot;5 3 19 12 5 21 5 3&quot;&gt;&lt;/polygon&gt; &lt;/svg&gt; &lt;svg class=&quot;stv-pause-icon hidden&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt; &lt;rect x=&quot;6&quot; y=&quot;4&quot; width=&quot;4&quot; height=&quot;16&quot;&gt;&lt;/rect&gt; &lt;rect x=&quot;14&quot; y=&quot;4&quot; width=&quot;4&quot; height=&quot;16&quot;&gt;&lt;/rect&gt; &lt;/svg&gt; &lt;/button&gt; &lt;button class=&quot;stv-next p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Next step&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;polygon points=&quot;13 19 22 12 13 5 13 19&quot;&gt;&lt;/polygon&gt; &lt;polygon points=&quot;2 19 11 12 2 5 2 19&quot;&gt;&lt;/polygon&gt; &lt;/svg&gt; &lt;/button&gt; &lt;button class=&quot;stv-end p-1.5 rounded hover:bg-action-hover transition-colors&quot; title=&quot;Skip to end&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;polygon points=&quot;5 4 15 12 5 20 5 4&quot;&gt;&lt;/polygon&gt; &lt;line x1=&quot;19&quot; y1=&quot;5&quot; x2=&quot;19&quot; y2=&quot;19&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/button&gt; &lt;select class=&quot;stv-speed ml-1.5 px-1.5 py-1 text-xs border border-border-default rounded
                      bg-input text-default cursor-pointer&quot;&gt; &lt;option value=&quot;0.5&quot;&gt;0.5x&lt;/option&gt; &lt;option value=&quot;1&quot; selected&gt;1x&lt;/option&gt; &lt;option value=&quot;2&quot;&gt;2x&lt;/option&gt; &lt;option value=&quot;4&quot;&gt;4x&lt;/option&gt; &lt;/select&gt; &lt;/div&gt; &lt;!-- Indexed strings tags (inline with playback) --&gt; &lt;div class=&quot;stv-strings flex flex-wrap items-center gap-1.5&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;!-- Add / Search --&gt; &lt;div class=&quot;flex flex-wrap items-center gap-2 mb-3&quot;&gt; &lt;div class=&quot;flex items-center gap-1.5 flex-grow min-w-[180px]&quot;&gt; &lt;input type=&quot;text&quot; class=&quot;stv-input flex-grow px-2.5 py-1.5 text-sm border border-border-default rounded
               bg-input text-default placeholder-muted
               focus:ring-2 focus:ring-primary focus:border-transparent outline-none&quot; placeholder=&quot;Type a string...&quot; maxlength=&quot;60&quot;&gt; &lt;button class=&quot;stv-add px-3 py-1.5 text-sm font-medium rounded
               bg-primary text-on-primary hover:opacity-90
               transition-colors disabled:opacity-40 disabled:cursor-not-allowed&quot;&gt;
Add
&lt;/button&gt; &lt;/div&gt; &lt;div class=&quot;flex items-center gap-1.5 flex-grow min-w-[180px]&quot;&gt; &lt;input type=&quot;text&quot; class=&quot;stv-search flex-grow px-2.5 py-1.5 text-sm border border-border-default rounded
               bg-input text-default placeholder-muted
               focus:ring-2 focus:ring-primary focus:border-transparent outline-none&quot; placeholder=&quot;Search...&quot; maxlength=&quot;60&quot;&gt; &lt;button class=&quot;stv-search-btn px-3 py-1.5 text-sm font-medium rounded
               bg-primary text-on-primary hover:opacity-90
               transition-colors disabled:opacity-40 disabled:cursor-not-allowed&quot;&gt;
Search
&lt;/button&gt; &lt;/div&gt; &lt;/div&gt; &lt;!-- SVG container + overlay prompt --&gt; &lt;div class=&quot;relative&quot; style=&quot;height: 550px&quot;&gt; &lt;div class=&quot;stv-canvas absolute inset-0 rounded-lg border border-border-default bg-card overflow-hidden&quot;&gt;&lt;/div&gt; &lt;!-- Empty state prompt (sits above the SVG) --&gt; &lt;div class=&quot;stv-prompt absolute inset-0 flex flex-col items-center justify-center text-muted pointer-events-none&quot;&gt; &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;32&quot; height=&quot;32&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; class=&quot;opacity-40 mb-3&quot;&gt; &lt;polygon points=&quot;5 3 19 12 5 21 5 3&quot;&gt;&lt;/polygon&gt; &lt;/svg&gt; &lt;p class=&quot;stv-prompt-text text-sm&quot;&gt;Press play to watch the tree being built&lt;/p&gt; &lt;/div&gt; &lt;/div&gt; &lt;!-- Step description --&gt; &lt;div class=&quot;stv-info mt-3 p-3 rounded-lg border border-border-default bg-card text-sm min-h-[3.5em]&quot;&gt; &lt;p class=&quot;stv-desc text-muted italic&quot;&gt;Add a string to begin building the suffix tree.&lt;/p&gt; &lt;div class=&quot;stv-progress mt-2 hidden&quot;&gt; &lt;div class=&quot;flex justify-between text-xs text-muted mb-1&quot;&gt; &lt;span class=&quot;stv-counter&quot;&gt;Step 0 / 0&lt;/span&gt; &lt;span class=&quot;stv-phase&quot;&gt;&lt;/span&gt; &lt;/div&gt; &lt;div class=&quot;w-full bg-border-default rounded-full h-1&quot;&gt; &lt;div class=&quot;stv-bar bg-primary rounded-full h-1 transition-all duration-200&quot; style=&quot;width: 0%&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;!-- Length warning --&gt; &lt;div class=&quot;stv-warning hidden mt-2 p-2 rounded text-xs text-primary border border-primary/30 bg-primary/10&quot;&gt;
Long strings produce large trees that may be hard to read. Use zoom (scroll) and pan (drag) to navigate.
&lt;/div&gt; &lt;/div&gt; 
&lt;h2 id=&quot;beyond-suffix-trees&quot;&gt;Beyond suffix trees&lt;/h2&gt;
&lt;p&gt;What excites me most is how well this generalizes. The gap between an algorithm on paper and an algorithm in memory has always been one of the hardest parts of learning computer science. Textbooks give you static diagrams. Debuggers give you one node at a time. Neither shows you the whole picture in motion.&lt;/p&gt;
&lt;p&gt;Browser-based rendering, interactive SVGs, and JavaScript engines fast enough to run non-trivial algorithms client-side make it possible to close that gap for almost any data structure. Red-black trees, B-trees, tries, skip lists, hash tables with open addressing: all of them would benefit from this kind of treatment. Not as a replacement for the theory, but as a companion to it. Read the algorithm, then &lt;em&gt;watch&lt;/em&gt; it work.&lt;/p&gt;
&lt;p&gt;There is an obvious question lurking here: why bother learning algorithms at all when you can ask an LLM to write one for you? I think the question misses the more interesting possibility. LLMs are not just code generators; they are learning accelerators. You can ask one to explain a single step of an algorithm, to walk through an edge case, or to generate a diagram of how components interact. When I started working in a new codebase recently, the fastest way for me to build a mental model was not reading code or documentation. It was asking an LLM to produce component and sequence diagrams: a much higher-bandwidth channel for understanding, at least for the way I think.&lt;/p&gt;
&lt;p&gt;That is the real shift. Not that machines can write algorithms so we don’t have to learn them, but that they can teach us in ways that adapt to how each of us actually learns. Through visualizations, through diagrams, through conversation, through whatever representation makes the concept click. This post is one example. The next one might look completely different, tailored to a different person and a different way of thinking.&lt;/p&gt;
&lt;p&gt;We write fewer algorithms from scratch in our day-to-day work than we used to. But we still benefit from understanding them, whether it’s to choose the right data structure, to debug performance issues, or to evaluate tradeoffs. And for those of us who enjoy algorithms for their own sake, the tools for learning them have never been better.&lt;/p&gt;
&lt;p&gt;The original Java suffix tree implementation is &lt;a href=&quot;https://github.com/abahgat/suffixtree&quot;&gt;open source on GitHub&lt;/a&gt;. For the full backstory, see the &lt;a href=&quot;https://www.abahgat.com/project/suffix-tree/&quot;&gt;project page&lt;/a&gt; and the &lt;a href=&quot;https://www.abahgat.com/blog/the-programming-puzzle-that-got-me-my-job/&quot;&gt;story of the programming puzzle&lt;/a&gt; that started it all. Ukkonen’s &lt;a href=&quot;https://www.cs.helsinki.fi/u/ukkonen/SuffixT1withFigs.pdf&quot;&gt;original paper&lt;/a&gt; remains the definitive reference for the algorithm.&lt;/p&gt;</content:encoded></item><item><title>The Velocity Paradox</title><link>https://www.abahgat.com/blog/the-velocity-paradox</link><guid isPermaLink="true">https://www.abahgat.com/blog/the-velocity-paradox</guid><description>AI agents can generate code 100x faster, but for companies stuck in the &quot;Unhappy Middle&quot; — with legacy debt, bespoke frameworks, and zero slack — the bottleneck has shifted from writing code to verifying it. Here&apos;s how engineering leaders can cross the chasm by becoming gardeners, not janitors.</description><pubDate>Mon, 23 Feb 2026 20:30:00 GMT</pubDate><content:encoded>&lt;p&gt;We’ve all been there. You sit down with an AI agent on a Saturday morning to hack on a side project and it feels like magic. Ten minutes in, you are blown away by how quickly the agent can turn even poorly organized thoughts into working prototypes. You feel like you could do this all day.&lt;/p&gt;
&lt;p&gt;And clearly, many of us do: we’re rediscovering our passion for side projects, and every day a thousand bespoke ToDo apps are born, perfectly tailored to the unique needs of their creators.&lt;/p&gt;
&lt;p&gt;At the same time, if you’re in an engineering leadership role, you’re also seeing your stakeholders dabble with agentic coding. They are shipping side-hustles on the weekend, and respectable work applications in an afternoon. Some of them might even look at you with ill-concealed suspicion. They want to know why their “pet feature” is stuck in a two-week cycle when they just whipped up a functional prototype over coffee.&lt;/p&gt;
&lt;p&gt;And they aren’t entirely wrong. AI agents have been writing 100% of my code for several months now. Informed by the wins on my side-projects, I wanted to see how much faster we could build at work. During the holiday break, I spent a few hours having Claude write a non-trivial feature that touched our database, cloud infra, mobile app, and the embedded application that runs on our hardware devices at &lt;a href=&quot;https://www.quilt.com&quot;&gt;Quilt&lt;/a&gt;. What would have taken me a week to write took an afternoon to generate.&lt;/p&gt;
&lt;p&gt;Yet it still took weeks to get it tested and merged.&lt;/p&gt;
&lt;p&gt;It felt like strapping a rocket engine to a tricycle. Exhilarating, sure, but the road ahead is still full of potholes, and there’s a canyon where the bridge used to be. So why isn’t the 100x improvement in how fast AI can &lt;em&gt;generate&lt;/em&gt; code moving the needle on how fast we can &lt;em&gt;ship&lt;/em&gt; features and improvements?&lt;/p&gt;
&lt;p&gt;Coding was never 100% of the job. But for those of us managing legacy debt, AI doesn’t just fail to solve our problems; it collides with them.&lt;/p&gt;
&lt;p&gt;I’ve been at several conferences recently where I met leaders from “AI-native” companies, organizations founded in an age where agentic coding is the baseline. One founder told me they don’t do code reviews at all; their CI pipeline is the reviewer. Another gives agents full control of their production infrastructure. For those of us anchored to a culture that is older than even just two years, these practices feel reckless. Yet even more measured companies are rethinking the fundamentals. OpenAI recently pulled back the curtain with their &lt;a href=&quot;https://openai.com/index/harness-engineering/&quot;&gt;Harness Engineering&lt;/a&gt; article, showing engineering re-architected around AI from the ground up.&lt;/p&gt;
&lt;p&gt;For the rest of us, the gap between “generating code” and “shipping value” is becoming a chasm. We are stuck in the Unhappy Middle, where the cost of code is diminishing rapidly, but the cost of review and verification is skyrocketing.&lt;/p&gt;
&lt;h1 id=&quot;the-unhappy-middle&quot;&gt;The Unhappy Middle&lt;/h1&gt;
&lt;p&gt;To understand why the promise of 100x faster progress thanks to AI still feels like an illusion, we have to look at the two forces we’re being squeezed by.&lt;/p&gt;
&lt;p&gt;On one side, we have the AI-Natives. These are companies and teams founded in the AI era. They have zero legacy debt, they can approach the craft of engineering with an open mind, and they use the same exact “boring” tech stacks the models were trained on. They don’t have to go out of their way to “integrate” AI; they are born out of it. They don’t have to refactor their code to support automated verification, they never knew a world without it.&lt;/p&gt;
&lt;p&gt;On the other side, you have the companies with the slack to reinvent themselves. Shopify’s CEO made headlines when he declared that &lt;a href=&quot;https://x.com/tobi/status/1909251946235437514&quot;&gt;AI proficiency is now a baseline expectation&lt;/a&gt; and that teams must justify why a job can’t be done by AI before requesting headcount. Companies like that (or Google, I bet) can dedicate teams to rearchitect their codebase, tooling and processes and build the scaffolding that is required to make AI work at scale.&lt;/p&gt;
&lt;p&gt;Then, there’s the rest of us. I call it the Unhappy Middle.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We support live products and services, with customers trusting us and depending on us daily. The cost of failure is higher than a toy prototype. Unlike your ToDo app, you can’t just throw an agent at a problem and hope it doesn’t break your production environment.&lt;/li&gt;
&lt;li&gt;We have accumulated technical debt as we were racing towards product/market fit, and yet never had the resources to pay it back. We have to balance work on infrastructure and developer experience with business priorities like opening new product lines. Most of these target ambitious schedules which (you guessed right) require taking on additional technical debt.&lt;/li&gt;
&lt;li&gt;With the age of Zero Interest-Rate Policies well behind us, but not quite with the coffers of a larger company, we always have to be mindful of our runway, are constantly short-staffed and always “do more with less”.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short, we have to balance the technical complexity of an established company with the reality of a startup. Our survival depends on crossing the chasm as quickly as possible. Not every team is here. If your stack is standard and your tests are green, you may already be seeing the gains. But if any of this sounds familiar, the path forward is harder. Here are some examples from my reality.&lt;/p&gt;
&lt;h2 id=&quot;bespoke-frameworks-from-asset-to-dead-weight&quot;&gt;Bespoke Frameworks: from Asset to Dead Weight&lt;/h2&gt;
&lt;p&gt;Before AI, we may have optimized for human speed by building bespoke frameworks, custom boilerplate generators or domain-specific languages and abstractions. For many teams, these were their “secret sauce”: internal abstractions that helped teams move fast in 2022. They came at a price (typically, new engineers have to take some time getting comfortable with them), but they often paid off.&lt;/p&gt;
&lt;p&gt;Today, those clever optimizations are anchors holding us back. AI agents are brilliant at standard React and Python because they’ve seen it a billion times. And, at the same time, they are completely illiterate in our proprietary and opinionated internals. Every time I ask an agent to work in our bespoke code, I’m paying an invisible tax: I spend a third of my time fixing hallucinations because our “clever” code isn’t in anyone’s training set. (I wrote more about why this happens in &lt;a href=&quot;https://www.abahgat.com/blog/the-ghost-in-the-training-set&quot;&gt;The Ghost in the Training Set&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;And you know what’s funny? That’s often why some of the best engineers I know are unimpressed by AI agents: they focus on the last time they saw Claude trip on a gotcha that’s specific to their codebase and ignore the fact that it can build flawless React in the blink of an eye.&lt;/p&gt;
&lt;h2 id=&quot;zero-slack&quot;&gt;Zero Slack&lt;/h2&gt;
&lt;p&gt;We know technical debt is there, we always wanted to increase test coverage, we defer refactoring for testability because we need to fit one more feature before the release cut. We know that frameworks need to be standardized to become “AI-hospitable.” But in the Unhappy Middle, you have zero slack. You’re always racing, either to hit product-market fit or to extend your runway, and “cleaning up” feels like a luxury you can’t afford.&lt;/p&gt;
&lt;p&gt;This creates a painful tradeoff. In a side project, or a non-critical business app, failure is cheap. For a company with a legacy codebase, complex release processes and addressing user-critical needs, the stakes are considerably higher. Without the slack to build automated guardrails, we’re left with manual human review and auditing.&lt;/p&gt;
&lt;p&gt;And that’s where the 100x speed gain from AI goes to die.&lt;/p&gt;
&lt;h1 id=&quot;when-generation-outruns-verification&quot;&gt;When Generation Outruns Verification&lt;/h1&gt;
&lt;p&gt;We often think of the craft of software engineering as composed of several loops, each covering a different stage of the lifecycle, from idea to product. A good visual to illustrate this is the slide below, from a &lt;a href=&quot;https://youtu.be/qi89lhRI8zc?t=207&quot;&gt;talk Addy Osmani gave at LeadDev New York 2025&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&quot;my-6&quot;&gt; &lt;img src=&quot;https://www.abahgat.com/_astro/devloop.CPhBLpoT.png&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot; width=&quot;1476&quot; height=&quot;820&quot; srcset=&quot;https://www.abahgat.com/_astro/devloop.CPhBLpoT_Z1g0ytw.webp 400w, /_astro/devloop.CPhBLpoT_NyH4h.webp 768w, /_astro/devloop.CPhBLpoT_2ggJxq.webp 1024w, /_astro/devloop.CPhBLpoT_2gfRf3.webp 1476w, /_astro/devloop.CPhBLpoT_1LXRS.webp 2040w, /_astro/devloop.CPhBLpoT_Z1tBj9K.webp 2952w&quot; sizes=&quot;(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px&quot; style=&quot;object-fit: cover; object-position: center; max-width: 1476px; max-height: 820px; aspect-ratio: 1.8; width: 100%;&quot; alt=&quot;The development loop&quot; class=&quot;mx-auto rounded-md shadow-lg bg-border-default dark:bg-card w-full&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt; &lt;figcaption class=&quot;mt-2 text-center text-sm text-subtle dark:text-muted&quot;&gt; From Addy Osmani&apos;s talk at LeadDev New York 2025 &lt;/figcaption&gt; &lt;/figure&gt;
&lt;p&gt;At the center is the &lt;em&gt;Inner Loop&lt;/em&gt;: the tight cycle of thinking, coding, building and testing. This is where “flow” happens. Surrounding that is the &lt;em&gt;Submit Loop&lt;/em&gt;, where your code goes through linting and code review, and the &lt;em&gt;Outer Loop&lt;/em&gt;, where it finally gets deployed and gets tested in the real world.&lt;/p&gt;
&lt;p&gt;The promise of AI-assisted engineering is to effectively collapse the Inner Loop. When an agent can “Think” and “Code” a cross-stack feature in a single morning, that center circle feels like it’s spinning at the speed of light.&lt;/p&gt;
&lt;p&gt;But for those of us who are still in the Unhappy Middle, that loop is often broken before it even starts.&lt;/p&gt;
&lt;h2 id=&quot;the-broken-inner-loop&quot;&gt;The Broken Inner Loop&lt;/h2&gt;
&lt;aside class=&quot;not-prose my-8 py-6 px-8 rounded-r-lg border-l-4 border-link bg-link/[0.07]&quot; role=&quot;note&quot; data-astro-cid-rssm4oil&gt; &lt;p class=&quot;text-xl md:text-2xl leading-relaxed text-heading italic pullquote-serif&quot; data-astro-cid-rssm4oil&gt; You were promised AI agents working for you. Instead, you are working for your agents. &lt;/p&gt; &lt;/aside&gt;
&lt;p&gt;The first problem teams are likely to encounter is a broken Inner Loop. Before AI, back in the day when code was expensive to write, tests were the first aspect of a healthy architecture to be sacrificed (or, in the best case scenario, deferred). When we skip writing tests, it’s common to end up with code for which it’s hard to write tests in the long run.&lt;/p&gt;
&lt;p&gt;When you can’t give an agent a deterministic way to verify its own work, the feedback cycle doesn’t feed back into the AI, it feeds back into &lt;strong&gt;you&lt;/strong&gt;. The agent isn’t looping, it’s just throwing code over the wall and waiting for you to tell it what happened.&lt;/p&gt;
&lt;p&gt;In the best scenario you can imagine, the loop is closed by automation. The agent writes code, runs a test, sees the failure and iterates until it’s green. The feedback is a tight, self-correcting circuit.&lt;/p&gt;
&lt;p&gt;Without a way to automate verification, you’re just making a mountain of work for yourself, or accepting to take an enormous amount of risk by shipping code that hasn’t been properly tested.&lt;/p&gt;
&lt;p&gt;You were promised AI agents working for you to help you be more effective; instead, you are working for your agents. Not only is it not fun, it’s also a huge waste of your time because you are 100x slower than a software agent.&lt;/p&gt;
&lt;p&gt;In my world, this isn’t just a metaphor. I feel it physically. At Quilt, we make hardware devices, and you can’t throw prompt engineering at the physical world. If a test requires me to get up, walk to a test bench and manually press a button, the inner loop isn’t just broken; it’s wide open.&lt;/p&gt;
&lt;p&gt;And there are even worse consequences downstream.&lt;/p&gt;
&lt;h2 id=&quot;the-slowing-submit-loop&quot;&gt;The Slowing Submit Loop&lt;/h2&gt;
&lt;p&gt;Before AI agents were this capable, the high cost of &lt;em&gt;writing&lt;/em&gt; code carried a hidden benefit. If an engineer spent two days wrestling with a complex feature, they effectively distilled a lot of context information into their brain. By the time they put a change up for review, the author was the deepest expert on those 200 lines of code.&lt;/p&gt;
&lt;p&gt;That’s not how it works today.&lt;/p&gt;
&lt;p&gt;As wonderful as the democratizing effect of AI agents is (they enable engineers to contribute well beyond their historical area of expertise), it comes with downsides.&lt;/p&gt;
&lt;p&gt;If an agent can’t automatically verify its changes, and the author is not the most experienced engineer in the area affected by a change, the bulk of the burden of audit and review will shift to the reviewer.&lt;/p&gt;
&lt;p&gt;On the average team, code reviews are assigned to the most experienced engineers in a given area or domain. In this new world, these folks are getting overloaded with more code to review. Worse, they can no longer assume that the author has the same depth of knowledge about the code that reviewers historically could take for granted.&lt;/p&gt;
&lt;p&gt;At the extreme, this has multiple effects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Because the agent did the heavy lifting, the human author may have a shallower understanding of the “why” behind specific implementation choices.&lt;/li&gt;
&lt;li&gt;The reviewer is now receiving 10x more code, but with 10x less intent provided by the author. If the reviewer didn’t (or couldn’t) do a thorough review themselves, it’s 10x more code reviews of a higher intensity. Think more of a forensic audit than a style check.&lt;/li&gt;
&lt;li&gt;In a legacy codebase with bespoke frameworks, this can be extremely challenging. If neither the author nor the reviewer fully understands the “clever” choices the AI made, they can’t distinguish between valuable additions and hallucinations, and therefore are taking a high risk shipping this to production.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The practical consequences are tangible. Code ends up spending more time waiting for review than in development (this is what happened to my proof of concept I mentioned earlier). Your most experienced engineers struggle to be productive themselves because they are drowning in code reviews.&lt;/p&gt;
&lt;p&gt;But the most worrisome part is what this does at an emotional level.&lt;/p&gt;
&lt;h1 id=&quot;from-craftspeople-to-janitors&quot;&gt;From Craftspeople to Janitors&lt;/h1&gt;
&lt;p&gt;If we take the patterns above to the extreme and let them fester without fixing them, then we are taking on a huge organizational risk by turning our most senior engineers into Janitors.&lt;/p&gt;
&lt;p&gt;Instead of going to a challenging workday where, at the end, we experience the joy of having created something new, we now have to pore over someone (or, rather, something) else’s code to spot issues and problems. Some engineers feel like they are being paid to clean up AI hallucinations.&lt;/p&gt;
&lt;p&gt;This can be deeply demotivating. No one likes being a linear bottleneck downstream of a stage that is accelerating at exponential speed. This is even more difficult at the speed this shift is happening, as many people are mourning the loss of the craft, made worse by simplistic takes about how the world of tomorrow needs fewer engineers.&lt;/p&gt;
&lt;p&gt;I still deeply enjoy coding but I recognize that, even in the best of days, a lot of the code I wrote was boilerplate needed to wire together different application components. A very common micro-kitchen joke from my time at Google was that we were all just highly-compensated Protocol Buffer translators.&lt;/p&gt;
&lt;p&gt;We miss the 20% of the code we used to write that was high-leverage and intellectually interesting, and forget the other 80% that was toilsome and repetitive.&lt;/p&gt;
&lt;h1 id=&quot;from-janitors-to-gardeners&quot;&gt;From Janitors to Gardeners&lt;/h1&gt;
&lt;p&gt;If you treat every AI-generated PR like a chore to be cleaned up, you are a Janitor. To move fast in a legacy codebase, we need a considerable change in mindset. If you allow me another metaphor, we need to start treating our codebase less like a perfect jewel to polish and more like a plot of land to tend to.&lt;/p&gt;
&lt;p&gt;I’ve been thinking about this metaphor for a while. As you scale an organization, you can’t afford to micromanage; you provide structure and support so that decisions happen organically, aligned to what the business needs. The same applies to codebases.&lt;/p&gt;
&lt;p&gt;Playing into the metaphor, a gardener may focus their attention on a few things:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tending the Soil&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hospitable Ground&lt;/strong&gt; — Transforming AI-Hostile codebases into an AI-Hospitable playing field requires investing in reducing technical debt, so that AI can’t hide behind it. It may mean moving away from bespoke patterns that routinely trip up agents, or making them work reliably. It means standardizing on a well-defined and documented set of abstractions, instead of having 3 different ways to set up an API server because we never finish migrations every time we deprecate an old pattern.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nutrient-Rich Soil&lt;/strong&gt; — Agents are great at brute-forcing their way to a workable solution, but very often they struggle because the codebase lacks information beyond the code itself. Code written in haste often lacks documentation about “Intent” and the “Why” we made decisions. If we don’t expose context about tradeoffs and historical decisions, our agents are operating with limited information. Well structured &lt;code&gt;agents.md&lt;/code&gt; files are a good start. Checking in architectural guidelines and making them discoverable is increasingly paying off. Ironically, if you keep your design docs locked in Google Docs, your agent is blind to them (hey Google, when can we have MCP access to Google Docs?)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Scaffolding and Direction&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scaffolding&lt;/strong&gt; — You don’t tell plants how to grow and expect them to listen; you provide scaffolding and support. In software, this can be types, interfaces and architectural boundaries. Well crafted designs that reduce coupling and abstract complexity behind well-defined interfaces are how you give agents a way to grow that is aligned to what you need.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resilience&lt;/strong&gt; — Automated tests, lint checks and verifications are much more helpful for AI agents than they are to humans, as they enable both faster iteration speed and more confidence in the review stage of the submit loop. In the gardening metaphor, this is akin to the sturdy fencing that protects your plants from critters.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I find it ironic that many of the principles above are ones that practitioners have been advocating for under the banner of clean code, test-driven development and many others. We might callously shrug at the idea that we struggled to adopt them for the sake of our human co-workers and are now prioritizing them for the sake of our AI-agents. But the truth is that in the last decade, writing effective tests and good documentation cost us time: the time to think about them, and the time to type them. With AI agents being this capable, the typing cost is approaching zero. What remains is the thinking, and that was always the valuable part.&lt;/p&gt;
&lt;h1 id=&quot;building-the-dark-factory&quot;&gt;Building the Dark Factory&lt;/h1&gt;
&lt;aside class=&quot;not-prose my-8 py-6 px-8 rounded-r-lg border-l-4 border-link bg-link/[0.07]&quot; role=&quot;note&quot; data-astro-cid-rssm4oil&gt; &lt;p class=&quot;text-xl md:text-2xl leading-relaxed text-heading italic pullquote-serif&quot; data-astro-cid-rssm4oil&gt; Our job is no longer to write the code. It’s to build the factory that builds the code. &lt;/p&gt; &lt;/aside&gt;
&lt;p&gt;By now, it should be obvious that if we use AI only to automate the “Coding” stage of the development loop, we may not only struggle to make our team more effective, we may even hurt their effectiveness.&lt;/p&gt;
&lt;p&gt;In the same talk by Addy Osmani I referenced earlier, he goes on to show several areas where AI can be effectively adopted to improve developer experience. In my day-to-day work, I’ve had considerable success using AI agents to troubleshoot bug reports and infrastructure alerts from our production fleet. The gains are real.&lt;/p&gt;
&lt;figure class=&quot;my-6&quot;&gt; &lt;img src=&quot;https://www.abahgat.com/_astro/devloop-annotated.DskcXKBh.png&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot; width=&quot;1570&quot; height=&quot;873&quot; srcset=&quot;https://www.abahgat.com/_astro/devloop-annotated.DskcXKBh_ZlcDUy.webp 400w, /_astro/devloop-annotated.DskcXKBh_20D2bA.webp 768w, /_astro/devloop-annotated.DskcXKBh_Z28MBIu.webp 1024w, /_astro/devloop-annotated.DskcXKBh_Z2wVHMf.webp 1570w, /_astro/devloop-annotated.DskcXKBh_Z2aAzMm.webp 2040w, /_astro/devloop-annotated.DskcXKBh_UUgiS.webp 3140w&quot; sizes=&quot;(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px&quot; style=&quot;object-fit: cover; object-position: center; max-width: 1570px; max-height: 873px; aspect-ratio: 1.7983963344788088; width: 100%;&quot; alt=&quot;The development loop, annotated&quot; class=&quot;mx-auto rounded-md shadow-lg bg-border-default dark:bg-card w-full&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt; &lt;figcaption class=&quot;mt-2 text-center text-sm text-subtle dark:text-muted&quot;&gt; From Addy Osmani&apos;s talk at LeadDev New York 2025 &lt;/figcaption&gt; &lt;/figure&gt;
&lt;p&gt;There is a growing conversation in engineering circles about “&lt;a href=&quot;https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory/&quot;&gt;Dark Factories&lt;/a&gt;”: fully automated systems that run without human intervention. In the age of AI, our job is no longer to write the code; it’s to &lt;strong&gt;build the factory that builds the code.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Some high-leverage areas to start:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Verification Machine&lt;/strong&gt; — Good test infrastructure should be the top priority. Well-written tests enable AI-agents to have much faster inner loops, but they also greatly help with faster code reviews. With good test scaffolding, you don’t just ask “Will this code work in this scenario?” You can ask an agent to demonstrate the expected behavior via a unit test.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Address common tripping hazards for agents&lt;/strong&gt; — You likely have a few areas where agents routinely struggle. Don’t just scoff when that happens, and use it to say “AI isn’t quite there yet”. Ask yourself &lt;em&gt;why&lt;/em&gt; agents are struggling. Is it because of inconsistent patterns? Lack of context or documentation? Because your bespoke framework requires 1 year of experience &lt;em&gt;in your own codebase&lt;/em&gt; to master? Making sure agents don’t make the same mistake twice should be part of our responsibilities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reducing human dependencies for mechanical tasks&lt;/strong&gt; — Invest in building reliable automated end to end tests that rely on production-like observability to spot issues and regressions. Wherever manual testing is required, ask yourself “what would it take for this test to happen automatically?” In a hardware company like Quilt, this means augmenting our ability to perform more tests in software.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Lights-Out Goal&lt;/strong&gt; — Aim to have a “Submit Loop” so robust that if tests pass and the architectural boundaries are respected, the code is “shippable” by default. Even if that goal feels unrealistic (e.g. for code that is security-critical or that runs on devices that are hard to recover), ask yourself “What would it take for me to be 100% confident in a change without needing to review it?”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A word of warning: don’t confuse building the factory with building more features. If you ship 10x more features without correspondingly improving your infrastructure, you’re taking on a compounding liability. If AI agents today are enabling you to move even just a bit faster than yesterday, aim to put some of those velocity gains towards your scaffolding, instead of putting everything on more features.&lt;/p&gt;
&lt;h1 id=&quot;crossing-the-chasm&quot;&gt;Crossing the Chasm&lt;/h1&gt;
&lt;figure class=&quot;my-6&quot;&gt; &lt;img src=&quot;https://www.abahgat.com/_astro/chasm.r78RJefF.png&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot; width=&quot;3168&quot; height=&quot;1344&quot; srcset=&quot;https://www.abahgat.com/_astro/chasm.r78RJefF_Znct6D.webp 400w, /_astro/chasm.r78RJefF_Z1sM3lB.webp 768w, /_astro/chasm.r78RJefF_2f4awa.webp 1024w, /_astro/chasm.r78RJefF_Z1uMLRh.webp 2040w, /_astro/chasm.r78RJefF_ZTp7TR.webp 3168w, /_astro/chasm.r78RJefF_ZXFBLp.webp 6336w&quot; sizes=&quot;(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px&quot; style=&quot;object-fit: cover; object-position: center; max-width: 3168px; max-height: 1344px; aspect-ratio: 2.357142857142857; width: 100%;&quot; alt=&quot;A rocket-powered bicycle approaching a broken bridge over a canyon&quot; class=&quot;mx-auto rounded-md shadow-lg bg-border-default dark:bg-card w-full&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;  &lt;/figure&gt;
&lt;aside class=&quot;not-prose my-8 py-6 px-8 rounded-r-lg border-l-4 border-link bg-link/[0.07]&quot; role=&quot;note&quot; data-astro-cid-rssm4oil&gt; &lt;p class=&quot;text-xl md:text-2xl leading-relaxed text-heading italic pullquote-serif&quot; data-astro-cid-rssm4oil&gt; If the smartest AI in the world can’t understand your code, it might not be the AI’s fault. &lt;/p&gt; &lt;/aside&gt;
&lt;p&gt;The Unhappy Middle is a trap, but it’s also an opportunity to rethink what engineering leadership looks like.&lt;/p&gt;
&lt;p&gt;This requires a fundamental shift in our ego as developers. Instead of ‘pwning’ the agent every time it trips on our proprietary abstractions, we need to ‘own’ our codebase and make it more AI-hospitable. If the smartest AI in the world can’t understand your code, it might not be the AI’s fault, but it might be a sign that our “cleverness” has become our biggest liability.&lt;/p&gt;
&lt;p&gt;If we don’t cross the chasm quickly and change our mindset about how we write software, we risk being buried under our own AI-generated slop. The first step is to stop prioritizing just features as our primary output and start prioritizing the speed and accuracy of the factory.&lt;/p&gt;
&lt;p&gt;It is notoriously hard to get organizational buy-in to address technical debt. The key is to reframe: this isn’t about “cleaning up” to pay off debt, it’s about investing in tooling to accomplish 10x velocity.&lt;/p&gt;
&lt;p&gt;And even then, there are harder questions ahead. If you actually succeed in building the “factory,” you’ll quickly find that the technical bottleneck has evaporated, only to leave you with an organizational one. A 10x software factory is effectively useless if it’s embedded in a 1x decision-making process. And it is possible that we are approaching a &lt;a href=&quot;https://en.wikipedia.org/wiki/Great_Filter&quot;&gt;Great Filter&lt;/a&gt;-like event for companies in the business of software — one that separates those who adapt from those who drown. But those are topics for another day.&lt;/p&gt;
&lt;p&gt;For now, the goal is clear: stop just auditing lines of code and start building the systems that define the future of our industry.&lt;/p&gt;
&lt;p&gt;Let us begin.&lt;/p&gt;
&lt;div class=&quot;not-prose flex gap-3 p-4 rounded-lg border bg-surface border-border-default text-default  alert alert-note&quot;&gt; &lt;svg width=&quot;1em&quot; height=&quot;1em&quot; class=&quot;w-5 h-5 flex-shrink-0 mt-2&quot; data-icon=&quot;tabler:info-circle&quot;&gt;   &lt;symbol id=&quot;ai:tabler:info-circle&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;g fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path d=&quot;M3 12a9 9 0 1 0 18 0a9 9 0 0 0-18 0m9-3h.01&quot;/&gt;&lt;path d=&quot;M11 12h1v4h1&quot;/&gt;&lt;/g&gt;&lt;/symbol&gt;&lt;use href=&quot;#ai:tabler:info-circle&quot;&gt;&lt;/use&gt;  &lt;/svg&gt; &lt;div&gt; &lt;p class=&quot;font-bold mb-1&quot;&gt;Update — March 2026&lt;/p&gt; &lt;div class=&quot;text-sm prose-p:my-0 prose-p:first:mt-0 prose-p:last:mb-0 [&amp;&gt;p]:my-0&quot;&gt; &lt;p&gt;I explored the “1x decision-making process” problem further in &lt;a href=&quot;https://www.abahgat.com/blog/permission-structure&quot;&gt;Permission Structure&lt;/a&gt;.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt;</content:encoded></item><item><title>The Ghost in the Training Set</title><link>https://www.abahgat.com/blog/the-ghost-in-the-training-set</link><guid isPermaLink="true">https://www.abahgat.com/blog/the-ghost-in-the-training-set</guid><description>LLMs have statistical momentum: even when they know a new standard like Streamable HTTP exists, they often revert to the legacy patterns they saw most in training. Here is how to use &quot;strong anchors&quot; and &quot;zero-prompt pruning&quot; to keep your agentic systems from being haunted by 2024.</description><pubDate>Sat, 14 Feb 2026 08:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Over the last several weeks, I’ve had to spend time setting up Model Context Protocol (MCP) servers. As the ecosystem matures, it is already navigating its first major paradigm shifts. Specifically, in early 2025, the recommended transport for MCP over HTTP shifted from Server-Sent Events (SSE) to &lt;a href=&quot;https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http&quot;&gt;&lt;strong&gt;Streamable HTTP&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To my surprise, the agents I use most (Gemini and Claude) kept reverting to SSE. They were well “aware”, at least as much as a machine could be, that Streamable HTTP was the new standard (they could competently answer questions about it) but they were haunted by the &lt;strong&gt;statistical momentum&lt;/strong&gt; of their own training data. When it came time to actually generate code, they defaulted to the pattern they had seen thousands of times before.&lt;/p&gt;
&lt;h2 id=&quot;the-invisible-weight-of-training-bias&quot;&gt;The Invisible Weight of Training Bias&lt;/h2&gt;
&lt;p&gt;Taking a step back, this makes perfect sense: LLMs don’t just “read” instructions in a traditional sense: they weigh them against their internal probability map. If most of the MCP implementations they had seen were built over SSE, that gives them a huge bias in that direction.&lt;/p&gt;
&lt;p&gt;Once I started noticing this pattern, I had found it more and more often: LLMs seem to struggle more with bleeding edge patterns and technologies (again, their training dataset has more examples built on deprecated patterns than newer standards).&lt;/p&gt;
&lt;p&gt;This is a sneaky pattern, because we don’t naturally think about how old (or new) a model’s training set is, so we can’t realize this is happening unless we pay attention. If you’re working on a bleeding edge domain and you’re not careful, you may find yourself with an agent offering you a beautiful implementation that is actually a frozen snapshot of &lt;em&gt;last year&lt;/em&gt;’s best practices.&lt;/p&gt;
&lt;p&gt;The challenge grows with the uniqueness of your environment. This problem is even worse with codebases that adopt bespoke frameworks and patterns for which there is no published precedent. Agents thrive on &lt;em&gt;Common Knowledge&lt;/em&gt;, and they struggle with &lt;em&gt;Private Context&lt;/em&gt;. When we use bespoke patterns, we are essentially moving the agent into a zero-shot environment without even realizing it. The result is a performance degradation that looks like a “dumb” model but it is actually a lack of statistical grounding.&lt;/p&gt;
&lt;h2 id=&quot;from-prompting-to-infrastructure&quot;&gt;From Prompting to Infrastructure&lt;/h2&gt;
&lt;p&gt;You may be tempted to try to overcome this through prompting, and try to give strong instructions to anchor your agent towards the new standard by including strong language in your prompt (&lt;code&gt;ALWAYS use Streamable HTTP when implementing MCP services&lt;/code&gt;). You need strong anchors to overcome strong biases. But prompts are often lossy, inconsistent and error-prone.&lt;/p&gt;
&lt;p&gt;A more sustainable strategy is to start including these guardrails into your &lt;code&gt;agents.md&lt;/code&gt;&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; files, or even better in tooling infrastructure. For example, Claude includes an &lt;code&gt;/mcp-builder&lt;/code&gt; skill, which serves as a specialized instruction package anchored on the most recent standards, ensuring you land with a well-functioning implementation that overcomes the inherent bias in the models. In contrast, if you tried building an MCP server with Gemini now you may find yourself surprised by a perfectly functional implementation built on the deprecated 2024 pattern.&lt;/p&gt;
&lt;h2 id=&quot;the-trap-of-contextual-debt&quot;&gt;The Trap of “Contextual Debt”&lt;/h2&gt;
&lt;p&gt;Just like code accumulates technical debt, continuously adding to &lt;code&gt;agents.md&lt;/code&gt; without ever cleaning up leads to “contextual debt”. Over time, these files become bloated with a mountain of “Don’t do X” or “Remember Y.” Even worse, because you can have &lt;code&gt;agents.md&lt;/code&gt; files scattered through your repo, and other &lt;code&gt;.md&lt;/code&gt; files as documentation, you can find yourself with clashing instructions that throw agents for a loop in ways that are surprisingly difficult to detect and remedy.&lt;/p&gt;
&lt;p&gt;We are reaching a point where our “Instruction Budget” is as important as our compute budget. If you have clashing instructions across multiple &lt;code&gt;.md&lt;/code&gt; files, you’re not just wasting tokens, you’re creating “hallucination traps” that are far more expensive to debug than a standard syntax error.&lt;/p&gt;
&lt;p&gt;Here are a few things that worked well for me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Progressive Disclosure&lt;/strong&gt;: Borrowing from the &lt;a href=&quot;https://resources.anthropic.com/hubfs/The-Complete-Guide-to-Building-Skill-for-Claude.pdf&quot;&gt;Claude skills playbook&lt;/a&gt;, instead of having a giant instruction file, use a modular approach (e.g., a &lt;code&gt;docs/MCP_STANDARDS.md&lt;/code&gt; file linked from your root &lt;code&gt;agents.md&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The “Zero-Prompt Test” Stress Test&lt;/strong&gt;: Periodically run an agent on your project with a blank instruction file (especially after significant model updates). If performance remains stable, the underlying training set has likely caught up to the new standard. At that point, your manual instructions are no longer necessary; they are cruft. Delete them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ownership of Configs&lt;/strong&gt;: Treat agent configurations with as much rigor as a CI/CD pipeline. Obsolete agent instructions have even more impact on your velocity than obsolete documentation, and ironically, up-to-date documentation is now more precious than ever.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With the rapid pace at which things are evolving, I would not be surprised if in a year, half of these strategies would not be necessary as agents get better. And perhaps they will be superseded by a new set of practices.&lt;/p&gt;
&lt;h2 id=&quot;conclusion-managing-the-agents-ai-memory&quot;&gt;Conclusion: Managing the Agent’s AI “Memory”&lt;/h2&gt;
&lt;p&gt;Regardless of what you might think about the tropes around “software engineering being dead”, it is undeniable that the focus of our job is moving away (or perhaps upward) from writing code.&lt;/p&gt;
&lt;p&gt;As we spend more effort managing attention and memory of our agents, in the most sustainable agentic systems, instructions and scaffolding will be pruned as ruthlessly, if not more so, than the code itself.&lt;/p&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;In this post, we’ll reference only &lt;code&gt;agents.md&lt;/code&gt;. Hopefully we’re not far from the day where we don’t need to maintain a separate configuration for Claude. &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded></item><item><title>Receiving Feedback Is A Skill</title><link>https://www.abahgat.com/blog/receiving-feedback</link><guid isPermaLink="true">https://www.abahgat.com/blog/receiving-feedback</guid><description>Delivering feedback is a critical part of my day job as a manager at Google. However, it took me a while to realize that receiving feedback is one of the skills that helped me grow the most in my career. Here a few things I learned in the process.</description><pubDate>Tue, 25 Aug 2020 19:35:24 GMT</pubDate><content:encoded>&lt;p&gt;Delivering feedback is a critical part of my day job as a manager at Google. However, it took me a while to realize that &lt;em&gt;receiving&lt;/em&gt; feedback is one of the skills that helped me grow the most in my career.&lt;/p&gt;
&lt;p&gt;For many of us, our job is the first setting where we receive developmental feedback from people other than our parents or teachers. That experience may be quite shocking.&lt;/p&gt;
&lt;p&gt;I still remember the first time I got professional feedback early in my career. I remember almost every single word that my manager chose to use.&lt;/p&gt;
&lt;p&gt;What I remember even more vividly though is the strong reaction that feedback caused in me. Within seconds, I got defensive, I felt like I was being criticized, attacked, unappreciated. I heard what they were trying to tell me, but something inside me kept translating that into a personal criticism. A statement about how I, personally, fell short of expectations.&lt;/p&gt;
&lt;p&gt;Good feedback sounds like “here’s one thing you can do better next time”. Better feedback sounds like “here’s one thing that you could do differently to achieve a greater result”.&lt;/p&gt;
&lt;p&gt;Embracing that mindset allowed me to accept, process and build on feedback. While I can’t say I &lt;em&gt;prefer&lt;/em&gt; criticism over praise, constructive feedback no longer makes me uncomfortable. Instead, I actively seek it.&lt;/p&gt;
&lt;p&gt;Changing my mindset around feedback required me to make two key changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;stop doing things that hurt my ability to improve&lt;/li&gt;
&lt;li&gt;start doing things that help build on what I hear&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;things-i-stopped-doing&quot;&gt;Things I Stopped Doing&lt;/h2&gt;
&lt;h3 id=&quot;taking-it-personally&quot;&gt;Taking It Personally&lt;/h3&gt;
&lt;p&gt;The main reason I had a difficult time processing feedback is the fact that I often took it personally.&lt;/p&gt;
&lt;p&gt;When receiving feedback about something I did, I often read it as feedback about &lt;em&gt;me&lt;/em&gt;. Oftentimes, that was not the intention.&lt;/p&gt;
&lt;p&gt;Instead of hearing “this email was hard to understand”, I heard “you do not communicate effectively”. When the other party was saying “this piece of code is brittle”, I was hearing “you are a lousy programmer”.&lt;/p&gt;
&lt;p&gt;I often ended up reacting defensively. I was unable to hear and processing the actual message I needed to receive.&lt;/p&gt;
&lt;p&gt;Most developmental feedback will naturally trigger a defensive attitude. That prevents us from getting the full value of what the other person is trying to tell us. &lt;em&gt;We need to make a conscious effort to not jump to defensive mode, and rather engage in active listening&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id=&quot;arguing-with-feedback&quot;&gt;Arguing With Feedback&lt;/h3&gt;
&lt;p&gt;Even worse than taking feedback personally, I sometimes found myself wanting to argue with the person delivering it. I wanted to explain why I disagreed with what they were seeing or try to convince them that they were wrong.&lt;/p&gt;
&lt;p&gt;In most cases, arguing with feedback is pointless. Take an example from many years ago.&lt;/p&gt;
&lt;p&gt;A colleague approached me and told me “I think the comments you left in this review were too harsh”.&lt;/p&gt;
&lt;p&gt;Now, if they cared enough to bring up this feedback, perhaps they were not the only ones. Or maybe my communication style could have had an unintended effect on some people, some time.&lt;/p&gt;
&lt;p&gt;Yes, I could have argued with my colleague, perhaps even convince them that my tone was not that bad. Winning the argument might even have felt better.&lt;/p&gt;
&lt;p&gt;That would not have changed the my comments did trigger a negative reaction for them. Quite likely, others might have had the same reaction. Knowing that, having that awareness, made me more thoughtful when writing review comments. I can tell they were better received from that moment on.&lt;/p&gt;
&lt;p&gt;Arguing with people who are trying to give us feedback, does not help us. Eventually, people will shy away from telling us where we can improve. It leads us to us working with less information about what we can do to get better. In the long run, we miss out on a significant opportunity.&lt;/p&gt;
&lt;h2 id=&quot;things-i-learned-to-do-instead&quot;&gt;Things I Learned To Do Instead&lt;/h2&gt;
&lt;h3 id=&quot;being-thankful&quot;&gt;Being Thankful&lt;/h3&gt;
&lt;p&gt;A friend of mine once shared a quote that sounded like “feedback is a gift”&lt;/p&gt;
&lt;p&gt;Good feedback is thoughtful and timely. Often, it is as difficult to deliver as it is to receive. It is especially difficult for people we are not very close with.&lt;/p&gt;
&lt;p&gt;Any yet, some people choose to take a risk. They let us know where we can do better. They do that knowing well that we may feel hurt by what they say.&lt;/p&gt;
&lt;p&gt;Because of this, the first thing I do when receiving feedback is thank whoever is giving it. I thank them because they took a risk and did something uncomfortable. I also thank them because what they are telling me has the potential of making me much better.&lt;/p&gt;
&lt;p&gt;Good feedback allows us to identify growth areas. Areas where we could invest more to get better at something we have been trying to do. Even those of us that have good self-awareness often need to work hard to find where they need to improve the most.&lt;/p&gt;
&lt;p&gt;If someone is coming to us with feedback, they may be sparing us a lot of hard work required to identify areas of improvement.&lt;/p&gt;
&lt;p&gt;The least we can do is thank them profusely for the gift they just gave us and get to work.&lt;/p&gt;
&lt;h3 id=&quot;following-up&quot;&gt;Following Up&lt;/h3&gt;
&lt;p&gt;Whenever I receive feedback about something I can improve and want to work on, I note it down. Over time, this list becomes my feedback log.&lt;/p&gt;
&lt;p&gt;Keeping a list of the items I am trying to get better at is a way to hold myself accountable. I go through this feedback log every few weeks and reflect on the progress (or lack of progress) I have seen so far.&lt;/p&gt;
&lt;p&gt;This helps me making sure I make the most of the feedback I was generously given and use it to gradually get better. I try to spend some time every week to work on some of the most important items on the feedback log.&lt;/p&gt;
&lt;p&gt;Doing this helps me well beyond the result of addressing feedback. It also helps me ground my identity as someone who can accept feedback gracefully and use it as a tool to keep growing every day.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;A few simple changes in perspective helped me change my view on feedback. I went from seeing it as a threat to my own self-worth to a stepping stone to become a better version of myself.&lt;/p&gt;
&lt;p&gt;The results of this attitude compound over time as I keep focusing my energy towards addressing the most critical feedback items.&lt;/p&gt;</content:encoded></item><item><title>Programming Machine Learning</title><link>https://www.abahgat.com/blog/programming-machine-learning</link><guid isPermaLink="true">https://www.abahgat.com/blog/programming-machine-learning</guid><description>A book written with developers in mind, covering Machine Learning with a hands-on approach. Each new topic is introduced by laying out a real world problem, guiding readers through implementing a working solution based on ML algorithms and then explaining the theoretical foundations in a very accessible way.</description><pubDate>Mon, 04 May 2020 14:47:47 GMT</pubDate><content:encoded>&lt;p&gt;I just received my copy of &lt;a href=&quot;https://amzn.to/3bUyn5a&quot;&gt;Programming Machine Learning&lt;/a&gt;, a book by &lt;a href=&quot;https://twitter.com/nusco&quot;&gt;Paolo Perrotta&lt;/a&gt;. I had the pleasure of being one of the technical reviewers of the draft and, while this is not the first book I read about Machine Learning, I must say it became one of my favorites.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.abahgat.com/_astro/programming-ml.yXEqwxlW_1oikSk.webp&quot; alt=&quot;The book cover&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;300&quot; height=&quot;360&quot;&gt;&lt;/p&gt;
&lt;p&gt;Paolo promises, at the beginning of the book, to write a book meant for developers, and he delivers on that promise.&lt;/p&gt;
&lt;p&gt;In his words,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is the book I missed when I got started with machine learning: &lt;mark&gt;an introduction for developers, written in our own language&lt;/mark&gt;. After reading it, you’ll be comfortable with the fundamentals, and able to write machine learning programs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;Programming Machine Learning&lt;/em&gt; is a book that teaches the foundations of ML by walking the reader through the process of implementing working solutions for a few concrete and specific use cases, such as predicting sales volume for a pizzeria, recognizing hand-written digits or classifying images.&lt;/p&gt;
&lt;p&gt;Each chapter introduces a challenge, lays out the foundations of a technical implementation and explains the theoretical background behind the techniques adopted.&lt;/p&gt;
&lt;p&gt;As a result, the book is much easier to follow than many others on this subject: even when diving deeper into the technical or mathematical aspects of any of the topics covered, the reader is able to build on the empirical intuition that comes from having implemented ML algorithms and having seen them in action. Every chapter is engaging, starting from the first ones, about trying to predict pizza sales via linear regression and simple perceptrons, to the last ones, leveraging &lt;a href=&quot;https://keras.io/&quot;&gt;Keras&lt;/a&gt; to classify images.&lt;/p&gt;
&lt;p&gt;I found the overall approach quite novel and refreshing. I would definitely recommend &lt;em&gt;Programming Machine Learning&lt;/em&gt;, especially if you are the type of engineer who generally enjoys learning by doing.&lt;/p&gt;</content:encoded></item><item><title>The programming puzzle that landed me my job</title><link>https://www.abahgat.com/blog/the-programming-puzzle-that-got-me-my-job</link><guid isPermaLink="true">https://www.abahgat.com/blog/the-programming-puzzle-that-got-me-my-job</guid><description>And how solving it required a truly full-stack solution, covering web development, data structures and memory optimization</description><pubDate>Tue, 01 Oct 2019 03:35:33 GMT</pubDate><content:encoded>&lt;p&gt;Back in 2011, as I was getting a bored with my job and I started looking for new options. During my search, my friend Daniele (with whom I had built &lt;a href=&quot;https://www.abahgat.com/project/novlet&quot;&gt;Novlet&lt;/a&gt; and &lt;a href=&quot;https://www.abahgat.com/project/bitlet&quot;&gt;Bitlet&lt;/a&gt; years before) forwarded me a link to the careers page of the company he was working for at the time, &lt;a href=&quot;https://en.wikipedia.org/wiki/ITA_Software&quot;&gt;ITA Software&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While Google was in the process of acquiring ITA Software, ITA still had a number of open positions they were looking to hire for. Unlike Google, however, they required candidates to &lt;a href=&quot;https://web.archive.org/web/20111012115624/http://itasoftware.com/careers/work-at-ita/hiring-puzzles.html&quot;&gt;solve a programming challenge&lt;/a&gt; before applying to engineering roles.&lt;/p&gt;
&lt;p&gt;The problems to solve were surprisingly varied, ranging from purely algorithmic challenges to more broadly scoped problems that still required some deep technical insight. As I browsed through the options, I ended up settling on a problem that intrigued me because I thought it resembled a problem I might one day wanted to solve in the real world and seemed to try to test both the breadth of my knowledge (it required good full stack skills) as well as my understanding of deep technical details.&lt;/p&gt;
&lt;p&gt;I have good memories of the time I spent investigating this problem and coming up with a solution. When I was done, I had learned about a new class of data structures (suffix trees), gained a deeper understanding of Java’s internals. A year later, I got a job offer due in part to this puzzle.&lt;/p&gt;
&lt;figure class=&quot;my-6&quot;&gt; &lt;img src=&quot;https://www.abahgat.com/_astro/ita-puzzle.CJ0ogDvj.png&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot; width=&quot;1366&quot; height=&quot;1112&quot; srcset=&quot;https://www.abahgat.com/_astro/ita-puzzle.CJ0ogDvj_hqqIh.webp 400w, /_astro/ita-puzzle.CJ0ogDvj_Z1hIlW5.webp 768w, /_astro/ita-puzzle.CJ0ogDvj_26c0Yf.webp 1024w, /_astro/ita-puzzle.CJ0ogDvj_ZSL0TB.webp 1366w, /_astro/ita-puzzle.CJ0ogDvj_ZO4CEz.webp 2040w, /_astro/ita-puzzle.CJ0ogDvj_9cYC1.webp 2732w&quot; sizes=&quot;(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px&quot; style=&quot;object-fit: cover; object-position: center; max-width: 1366px; max-height: 1112px; aspect-ratio: 1.2284172661870503; width: 100%;&quot; alt=&quot;Instant Search puzzle brief on itasoftware.com (as of 2011)&quot; class=&quot;mx-auto rounded-md shadow-lg bg-border-default dark:bg-card w-full&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt; &lt;figcaption class=&quot;mt-2 text-center text-sm text-subtle dark:text-muted&quot;&gt; Instant Search puzzle brief on itasoftware.com (as of 2011) &lt;/figcaption&gt; &lt;/figure&gt;
&lt;h4 id=&quot;the-problem-statement&quot;&gt;The Problem Statement&lt;/h4&gt;
&lt;p&gt;The brief for the challenge was the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Instant Search&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Write a Java web application which provides “instant search” over properties listed in the National Register of Historic Places. Rather than waiting for the user to press a submit button, your application will dynamically update search results as input is typed. We provide the file &lt;code&gt;nrhp.xml.gz&lt;/code&gt;, which contains selected information from the register’s database.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Database&lt;/strong&gt; The key component of your server-side application is an efficient, in-memory data structure for looking up properties (written in pure Java). A good solution may take several minutes to load, but can answer a query in well under 0.1 ms on a modern PC. (Note that a sequential search of all properties is probably too slow!) An input matches a property if it is found at any position within that property’s names, address, or city+state. Matches are case-insensitive, and consider only the characters A-Z and 0-9, e.g. the input “mainst” matches “200 S Main St” and “red” matches “Lakeshore Dr.” Note that the server’s JVM will be configured with 1024M maximum heap space. Please conform to the interfaces specified in &lt;code&gt;nrhp.jar&lt;/code&gt; when creating your database.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Servlet&lt;/strong&gt; Your servlet should accept an input string as the request parameter to a GET request. Results should include the information for a pre-configured number of properties (e.g. 10), the total number of matches which exist in the database, and the time taken by your search algorithm. Your servlet should be stateless, ie. not depend on any per-user session information. Paginate your additional results as a bonus!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Client&lt;/strong&gt; Your web page should access the servlet using JavaScript’s XMLHttpRequest object. As the user types, your interface should repeatedly refine the list of search results without refreshing the page. Your GUI does not have to be complicated, but should be polished and look good.&lt;/p&gt;
&lt;p&gt;Please submit a WAR file, configuration instructions, your source code, and any comments on your approach. Your application will be tested with Tomcat on Sun’s 64-bit J2SE and a recent version of Firefox.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure class=&quot;my-6&quot;&gt; &lt;img src=&quot;https://www.abahgat.com/_astro/screenshot.DL2AXHKQ.png&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot; width=&quot;411&quot; height=&quot;694&quot; srcset=&quot;https://www.abahgat.com/_astro/screenshot.DL2AXHKQ_1cE7rq.webp 400w, /_astro/screenshot.DL2AXHKQ_Z2isL9m.webp 411w, /_astro/screenshot.DL2AXHKQ_ZbXWYw.webp 768w, /_astro/screenshot.DL2AXHKQ_Z2vH72Y.webp 822w&quot; sizes=&quot;(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px&quot; style=&quot;object-fit: cover; object-position: center; max-width: 411px; max-height: 694px; aspect-ratio: 0.5922190201729106; width: 100%;&quot; alt=&quot;Reference UI screenshot accompanying the puzzle brief. I ended up using it as a spec for my client code.&quot; class=&quot;mx-auto rounded-md shadow-lg bg-border-default dark:bg-card w-full&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt; &lt;figcaption class=&quot;mt-2 text-center text-sm text-subtle dark:text-muted&quot;&gt; Reference UI screenshot accompanying the puzzle brief. I ended up using it as a spec for my client code. &lt;/figcaption&gt; &lt;/figure&gt;
&lt;h4 id=&quot;client&quot;&gt;Client&lt;/h4&gt;
&lt;p&gt;I started building this from the UI down.
The puzzle brief mentioned using &lt;code&gt;XMLHttpRequest&lt;/code&gt;, so I avoided using any client-side libraries (the functionality I was asked to build on the client was, after all, quite simple).
The screenshot included with the puzzle brief included just a text field for the search query and a list of results.&lt;/p&gt;
&lt;p&gt;I wrote a function to listen for key presses, dispatch an asynchronous call to the server and render the response as soon as it came back. By 2011, I had been coding web applications for a while and I was able to implement that functionality in less than an hour of work.&lt;/p&gt;
&lt;h4 id=&quot;web-application-and-servlet-code&quot;&gt;Web application and Servlet code&lt;/h4&gt;
&lt;p&gt;The Servlet layer was also quite simple, since all it had to was handle an incoming XML request and dispatch it to what the brief called a &lt;em&gt;database&lt;/em&gt;. Again, less than an hour of work here.&lt;/p&gt;
&lt;p&gt;At this level, I also wrote code to parse the database of strings to index from an XML file containing data from the National Register of Historic Places. The Tomcat server would run this code when loading my web application and use the resulting data to construct a data structure to use as an index for power the fast search functionality I needed to build. I needed to figure that out next.&lt;/p&gt;
&lt;h4 id=&quot;finding-a-suitable-data-structure&quot;&gt;Finding a suitable data structure&lt;/h4&gt;
&lt;p&gt;This is, unsurprisingly, the most challenging part of the puzzle and where I focused my efforts the most. As pointed out in the problem description, looping sequentially through the list of landmarks would not work (it would take much longer than the target 0.1ms threshold). I needed to find data structure with good runtime complexity associated with lookup operations.&lt;/p&gt;
&lt;p&gt;I spent some time thinking about how I would implement a data structure allowing the fast lookup times required in this case. The most common fast-lookup option I was familiar with, the &lt;em&gt;hash table&lt;/em&gt;, would not work straight away with this problem because it would expect the search operation to have the full key string.
In this problem, however, I wanted to be able to look up entries in my index even when given an incomplete substring, which would have required me to store all possible substrings as keys in the table.&lt;/p&gt;
&lt;p&gt;After doing some sketching on paper, it seemed reasonable to expect that &lt;a href=&quot;https://xlinux.nist.gov/dads/HTML/trie.html&quot;&gt;tries&lt;/a&gt; would work better here.&lt;/p&gt;
&lt;h4 id=&quot;suffix-trees&quot;&gt;Suffix trees&lt;/h4&gt;
&lt;p&gt;As I was researching data structures providing fast lookup operations given partial strings, I stumbled upon a number of papers referencing suffix trees, commonly used in computational biology and text processing, offering lookup operations with linear runtime with respect to the length of the string to search &lt;em&gt;for&lt;/em&gt; (as opposed to the length of the string to search &lt;em&gt;within&lt;/em&gt;).&lt;/p&gt;
&lt;figure class=&quot;my-6&quot;&gt; &lt;img src=&quot;https://www.abahgat.com/_astro/cacao-st.DNFtSipy.svg&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot; width=&quot;221&quot; height=&quot;240&quot; srcset=&quot;https://www.abahgat.com/_astro/cacao-st.DNFtSipy_Z1Yw6sR.svg 221w, /_astro/cacao-st.DNFtSipy_ErYNE.svg 400w, /_astro/cacao-st.DNFtSipy_1rMIv8.svg 442w&quot; sizes=&quot;(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px&quot; style=&quot;object-fit: cover; object-position: center; max-width: 221px; max-height: 240px; aspect-ratio: 0.9208333333333333; width: 100%;&quot; alt=&quot;Suffix Tree for the string `cacao`. A suffix is said to be contained in the tree if there is a path from the root node where the string obtained by concatenating the edge labels has the same prefix as the suffix being looked up. Highlighted the path corresponding to the `cao` suffix.&quot; class=&quot;mx-auto rounded-md shadow-lg bg-border-default dark:bg-card w-full&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt; &lt;figcaption class=&quot;mt-2 text-center text-sm text-subtle dark:text-muted&quot;&gt; Suffix Tree for the string `cacao`. A suffix is said to be contained in the tree if there is a path from the root node where the string obtained by concatenating the edge labels has the same prefix as the suffix being looked up. Highlighted the path corresponding to the `cao` suffix. &lt;/figcaption&gt; &lt;/figure&gt;
&lt;p&gt;Plain suffix trees, however, are designed to find matches of a given candidate string sequence within a &lt;em&gt;single&lt;/em&gt;, longer, string, while this puzzle revolved around a slightly different use case: instead of having a single long string to look up matches in, I needed to be able to find matches in multiple strings. Thankfully, I read some more and found a good number of papers documenting data structures called &lt;a href=&quot;https://www.abahgat.com/project/suffix-tree&quot;&gt;&lt;em&gt;generalized&lt;/em&gt; suffix trees&lt;/a&gt; that do exactly that.&lt;/p&gt;
&lt;p&gt;Based on what I had learned so far, I was convinced this type of tree could fit my requirements but I had two likely challenges to overcome:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Suffix trees tend to occupy much more space than the strings they are indexing and, based on the problem statement, “the server’s JVM will be configured with 1024M maximum heap space” and that needed to accommodate the Tomcat server, my whole web application and the tree I was looking to build.&lt;/li&gt;
&lt;li&gt;Much of the complexity of working with suffix tree lies in &lt;em&gt;constructing&lt;/em&gt; the trees themselves. While the puzzle brief was explicitly saying my solution could take “several minutes to load”, I did not want the reviewer of my solution to have to wait several hours before they could test my submission.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;ukkonens-algorithm-for-linear-runtime-tree-construction&quot;&gt;Ukkonen’s algorithm for linear runtime tree construction&lt;/h4&gt;
&lt;p&gt;Thankfully, had I found a popular algorithm for generating Suffix Trees in linear time (linear in the total length of the strings to be indexed), described by Ukkonen in a paper published in 1995 (&lt;a href=&quot;https://www.cs.helsinki.fi/u/ukkonen/SuffixT1withFigs.pdf&quot;&gt;On–line construction of suffix trees&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;It took me a couple days of intermittent work (remember: I was working on this during nights and weekends — I had another day job back then) to get my suffix tree to work as expected.&lt;/p&gt;
&lt;p&gt;Interestingly, some of the challenges with this stage were revolving around a completely unexpected theme: Ukkonen’s paper includes the full algorithm written in pseudo-code and good prose detailing the core steps. However, that same pseudo-code is written at such a high level of abstraction that it did take some work to reconduct it to fast and efficient Java code.&lt;/p&gt;
&lt;figure class=&quot;my-6&quot;&gt; &lt;img src=&quot;https://www.abahgat.com/_astro/ukkonen-pseudocode.C7lPd-N0.png&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot; width=&quot;890&quot; height=&quot;514&quot; srcset=&quot;https://www.abahgat.com/_astro/ukkonen-pseudocode.C7lPd-N0_Z1pgFT8.webp 400w, /_astro/ukkonen-pseudocode.C7lPd-N0_19w43J.webp 768w, /_astro/ukkonen-pseudocode.C7lPd-N0_ZbEqbH.webp 890w, /_astro/ukkonen-pseudocode.C7lPd-N0_Z7zVcd.webp 1024w, /_astro/ukkonen-pseudocode.C7lPd-N0_Z1LBokq.webp 1780w&quot; sizes=&quot;(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px&quot; style=&quot;object-fit: cover; object-position: center; max-width: 890px; max-height: 514px; aspect-ratio: 1.7315175097276265; width: 100%;&quot; alt=&quot;Pseudo-code from Ukkonen&apos;s paper. While clear and easy to follow on the original paper, its translation to Java is much more verbose than this.&quot; class=&quot;mx-auto rounded-md shadow-lg bg-border-default dark:bg-card w-full&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt; &lt;figcaption class=&quot;mt-2 text-center text-sm text-subtle dark:text-muted&quot;&gt; Pseudo-code from Ukkonen&apos;s paper. While clear and easy to follow on the original paper, its translation to Java is much more verbose than this. &lt;/figcaption&gt; &lt;/figure&gt;
&lt;p&gt;Also, the pseudo-code algorithm is written assuming we are working with a single string represented as a character array, so many of the operations outlined there deal with &lt;em&gt;indices&lt;/em&gt; within that large array (e.g. &lt;em&gt;k&lt;/em&gt; and &lt;em&gt;i&lt;/em&gt; in the procedure above).&lt;/p&gt;
&lt;p&gt;In my Java implementation, instead, I wanted to work with &lt;code&gt;String&lt;/code&gt; objects as much as possible. I was driven by a few different reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Java implements &lt;a href=&quot;https://en.wikipedia.org/wiki/String_interning&quot;&gt;string interning&lt;/a&gt; by default — there is no memory benefit in representing substrings by manually manipulating indices within an array of characters representing the containing string: the JVM &lt;em&gt;already does that&lt;/em&gt; transparently for us.&lt;/li&gt;
&lt;li&gt;Working with &lt;code&gt;String&lt;/code&gt; references led to code that was much more legible to me.&lt;/li&gt;
&lt;li&gt;I knew my next step would be to generalize the algorithm to handle building an index on &lt;em&gt;multiple&lt;/em&gt; strings and that was going to be much more difficult if I had to deal with low level specifics about which array of character represented which input string.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;generalized-suffix-trees&quot;&gt;&lt;em&gt;Generalized&lt;/em&gt; Suffix Trees&lt;/h4&gt;
&lt;p&gt;This last consideration proved to be critical: generalizing the suffix tree I had up to this point to work with multiple input strings was fairly straightforward. All I had to do was to make sure the nodes in my tree could carry some &lt;em&gt;payload&lt;/em&gt; denoting which of the strings in the index would match a given query string. This stage amounted to a couple hours of work, but only because I had good unit tests.&lt;/p&gt;
&lt;p&gt;At this point, things were looking great. I had spent maybe a couple days reading papers about suffix trees and another couple days writing all the code I had so far. I was ready to try out running my application with the input data provided with the puzzle brief: the entire National Register of Historic Places, an XML feed totaling a few hundred megabytes.&lt;/p&gt;
&lt;h4 id=&quot;trial-by-fire-outofmemoryerror&quot;&gt;Trial by fire: &lt;code&gt;OutOfMemoryError&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;The first run of my application was disappointing. I started up Tomcat and deployed my web application archive, which triggered parsing the XML database provided as input and started to build the generalized suffix tree to use as an index for fast search. Not even two minutes into the suffix tree construction, the server crashed with an &lt;code&gt;OutOfMemoryError&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The 1024 megabytes I had were not enough.&lt;/p&gt;
&lt;p&gt;Thankfully, a couple years earlier I had worked with a client that had a difficult time keeping their e-commerce site up during peak holiday shopping season. Their servers kept crashing because they were running out of memory. That in turn led me to learn how to read and make sense of JVM memory dumps.&lt;/p&gt;
&lt;p&gt;I never thought I would make use of that skill for my own personal projects but this puzzle proved me wrong. I fired up &lt;a href=&quot;https://visualvm.github.io&quot;&gt;visualvm&lt;/a&gt; and started looking for the largest contributors to memory consumption.&lt;/p&gt;
&lt;figure class=&quot;my-6&quot;&gt; &lt;img src=&quot;https://www.abahgat.com/_astro/heapdump-classes-screen.ZqHPfiwz.png&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot; width=&quot;600&quot; height=&quot;483&quot; srcset=&quot;https://www.abahgat.com/_astro/heapdump-classes-screen.ZqHPfiwz_Z7WpLa.webp 400w, /_astro/heapdump-classes-screen.ZqHPfiwz_2kDq5z.webp 600w, /_astro/heapdump-classes-screen.ZqHPfiwz_ZgB3ME.webp 768w, /_astro/heapdump-classes-screen.ZqHPfiwz_2c5YK0.webp 1024w, /_astro/heapdump-classes-screen.ZqHPfiwz_Z1GmVzP.webp 1200w&quot; sizes=&quot;(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px&quot; style=&quot;object-fit: cover; object-position: center; max-width: 600px; max-height: 483px; aspect-ratio: 1.2422360248447204; width: 100%;&quot; alt=&quot;A screenshot of VisualVM used to inspect a heap dump (from the official documentation)&quot; class=&quot;mx-auto rounded-md shadow-lg bg-border-default dark:bg-card w-full&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt; &lt;figcaption class=&quot;mt-2 text-center text-sm text-subtle dark:text-muted&quot;&gt; A screenshot of VisualVM used to inspect a heap dump (from the official documentation) &lt;/figcaption&gt; &lt;/figure&gt;
&lt;p&gt;It did not take long to find that there were a few memory allocation patterns that were not efficient. Many of these items would hardly be an issue for an average application, but they all ended up making a difference in this case because of the sheer size of the tree data structure being constructed.&lt;/p&gt;
&lt;h4 id=&quot;memory-micro-optimizations&quot;&gt;Memory micro-optimizations&lt;/h4&gt;
&lt;p&gt;Analyzing a few heap dumps suggested me a series of possible changes that would lead to savings in memory, usually at the cost of additional complexity or switching from a general purpose data structure implementation (e.g. maps) to special purpose equivalent tailored to this use case and its constraints.&lt;/p&gt;
&lt;p&gt;I ranked possible optimizations by their expected return on investment (i.e. comparing value of the memory savings to the additional implementation complexity, slower runtime and other factors) and implemented a few items at the top of the list.&lt;/p&gt;
&lt;p&gt;The most impactful changes involved optimizing the memory footprint of the suffix tree &lt;em&gt;nodes&lt;/em&gt;: considering my application required constructing a very large graph (featuring tens of thousands of nodes), any marginal savings coming from a more efficient node representation would end up making a meaningful difference.&lt;/p&gt;
&lt;p&gt;A property of suffix tree nodes is that no outgoing edges can be labeled with strings sharing a prefix. In practice, this means that the data structure implementing a node must hold a reference to a set of outgoing edges keyed by the first character on the label.&lt;/p&gt;
&lt;p&gt;The first version of my solution was using a &lt;code&gt;HashMap&amp;lt;Character,Edge&amp;gt;&lt;/code&gt; to represent this. As soon as I looked at the heap dump, I noticed this representation was extremely inefficient for my use case.&lt;/p&gt;
&lt;p&gt;Hash Maps in Java are initialized with a &lt;a href=&quot;https://en.wikipedia.org/wiki/Hash_table#Key_statistics&quot;&gt;load factor&lt;/a&gt; of 0.75 (meaning they generally reserve memory for at least 25% more key/value pairs than they hold at any given point) and, more importantly, with enough initial capacity to hold 16 elements.&lt;/p&gt;
&lt;p&gt;The latter item was a particularly poor fit for my use case: since I was indexing strings using the English alphabet (26 distinct characters) a map of size 16 would be large enough to accommodate more than half the possible characters and would often be wasteful.&lt;/p&gt;
&lt;p&gt;I could have mitigated this problem by tuning the sizing and load factor parameters but I thought I could save even more memory by switching to a specialized collection type.
The default map implementations included in the standard library require the key and value types to be reference types rather than native types (i.e. the map is keyed by &lt;code&gt;Character&lt;/code&gt; instead of &lt;code&gt;char&lt;/code&gt;) and reference types tend to be much less memory efficient (since their representation is more complex).&lt;/p&gt;
&lt;p&gt;I wrote a special-purpose map implementation, called &lt;code&gt;EdgeBag&lt;/code&gt;, which featured a few tweaks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;stored keys and values and two parallel arrays,&lt;/li&gt;
&lt;li&gt;the arrays would start small gradually grew if more space if necessary,&lt;/li&gt;
&lt;li&gt;relied on a linear scan for lookup operation if the bag contained a small number of elements and switched to using binary search on a sorted key set if the bag had grown to contain more than a few units,&lt;/li&gt;
&lt;li&gt;used &lt;code&gt;byte[]&lt;/code&gt; (instead of &lt;code&gt;char[]&lt;/code&gt;) to represent the characters in the keys. Java’s 16-bit &lt;code&gt;char&lt;/code&gt; type takes twice as much space as a &lt;code&gt;byte&lt;/code&gt;. I knew all my keys were ASCII characters, so I could forgo Unicode support here and could squeeze some more savings by casting to a more narrow value range.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some more specific details on this and other changes to reduce the memory footprint of my suffix tree implementation are in the &lt;a href=&quot;https://www.abahgat.com/project/suffix-tree#problem-specific-optimizations&quot;&gt;Problem-specific optimizations&lt;/a&gt; section of the Suffix Tree project page.&lt;/p&gt;
&lt;h4 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;When I tested out my program after the memory optimizations, I was delighted to see it met the problem requirements: lookups were lightning fast, well under 0.1ms using the machine I had back then (based on an Intel Q6600 2.4GHz CPU) and the unit tests I had written gave me good confidence that the program behaved as required.&lt;/p&gt;
&lt;p&gt;I packaged up the solution as a WAR archive, wrote a brief README file outlining design considerations and instructions on how to run it (just deploy on a bare Tomcat 6 server) and sent it over email. Almost a year later, I was packing my bags and moving to Amsterdam to join Google (which had by then acquired ITA Software).&lt;/p&gt;
&lt;p&gt;I owe it in no small part to the fun I had with this coding puzzle.&lt;/p&gt;
&lt;p&gt;When I think of how much I enjoyed the time I spent building Instant Search, I think it must be because it required both breadth (to design a full stack application, albeit a simple one) and depth (to research the best data structure for the job and follow up with optimizations as required). It allowed me to combine my background as a generalist with my interest with the theoretical foundations of Computer Science.&lt;/p&gt;
&lt;p&gt;The careful choice of specifying both memory and runtime constraints as part of the problem requirements made the challenge much more fun. When the first version I coded did not work, I was able to reuse my experience with memory profiling tools to identify which optimizations to follow up with. At the same time, I built a stronger understanding of Java’s internals and learned a lot more about implementation details I had, until then, just given for granted.&lt;/p&gt;
&lt;p&gt;When ITA retired Instant Search (and other programming puzzles&lt;sup&gt;&lt;a href=&quot;#user-content-fn-puzzles&quot; id=&quot;user-content-fnref-puzzles&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;), I decided to &lt;a href=&quot;https://www.abahgat.com/project/suffix-tree&quot;&gt;release the Java Generalized Suffix Tree as open source&lt;/a&gt; for others to use. Despite the many problem-specific optimizations I ended up making, it is generic enough that has been used in a few other applications since I built it, which gives me one more thing to be thankful for.&lt;/p&gt;
&lt;section data-footnotes class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-puzzles&quot;&gt;
&lt;p&gt;While the original page is no longer online, the Wayback Machine still has a snapshot of the original page with the original selection of &lt;a href=&quot;https://web.archive.org/web/20111012115624/http://itasoftware.com/careers/work-at-ita/hiring-puzzles.html&quot;&gt;past programming puzzles&lt;/a&gt;. They are still a great way to test your programming skills. &lt;a href=&quot;#user-content-fnref-puzzles&quot; data-footnote-backref aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded></item><item><title>What to look for when hiring</title><link>https://www.abahgat.com/blog/what-to-look-for-when-hiring</link><guid isPermaLink="true">https://www.abahgat.com/blog/what-to-look-for-when-hiring</guid><description>A while ago, I found myself in the enviable position of having to rapidly grow my team. Here a list of the most important characteristics I learned to value in anyone I work with, regardless of job function.</description><pubDate>Mon, 26 Aug 2019 19:09:10 GMT</pubDate><content:encoded>&lt;p&gt;A while ago, I found myself in the enviable position of having to rapidly grow my team. By then, I had done a large number of technical interviews, so I had an idea of what to look for in strong candidates for Software Engineering positions. However, I felt like I lacked a framework for understanding how likely a given candidate was to succeed if they had joined my team, beyond a very loose definition of “culture fit”.&lt;/p&gt;
&lt;p&gt;As I was trying to better understand what I was looking for, I started to think about what I value in the people I work with and to reflect on traits I found to be quite common among some of the most successful people I have worked with over the course of my career.&lt;/p&gt;
&lt;p&gt;While I would not expect every person I work with to exhibit &lt;em&gt;all&lt;/em&gt; the qualities I list here, I am always positively impressed when I come across someone who exhibits more than a few and equally concerned when I see no hint of any of these characteristics.&lt;/p&gt;
&lt;p&gt;Over time, I became quite sensitive to some hints that suggest someone could possess one of the these traits and I learned to probe further whenever I see them.&lt;/p&gt;
&lt;p&gt;Here a list of the most important characteristics I learned to value in anyone I work with, regardless of job function.&lt;/p&gt;
&lt;h2 id=&quot;intrinsic-motivation&quot;&gt;Intrinsic Motivation&lt;/h2&gt;
&lt;p&gt;Many of the best people I worked with are motivated by their own desire to improve, regardless of the environment around them. Certainly, having a great team and a lot of attention from their manager will help them as well as it would help anyone else, but being intrinsically motivated means they are able to find satisfaction without relying on artificial nudges from the system around them.&lt;/p&gt;
&lt;p&gt;I tend to enjoy working with people who think this way because they are often pushing themselves to get better every day, react better to difficulties and challenges and, as a result, push me to get better as well.&lt;/p&gt;
&lt;p&gt;I know I am looking at someone who has this kind of attitude when they show they are driven by things such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;learning something new every day&lt;/li&gt;
&lt;li&gt;mastering a skill or a craft&lt;/li&gt;
&lt;li&gt;accomplishing something they thought of as difficult&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having hobbies and non-trivial side-projects (for those of us who are at a point where they can afford the time required) is often a sign of being intrinsically motivated.&lt;/p&gt;
&lt;h2 id=&quot;relentless-focus&quot;&gt;Relentless Focus&lt;/h2&gt;
&lt;p&gt;Success often requires from focusing on the most important things first and almost ignoring everything else.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Effective executives concentrate on the few major areas where superior performance will produce outstanding results. They force themselves to set priorities and stay with their priority decisions. They know that they have no choice but to do first things first—and second things not at all. The alternative is to get nothing done. &lt;span class=&quot;blockquote-footer&quot;&gt;Peter F. Drucker, &lt;a href=&quot;https://amzn.to/2ZndZml&quot;&gt;&lt;cite title=&quot;The Effective Executive&quot;&gt;The Effective Executive&lt;/cite&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I found it hard to gauge how good anyone is at focusing on top priorities based solely on casual conversations. One decent proxy, at least for technical roles, are open ended system design interviews. Many good questions involve asking to solve a problem too large to be tackled within the allotted time or with the given constraints. That forces the candidate to narrow down the scope and focus on the most important aspects of the problem and set everything else aside.&lt;/p&gt;
&lt;h2 id=&quot;independent-thinking&quot;&gt;Independent Thinking&lt;/h2&gt;
&lt;p&gt;A couple of the best people I worked with have a way of asking questions that sometimes can come across as blunt or excessively direct. In their case, I have never had a problem with it, since it is tied to what I believe to be one of their strengths: they are not afraid to question a line of thought if they do not fully understand it or if they disagree with it.&lt;/p&gt;
&lt;p&gt;In cultures where it is more comfortable to agree with others than to challenge their thinking, it takes courage to express dissent.&lt;/p&gt;
&lt;p&gt;I &lt;a href=&quot;https://www.abahgat.com/post/2012-03-12-building-a-culture-of-objection/index.md&quot;&gt;wrote before&lt;/a&gt; how much I value a culture where anyone feels free to voice their disagreement: I value even more individuals who are comfortable speaking up regardless of what the environment surrounding them looks like.&lt;/p&gt;
&lt;p&gt;This is another trait that is be hard to spot in casual conversations, I have seen this come across as a set of pointed, specific questions aimed at developing a stronger understanding of a topic and then thoughtfully suggesting there might be a different way to approach a problem.&lt;/p&gt;
&lt;p&gt;However, there is a fine line between being willing to challenge ideas when they are not rock solid and being contrarian by default: it is hard to work with someone who disagrees with everything on principle.&lt;/p&gt;
&lt;h2 id=&quot;fast-learning&quot;&gt;Fast Learning&lt;/h2&gt;
&lt;p&gt;The ability to learn quickly and adapt to changing circumstances is one of the most critical skills to have in this day and age. To me, it means that I can trust someone to be able to be asked to do something they have not done before and rapidly get up to speed.&lt;/p&gt;
&lt;p&gt;I generally see this through evidence of high rate of improvement; whether it shows as gaining mastery of many technologies in a short time, working across a number of different domains or being promoted repeatedly while at the same company, this shows an ability to adapt to changing circumstances.&lt;/p&gt;
&lt;h2 id=&quot;responsiveness-and-follow-through&quot;&gt;Responsiveness and Follow Through&lt;/h2&gt;
&lt;p&gt;One of the main differences between working with a team and working by ourselves is that when we are part of a team others tend to depend on our output for their own progress.&lt;/p&gt;
&lt;p&gt;Oftentimes, managers end being stuck having to play the role of the persistent nag, reminding others of their prior commitments and making sure that any work that was agreed upon is eventually delivered. Clearly, this is a way around a fairly common problem: the average person is not great at following through.&lt;/p&gt;
&lt;p&gt;By contrast, the most effective team players I have worked with hardly need any nudges: they will stay on top of their to-do list and consistently deliver anything they agreed to do by the time they said they would, without you ever needing to ask again.
If you do ask something of them, they respond right away.&lt;/p&gt;
&lt;p&gt;Sadly, I do not know of a way to assess how well anyone would do on this point without speaking to anyone who has worked with them before.&lt;/p&gt;
&lt;h2 id=&quot;decisiveness&quot;&gt;Decisiveness&lt;/h2&gt;
&lt;p&gt;Many people struggle with decisions, for fear of making a mistake, being proven wrong and fallible or committing to the wrong direction. Whatever the reason, shying away from decisions is rarely helpful.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“In effect, the lack of a decision is the same as a negative decision; no green light is a red light, and work can stop for a whole organization.” &lt;span class=&quot;blockquote-footer&quot;&gt;Andrew S. Grove, &lt;a href=&quot;https://amzn.to/32dlLB9&quot;&gt;&lt;cite title=&quot;High Output Management&quot;&gt;High Output Management&lt;/cite&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The truth is that many decisions are relatively easy to reverse if necessary but the cost of paralysis is too high for most teams and organizations to afford. High-stakes decisions are rare, but when facing one it is important to treat it as a priority and not linger too long. The worst thing we can do is simply dwell on it and get stuck.&lt;/p&gt;
&lt;p&gt;Decisiveness is often the driving force behind the &lt;a href=&quot;#responsiveness-and-follow-through&quot;&gt;responsiveness&lt;/a&gt; in the previous section.&lt;/p&gt;
&lt;h2 id=&quot;curiosity-and-inquisitiveness&quot;&gt;Curiosity and Inquisitiveness&lt;/h2&gt;
&lt;p&gt;Beyond being a &lt;a href=&quot;#fast-learning&quot;&gt;fast learner&lt;/a&gt; or being passionate about the specifics of someone’s own job, being curious and inquisitive can be invaluable in understanding one’s own teammates, manager, users and competitors.&lt;/p&gt;
&lt;p&gt;By wondering about the “why” behind anything we observe, we develop a stronger understanding of the problem we are trying to solve or the parties and organizations we are working with. An understanding that inevitably helps us be more effective.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“When you get curious and learn how to turn that disagreement into honest questioning, you can learn more about other perspectives on the issue because your team will open up.”
&lt;span class=&quot;blockquote-footer&quot;&gt;Camille Fournier, &lt;a href=&quot;https://amzn.to/2KY97jH&quot;&gt;&lt;cite title=&quot;The Manager&amp;#x27;s Path&quot;&gt;The Manager’s Path&lt;/cite&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Oddly enough, at least based on my own experience, it is fairly common to find engineers who are extremely curious about technical topics but tend to be less interested about understanding less technical subjects (such as organizations and other humans). People I worked with who are truly inquisitive tend to demonstrate it by being uncommonly interested in the motivation behind the status quo or previous decisions. They often ask questions such as “Why do we do things this way?”&lt;/p&gt;
&lt;h2 id=&quot;communication&quot;&gt;Communication&lt;/h2&gt;
&lt;p&gt;So much of teamwork is communication, yet communication skills are often overlooked. It is hard to overstate the importance of communication in teamwork. Effective communication means, among other things,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;being able to make one’s point of view understood,&lt;/li&gt;
&lt;li&gt;resolving conflicts,&lt;/li&gt;
&lt;li&gt;selling our own vision,&lt;/li&gt;
&lt;li&gt;making sure others are aware of our work (and why it matters)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of all the traits I learned to appreciate, this is perhaps the most visible. If you spend even a few minutes speaking with someone and they are an effective communicator, you will notice.&lt;/p&gt;
&lt;h2 id=&quot;going-the-extra-mile&quot;&gt;Going the Extra Mile&lt;/h2&gt;
&lt;p&gt;Many successful people consistently overdeliver. It is quite difficult to have any sort of success by just doing the bare minimum. Sure, one can get lucky once or twice, but solid careers are built on strings of consistent achievements.&lt;/p&gt;
&lt;p&gt;I often see this in coming through from people’s passions. It often shows as side projects (work-like activities they chose to do in their own time&lt;sup&gt;&lt;a href=&quot;#user-content-fn-side-projects&quot; id=&quot;user-content-fnref-side-projects&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;) or initiatives at work that they started without anyone asking them to do so (e.g. 20% projects at Google).&lt;/p&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-side-projects&quot;&gt;
&lt;p&gt;Note that this is not always possible for people to do, depending on their situation. &lt;a href=&quot;#user-content-fnref-side-projects&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded></item><item><title>Visual and HTML Testing for Static Sites</title><link>https://www.abahgat.com/blog/testing-static-sites</link><guid isPermaLink="true">https://www.abahgat.com/blog/testing-static-sites</guid><description>I set up a CI/CD pipeline to test my website for markup and rendering issues. It proved to be so useful that I can not imagine going back.</description><pubDate>Tue, 06 Aug 2019 11:06:27 GMT</pubDate><content:encoded>&lt;p&gt;Over a year ago I switched from having my site hosted on a CMS to having it &lt;a href=&quot;https://www.abahgat.com/blog/2018-03-14-migrating-from-wordpress-to-hugo&quot; title=&quot;Migrating from Wordpress to Hugo.&quot;&gt;built statically&lt;/a&gt; and served as a collection of static pages. I have been extremely happy with the end result for all these months — the site is very easy to update and effortless to maintain — but I just made a few changes that made my experience even better.&lt;/p&gt;

&lt;h2 id=&quot;why-test-static-sites&quot;&gt;Why test Static Sites&lt;/h2&gt;
&lt;p&gt;Even for sites as simple as this, it is surprisingly easy to make breaking changes without realizing. Over the time I have been maintaining abahgat.com, I ended up accidentally introduction bugs more than a few times. Here a few examples of things I ran into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;broken links&lt;/em&gt; — by default, Hugo does not validate any of the links in the content I am editing, which means that I have to be careful and make sure all URLs and paths are valid&lt;/li&gt;
&lt;li&gt;&lt;em&gt;incorrect theme configuration&lt;/em&gt; — the more complex the theme I am using is, the more configuration options it will offer. The more options I have to configure, the more likely I am to make mistakes.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;bugs in theme customizations&lt;/em&gt; — Hugo is great at allowing to override and customize theme templates. However, this is another source of potential issues.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;bugs in the theme code itself&lt;/em&gt; — No software is perfect, and any theme I might be using can have its own bugs and edge cases. This might be especially true for you if you are actively developing your own theme or you frequently update it to the most recent version available.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most of the issues above still affected me when I was hosting my site on Wordpress (I did break links and styling every now and then) but one advantage of working with a statically generated site is that we can leverage many of the tools that are available to web developers to catch issues early (and potentially block deploys if any issues are detected). So I set out to find what kind of options I had to improve my workflow so that I could make changes with more confidence that I wouldn’t accidentally break my site.&lt;/p&gt;
&lt;h2 id=&quot;what-can-be-tested&quot;&gt;What can be tested&lt;/h2&gt;
&lt;p&gt;Based on the list above, I knew I was looking to set up tests to detect, in order of priority, problems such as:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;broken internal links&lt;/li&gt;
&lt;li&gt;invalid or malformed HTML&lt;/li&gt;
&lt;li&gt;issues with layout or presentation&lt;/li&gt;
&lt;li&gt;invalid RSS feed entries&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Thankfully, I was able to find a way to cover most of these.&lt;/p&gt;
&lt;h3 id=&quot;testing-html-with-html-proofer&quot;&gt;Testing HTML with &lt;code&gt;html-proofer&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Covering the first items on the list has been fairly straightforward with &lt;a href=&quot;https://github.com/gjtorikian/html-proofer&quot;&gt;&lt;code&gt;html-proofer&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Provided you have Ruby installed, you can get &lt;code&gt;html-proofer&lt;/code&gt; as a gem via the command below&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gem&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; html-proofer&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and then run it via&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;htmlproofer&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --extension&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; .html&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ./public&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will scan the &lt;code&gt;./public&lt;/code&gt; directory for any files with &lt;code&gt;html&lt;/code&gt; extension and output a report listing any issues with the markup in those files.&lt;/p&gt;
&lt;p&gt;When I first ran it on my site, I got a pretty good list of actionable warnings. The messages are fairly specific and easy to understand, as you can tell by looking at the snippet below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- ./public/author/abahgat/index.html&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  *  356:11: ERROR: Opening and ending tag mismatch: section and div (line 356)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- ./public/author/index.html&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  *  356:11: ERROR: Opening and ending tag mismatch: section and div (line 356)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- ./public/blog/index.html&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  *  829:2157: ERROR: Unexpected end tag : p (line 829)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- ./public/blog/maps-for-public-transport-users/index.html&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  *  internally linking to uploads/2009/01/p-480-320-0e6ac38d-252e-47fa-be79-0ae974dad8d2.jpeg, which does not exist (line 476)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &amp;lt;a href=&amp;quot;uploads/2009/01/p-480-320-0e6ac38d-252e-47fa-be79-0ae974dad8d2.jpeg&amp;quot;&amp;gt;&amp;lt;img class=&amp;quot;size-full wp-image-364 aligncenter&amp;quot; src=&amp;quot;https://www.abahgat.com/img/wp-uploads/2009/01/p-480-320-0e6ac38d-252e-47fa-be79-0ae974dad8d2.jpeg&amp;quot; alt=&amp;quot;&amp;quot; width=&amp;quot;200&amp;quot; height=&amp;quot;300&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- ./public/blog/page/2/index.html&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  *  linking to internal hash #broken-priorites that does not exist (line 1456)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &amp;lt;a href=&amp;quot;#broken-priorites&amp;quot;&amp;gt;The way priorities are managed is broken&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  *  linking to internal hash #duplicates that does not exist (line 1453)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &amp;lt;a href=&amp;quot;#duplicates&amp;quot;&amp;gt;Lots of issues are duplicates&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  *  linking to internal hash #missing-info that does not exist (line 1455)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &amp;lt;a href=&amp;quot;#missing-info&amp;quot;&amp;gt;Bug reports do not include enough information&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  *  linking to internal hash #processes that does not exist (line 1454)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &amp;lt;a href=&amp;quot;#processes&amp;quot;&amp;gt;The system imposes over-engineered processes&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  *  linking to internal hash #tracker-misuse that does not exist (line 1452)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &amp;lt;a href=&amp;quot;#tracker-misuse&amp;quot;&amp;gt;The issue tracking system is misused&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even with default settings, &lt;code&gt;html-proofer&lt;/code&gt; is able to catch most of the issues I was interested in detecting: the list above features a good mix of problems caused by invalid links in my Markdown sources, errors due to how I was misusing my template and bugs in the template I was using.&lt;/p&gt;
&lt;p&gt;Fixing the issues required a combination of updating a few broken links, cleaning up the Markdown sources for my site, submitting a few bugs and Pull Requests against the theme I am using.&lt;/p&gt;
&lt;p&gt;Overall, all the issues flagged made sense and worth fixing.&lt;/p&gt;
&lt;h3 id=&quot;visual-testing-with-percy&quot;&gt;Visual Testing with Percy&lt;/h3&gt;
&lt;p&gt;As useful as &lt;code&gt;html-proofer&lt;/code&gt; is, it does not help catching layout and presentational issues that are not due to invalid markup. I have had good experiences with visual testing and review at work and I was interested in using screenshots to detect layout issues and catch any unintended presentational changes on my own site too.&lt;/p&gt;
&lt;p&gt;I cared about this because upgrading my Hugo theme sometimes involves non-trivial changes that could go wrong (despite George, the author, keeping &lt;a href=&quot;https://sourcethemes.com/academic/updates/v4.5.0/&quot;&gt;really good change logs&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Also, I wanted to make customizations to the theme and having testing in place is the only way I know to make sure I don’t inadvertently break anything (since I will not review every single page manually every time I make layout changes, having a way to be warned about any differences is very valuable).&lt;/p&gt;
&lt;p&gt;I ended up settling on &lt;a href=&quot;https://www.percy.io&quot;&gt;Percy&lt;/a&gt;, a tool that was clearly designed first and foremost for testing dynamic web applications but also offered an option to &lt;a href=&quot;https://docs.percy.io/docs/static-sites&quot;&gt;test static sites&lt;/a&gt; via a command line program.&lt;/p&gt;
&lt;p&gt;The main idea behind a snapshot testing system is to keep a set of approved snapshots (“goldens”), capture a new set of snapshots upon change and flag any differences for review. Changes can be either intended (in which case the screenshot is approved and becomes the new golden) or accidental (in which case they are flagged as regressions and expected to be fixed before pushing a new version).&lt;/p&gt;
&lt;figure class=&quot;my-6&quot;&gt; &lt;img src=&quot;https://www.abahgat.com/_astro/diff-categories.CIjbA6Go.png&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot; width=&quot;2235&quot; height=&quot;741&quot; srcset=&quot;https://www.abahgat.com/_astro/diff-categories.CIjbA6Go_29Y56G.webp 400w, /_astro/diff-categories.CIjbA6Go_1NlLLV.webp 768w, /_astro/diff-categories.CIjbA6Go_sJpgU.webp 1024w, /_astro/diff-categories.CIjbA6Go_EldWH.webp 2040w, /_astro/diff-categories.CIjbA6Go_iR9z9.webp 2235w, /_astro/diff-categories.CIjbA6Go_Z2iRhFz.webp 4470w&quot; sizes=&quot;(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px&quot; style=&quot;object-fit: cover; object-position: center; max-width: 2235px; max-height: 741px; aspect-ratio: 3.016194331983806; width: 100%;&quot; alt=&quot;Example screenshot highlighting differences introduced by a specific commit.&quot; class=&quot;mx-auto rounded-md shadow-lg bg-border-default dark:bg-card w-full&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt; &lt;figcaption class=&quot;mt-2 text-center text-sm text-subtle dark:text-muted&quot;&gt; Example screenshot highlighting differences introduced by a specific commit. &lt;/figcaption&gt; &lt;/figure&gt;
&lt;p&gt;Percy offers a nice interface to highlight any difference between snapshots and can be easily integrated with GitHub and other source control systems to make approving any updated snapshots part of the code review process.&lt;/p&gt;
&lt;p&gt;Percy runs as a service, so you will need to create an account with them before being able to use it. Once you have done that you can try it by following the instructions on &lt;a href=&quot;https://docs.percy.io/docs/command-line-client&quot;&gt;their documentation page&lt;/a&gt; and running the following command on your site (where &lt;code&gt;./public&lt;/code&gt; is a directory containing your static pages):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npx&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; percy&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; snapshot&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ./public&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;running-tests-on-every-change-via-ci-services&quot;&gt;Running tests on every change via CI services&lt;/h2&gt;
&lt;p&gt;Unlike the HTML tests, which test a specific version of your site in isolation, the value of snapshot testing lies in comparing your site against a previously approved set of snapshots, which need to be kept up to date.&lt;/p&gt;
&lt;p&gt;I then configured a simple workflow with &lt;a href=&quot;https://circleci.com/&quot;&gt;CircleCI&lt;/a&gt;, having it build my site with Hugo, run &lt;code&gt;html-proofer&lt;/code&gt; on the generated sources, grab a fresh set of screenshots on every change and flag any differences for review.&lt;/p&gt;
&lt;p&gt;From what I could tell, many other CI services can be configured to do the same; I ended up choosing CircleCI because I thought its Docker-based setup worked better for what I was trying to do and I had little trouble finding Docker images suitable for running the steps in my workflow.&lt;/p&gt;
&lt;p&gt;Below the resulting configuration:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;orbs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  hugo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;circleci/hugo@0.3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;jobs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  snapshot&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    docker&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;image&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;buildkite/puppeteer:v1.15.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    steps&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;attach_workspace&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          at&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;npm install percy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;PERCY_TOKEN=$PERCY_TOKEN npx percy snapshot ./public&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;workflows&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  main&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    jobs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;hugo/build&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          version&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;0.55.6&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          html-proofer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;snapshot&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          requires&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;hugo/build&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first section sets up build with Hugo via an &lt;em&gt;Orb&lt;/em&gt; (&lt;a href=&quot;https://circleci.com/orbs/&quot;&gt;Orbs&lt;/a&gt; are CircleCI’s packages of functionality that can be packaged and reused) that also runs &lt;code&gt;html-proofer&lt;/code&gt; tests on the resulting build.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;snapshot&lt;/code&gt; task installs &lt;code&gt;percy&lt;/code&gt; via npm and then invokes it on the directory containing the sources generated in the previous step. It runs on the &lt;a href=&quot;https://hub.docker.com/r/buildkite/puppeteer&quot;&gt;Docker Puppeteer&lt;/a&gt; image, which comes with most of Percy’s package dependencies already installed.&lt;/p&gt;
&lt;div class=&quot;not-prose flex gap-3 p-4 rounded-lg border bg-surface border-border-default text-default  alert alert-note&quot;&gt; &lt;svg width=&quot;1em&quot; height=&quot;1em&quot; class=&quot;w-5 h-5 flex-shrink-0 mt-2&quot; data-icon=&quot;tabler:info-circle&quot;&gt;   &lt;symbol id=&quot;ai:tabler:info-circle&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;g fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path d=&quot;M3 12a9 9 0 1 0 18 0a9 9 0 0 0-18 0m9-3h.01&quot;/&gt;&lt;path d=&quot;M11 12h1v4h1&quot;/&gt;&lt;/g&gt;&lt;/symbol&gt;&lt;use href=&quot;#ai:tabler:info-circle&quot;&gt;&lt;/use&gt;  &lt;/svg&gt; &lt;div&gt;  &lt;div class=&quot;text-sm prose-p:my-0 prose-p:first:mt-0 prose-p:last:mb-0 [&amp;&gt;p]:my-0&quot;&gt; &lt;p&gt;There seems to be a &lt;a href=&quot;https://hub.docker.com/r/percyio/agent&quot;&gt;Docker image maintained by Percy&lt;/a&gt; but I could not get it
to work. I suspect it is because it ships with an old version of the &lt;code&gt;percy&lt;/code&gt; command, I did not investigate this
further.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt;
&lt;p&gt;With this configuration, every commit and Pull Request will trigger a Hugo build, run your site through &lt;code&gt;html-proofer&lt;/code&gt; and capture a new set of snapshots. If any visual differences are detected, they can be inspected and approved via Percy’s web interface.&lt;/p&gt;
&lt;figure class=&quot;my-6&quot;&gt; &lt;img src=&quot;https://www.abahgat.com/_astro/pr-with-checks.qoXq374x.png&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot; width=&quot;1359&quot; height=&quot;704&quot; srcset=&quot;https://www.abahgat.com/_astro/pr-with-checks.qoXq374x_Z1NtvC7.webp 400w, /_astro/pr-with-checks.qoXq374x_15dUCV.webp 768w, /_astro/pr-with-checks.qoXq374x_Z1etCH8.webp 1024w, /_astro/pr-with-checks.qoXq374x_13KOV8.webp 1359w, /_astro/pr-with-checks.qoXq374x_nEVrL.webp 2040w, /_astro/pr-with-checks.qoXq374x_Z1xNOU8.webp 2718w&quot; sizes=&quot;(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px&quot; style=&quot;object-fit: cover; object-position: center; max-width: 1359px; max-height: 704px; aspect-ratio: 1.9303977272727273; width: 100%;&quot; alt=&quot;GitHub will show the latest status of your tests on every commit and Pull Request.&quot; class=&quot;mx-auto rounded-md shadow-lg bg-border-default dark:bg-card w-full&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt; &lt;figcaption class=&quot;mt-2 text-center text-sm text-subtle dark:text-muted&quot;&gt; GitHub will show the latest status of your tests on every commit and Pull Request. &lt;/figcaption&gt; &lt;/figure&gt;
&lt;p&gt;Note that there is no &lt;em&gt;deploy&lt;/em&gt; workflow since I configured Netlify to automatically publish a new version of my site whenever I push to the &lt;em&gt;master&lt;/em&gt; branch.&lt;/p&gt;
&lt;h2 id=&quot;tweaking-the-setup&quot;&gt;Tweaking the setup&lt;/h2&gt;
&lt;p&gt;If you got to this point, your configuration will feature sensible defaults and help you capture a number of issues caused by your own mistakes or any issues introduced by the theme upstream.&lt;/p&gt;
&lt;p&gt;There are a few opportunities to make the setup more efficient, but they require making changes with the CircleCI configuration above since the Orb we used before does not expose a good way to pass flags to tweak neither the build nor test test. (This &lt;a href=&quot;https://github.com/CircleCI-Public/hugo-orb/issues?q=author%3Aabahgat&quot;&gt;might be fixed&lt;/a&gt; by the time you read this).&lt;/p&gt;
&lt;p&gt;You can &lt;a href=&quot;https://gist.github.com/abahgat/e7fc5b3023692610c4760fedcd8e3b43&quot;&gt;click here&lt;/a&gt; to see a CircleCI configuration file that you can further customize based on the sections below.&lt;/p&gt;
&lt;p&gt;Here some of the tweaks you might consider implementing.&lt;/p&gt;
&lt;h3 id=&quot;test-pages-with-a-future-publish-date-and-drafts&quot;&gt;Test pages with a future publish date and drafts&lt;/h3&gt;
&lt;p&gt;Hugo allows you mark pages as drafts or to set a publish date to a future time (for scheduled content). Neither of these pages will be built by default in your deploy workflow, but you might want to do that when running your tests so that you ensure that content passes validation even as it is being edited (as opposed to being surprised by unexpected errors just when you thought you were ready to publish).&lt;/p&gt;
&lt;p&gt;You can do this by passing the &lt;code&gt;-D&lt;/code&gt; and &lt;code&gt;-F&lt;/code&gt; flags to the &lt;code&gt;hugo&lt;/code&gt; command during the build step.&lt;/p&gt;
&lt;h3 id=&quot;consider-enabling-minification&quot;&gt;Consider enabling minification&lt;/h3&gt;
&lt;p&gt;If you are building your site with minification enabled when you are deploying, you might have to make a decision:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if you enable minification only on the deploy workflow (and leave it disabled for development), the version of the site you will be testing will not be identical to the version you are publishing. This might hide subtle bugs that you would not be able to track down easily (such as &lt;a href=&quot;https://github.com/gcushen/hugo-academic/issues/1219&quot;&gt;this one&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;on the contrary, if you do enable minification, debugging issues flagged by &lt;code&gt;html-proofer&lt;/code&gt; and &lt;code&gt;percy&lt;/code&gt; might be slightly more difficult, since the resulting source code will be more difficult to read.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I do not have a firm recommendation here, I am currently working with the latter setup and it has been working fine so far but isolating the cause of an issue is slightly harder this way.&lt;/p&gt;
&lt;p&gt;If you want try this, you need to pass &lt;code&gt;--minify&lt;/code&gt; to the &lt;code&gt;hugo&lt;/code&gt; command during the build step.&lt;/p&gt;
&lt;h3 id=&quot;skip-redundant-screenshots&quot;&gt;Skip redundant screenshots&lt;/h3&gt;
&lt;p&gt;Just like, when writing unit tests, we don’t want to have multiple redundant tests that cover the same behavior, in most cases it is not necessary to take screenshots of pages that use the same template and have very similar content.&lt;/p&gt;
&lt;p&gt;For example, if part of your site is a blog that features tags and categories (in Hugo, this would apply to any &lt;a href=&quot;https://gohugo.io/content-management/taxonomies/&quot;&gt;&lt;em&gt;taxonomy&lt;/em&gt;&lt;/a&gt;), you will not need to take screenshot of every individual tag page as you won’t get much value out of them, since they all look the same. They will rather be a burden to maintain (should your theme ever change, you’d have many more — very similar — screenshots to approve).&lt;/p&gt;
&lt;p&gt;You can probably make a similar case for directory pages (say, if you have 40 pages of articles, the screenshots for the second to thirty-ninth pages are likely going to be the same.
There could be value in testing the first and last page separately since you’d imagine they would have a different configuration for the next/previous navigation elements, but that is up to you.&lt;/p&gt;
&lt;p&gt;Thankfully, the &lt;code&gt;percy&lt;/code&gt; command offers a way to manually exclude certain paths from being considered when grabbing screenshots. The syntax for that argument expects &lt;a href=&quot;https://en.wikipedia.org/wiki/Glob_(programming)&quot;&gt;globs&lt;/a&gt;, which can take some trial and error to get right.&lt;/p&gt;
&lt;p&gt;In case it helps, here a configuration that worked reasonably well for me so far:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npx&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; percy&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; snapshot&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ./public&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &amp;#39;categories/!(coding|coding/**)/*.html&amp;#39;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &amp;#39;tags/!(amsterdam|amsterdam/**)/*.html&amp;#39;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &amp;#39;blog/page/!(1|2)/*.html&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What the above is doing is excluding all categories but one (&lt;a href=&quot;https://www.abahgat.com/categories/coding&quot;&gt;Coding&lt;/a&gt;) and all tags excluding one (&lt;a href=&quot;https://www.abahgat.com/tags/amsterdam&quot;&gt;Amsterdam&lt;/a&gt;). It is also ignoring any page beyond the second in the &lt;code&gt;/blog&lt;/code&gt; directory.&lt;/p&gt;
&lt;h3 id=&quot;capture-screenshots-less-frequently&quot;&gt;Capture screenshots less frequently&lt;/h3&gt;
&lt;p&gt;I have yet to run into this limitation but I could see how, if your site is very large and/or if you commit very frequently, you may be concerned about exceeding Percy’s free quota (5000 screenshots/month).&lt;/p&gt;
&lt;p&gt;I have not had to handle this in any special way so far, but here a few options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Percy grabs screenshots of each page on your site in both Chrome and Firefox to ensure your site behaves well across browsers. You may decide you are comfortable with taking the risk of having smaller issues undetected and grab screenshots only on one of the two. This will mean you will consume half as many snapshots every time you run visual tests.&lt;/li&gt;
&lt;li&gt;Percy will also test your site on a couple different viewport sizes. This is helpful to ensure your site works well on desktop and mobile devices. Again, you may be comfortable with just running tests on one configuration in order to reduce resource consumption by half.&lt;/li&gt;
&lt;li&gt;You may configure your CircleCI workflow to &lt;a href=&quot;https://circleci.com/docs/2.0/workflows/#holding-a-workflow-for-a-manual-approval&quot;&gt;toggle the snapshot step manually&lt;/a&gt; and run it only when you have meaningful changes to test (e.g. if you are adding new content or upgrading your theme). If you do this, you still want to make sure you refresh your screenshots based on &lt;em&gt;master&lt;/em&gt; fairly often, otherwise you might find yourself with visual diffs that cover so many changes together that are no longer informative. And if you run this very infrequently, you might as well just choose to run the &lt;code&gt;percy&lt;/code&gt; command locally.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Realistically, for most personal sites, you can likely go a long way with the free quota. If you are considering this for a large corporate site, I would rather consider paying for a higher tier and get more snapshots rather than trying too hard to capture fewer and have a less informative workflow.&lt;/p&gt;
&lt;h2 id=&quot;tests-are-even-more-valuable-if-you-are-a-theme-developer&quot;&gt;Tests are even more valuable if you are a theme developer&lt;/h2&gt;
&lt;p&gt;If you are developing a theme that others are going to use, testing this way is likely to be even more impactful: you can save yourself quite a bit of time by having a way to catch issues before you ship a new version instead of relying on your users to report problems they run into after they upgrade.&lt;/p&gt;
&lt;p&gt;You can apply most of the suggestions above by making sure that you have an example site (the &lt;a href=&quot;https://sourcethemes.com/academic/&quot;&gt;Academic&lt;/a&gt; theme I use is great for this) that exercises most of the features in your theme, &lt;em&gt;especially the ones that are not enabled by default&lt;/em&gt;. This would also likely reduce the time you spend manually inspecting your pages to make sure they still render as expected.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This has been a great opportunity to learn about great tools that are available out there (I will definitely consider Percy for the next app I will build in my own time) and how they can help greatly even with sites that are &lt;a href=&quot;https://www.abahgat.com/tags/StaticGen&quot;&gt;statically generated&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have accomplished most of the goals I had in mind when I started playing with this. There is one item left open for future investigation (mainly, a way to ensure the RSS for my site is valid and well-formed) but the CircleCI workflow I set up gave me a good foundation I can extend to cover more tests.&lt;/p&gt;</content:encoded></item><item><title>Zing LED Smart Night Light</title><link>https://www.abahgat.com/blog/zing-night-light</link><guid isPermaLink="true">https://www.abahgat.com/blog/zing-night-light</guid><description>I liked these WiFi enabled, motion-sensing night lights so far, I only wish they had 3 more features.</description><pubDate>Mon, 18 Feb 2019 21:24:08 GMT</pubDate><content:encoded>&lt;p&gt;Several months ago I was looking for a night light when I stumbled upon &lt;a href=&quot;https://www.indiegogo.com/projects/zing-smart-night-light#/&quot;&gt;Zing’s Indiegogo page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The main feature I was looking for was for the light to activate automatically when I was walking past it and to turn off a few seconds later. Zing seemed to be able to do this and more: after seeing browsing the site, what intrigued me were the many possibilities for customization, the integration with &lt;a href=&quot;https://www.ifttt.com&quot;&gt;IFTTT&lt;/a&gt; and the fact that each light has a temperature sensor — which I was hoping I would eventually be able to access via API.&lt;/p&gt;
&lt;p&gt;Some features, such as automatic path lighting and the locator feature, were not a part of the decision.
Others, event notification in particular, I knew I would not use (I am trying to minimize the notifications I get while I am home).&lt;/p&gt;
&lt;p&gt;I got a pack of 3 on Indiegogo, hoping to receive them relatively soon. The wait turned out to be longer than I expected (shipping ended up being a few months late due to some complications in the production process) but I finally received my lights in October.&lt;/p&gt;
&lt;figure class=&quot;my-6&quot;&gt; &lt;img src=&quot;https://www.abahgat.com/_astro/box.DTskC9CD.jpg&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot; width=&quot;2637&quot; height=&quot;2867&quot; srcset=&quot;https://www.abahgat.com/_astro/box.DTskC9CD_Z1i1lHr.webp 400w, /_astro/box.DTskC9CD_1tYH6L.webp 768w, /_astro/box.DTskC9CD_16H58c.webp 1024w, /_astro/box.DTskC9CD_Z1PDJIk.webp 2040w, /_astro/box.DTskC9CD_114ak7.webp 2637w, /_astro/box.DTskC9CD_ZAIOA5.webp 5274w&quot; sizes=&quot;(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px&quot; style=&quot;object-fit: cover; object-position: center; max-width: 2637px; max-height: 2867px; aspect-ratio: 0.9197767701430066; width: 100%;&quot; alt=&quot;The box the lights come in.&quot; class=&quot;mx-auto rounded-md shadow-lg bg-border-default dark:bg-card w-full&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt; &lt;figcaption class=&quot;mt-2 text-center text-sm text-subtle dark:text-muted&quot;&gt; The box the lights come in. &lt;/figcaption&gt; &lt;/figure&gt;
&lt;p&gt;So far, I have been quite happy with them. I liked the fact that each light is configurable and offers a number of settings to customize that will help you make sure it works with your environment and preferences.&lt;/p&gt;
&lt;p&gt;I installed three lights (two are in bedrooms and a third is in the master bathroom) and set all of them to a warm, yellow glow. I initially configured the bathroom light to a multi-colored rotating pattern (see the screenshot below) but shortly after I opted for a more relaxing solid color and static pattern.&lt;/p&gt;
&lt;div id=&quot;gallery-28y&quot; class=&quot;gallery-wrapper not-prose&quot; data-images=&quot;[]&quot;&gt; &lt;div class=&quot;grid gap-2 my-4 grid-cols-1 sm:grid-cols-2 md:grid-cols-3&quot;&gt;  &lt;/div&gt; &lt;!-- Lightbox Modal --&gt; &lt;div class=&quot;lightbox-modal fixed inset-0 z-[9999] w-full h-full p-0 m-0 bg-transparent backdrop:bg-black/90 pointer-events-none hidden opacity-0 transition-opacity duration-300&quot; role=&quot;dialog&quot; aria-modal=&quot;true&quot;&gt; &lt;!-- Backdrop --&gt; &lt;div class=&quot;fixed inset-0 bg-black/90 backdrop-blur-sm pointer-events-auto modal-close-area&quot;&gt;&lt;/div&gt; &lt;div class=&quot;relative w-full h-full flex items-center justify-center p-4 pointer-events-none&quot;&gt; &lt;!-- Close Button --&gt; &lt;button class=&quot;absolute top-4 right-4 z-50 p-2 text-white/70 hover:text-white bg-black/50 rounded-full hover:bg-white/20 transition-all pointer-events-auto modal-close-btn&quot; aria-label=&quot;Close&quot;&gt; &lt;svg width=&quot;1em&quot; height=&quot;1em&quot; class=&quot;w-8 h-8&quot; data-icon=&quot;tabler:x&quot;&gt;   &lt;symbol id=&quot;ai:tabler:x&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot; d=&quot;M18 6L6 18M6 6l12 12&quot;/&gt;&lt;/symbol&gt;&lt;use href=&quot;#ai:tabler:x&quot;&gt;&lt;/use&gt;  &lt;/svg&gt; &lt;/button&gt; &lt;!-- Previous Button --&gt; &lt;button class=&quot;absolute left-4 z-50 p-3 text-white/70 hover:text-white bg-black/50 rounded-full hover:bg-white/20 transition-all pointer-events-auto prev-btn&quot; aria-label=&quot;Previous&quot;&gt; &lt;svg width=&quot;1em&quot; height=&quot;1em&quot; class=&quot;w-8 h-8&quot; data-icon=&quot;tabler:chevron-left&quot;&gt;   &lt;symbol id=&quot;ai:tabler:chevron-left&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot; d=&quot;m15 6l-6 6l6 6&quot;/&gt;&lt;/symbol&gt;&lt;use href=&quot;#ai:tabler:chevron-left&quot;&gt;&lt;/use&gt;  &lt;/svg&gt; &lt;/button&gt; &lt;!-- Next Button --&gt; &lt;button class=&quot;absolute right-4 z-50 p-3 text-white/70 hover:text-white bg-black/50 rounded-full hover:bg-white/20 transition-all pointer-events-auto next-btn&quot; aria-label=&quot;Next&quot;&gt; &lt;svg width=&quot;1em&quot; height=&quot;1em&quot; class=&quot;w-8 h-8&quot; data-icon=&quot;tabler:chevron-right&quot;&gt;   &lt;symbol id=&quot;ai:tabler:chevron-right&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot; d=&quot;m9 6l6 6l-6 6&quot;/&gt;&lt;/symbol&gt;&lt;use href=&quot;#ai:tabler:chevron-right&quot;&gt;&lt;/use&gt;  &lt;/svg&gt; &lt;/button&gt; &lt;!-- Main Image --&gt; &lt;img src=&quot;&quot; alt=&quot;Lightbox&quot; class=&quot;max-w-[90vw] max-h-[80vh] object-contain shadow-2xl rounded-sm pointer-events-auto lightbox-image transition-transform duration-300&quot;&gt; &lt;p class=&quot;lightbox-caption absolute bottom-4 left-0 right-0 text-center text-white bg-black/50 p-2 mx-auto max-w-3xl rounded backdrop-blur-sm pointer-events-auto opacity-0 transition-opacity duration-300&quot;&gt;&lt;/p&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 
&lt;p&gt;The Zing app allows configuration of many parameters for the lights, such as the light color, intensitiy, spread (influencing how wide of an area the light would illuminate) and speed (for moving patterns).&lt;/p&gt;
&lt;p&gt;Unfortunately, the Android app seems to be lagging behind with respect to the iOS one in terms of functionality — more on this later.&lt;/p&gt;
&lt;div id=&quot;gallery-28y&quot; class=&quot;gallery-wrapper not-prose&quot; data-images=&quot;[]&quot;&gt; &lt;div class=&quot;grid gap-2 my-4 grid-cols-1 sm:grid-cols-2 md:grid-cols-3&quot;&gt;  &lt;/div&gt; &lt;!-- Lightbox Modal --&gt; &lt;div class=&quot;lightbox-modal fixed inset-0 z-[9999] w-full h-full p-0 m-0 bg-transparent backdrop:bg-black/90 pointer-events-none hidden opacity-0 transition-opacity duration-300&quot; role=&quot;dialog&quot; aria-modal=&quot;true&quot;&gt; &lt;!-- Backdrop --&gt; &lt;div class=&quot;fixed inset-0 bg-black/90 backdrop-blur-sm pointer-events-auto modal-close-area&quot;&gt;&lt;/div&gt; &lt;div class=&quot;relative w-full h-full flex items-center justify-center p-4 pointer-events-none&quot;&gt; &lt;!-- Close Button --&gt; &lt;button class=&quot;absolute top-4 right-4 z-50 p-2 text-white/70 hover:text-white bg-black/50 rounded-full hover:bg-white/20 transition-all pointer-events-auto modal-close-btn&quot; aria-label=&quot;Close&quot;&gt; &lt;svg width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; class=&quot;w-8 h-8&quot; data-icon=&quot;tabler:x&quot;&gt;   &lt;use href=&quot;#ai:tabler:x&quot;&gt;&lt;/use&gt;  &lt;/svg&gt; &lt;/button&gt; &lt;!-- Previous Button --&gt; &lt;button class=&quot;absolute left-4 z-50 p-3 text-white/70 hover:text-white bg-black/50 rounded-full hover:bg-white/20 transition-all pointer-events-auto prev-btn&quot; aria-label=&quot;Previous&quot;&gt; &lt;svg width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; class=&quot;w-8 h-8&quot; data-icon=&quot;tabler:chevron-left&quot;&gt;   &lt;use href=&quot;#ai:tabler:chevron-left&quot;&gt;&lt;/use&gt;  &lt;/svg&gt; &lt;/button&gt; &lt;!-- Next Button --&gt; &lt;button class=&quot;absolute right-4 z-50 p-3 text-white/70 hover:text-white bg-black/50 rounded-full hover:bg-white/20 transition-all pointer-events-auto next-btn&quot; aria-label=&quot;Next&quot;&gt; &lt;svg width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; class=&quot;w-8 h-8&quot; data-icon=&quot;tabler:chevron-right&quot;&gt;   &lt;use href=&quot;#ai:tabler:chevron-right&quot;&gt;&lt;/use&gt;  &lt;/svg&gt; &lt;/button&gt; &lt;!-- Main Image --&gt; &lt;img src=&quot;&quot; alt=&quot;Lightbox&quot; class=&quot;max-w-[90vw] max-h-[80vh] object-contain shadow-2xl rounded-sm pointer-events-auto lightbox-image transition-transform duration-300&quot;&gt; &lt;p class=&quot;lightbox-caption absolute bottom-4 left-0 right-0 text-center text-white bg-black/50 p-2 mx-auto max-w-3xl rounded backdrop-blur-sm pointer-events-auto opacity-0 transition-opacity duration-300&quot;&gt;&lt;/p&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 
&lt;p&gt;The predictive path lighting feature has been quite disappointing so far. Whenever one of the lights turns on (because you are walking past it), all the other two will turn on as well, almost as if the model powering the feature today wasn’t any sophisticated than “if motion is detected, turn on &lt;em&gt;all&lt;/em&gt; lights”. Not a big deal, but it meant that I turned off the feature on the light in the other bedroom, since I did not want any of getting up to trigger the light in our daughter’s room.&lt;/p&gt;
&lt;p&gt;I haven’t gotten to try neither the locator or the notification feature advertised on Indiegogo: I am not sure whether they are supported or not.&lt;/p&gt;
&lt;p&gt;Unfortunately, the version of the lights I received shipped with an older firmware version that is affected by a couple issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;some settings (e.g. blue light reduction) are not persisted if the light loses power;&lt;/li&gt;
&lt;li&gt;the activity indicator for the WiFi module often flashes blue.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I must say the latter issue is quite annoying in a night light. If you think about it, having a bright blue LED flash unexpectedly is quite noticeable in a dark room and almost defeats the purpose of having a night light.&lt;/p&gt;
&lt;p&gt;I am told that updating the device firmware might help with both of these issues but unfortunately the Android application is unable to perform the update so far. I have been in touch with the Zing Support team to understand what workaround are available (other than procuring an iPhone) and I am hoping to hear back soon.&lt;/p&gt;
&lt;p&gt;All considered, I have been quite happy with Zing, provided that I manage to fix the issue with the WiFi module.&lt;/p&gt;
&lt;p&gt;The features that I wish it had at this point are all related to software and am hoping they might happen soon:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Being able to prevent the lights from turning on at daytime or when the room is already bright enough;&lt;/li&gt;
&lt;li&gt;IFTTT/Google Assistant integration;&lt;/li&gt;
&lt;li&gt;Being able to access the temperature sensor via API.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are curious to check Zing out, they now have &lt;a href=&quot;https://zing.fm/&quot;&gt;an official site&lt;/a&gt;.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;strong&gt;UPDATE (January 2021)&lt;/strong&gt; I am still quite happy with the basic functionality of these lights. However, I can’t recommend them if you are an Android user.&lt;/p&gt;
&lt;p&gt;The Android application to control the lights has not received any updates in years. It also lacks several features that are present on the iOS version, such as controlling the lights based on a schedule, upgrading the device firmware and more.&lt;/p&gt;</content:encoded></item><item><title>Migrating From Wordpress to Hugo</title><link>https://www.abahgat.com/blog/migrating-from-wordpress-to-hugo</link><guid isPermaLink="true">https://www.abahgat.com/blog/migrating-from-wordpress-to-hugo</guid><description>After many years of running my site on Wordpress, I just migrated this site to Hugo. The migration was quite simple, this post outlines the main steps and offers a few helpful resources.</description><pubDate>Thu, 15 Mar 2018 00:35:57 GMT</pubDate><content:encoded>&lt;p&gt;After many years of running my own site on Wordpress, I finally pulled the trigger and decided to migrate to a different stack.&lt;/p&gt;
&lt;p&gt;Wordpress had been working quite well for me until I started to run into some with the hosted version and did not want to deal with having to set up and maintain my own server just for this site.&lt;/p&gt;
&lt;p&gt;When I found myself, unexpectedly, with some time to spare — rocking my newborn daughter back to sleep in the middle of the night — I took it as an opportunity to learn what kind of options are available for running simple websites in 2018. I had read so much about &lt;a href=&quot;https://www.smashingmagazine.com/2015/11/modern-static-website-generators-next-big-thing/&quot;&gt;static site generators&lt;/a&gt; and they seemed such a great fit for what I was trying to do, so I decided to give it a shot.&lt;/p&gt;
&lt;p&gt;I am surprised to see how far things have made it since when I last looked. If are interested in the current state of things, you can find a pretty good list on &lt;a href=&quot;http://staticgen.com&quot;&gt;StaticGen.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I had no shortage of alternatives to consider but I fairly quickly settled on setting my new site up with &lt;a href=&quot;https://gohugo.io&quot;&gt;Hugo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thankfully, the migration itself was not too daunting, I was able to complete most of it during the course of a few nights while holding a sleeping baby 😉&lt;/p&gt;
&lt;p&gt;In case you are considering doing the same migration, here an outline of the steps involved and a few articles I would recommend.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Decide whether you want to keep the same apperance or you are okay with selecting a theme you like and just exporting your comment. In my case, I decided to switch to a &lt;a href=&quot;https://themes.gohugo.io/academic/&quot;&gt;new theme&lt;/a&gt;, so I focused on mapping how my existing content would be organized in the theme I was migrating to.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Migrate your content to Markdown that Hugo can process. I found this article useful: &lt;a href=&quot;https://sourcethemes.com/academic/docs/migrate-from-wordpress/&quot;&gt;Migrating from Wordpress&lt;/a&gt;. Requires installing a plugin on your Wordpress site to export content in a format that &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; (another static site generator) can process and then transform that to the format Hugo expects&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;alert alert-warning&quot;&gt;If your site is on wordpress.com, the guide above won&apos;t work as is, since you will not be able to install plugins unless you are hosting your own server. I worked my way around this by [exporting an XML dump of my site](https://en.support.wordpress.com/export/), and then starting up a throwaway wordpress server (I did this with cloud9 when they offered a free plan, you can probably get a similar result by running it on [docker](https://docs.docker.com/compose/wordpress/)).&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Your site will likely require some fixes at this point. The specifics depend on what it looks like but it is likely that you will want at least to verify that the links between pages are working fine. Images often require some fixes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;My site had a fair number of incoming links from other places. I wanted to avoid breaking them if possible. This is where I was glad I was deploying my site on &lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt;, since they offer great support of &lt;a href=&quot;https://www.netlify.com/docs/redirects/&quot;&gt;Redirect &amp;#x26; Rewrite Rules&lt;/a&gt;, among many other features.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I had a good number of comments on my old site and I wanted to carry them over. For the sake of simplicity, I chose to use Disqus for my comments and thankfully they had a good article about &lt;a href=&quot;https://help.disqus.com/en/articles/1717131-importing-comments-from-wordpress&quot;&gt;Importing comments from WordPress&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;alert alert-note&quot;&gt;Disqus comments are associated with page URLs, so you will want to make sure your pages are served at the same URLs as before the migration. Alternatively, you can edit the URLs in the export file before importing it following the instructions above.&lt;/div&gt;
&lt;p&gt;I have yet to find a technical migration that completes without introducing new issues, so if you ever encounter any bugs on this site, I would ask you to &lt;a href=&quot;https://www.abahgat.com/#contact&quot;&gt;please let me know&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Feel free to leave a comment if you are trying to do the same migration and you run into trouble, I can try to help you out.&lt;/p&gt;</content:encoded></item><item><title>What’s wrong with Milan’s Open Data initiative</title><link>https://www.abahgat.com/blog/whats-wrong-with-milans-open-data-initiative</link><guid isPermaLink="true">https://www.abahgat.com/blog/whats-wrong-with-milans-open-data-initiative</guid><description>I spent some time playing with the Open Data published by the City of Milan, aiming to visualize public transport coverage. While I managed to create a heatmap, I was left unsatisfied by the data presentation and format. The initiative is promising but could be dramatically improved by adopting modern standards like GeoJSON instead of Shapefiles and including simple preview capabilities.</description><pubDate>Thu, 12 Sep 2013 10:38:51 GMT</pubDate><content:encoded>&lt;p&gt;I spent some time during the last weeks playing with the &lt;a href=&quot;http://dati.comune.milano.it&quot;&gt;Open Data published by the City of Milan&lt;/a&gt;. I did not have a clear goal in mind, except for building some interesting visualization of the Public Transport coverage of the city grounds.&lt;/p&gt;
&lt;p&gt;A quick exploration of the dataset seemed to be encouraging: while most of the data was relatively useless, some datasets were indeed promising and worth spending some time. While at the end of the week I was able to get the result I had in mind (the heatmap below), I was left with that lingering feeling of dissatisfaction that accompanies me when I see good initiatives that can be dramatically improved by changing a few specific features.&lt;/p&gt;
&lt;figure class=&quot;my-6&quot;&gt; &lt;img src=&quot;https://www.abahgat.com/_astro/milan-bus-stops.7OyF_V9N.png&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot; width=&quot;1026&quot; height=&quot;666&quot; srcset=&quot;https://www.abahgat.com/_astro/milan-bus-stops.7OyF_V9N_1U3lhR.webp 400w, /_astro/milan-bus-stops.7OyF_V9N_2lxFUE.webp 768w, /_astro/milan-bus-stops.7OyF_V9N_ZfzWIa.webp 1024w, /_astro/milan-bus-stops.7OyF_V9N_MSVsT.webp 1026w, /_astro/milan-bus-stops.7OyF_V9N_Z19w2fW.webp 2040w, /_astro/milan-bus-stops.7OyF_V9N_Z14Jz53.webp 2052w&quot; sizes=&quot;(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px&quot; style=&quot;object-fit: cover; object-position: center; max-width: 1026px; max-height: 666px; aspect-ratio: 1.5405405405405406; width: 100%;&quot; alt=&quot;Density of bus stops in Milan&quot; class=&quot;mx-auto rounded-md shadow-lg bg-border-default dark:bg-card w-full&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt; &lt;figcaption class=&quot;mt-2 text-center text-sm text-subtle dark:text-muted&quot;&gt; Density of bus stops in Milan &lt;/figcaption&gt; &lt;/figure&gt;
&lt;h2 id=&quot;presentation-of-data&quot;&gt;Presentation of data&lt;/h2&gt;
&lt;p&gt;If the purpose of a website is to publish data, data should be at the center. However, while CSV data sets featured a preview option, there was absolutely no way to preview topological data. &lt;span style=&quot;font-style:inherit;line-height:1.625;&quot;&gt;Of course geographical displays are a more complex problem to solve but as of 2013 there are many libraries that can effortlessly visualize geographical features.&lt;/span&gt; Topological data is presented in a textual catalogue, with abundant descriptions and numerous fields of metadata, but &lt;strong&gt;there is no map&lt;/strong&gt;. The screenshot below is the page on the website that describes the data about Parco Nord (a park where I used to go running). Note that it does not offer any hint about what the data look like. &lt;img src=&quot;https://www.abahgat.com/img/wp-uploads/2013/09/screen-shot-2013-09-12-at-11-18-33-am-e1378977609386.png&quot; alt=&quot;Parco Nord Page&quot; loading=&quot;lazy&quot;/&gt; Compare this with the element below: (almost) the same data visualized on GitHub as a GeoJSON file. I believe this format is much more effective in communicating what the data look like. I suspect you will agree with me.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;[Embedded content not available in RSS — &lt;a href=&quot;https://www.abahgat.com/blog/whats-wrong-with-milans-open-data-initiative&quot;&gt;view on site&lt;/a&gt;]&lt;/em&gt;&lt;/p&gt; 
&lt;h2 id=&quot;choosing-the-right-format&quot;&gt;Choosing the right format&lt;/h2&gt;
&lt;p&gt;Topological data offered by the initiative is coded using the &lt;a href=&quot;http://en.wikipedia.org/wiki/Shapefile&quot;&gt;Shapefile&lt;/a&gt; data format, introduced in the 1990s for use with desktop GIS software. It is a very rich and powerful format but it encodes data as a set of compressed binary files, making it unusable with modern web applications without doing &lt;a href=&quot;http://ben.balter.com/2013/06/26/how-to-convert-shapefiles-to-geojson-for-use-on-github/&quot;&gt;some prior processing&lt;/a&gt;. While Shapefiles are great for professional GIS users, for an Open Data initiative to reach the most developers, using a text based format like &lt;a href=&quot;https://developers.google.com/kml/documentation/&quot;&gt;KML&lt;/a&gt; or &lt;a href=&quot;http://www.geojson.org/&quot;&gt;GeoJSON&lt;/a&gt; would have been a wiser choice, as it lowers the barrier for the general public to consume open data information. Both formats are sufficiently rich to encode structured information: the map below is a good example (and the &lt;a href=&quot;https://gist.github.com/abahgat/6359868/raw/3b693d0b179338054ab4385f513f2c2298a991bd/rete-metro-milano.geojson&quot;&gt;raw file&lt;/a&gt; is still human-readable).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;[Embedded content not available in RSS — &lt;a href=&quot;https://www.abahgat.com/blog/whats-wrong-with-milans-open-data-initiative&quot;&gt;view on site&lt;/a&gt;]&lt;/em&gt;&lt;/p&gt; 
&lt;h2 id=&quot;the-end-result&quot;&gt;The end result&lt;/h2&gt;
&lt;p&gt;After spending some time on this I ended up creating a &lt;a href=&quot;http://abahgat.github.io/opendata-milano/&quot;&gt;GitHub repository&lt;/a&gt; with the data I played with converted to GeoJSON, ready for use with web applications, and wrote a simple visualization of the &lt;a href=&quot;http://abahgat.github.io/opendata-milano/experiments/transport.html&quot;&gt;coverage of the city of Milan by the public transport network&lt;/a&gt; (the image you can see at the beginning of this post). Now, it would be great if whoever is responsible for Milan’s Open Data could look into making information available through better formats, leveraging &lt;a href=&quot;https://mapsengine.google.com/&quot;&gt;Google Maps Engine&lt;/a&gt; or GitHub’s support for GeoJSON. While we wait for that to happen, if you convert more data to GeoJSON, feel free to fork &lt;a href=&quot;https://github.com/abahgat/opendata-milano&quot;&gt;opendata-milano&lt;/a&gt; on GitHub and contribute there.&lt;/p&gt;</content:encoded></item><item><title>Appsterdam Guru Session: Google App Engine for beginners</title><link>https://www.abahgat.com/blog/appsterdam-guru-session-google-app-engine-for-beginners</link><guid isPermaLink="true">https://www.abahgat.com/blog/appsterdam-guru-session-google-app-engine-for-beginners</guid><description>One of the things I was not expecting when I moved to Amsterdam was its active and vibrant tech community. Appsterdam, a non-profit organization focused around aggregating people with a passion for te...</description><pubDate>Sat, 06 Jul 2013 02:21:19 GMT</pubDate><content:encoded>&lt;p&gt;One of the things I was not expecting when I &lt;a href=&quot;https://www.abahgat.com/post/2012-11-20-what-you-should-know-before-moving-to-amsterdam/index.md&quot; title=&quot;What you should know before moving to Amsterdam&quot;&gt;moved to Amsterdam&lt;/a&gt; was its active and vibrant tech community. &lt;a href=&quot;http://appsterdam.rs/&quot;&gt;Appsterdam&lt;/a&gt;, a non-profit organization focused around aggregating people with a passion for technology, is probably one of the central forces in this movement.&lt;/p&gt;
&lt;p&gt;In my year in Amsterdam I had been to a few meetups organized by people from Appsterdam and always came back home having learned something new. This is why when my colleague &lt;a href=&quot;https://twitter.com/mattfgl&quot;&gt;Matt&lt;/a&gt; (who himself is quite an active Appsterdam member) talked me into presenting a guru session on Google App Engine, I saw that as an opportunity to return the favor.&lt;/p&gt;
&lt;p&gt;While I tried to give an overview of App Engine in general (and the Python flavor, specifically), I also wanted to offer attendees the chance to work on some examples that were more interesting than the typical guestbook application that comes with all the tutorials you can find online.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/abahgat/gae4beginners-demos&quot;&gt;code examples&lt;/a&gt; build on two of the many APIs App Engine has to offer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;a href=&quot;https://developers.google.com/appengine/docs/python/channel/&quot;&gt;Channel API&lt;/a&gt; to build a web page that displays the current cursor position of every user looking at that site,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/appengine/docs/python/endpoints/&quot;&gt;Google Cloud Endpoints&lt;/a&gt; to implement a simple REST-like backend for a webpage.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can check out the &lt;a href=&quot;https://speakerdeck.com/abahgat/google-app-engine-for-beginners&quot;&gt;slide deck&lt;/a&gt; below and get the code examples from the &lt;a href=&quot;https://github.com/abahgat/gae4beginners-demos&quot;&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;[Embedded content not available in RSS — &lt;a href=&quot;https://www.abahgat.com/blog/appsterdam-guru-session-google-app-engine-for-beginners&quot;&gt;view on site&lt;/a&gt;]&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;App Engine is a lot more than an advanced infrastructure to deploy applications: the numerous APIs and services it offers can enable developers to build advanced applications with limited effort. I hope this presentation, while just scratching the surface, gives you a glimpse on the possibilities.&lt;/p&gt;
&lt;hr&gt;
&lt;p style=&quot;font-size:smaller;&quot;&gt;
  Special thanks to &lt;a href=&quot;https://twitter.com/mattfgl&quot;&gt;Matt&lt;/a&gt; for pushing me to do this and &lt;a href=&quot;https://twitter.com/chiya_serena&quot;&gt;Serena&lt;/a&gt; for her help with example 2.
&lt;/p&gt;</content:encoded></item><item><title>Presenting Professional Invaders</title><link>https://www.abahgat.com/blog/presenting-professional-invaders</link><guid isPermaLink="true">https://www.abahgat.com/blog/presenting-professional-invaders</guid><description>The story of Professional Invaders, the game we built during TNW Conference&apos;s Hack Battle.</description><pubDate>Thu, 06 Jun 2013 07:25:00 GMT</pubDate><content:encoded>&lt;p&gt;A few weeks ago I attended &lt;a href=&quot;http://thenextweb.com/conference/&quot; target=&quot;_blank&quot;&gt;The Next Web Conference&lt;/a&gt; in Amsterdam and joined a bunch of fellow programmers for another edition of the Kings of Code Hack Battle, the same kind of event as the one where &lt;a title=&quot;Story of a hack: Bring Your Own Music!&quot; href=&quot;https://www.abahgat.com/blog/story-of-a-hack-bring-your-own-music/&quot; target=&quot;_blank&quot;&gt;Bring Your Own Music&lt;/a&gt; was born.&lt;/p&gt;
&lt;p&gt;Following the usual schedule, after a brief presentation from the API partners (Spotify, SendGrid, Braintree, Deezer, Pearson, Nokia, Rebtel, Bol.com, Smart TV Alliance and LinkedIn), all the attendees started evaluating ideas about what to build.&lt;/p&gt;
&lt;blockquote class=&quot;instagram-media&quot; data-instgrm-captioned=&quot;&quot; data-instgrm-permalink=&quot;https://www.instagram.com/p/Yf2NZsjz_5/?utm_source=ig_embed&amp;#x26;utm_campaign=loading&quot; data-instgrm-version=&quot;13&quot; style=&quot; background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:540px; min-width:326px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);&quot;&gt;&lt;div style=&quot;padding:16px;&quot;&gt; &lt;a href=&quot;https://www.instagram.com/p/Yf2NZsjz_5/?utm_source=ig_embed&amp;#x26;utm_campaign=loading&quot; style=&quot; background:#FFFFFF; line-height:0; padding:0 0; text-align:center; text-decoration:none; width:100%;&quot; target=&quot;_blank&quot;&gt; &lt;div style=&quot; display: flex; flex-direction: row; align-items: center;&quot;&gt; &lt;div style=&quot;background-color: #F4F4F4; border-radius: 50%; flex-grow: 0; height: 40px; margin-right: 14px; width: 40px;&quot;&gt;&lt;/div&gt; &lt;div style=&quot;display: flex; flex-direction: column; flex-grow: 1; justify-content: center;&quot;&gt; &lt;div style=&quot; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; margin-bottom: 6px; width: 100px;&quot;&gt;&lt;/div&gt; &lt;div style=&quot; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; width: 60px;&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;padding: 19% 0;&quot;&gt;&lt;/div&gt; &lt;div style=&quot;display:block; height:50px; margin:0 auto 12px; width:50px;&quot;&gt;&lt;svg width=&quot;50px&quot; height=&quot;50px&quot; viewBox=&quot;0 0 60 60&quot; version=&quot;1.1&quot; xmlns=&quot;https://www.w3.org/2000/svg&quot; xmlns:xlink=&quot;https://www.w3.org/1999/xlink&quot;&gt;&lt;g stroke=&quot;none&quot; stroke-width=&quot;1&quot; fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;g transform=&quot;translate(-511.000000, -20.000000)&quot; fill=&quot;#000000&quot;&gt;&lt;g&gt;&lt;path d=&quot;M556.869,30.41 C554.814,30.41 553.148,32.076 553.148,34.131 C553.148,36.186 554.814,37.852 556.869,37.852 C558.924,37.852 560.59,36.186 560.59,34.131 C560.59,32.076 558.924,30.41 556.869,30.41 M541,60.657 C535.114,60.657 530.342,55.887 530.342,50 C530.342,44.114 535.114,39.342 541,39.342 C546.887,39.342 551.658,44.114 551.658,50 C551.658,55.887 546.887,60.657 541,60.657 M541,33.886 C532.1,33.886 524.886,41.1 524.886,50 C524.886,58.899 532.1,66.113 541,66.113 C549.9,66.113 557.115,58.899 557.115,50 C557.115,41.1 549.9,33.886 541,33.886 M565.378,62.101 C565.244,65.022 564.756,66.606 564.346,67.663 C563.803,69.06 563.154,70.057 562.106,71.106 C561.058,72.155 560.06,72.803 558.662,73.347 C557.607,73.757 556.021,74.244 553.102,74.378 C549.944,74.521 548.997,74.552 541,74.552 C533.003,74.552 532.056,74.521 528.898,74.378 C525.979,74.244 524.393,73.757 523.338,73.347 C521.94,72.803 520.942,72.155 519.894,71.106 C518.846,70.057 518.197,69.06 517.654,67.663 C517.244,66.606 516.755,65.022 516.623,62.101 C516.479,58.943 516.448,57.996 516.448,50 C516.448,42.003 516.479,41.056 516.623,37.899 C516.755,34.978 517.244,33.391 517.654,32.338 C518.197,30.938 518.846,29.942 519.894,28.894 C520.942,27.846 521.94,27.196 523.338,26.654 C524.393,26.244 525.979,25.756 528.898,25.623 C532.057,25.479 533.004,25.448 541,25.448 C548.997,25.448 549.943,25.479 553.102,25.623 C556.021,25.756 557.607,26.244 558.662,26.654 C560.06,27.196 561.058,27.846 562.106,28.894 C563.154,29.942 563.803,30.938 564.346,32.338 C564.756,33.391 565.244,34.978 565.378,37.899 C565.522,41.056 565.552,42.003 565.552,50 C565.552,57.996 565.522,58.943 565.378,62.101 M570.82,37.631 C570.674,34.438 570.167,32.258 569.425,30.349 C568.659,28.377 567.633,26.702 565.965,25.035 C564.297,23.368 562.623,22.342 560.652,21.575 C558.743,20.834 556.562,20.326 553.369,20.18 C550.169,20.033 549.148,20 541,20 C532.853,20 531.831,20.033 528.631,20.18 C525.438,20.326 523.257,20.834 521.349,21.575 C519.376,22.342 517.703,23.368 516.035,25.035 C514.368,26.702 513.342,28.377 512.574,30.349 C511.834,32.258 511.326,34.438 511.181,37.631 C511.035,40.831 511,41.851 511,50 C511,58.147 511.035,59.17 511.181,62.369 C511.326,65.562 511.834,67.743 512.574,69.651 C513.342,71.625 514.368,73.296 516.035,74.965 C517.703,76.634 519.376,77.658 521.349,78.425 C523.257,79.167 525.438,79.673 528.631,79.82 C531.831,79.965 532.853,80.001 541,80.001 C549.148,80.001 550.169,79.965 553.369,79.82 C556.562,79.673 558.743,79.167 560.652,78.425 C562.623,77.658 564.297,76.634 565.965,74.965 C567.633,73.296 568.659,71.625 569.425,69.651 C570.167,67.743 570.674,65.562 570.82,62.369 C570.966,59.17 571,58.147 571,50 C571,41.851 570.966,40.831 570.82,37.631&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/div&gt;&lt;div style=&quot;padding-top: 8px;&quot;&gt; &lt;div style=&quot; color:#3897f0; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:550; line-height:18px;&quot;&gt; View this post on Instagram&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;padding: 12.5% 0;&quot;&gt;&lt;/div&gt; &lt;div style=&quot;display: flex; flex-direction: row; margin-bottom: 14px; align-items: center;&quot;&gt;&lt;div&gt; &lt;div style=&quot;background-color: #F4F4F4; border-radius: 50%; height: 12.5px; width: 12.5px; transform: translateX(0px) translateY(7px);&quot;&gt;&lt;/div&gt; &lt;div style=&quot;background-color: #F4F4F4; height: 12.5px; transform: rotate(-45deg) translateX(3px) translateY(1px); width: 12.5px; flex-grow: 0; margin-right: 14px; margin-left: 2px;&quot;&gt;&lt;/div&gt; &lt;div style=&quot;background-color: #F4F4F4; border-radius: 50%; height: 12.5px; width: 12.5px; transform: translateX(9px) translateY(-18px);&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;margin-left: 8px;&quot;&gt; &lt;div style=&quot; background-color: #F4F4F4; border-radius: 50%; flex-grow: 0; height: 20px; width: 20px;&quot;&gt;&lt;/div&gt; &lt;div style=&quot; width: 0; height: 0; border-top: 2px solid transparent; border-left: 6px solid #f4f4f4; border-bottom: 2px solid transparent; transform: translateX(16px) translateY(-4px) rotate(30deg)&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;margin-left: auto;&quot;&gt; &lt;div style=&quot; width: 0px; border-top: 8px solid #F4F4F4; border-right: 8px solid transparent; transform: translateY(16px);&quot;&gt;&lt;/div&gt; &lt;div style=&quot; background-color: #F4F4F4; flex-grow: 0; height: 12px; width: 16px; transform: translateY(-4px);&quot;&gt;&lt;/div&gt; &lt;div style=&quot; width: 0; height: 0; border-top: 8px solid #F4F4F4; border-left: 8px solid transparent; transform: translateY(-4px) translateX(8px);&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt; &lt;div style=&quot;display: flex; flex-direction: column; flex-grow: 1; justify-content: center; margin-bottom: 24px;&quot;&gt; &lt;div style=&quot; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; margin-bottom: 6px; width: 224px;&quot;&gt;&lt;/div&gt; &lt;div style=&quot; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; width: 144px;&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;p style=&quot; color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;&quot;&gt;&lt;a href=&quot;https://www.instagram.com/p/Yf2NZsjz_5/?utm_source=ig_embed&amp;#x26;utm_campaign=loading&quot; style=&quot; color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;&quot; target=&quot;_blank&quot;&gt;A post shared by Alessandro Bahgat (@abahgat)&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;/blockquote&gt; 
&lt;p&gt;I teamed up with &lt;a href=&quot;http://alexdeleon.name/&quot;&gt;Alexander&lt;/a&gt;, a friend of mine I already had the chance to work with back in the days when I when I was consulting.&lt;/p&gt;
&lt;p&gt;Having LinkedIn among the sponsors seemed to encourage us to build serious applications for serious professionals, but after discarding a few alternatives that would have been better projects for a Startup Weekend than a hackathon, we decided to take the opposite direction: building the silliest possible thing with the APIs we had access to.&lt;/p&gt;
&lt;p&gt;We eventually decided to work on a game and tried to build a Space Invaders clone that would let you throw paper balls at your professional connections.&lt;/p&gt;
&lt;p&gt;After some research, we found a well written Space Invaders implementation on GitHub (thanks &lt;a href=&quot;https://github.com/Calamari&quot;&gt;Calamari&lt;/a&gt;) and we started adding the silliness to it.&lt;/p&gt;
&lt;p&gt;The first day we focused on getting the game to work as we expected:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;each invader would be one of your connections on LinkedIn,&lt;/li&gt;
&lt;li&gt;a Boss would spawn every now and then,&lt;/li&gt;
&lt;li&gt;the game would have some sort of soundtrack (thanks Deezer),&lt;/li&gt;
&lt;li&gt;while in “Boss mode”, the game would have a distinctive appearance (blinking red background and a different theme song).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The second day we turned our attention to features that were just fun to build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a coin slot where players could buy more coins with their own credit card (API courtesy of Braintree),&lt;/li&gt;
&lt;li&gt;an easter egg we planned to use in the demo: attendees could spawn the Boss by sending email to an address we set up for the occasion (thanks Sendgrid).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The video below (3:11) shows the major changes the application went through. It was created by replaying significant entries in the commit log and recording what the game looked like at that time.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;[Embedded content not available in RSS — &lt;a href=&quot;https://www.abahgat.com/blog/presenting-professional-invaders&quot;&gt;view on site&lt;/a&gt;]&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We approached the deadline with only one objective: making people laugh. Despite some technical issues (amusing at a tech conference), we managed to demo our hack and people seemed to have liked it: the guys from Sendgrid even decided to award us with a prize 🙂&lt;/p&gt;
&lt;p&gt;You play the game &lt;a title=&quot;Play Professional Invaders&quot; href=&quot;http://alexdeleon.github.io/professional_invaders/&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;here&lt;/strong&gt;&lt;/a&gt;. This version is a slightly different from what we presented at the hack battle, since we decided to keep only the features that made sense if we were to offer it online.&lt;/p&gt;
&lt;p&gt;We hope you’ll have as much fun playing it as we had putting it together!&lt;/p&gt;</content:encoded></item><item><title>What Van Gogh can teach us about persistence</title><link>https://www.abahgat.com/blog/what-van-gogh-can-teach-us-about-persistence</link><guid isPermaLink="true">https://www.abahgat.com/blog/what-van-gogh-can-teach-us-about-persistence</guid><description>I visited the Van Gogh museum in Amsterdam recently and, to my surprise, I left the exposition having learned something that matters beyond art.</description><pubDate>Mon, 04 Mar 2013 13:33:43 GMT</pubDate><content:encoded>&lt;p&gt;I visited the Van Gogh museum in Amsterdam recently and, to my surprise, I left the exposition having learned something that matters beyond art.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;According to his &lt;a href=&quot;http://en.wikipedia.org/wiki/Van_Gogh&quot;&gt;biography&lt;/a&gt;,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Van Gogh began to draw as a child, and he continued to draw throughout the years that led up to his decision to become an artist. He did not begin painting until his late twenties, completing many of his best-known works during the last two years of his life. In just over a decade, he produced more than 2,100 artworks, consisting of &lt;a href=&quot;http://en.wikipedia.org/wiki/List_of_works_by_Vincent_van_Gogh&quot; title=&quot;List of works by Vincent van Gogh&quot;&gt;860 oil paintings&lt;/a&gt; and more than &lt;a href=&quot;http://en.wikipedia.org/wiki/Drawings,_water-colours_and_prints_by_Vincent_van_Gogh&quot; title=&quot;Drawings, water-colours and prints by Vincent van Gogh&quot;&gt;1,300 watercolors, drawings, sketches and prints&lt;/a&gt;. […]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Before focusing on painting, he worked as an art dealer, teacher and missionary. It wasn’t until he was 32 that he painted his first major work.&lt;/p&gt;
&lt;p&gt;He did not have the fortune of being recognized as a talented artist in his young age like Michelangelo and others and yet still he did not let go of his desire of becoming a painter. The thing that strikes most of the museum is the quantity of studies and sketches Van Gogh made throughout his live in order to improve his skills. &lt;span style=&quot;font-style:inherit;line-height:1.625;&quot;&gt;He wanted to paint so much that &lt;/span&gt;&lt;strong style=&quot;font-style:inherit;line-height:1.625;&quot;&gt;he kept practicing&lt;/strong&gt;&lt;span style=&quot;font-style:inherit;line-height:1.625;&quot;&gt; and put so much effort in improving that it eventually paid off: he is now remembered as the author of &lt;/span&gt;&lt;span style=&quot;font-style:inherit;line-height:1.625;&quot;&gt;dozens of the most renown paintings of the history of art.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In an age where the reference point to define an accomplishment is starting a company at 16 and become a billionaire at 22, we risk underestimating the value of persistence. Sure, he did not reach fame and success while he was alive, and his life was not what you would define “happy”. But if he had quit because he was not an accomplished painter in his young age, art now would certainly be very different from what we know.&lt;/p&gt;
&lt;p&gt;The works of V&lt;span style=&quot;font-style:inherit;line-height:1.625;&quot;&gt;an Gogh are a proof that there is no such thing as &lt;/span&gt;&lt;strong style=&quot;font-style:inherit;line-height:1.625;&quot;&gt;being too late to accomplish something remarkable&lt;/strong&gt;&lt;span style=&quot;font-style:inherit;line-height:1.625;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;</content:encoded></item><item><title>Prettier source code on WordPress.com</title><link>https://www.abahgat.com/blog/prettier-source-code-on-wordpress-com</link><guid isPermaLink="true">https://www.abahgat.com/blog/prettier-source-code-on-wordpress-com</guid><description>Posting source code on WordPress.com is quite simple: the platform already provides an extremely easy to use shortcode called sourcecode, based on a fairly flexible syntax highlighter plugin. By looki...</description><pubDate>Mon, 21 Jan 2013 10:34:50 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://www.abahgat.com/img/wp-uploads/2013/01/screen-shot-2013-01-16-at-10-44-59-pm.png&quot; alt=&quot;Formatted source code&quot; loading=&quot;lazy&quot;/&gt;&lt;/p&gt;
&lt;p&gt;Posting source code on WordPress.com is quite simple: the platform already provides an extremely easy to use shortcode called &lt;a href=&quot;http://en.support.wordpress.com/code/posting-source-code/&quot;&gt;&lt;code&gt;sourcecode&lt;/code&gt;&lt;/a&gt;, based on a fairly flexible syntax highlighter plugin. By looking at the examples in the &lt;a href=&quot;http://en.support.wordpress.com/code/posting-source-code/&quot;&gt;documentation page&lt;/a&gt;, however, it is evident that the default styling used to render sources is quite old-fashioned and does not fit most modern themes.&lt;/p&gt;
&lt;p&gt;While the shortcode offers options to allow users to control many options of the rendering, it does not allow us to configure colors, fonts and size (the default size is so tiny that it is barely readable on high-resolution screens).&lt;/p&gt;
&lt;p&gt;When I was writing the previous technical post, I did some investigations to figure out what options are available to post more readable sources if your blog is hosted on WordPress.com and I found out there are basically two alternatives.&lt;/p&gt;
&lt;h1 id=&quot;embedding-gists&quot;&gt;Embedding Gists&lt;/h1&gt;
&lt;p&gt;The easiest option is to rely on &lt;a href=&quot;https://gist.github.com/&quot;&gt;Gist&lt;/a&gt; – GitHub’s tool for sharing snippets of code – which offers an extremely easy way to embed code in your blog. Just create a new snippet (gist) there and &lt;a href=&quot;http://en.support.wordpress.com/gist/&quot;&gt;follow the instructions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Unfortunately, the gist embed shortcode available on WordPress.com is less flexible than what you would get if you installed it as a &lt;a href=&quot;http://wordpress.org/extend/plugins/embed-github-gist/&quot;&gt;plugin&lt;/a&gt; on your own instance of WordPress, but it will be enough for most cases.&lt;/p&gt;
&lt;table&gt;&lt;tr&gt;&lt;th&gt;&lt;p&gt;Pros&lt;/p&gt;&lt;/th&gt;&lt;th&gt;&lt;p&gt;Cons&lt;/p&gt;&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p&gt;Easy to embed source&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p&gt;Suitable for posts with a few (long) code snippets&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p&gt;Code looks good and is readable&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p&gt;Does not always work perfectly with search engines&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p&gt;Easy for readers to access raw code&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p&gt;Does not work with RSS and posts over email&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h1 id=&quot;styling-source-code-by-customizing-your-css&quot;&gt;Styling source code by customizing your CSS&lt;/h1&gt;
&lt;p&gt;While Gists work great most of the time, they are a pain to create and maintain if you are working on a post that should include multiple short snippets of code. In that case, the amount of bookkeeping you have to do is significant (you will have to create and link many small chunks of code) and you may want to be able to manage your code right within the post.&lt;/p&gt;
&lt;p&gt;In that case, it may be more practical to fix the CSS theme used by the syntax highlight plugin to make it look post-2010. If you set your own custom CSS on WordPress.com, it will be supposed to be included as the last one to allow you to redefine the styles specified by the theme you are using.&lt;/p&gt;
&lt;p&gt;Unfortunately, the CSS used by the syntax highlight module was clearly not written with extensibility in mind, but quite the opposite:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;all the style declarations it includes make use of &lt;code&gt;!important&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;the plugin will dynamically include its own CSS as the last item in the &lt;code&gt;head&lt;/code&gt; node, meaning that it will have preference on the custom one you define.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This makes sense in the original context – the original syntax highlighter offered several themes you could choose from by including different stylesheets, but that feature is not available on WordPress.com – but will make your life more difficult. You will need to add &lt;code&gt;!important&lt;/code&gt; to &lt;strong&gt;all the CSS declarations you redefine&lt;/strong&gt; and you will need to use CSS selectors that are &lt;strong&gt;more specific&lt;/strong&gt; than the ones used by the plugin. You will be able to see the final result at the end of this post.&lt;/p&gt;
&lt;p&gt;WordPress’s syntax highlight is not perfect, and some things are still quite annoying (e.g. line numbers get in the way if you try selecting and copying source code). Most issues could be addressed by upgrading the plugin to use version 3 of &lt;a href=&quot;http://alexgorbatchev.com/SyntaxHighlighter/&quot;&gt;SyntaxHighlighter&lt;/a&gt; instead of the outdated version that is in use now, but it is something you will not be able to control unless the folks at Automattic decide to update it.&lt;/p&gt;
&lt;table&gt;&lt;tr&gt;&lt;th&gt;&lt;p&gt;Pros&lt;/p&gt;&lt;/th&gt;&lt;th&gt;&lt;p&gt;Cons&lt;/p&gt;&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p&gt;It is necessary to have access to Custom CSS (which is a paid feature)&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p&gt;Hard to copy sources without including line numbers (unless you disable them)&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p&gt;Access to advanced features (highlight lines, toggle line number display)&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p&gt;Search engines index sources with the post content&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p&gt;Source can be styled according to preference&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p&gt;Getting your CSS applied correctly can be difficult (but you can start from &lt;a href=&quot;https://gist.github.com/4464280&quot;&gt;here&lt;/a&gt;)&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h1 id=&quot;what-did-i-choose&quot;&gt;What did I choose?&lt;/h1&gt;
&lt;p&gt;Here is the &lt;a href=&quot;https://gist.github.com/4464280&quot;&gt;stylesheet&lt;/a&gt; (embedded as a Gist) I am currently using  on this blog, based on the pygments theme used to style code at &lt;a href=&quot;http://docs.python.org/&quot; title=&quot;docs.python.org&quot;&gt;docs.python.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;[Embedded content not available in RSS — &lt;a href=&quot;https://www.abahgat.com/blog/prettier-source-code-on-wordpress-com&quot;&gt;view on site&lt;/a&gt;]&lt;/em&gt;&lt;/p&gt; 
&lt;p&gt;You can can see what the final result looks like &lt;del&gt;in this post about &lt;a href=&quot;https://www.abahgat.com/post/2013-01-07-user-authentication-with-webapp2-on-google-app-engine/index.md&quot; title=&quot;User authentication with webapp2 on Google App Engine&quot;&gt;User authentication with webapp2 on Google App Engine&lt;/a&gt;&lt;/del&gt; and in the image at the beginning of this post.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update: I since migrated this blog to a new system, and am using a completely different way to render source code.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title>User authentication with webapp2 on Google App Engine</title><link>https://www.abahgat.com/blog/user-authentication-with-webapp2-on-google-app-engine</link><guid isPermaLink="true">https://www.abahgat.com/blog/user-authentication-with-webapp2-on-google-app-engine</guid><description>Google App Engine for Python ships with the capability to manage user accounts without the need of any additional library. This functionality is, however, insufficiently documented. This post is a step-by-step tutorial addressing user registration, login, password reset and a few other details.</description><pubDate>Mon, 07 Jan 2013 07:57:26 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;http://webapp-improved.appspot.com/&quot;&gt;&lt;em&gt;webapp2&lt;/em&gt;&lt;/a&gt; framework on Google App Engine for Python 2.7 is definitely a step forward from the original &lt;em&gt;webapp&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Despite the increase in flexibility and functionality, however, there are a few items that are still more laborious than in other frameworks. The most notable aspect is &lt;strong&gt;user account management&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Unsurprisingly, since it is meant to run on Google’s App Engine, using Google Accounts with webapp2 takes one line of code. OpenID authentication, while still defined &lt;em&gt;experimental&lt;/em&gt;, is almost trivial to implement as well. There are some open source projects like &lt;a href=&quot;https://github.com/crhym3/simpleauth&quot;&gt;SimpleAuth&lt;/a&gt; that attempt to offer a standard and unified API to handle signing in with Google, OAuth and OpenID accounts.&lt;/p&gt;
&lt;p&gt;While it generally makes sense to offer support for authentication through popular services – it decreases friction for new users to try a service – in some cases users may prefer having a special login to access your application.&lt;/p&gt;
&lt;p&gt;As experience teaches us, managing passwords securely is not a trivial task, and users legitimately expect application developers to take all the necessary measures to &lt;a href=&quot;https://www.abahgat.com/blog/are-our-passwords-safe/&quot; title=&quot;Are our passwords safe?&quot;&gt;protect their passwords&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since this is a use case that has to be considered countless time, there is significant value in using library functions to handle user accounts.&lt;/p&gt;
&lt;p&gt;Here is how to do that using the functionalities embedded in the &lt;code&gt;webapp2_extras&lt;/code&gt; package that is distributed with all standard installations of App Engine for Python 2.7.&lt;/p&gt;

&lt;h2 id=&quot;basics-and-prerequisites&quot;&gt;Basics and prerequisites&lt;/h2&gt;
&lt;p&gt;Our main interface when dealing with authentication is &lt;a href=&quot;http://webapp-improved.appspot.com/api/webapp2_extras/auth.html#module-webapp2_extras.auth&quot;&gt;&lt;code&gt;webapp2_extras.auth&lt;/code&gt;&lt;/a&gt;. This module leverages the rest of webapp2’s infrastructure to offer us a simpler way to manage user authentication.&lt;/p&gt;
&lt;p&gt;In particular, it relies on&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://webapp-improved.appspot.com/api/webapp2_extras/security.html#module-webapp2_extras.security&quot;&gt;&lt;code&gt;webapp2_extras.security&lt;/code&gt;&lt;/a&gt; to handle password hashing (so that passwords are never stored in clear text) and random string generation;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://webapp-improved.appspot.com/api/webapp2_extras/sessions.html#module-webapp2_extras.sessions&quot;&gt;&lt;code&gt;webapp2_extras.sessions&lt;/code&gt;&lt;/a&gt; to identify requests coming from the same user as part of a client-server conversation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since we are implementing our own user database, we will need to define a custom model class to represent users in our application. The auth framework described here works under the assumption that this model class defines the following instance method:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;get_id(self)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;and the following class methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;get_by_auth_token(cls, user_id, token)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_by_auth_password(cls, auth_id, password)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;create_auth_token(cls, user_id)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;delete_auth_token(cls, user_id, token)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The specific role of each of those methods is described &lt;a href=&quot;http://webapp-improved.appspot.com/tutorials/auth.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In addition to that, if you are interested in using the code for password reset provided as part of this tutorial, you will also need to implement the following class method:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;get_by_auth_token(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cls&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, user_id, token, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;subject&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;auth&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For each request, our application will then be able to do the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;read a cookie to figure out whether the current belongs to an existing session,&lt;/li&gt;
&lt;li&gt;if a cookie is found, read an authentication from it and load the corresponding session (if present) from the session store backend,&lt;/li&gt;
&lt;li&gt;loads some cached user information from the session.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We will be able to tune some details of the process via configuration options.&lt;/p&gt;
&lt;h2 id=&quot;extending-the-default-user-model&quot;&gt;Extending the default User model&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;webapp2&lt;/em&gt; already contains a reference &lt;a href=&quot;http://webapp-improved.appspot.com/api/webapp2_extras/appengine/auth/models.html#webapp2_extras.appengine.auth.models.User&quot;&gt;User&lt;/a&gt; model for Google App Engine that uses &lt;a href=&quot;https://developers.google.com/appengine/docs/python/ndb/overview&quot;&gt;NDB&lt;/a&gt; for storage. If you are willing to use that, you will find it good enough for most of the needs you may have.&lt;/p&gt;
&lt;p&gt;In particular,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it is an &lt;a href=&quot;https://developers.google.com/appengine/docs/python/datastore/expandoclass&quot;&gt;&lt;strong&gt;Expando&lt;/strong&gt;&lt;/a&gt; model – it can include properties that were not specified as part of the class definition but are added at run-time, so it will not be a problem if your application needs to store some specific user information. New properties are indexed by default, so queries should still be fast.&lt;/li&gt;
&lt;li&gt;it allows you to set uniqueness constraints – while NDB does not support unique properties by default, webapp2’s User model uses a custom mechanism to allow to allow that.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Oddly enough – since the User class is clearly well designed – the reference implementation does not offer any method to update the password for a given user once it has been set. Because of that, it is a good idea to extend the User model to add a &lt;code&gt;set_password&lt;/code&gt; method that will have the responsibility to securely hash passwords using the &lt;em&gt;security&lt;/em&gt; module.&lt;/p&gt;
&lt;p&gt;You can find an example User model implementation below: it will contain the password setter and the &lt;code&gt;get_by_auth_token&lt;/code&gt; class method (modeled after the one already provided by webapp2) that has been introduced in the previous section.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; webapp2_extras.appengine.auth.models&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; google.appengine.ext &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ndb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; webapp2_extras &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; security&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;webapp2_extras&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;appengine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;auth&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;models&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; set_password&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, raw_password):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;Sets the password for the current user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    :param raw_password:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        The raw password which will be hashed and stored&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.password &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; security.generate_password_hash(raw_password, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;12&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  @&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;classmethod&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get_by_auth_token&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(cls, user_id, token, subject&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;auth&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;Returns a user object based on a user ID and token.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    :param user_id:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        The user_id of the requesting user.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    :param token:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        The token string to be verified.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    :returns:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        A tuple ``(User, timestamp)``, with a user object and&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        the token timestamp, or ``(None, None)`` if both were not found.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    token_key &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; cls&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.token_model.get_key(user_id, subject, token)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    user_key &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ndb.Key(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cls&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, user_id)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # Use get_multi() to save a RPC call.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    valid_token, user &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ndb.get_multi([token_key, user_key])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; valid_token &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;and&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; user:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        timestamp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(time.mktime(valid_token.created.timetuple()))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; user, timestamp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;None&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;setting-up-the-configuration&quot;&gt;Setting up the configuration&lt;/h2&gt;
&lt;p&gt;With what we have seen so far, we are now able to configure our application. We will need to set some properties as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;config &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &amp;#39;webapp2_extras.auth&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &amp;#39;user_model&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;models.User&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &amp;#39;user_attributes&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &amp;#39;webapp2_extras.sessions&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &amp;#39;secret_key&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;YOUR_SECRET_KEY&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In particular,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;user_model&lt;/code&gt; is the name of the custom User model class we described earlier,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;user_attributes&lt;/code&gt; is a list of attributes in the User model that will be cached in the session. Ideally, frequently accessed properties should be stored here. The full User model will be accessible by querying the datastore.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;secret_key&lt;/code&gt; is the key used to secure the hash signature calculation for session cookies.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;creating-a-base-handler-class&quot;&gt;Creating a base handler class&lt;/h2&gt;
&lt;p&gt;Before writing the actual handlers that will implement the business logic to sign up and authenticate users we will group some utility functions in a base handler class, which will be extended by all the following handler classes.&lt;/p&gt;
&lt;p&gt;This will ensure that all handlers will inherit a set of useful utility functions and properties to access user data and infrastructure classes, but also ensures that all session data is properly saved on each request (see &lt;code&gt;dispatch&lt;/code&gt;).&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BaseHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;webapp2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;RequestHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  @webapp2.cached_property&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; auth&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;Shortcut to access the auth instance as a property.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; auth.get_auth()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  @webapp2.cached_property&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; user_info&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;Shortcut to access a subset of the user attributes that are stored&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    in the session.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    The list of attributes to store in the session is specified in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      config[&amp;#39;webapp2_extras.auth&amp;#39;][&amp;#39;user_attributes&amp;#39;].&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    :returns&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      A dictionary with most user information&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.auth.get_user_by_session()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  @webapp2.cached_property&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; user&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;Shortcut to access the current logged in user.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    Unlike user_info, it fetches information from the persistence layer and&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    returns an instance of the underlying model.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    :returns&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      The instance of the user model associated to the logged in user.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    u &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.user_info&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.user_model.get_by_id(u[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;user_id&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; u &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  @webapp2.cached_property&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; user_model&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;Returns the implementation of the user model.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    It is consistent with config[&amp;#39;webapp2_extras.auth&amp;#39;][&amp;#39;user_model&amp;#39;], if set.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.auth.store.user_model&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  @webapp2.cached_property&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; session&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &amp;quot;&amp;quot;&amp;quot;Shortcut to access the current session.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.session_store.get_session(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;backend&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;datastore&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; render_template&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, view_filename, params&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{}):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    user &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.user_info&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    params[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    path &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; os.path.join(os.path.dirname(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__file__&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;views&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, view_filename)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.response.out.write(template.render(path, params))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; display_message&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, message):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;Utility function to display a template with a simple message.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    params &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &amp;#39;message&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: message&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.render_template(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;message.html&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, params)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # this is needed for webapp2 sessions to work&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; dispatch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # Get a session store for this request.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.session_store &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sessions.get_store(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.request)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;          # Dispatch the request.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          webapp2.RequestHandler.dispatch(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      finally&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;          # Save all sessions.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;          self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.session_store.save_sessions(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.response)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;not-prose flex gap-3 p-4 rounded-lg border bg-surface border-border-default text-default  alert alert-note&quot;&gt; &lt;svg width=&quot;1em&quot; height=&quot;1em&quot; class=&quot;w-5 h-5 flex-shrink-0 mt-2&quot; data-icon=&quot;tabler:info-circle&quot;&gt;   &lt;symbol id=&quot;ai:tabler:info-circle&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;g fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path d=&quot;M3 12a9 9 0 1 0 18 0a9 9 0 0 0-18 0m9-3h.01&quot;/&gt;&lt;path d=&quot;M11 12h1v4h1&quot;/&gt;&lt;/g&gt;&lt;/symbol&gt;&lt;use href=&quot;#ai:tabler:info-circle&quot;&gt;&lt;/use&gt;  &lt;/svg&gt; &lt;div&gt;  &lt;div class=&quot;text-sm prose-p:my-0 prose-p:first:mt-0 prose-p:last:mb-0 [&amp;&gt;p]:my-0&quot;&gt; &lt;p&gt;You may want redefine the view-related methods to something different if you use a different templating engine.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt;
&lt;h2 id=&quot;registration-create-new-users&quot;&gt;Registration: create new users&lt;/h2&gt;
&lt;p&gt;If we are using the model class discussed in the previous sections, in order to create a new User we just need to call the &lt;code&gt;create_user&lt;/code&gt; method.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; SignupHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BaseHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.render_template(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;signup.html&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; post&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    user_name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.request.get(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;username&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    email &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.request.get(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;email&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.request.get(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    password &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.request.get(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;password&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    last_name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.request.get(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;lastname&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    unique_properties &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;email_address&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    user_data &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.user_model.create_user(user_name,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      unique_properties,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;      email_address&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;email, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;password_raw&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;password,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;      last_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;last_name, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verified&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; user_data[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]: &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#user_data is a tuple&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.display_message(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;Unable to create user for email &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because of &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        duplicate keys &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; %&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (user_name, user_data[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    user &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; user_data[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    user_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; user.get_id()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    token &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.user_model.create_signup_token(user_id)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    verification_url &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.uri_for(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;verification&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;v&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;user_id&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;user_id,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;      signup_token&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;token, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;_full&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    msg &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;Send an email to user in order to verify their address. &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          They will be able to do so by visiting  &amp;lt;a href=&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{url}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{url}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;lt;/a&amp;gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.display_message(msg.format(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;verification_url))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;create_user&lt;/code&gt; will accept the following parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;an &lt;em&gt;authentication id&lt;/em&gt;: the token (such as a username or an email address) users will use to identify themselves when trying to access our application. While users can have multiple authentication ids, only one is allowed at creation.&lt;/li&gt;
&lt;li&gt;a list of unique properties (in this case, &lt;code&gt;email_address&lt;/code&gt;): if this is specified, webapp2 will not allow us to create new users if others share the same values for the properties in this list.&lt;/li&gt;
&lt;li&gt;name/value pairs that are going to be set as properties of the resulting User model. If you want to add your own fields, this where to do it. If a parameter called &lt;code&gt;password_raw&lt;/code&gt; is present, it will be assumed to be the user password that will be used for authentication; webapp2 will hash it and store the hash the password field: we do not want to store passwords in clear text, do we?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We also create a &lt;em&gt;signup token&lt;/em&gt; and associate it to the newly created account: we will use to confirm the email address that has been provided during registration.&lt;/p&gt;
&lt;p&gt;Note that we are accessing the model as &lt;code&gt;self.user_model&lt;/code&gt; rather than calling it directly, so that we are free to change which implementation to use by updating the application configuration.&lt;/p&gt;
&lt;h2 id=&quot;login-and-logout&quot;&gt;Login and logout&lt;/h2&gt;
&lt;p&gt;If users are created as in the previous session, login is quite simple: the &lt;code&gt;get_user_by_password&lt;/code&gt; method can be used to retrieve a user by their credentials. In addition to the user credentials, the method accepts some additional parameters. The one we care about (and the only one we use here) is &lt;code&gt;remember&lt;/code&gt;: when set to &lt;code&gt;True&lt;/code&gt;, the cookie used to identify the session is saved as persistent and the browser will keep it even after the user will close its window.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; LoginHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BaseHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;._serve_page()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; post&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    username &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.request.get(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;username&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    password &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.request.get(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;password&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      u &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.auth.get_user_by_password(username, password, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;remember&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.redirect(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.uri_for(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;home&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    except&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (InvalidAuthIdError, InvalidPasswordError) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; e:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      logging.info(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;Login failed for user &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because of &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, username, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(e))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;._serve_page(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; _serve_page&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, failed&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    username &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.request.get(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;username&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    params &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &amp;#39;username&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: username,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &amp;#39;failed&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: failed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.render_template(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;login.html&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, params)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The implementation above renders the login form when the request comes via GET and processes the credentials upon POST. When authentication fails it renders the login form and passes the username to the template so that the corresponding field can be pre-filled.&lt;/p&gt;
&lt;p&gt;Implementing logout is even simpler: it is sufficient to get rid of the user session.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; LogoutHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BaseHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.auth.unset_session()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.redirect(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.uri_for(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;home&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;email-confirmation-and-password-reset&quot;&gt;Email confirmation and password reset&lt;/h2&gt;
&lt;p&gt;Signup tokens are one of the undocumented features of webapp2 (and they may possibly subject to change), but they can be quite handy when implementing a flow to confirming email addresses or recover passwords.&lt;/p&gt;
&lt;p&gt;As mentioned before, the webapp2 uses authentication tokens to identify users after they logged in: they are meant to be securely shared by a client and the server and exchanged when a client needs to prove its identity.&lt;/p&gt;
&lt;p&gt;As you probably imagine, this mechanism can be generalized to handle email confirmations and password resets: when websites send us an activation link after a registration, the URL usually contain their equivalent of signup tokens.&lt;/p&gt;
&lt;p&gt;webapp2 sets a &lt;code&gt;subject&lt;/code&gt; property for each of the tokens it generates, so the only difference between auth token and signup token is the value for that property. So, why do we want to use signup tokens?&lt;/p&gt;
&lt;p&gt;Setting a different value for that property allows us to partition tokens by their purpose: we can then implement useful features as &lt;em&gt;deleting all the password reset tokens that have not been used in 48 hours&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Here is a sample verification handler that is able to process email verification links:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; VerificationHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BaseHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;args, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kwargs):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    user &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    user_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; kwargs[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;user_id&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    signup_token &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; kwargs[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;signup_token&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    verification_type &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; kwargs[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;type&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # it should be something more concise like&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # self.auth.get_user_by_token(user_id, signup_token&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # unfortunately the auth interface does not (yet) allow to manipulate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # signup tokens concisely&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    user, ts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.user_model.get_by_auth_token(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(user_id), signup_token,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &amp;#39;signup&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; user:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      logging.info(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;Could not find any user with id &amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot; signup token &amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        user_id, signup_token)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.abort(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;404&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # store user data in the session&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.auth.set_session(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.auth.store.user_to_dict(user), &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;remember&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; verification_type &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;v&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # remove signup token, we don&amp;#39;t want users to come back with an old link&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.user_model.delete_signup_token(user.get_id(), signup_token)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; user.verified:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        user.verified &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        user.put()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.display_message(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;User email address has been verified.&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    elif&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; verification_type &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;p&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # supply user to the page&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      params &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &amp;#39;user&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: user,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &amp;#39;token&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: signup_token&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.render_template(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;resetpassword.html&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, params)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      logging.info(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;verification type not supported&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.abort(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;404&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This handler is meant to be used with a route using a template that matches URLs like &lt;code&gt;/v/USERID-TOKEN&lt;/code&gt;. You can configure it as follows (please refer to the sample code at the end of this article for the full routes configuration):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;webapp2.Route(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;/&amp;lt;type:v|p&amp;gt;/&amp;lt;user_id:\d+&amp;gt;-&amp;lt;signup_token:.+&amp;gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;  handler&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;VerificationHandler, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;verification&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two minor notes on this item:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;For increased security, we may require users to enter their password before authenticating them.&lt;/li&gt;
&lt;li&gt;Ideally, we may want to use a different subject for email confirmation and password reset tokens.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;ensure-users-are-logged-in&quot;&gt;Ensure users are logged in&lt;/h2&gt;
&lt;p&gt;Now that everything else is in place, we can decide whether users are allowed to access certain resources depending on their logged in state.&lt;/p&gt;
&lt;p&gt;The following decorator can be used to annotate handler methods that require users to be logged in.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; user_required&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(handler):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    Decorator that checks if there&amp;#39;s a user associated with the current session.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    Will also fail if there&amp;#39;s no session present.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; check_login&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;args, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kwargs):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    auth &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.auth&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; auth.get_user_by_session():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.redirect(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.uri_for(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;login&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;abort&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; handler(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;args, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kwargs)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; check_login&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just placing &lt;code&gt;@user_required&lt;/code&gt;, as in the following example, before those methods will ensure that anonymous users will be directed to a login page when attempting to go through the annotated handler method.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AuthenticatedHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BaseHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  @user_required&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.render_template(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;authenticated.html&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;finishing-touches&quot;&gt;Finishing touches&lt;/h2&gt;
&lt;p&gt;Before actually using this in code in production, there is at least one task we should take care of: calls that send passwords (like login, signup, password reset) should be using &lt;code&gt;https&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is quite easy to do and the documentation is quite straightforward: just follow the &lt;a href=&quot;https://developers.google.com/appengine/docs/python/config/appconfig#Secure_URLs&quot;&gt;instructions here&lt;/a&gt; and you will be all set.&lt;/p&gt;
&lt;p&gt;Your app.yaml file should include the following once you are done:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;handlers&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;/signup&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    script&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;main.app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    secure&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;always&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;/login&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    script&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;main.app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    secure&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;always&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;/forgot&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    script&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;main.app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    secure&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;always&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    script&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;main.app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;libraries&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;webapp2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    version&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;2.5.1&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course, we will also need some views to be able to use this application. While this is the typical task that is left as an exercise to the reader, the example implementation you will find in the following section will contain a fully working application you can play with.&lt;/p&gt;
&lt;h2 id=&quot;reference-code&quot;&gt;Reference code&lt;/h2&gt;
&lt;p&gt;You can find a ready to use application skeleton &lt;a href=&quot;https://github.com/abahgat/webapp2-user-accounts&quot;&gt;&lt;strong&gt;on GitHub&lt;/strong&gt;&lt;/a&gt;. Feel free to play with it to experiment the full flow described in this post, use it to bootstrap your project and to improve on it. Just post a comment if you have any question or suggestion.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;If you are interested in learning more on App Engine, you may want to check out &lt;a href=&quot;http://www.amazon.com/gp/product/144939826X/ref=as_li_ss_tl?ie=UTF8&amp;#38;camp=1789&amp;#38;creative=390957&amp;#38;creativeASIN=144939826X&amp;#38;linkCode=as2&amp;#38;tag=theunti-20&quot;&gt;Programming Google App Engine&lt;/a&gt; (&lt;a href=&quot;http://www.amazon.com/gp/product/B009OV6IZ2/ref=as_li_ss_tl?ie=UTF8&amp;#38;camp=1789&amp;#38;creative=390957&amp;#38;creativeASIN=B009OV6IZ2&amp;#38;linkCode=as2&amp;#38;tag=theunti-20&quot;&gt;Kindle edition&lt;/a&gt;)&lt;/p&gt;</content:encoded></item><item><title>Beyond keyboard shortcuts</title><link>https://www.abahgat.com/blog/beyond-keyboard-shortcuts</link><guid isPermaLink="true">https://www.abahgat.com/blog/beyond-keyboard-shortcuts</guid><description>In the age of touch devices, some days it seems like a day will come when we will not have to use a keyboard to interact with computers. A significant part of our relationship with technology passes t...</description><pubDate>Wed, 19 Dec 2012 08:13:57 GMT</pubDate><content:encoded>&lt;p&gt;In the age of touch devices, some days it seems like a day will come when we will not have to use a keyboard to interact with computers. A significant part of our relationship with technology passes through interfaces that were not common a decade ago: touch screens, accelerometers, cameras and microphones.&lt;/p&gt;
&lt;p&gt;Keyboards, however, are still the most efficient way to interact with a computer, and not only for typing email. From code editors like Emacs to advanced image manipulation tools like Photoshop, it is no wonder that most advanced programs can be controlled more efficiently by means of keyboard shortcuts.&lt;/p&gt;
&lt;p&gt;The learning curve for shortcuts is generally quite steep: while some of them are standard across programs and can be easily guessed, most shortcuts are complex abstract key combinations (as &lt;code&gt;⌘-Control-Shift-3&lt;/code&gt; on Mac, that takes a screenshot to the clipboard) and therefore not easy to remember.&lt;/p&gt;
&lt;p&gt;Some applications, however, are introducing smarter ways to control our computers using a keyboard.&lt;/p&gt;
&lt;h2 id=&quot;alfred-mac-at-your-fingertips&quot;&gt;Alfred: Mac at your fingertips&lt;/h2&gt;
&lt;p&gt;Mac OS has a long tradition of exposing keyboard shortcuts that allow power users to perform common tasks more efficiently. Introducing &lt;a href=&quot;http://en.wikipedia.org/wiki/Spotlight_(software)&quot;&gt;Spotlight&lt;/a&gt; was one more step towards keyboard driven interaction, as it allows to access applications, files and many other items by typing in a text box. &lt;a href=&quot;http://www.alfredapp.com/&quot;&gt;Alfred&lt;/a&gt;, takes this approach to a new level: just like with Spotlight, pressing a combination of keys brings up a text box where you can enter anything from file names to simple commands (like &lt;em&gt;empty trash&lt;/em&gt;, &lt;em&gt;shutdown&lt;/em&gt;, and many more).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.abahgat.com/img/wp-uploads/2012/12/a8ebd466-0c44-4365-b359-51a1c62611d6.png&quot; alt=&quot;Alfred in action&quot; loading=&quot;lazy&quot;/&gt;&lt;/p&gt;
&lt;p&gt;The advantage over the built-in solution is that those commands allow you to control many functionalities that traditionally would have required you to click through several items and menus. Alfred will even adjust its behavior depending on the way you use it: it will present you first the options you select the most.&lt;/p&gt;
&lt;h2 id=&quot;sublime-text-controlling-a-text-editor-through-text-commands&quot;&gt;Sublime Text: controlling a text editor through text commands&lt;/h2&gt;
&lt;p&gt;When working with a text editor, typing on a keyboard is – by definition – the main means of interaction and so these programs traditionally rely on keyboard shortcuts more than any other. The over 2000 keyboard shortcuts available in Emacs are the most significant evidence of the level of complexity shortcuts can reach: it is impossible to master all the key sequences, only the most common ones.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.sublimetext.com/&quot;&gt;Sublime Text,&lt;/a&gt; one of the best editors available today, takes an innovative approach to keyboard interaction: instead of requiring users to memorize arbitrary key sequences, it offers a mode called &lt;em&gt;Command Palette&lt;/em&gt;, that allows users to type the name of any command to launch it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.abahgat.com/img/wp-uploads/2012/12/screen-shot-2012-12-18-at-20-44-47.png&quot; alt=&quot;Sublime Text&apos;s command palette&quot; loading=&quot;lazy&quot;/&gt;&lt;/p&gt;
&lt;p&gt;Compared to having to learn or remember keyboard shortcuts, this approach saves users also the need to sift through the application menus to find the feature they are looking for. As a side effect, it is also a much more effective way to discover application features than reading a manual.&lt;/p&gt;
&lt;h2 id=&quot;ubuntu-using-hud-to-get-past-menus&quot;&gt;Ubuntu: using HUD to get past menus&lt;/h2&gt;
&lt;p&gt;If the previous mode of interaction is still an exception on Windows and Mac, Ubuntu took some bold steps towards that: they designed a new system called &lt;a href=&quot;http://www.markshuttleworth.com/archives/939&quot;&gt;HUD&lt;/a&gt; (as in &lt;em&gt;Head-Up Display&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;It immediately offers users the same benefit as Sublime Text’s command palette: faster interaction with menus items and easier discovery of features. In addition to that, however, it shares with Alfred its adaptive behavior and will learn by user behavior.&lt;/p&gt;
&lt;p&gt;While HUD is ultimately designed with voice control in mind, keyboard commands are still extremely efficient and already effective today.&lt;/p&gt;
&lt;p&gt;You can see it in action in the following video.&lt;/p&gt;
&lt;lite-youtube videoid=&quot;w_WW-DHqR3c&quot; style=&quot;background-image: url(&apos;https://i.ytimg.com/vi/w_WW-DHqR3c/hqdefault.jpg&apos;)&quot;&gt; &lt;a href=&quot;https://youtube.com/watch?v=w_WW-DHqR3c&quot; class=&quot;lyt-playbtn&quot;&gt; &lt;span class=&quot;lyt-visually-hidden&quot;&gt;Play&lt;/span&gt; &lt;/a&gt; &lt;/lite-youtube&gt; 
&lt;p&gt;Embedding features like these in the operating system (or, more precisely, in the desktop shell) means that we will be able to control any application in a faster and more efficient way, regardless of whether its developers planned for that feature in advance or not.&lt;/p&gt;
&lt;h2 id=&quot;conclusion-why-should-i-care&quot;&gt;Conclusion: why should I care?&lt;/h2&gt;
&lt;p&gt;Decades ago, keyboard shortcuts were born as a means to offer power users a more efficient way to perform their tasks. Nowadays, with fast access to command search (one or two keys) and smart algorithms to search through commands, anyone can become a power user in less time.&lt;/p&gt;
&lt;p&gt;Not all applications will benefit dramatically from command search, but this pattern will become more and more common when web sites/applications start offering it: look at &lt;a href=&quot;http://googledrive.blogspot.nl/2012/10/less-is-more.html&quot;&gt;Google Docs menu search&lt;/a&gt; and &lt;a href=&quot;https://github.com/blog/1264-introducing-the-command-bar&quot;&gt;GitHub’s command bar&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Story of a hack: Bring Your Own Music!</title><link>https://www.abahgat.com/blog/story-of-a-hack-bring-your-own-music</link><guid isPermaLink="true">https://www.abahgat.com/blog/story-of-a-hack-bring-your-own-music</guid><description>This post is a summary of the weekend we spent at the Kings of Code 2012 Hack Battle in Amsterdam. What started as an occasion to get to know smart people doing cool things in Amsterdam (something I l...</description><pubDate>Mon, 03 Dec 2012 23:19:00 GMT</pubDate><content:encoded>&lt;p&gt;This post is a summary of the weekend we spent at the &lt;a href=&quot;http://kingsofcode.com/&quot;&gt;Kings of Code 2012&lt;/a&gt; Hack Battle in Amsterdam. What started as an occasion to get to know smart people doing cool things in Amsterdam (something I look for since &lt;a href=&quot;https://www.abahgat.com/post/2012-11-20-what-you-should-know-before-moving-to-amsterdam/index.md&quot; title=&quot;What you should know before moving to Amsterdam&quot;&gt;I moved here&lt;/a&gt;) turned out to be one of the funniest experiences I had in a while.&lt;/p&gt;
&lt;p&gt;After a brief presentation of the services offered by the hackathon partners (&lt;a href=&quot;http://apigee.com/&quot;&gt;Apigee&lt;/a&gt;, &lt;a href=&quot;http://www.esri.com/&quot;&gt;Esri&lt;/a&gt;, &lt;a href=&quot;http://www.spotify.com/&quot;&gt;Spotify&lt;/a&gt; and &lt;a href=&quot;http://sendgrid.com/&quot;&gt;Sendgrid&lt;/a&gt;) &lt;a href=&quot;http://about.me/diderik/&quot;&gt;Diderik&lt;/a&gt;, &lt;a href=&quot;http://www.raibaz.com/&quot;&gt;Mattia&lt;/a&gt;, &lt;a href=&quot;https://github.com/mikepage&quot;&gt;Mike&lt;/a&gt; and I teamed up to build the hack featured here. We started with the most obvious concept we could come up with: putting songs on a map and having people visualize them. We tried to elaborate the concept to include as many of the partners’ APIs as we could, but then we decided for something simpler, something we could build over the weekend.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.abahgat.com/img/wp-uploads/2012/12/2012-12-02-13-01-47.jpg&quot; alt=&quot;Bring Your Own Music card reader&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;It took us a couple of iterations to get to the final idea we developed: &lt;strong&gt;Bring Your Own Music&lt;/strong&gt;, a toy application that allows users to control music playback through NFC-enabled objects by using an Arduino-powered NFC reader driving a Spotify app.&lt;/p&gt;
&lt;p&gt;BYOM is based upon the idea of attaching Spotify playlists to physical objects, provided they contain an NFC tag, like many of the cards we have in our wallet. Once you have done that, you can then bring your card with you to a party, tap it on an NFC reader, and have a Jukebox Spotify app like the one we built start playing your songs automatically.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.abahgat.com/img/wp-uploads/2012/12/byomscreenshot.png&quot; alt=&quot;A screenshot of the Spotify Jukebox app&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;If multiple people tap their own cards, the main Spotify playlist adjusts itself according to the general preferences of the party attendees. We even kept playing with NFC-enabled objects and linked Spotify playlists to all sort of cards, like London’s Oyster cards, OV-Chipkaarts and even the disposable tram tickets you get here in Amsterdam.&lt;/p&gt;
&lt;p&gt;As Mattia noted,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;wouldn’t it be great if passengers could listen to their favorite music when they check-in on trams?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;how-was-byom-built&quot;&gt;How was BYOM built?&lt;/h2&gt;
&lt;p&gt;We collected the identifiers of a bunch of NFC cards, OV-chipkaarts and tram tickets using Mike’s Arduino connected NFC reader. We then created a collection on &lt;a href=&quot;http://apigee.com/docs/app_services&quot;&gt;Apigee App Services&lt;/a&gt; to store the card ids and URIs of the Spotify playlists we wanted to link to them.&lt;/p&gt;
&lt;p&gt;We programmed Arduino to make a call to Apigee whenever a card is tapped on the reader, in order to store the card identifier into a &lt;em&gt;Swipes&lt;/em&gt; collection.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.abahgat.com/img/wp-uploads/2012/12/2012-12-02-17-33-16.jpg&quot; alt=&quot;Hey, look, there is an Arduino in the box!&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;At the same time, we wrote a Spotify application that periodically queried Apigee for new swipes and, as soon as a new card was detected, fetched the songs associated to it and merged them into the main playlist (the one the Spotify Jukebox app would play).&lt;/p&gt;
&lt;h2 id=&quot;what-have-we-learned-by-it&quot;&gt;What have we learned by it?&lt;/h2&gt;
&lt;p&gt;We (I, at least) walked away with some important teachings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;focus&lt;/strong&gt; is more important than ever: there is no point in imagining lots of features if you are not going to finish them. Pick a small set of features and make them work (or almost work: we had the inevitable demo-time malfunctions despite having tried the full example multiple times before presenting),&lt;/li&gt;
&lt;li&gt;build a &lt;strong&gt;story&lt;/strong&gt;: having a story (in our case, the party) around your hack helps you focus and helps you when presenting it,&lt;/li&gt;
&lt;li&gt;get some &lt;strong&gt;constraints&lt;/strong&gt;: there are thousands of interesting APIs, devices and ideas out there, and even more ways those can be combined. Choose some constraints to ensure you don’t wander off too much (in our case, we wanted to do something with Mike’s NFC-reader)&lt;/li&gt;
&lt;li&gt;have &lt;strong&gt;fun&lt;/strong&gt;: while these sounds like rules, they are more of an advice. Do not let them distract you from your goal: &lt;em&gt;having fun&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-about-the-technologies&quot;&gt;What about the technologies?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Arduino&lt;/strong&gt; is indeed an extremely versatile tool and the amount of sample code and libraries available out there is surprising. While it can be a pain to debug at times, the fact that it allows you to add a physical dimension to your hacks is invaluable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Spotify&lt;/strong&gt;‘s wise choice to build their application framework on top of Chromium means that developers can leverage the tools they already got to know by developing web applications. The actual app SDK is a JavaScript library you an include and debug using the tools in Chromium. We were able to get started in minutes and, once &lt;a href=&quot;https://twitter.com/mager&quot;&gt;Andrew&lt;/a&gt; put us on the right track (by pointing us at the experimental but more powerful 1.0 API), never hit a wall or get stuck at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Apigee App Services&lt;/strong&gt; is indeed a valuable tool: it saved us a lot of time by taking care of storing our data and, more important, exposing it over the internet with no hassle. (The fact that we were even able to interact with it through an Arduino is not trivial.) It also took away the need of building an admin interface for most basic needs. Quoting Diderik&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Apigee allowed us to get so much done on the first day of the hackathon that we could go home and get some sleep for the night.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;how-did-it-go&quot;&gt;How did it go?&lt;/h2&gt;
&lt;p&gt;Not bad at all. We managed to win a couple of prizes: each of us won a 6 months premium subscription to Spotify (thanks &lt;a href=&quot;https://twitter.com/mager&quot;&gt;Andrew&lt;/a&gt;!) and a gift certificate courtesy of Apigee (thanks &lt;a href=&quot;https://twitter.com/timanglade&quot;&gt;Tim&lt;/a&gt;!). All in all, we did not build BYOM for the prizes, but we were glad to get them.&lt;/p&gt;
&lt;p&gt;The most important thing is that we had great fun coming out with a solution that worked and thinking about how to communicate what seemed so cool to us. We had even more fun seeing what the other teams had built (there were some really brilliant hacks indeed). Isn’t that what a hackathon should be about?&lt;/p&gt;
&lt;h2 id=&quot;can-i-see-it&quot;&gt;Can I see it?&lt;/h2&gt;
&lt;p&gt;Sure, you can check the &lt;a href=&quot;https://github.com/abahgat/byom&quot;&gt;source code&lt;/a&gt;. Beware: it’s hackathon-grade code, so it probably does not look like the clean code you would normally want to write during the day. If you are curious, &lt;a href=&quot;http://www.ustream.tv/recorded/27445421&quot;&gt;here is a video&lt;/a&gt; with Bring Your Own Music and all the other cool hacks built at Kings of Code 2012 Hack Battle. (It is a bit dark, but you get the idea.)&lt;/p&gt;
&lt;p&gt;More from us:
&lt;a href=&quot;https://twitter.com/diderikvw?ref_src=twsrc%5Etfw&quot; class=&quot;twitter-follow-button&quot; data-show-count=&quot;false&quot;&gt;Follow @diderikvw&lt;/a&gt;
&lt;a href=&quot;https://twitter.com/nldmike?ref_src=twsrc%5Etfw&quot; class=&quot;twitter-follow-button&quot; data-show-count=&quot;false&quot;&gt;Follow @nldmike&lt;/a&gt;
&lt;a href=&quot;https://twitter.com/raibaz?ref_src=twsrc%5Etfw&quot; class=&quot;twitter-follow-button&quot; data-show-count=&quot;false&quot;&gt;Follow @raibaz&lt;/a&gt;
&lt;a href=&quot;https://twitter.com/abahgat?ref_src=twsrc%5Etfw&quot; class=&quot;twitter-follow-button&quot; data-show-count=&quot;false&quot;&gt;Follow @abahgat&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you guys for the great weekend!&lt;/p&gt;</content:encoded></item></channel></rss>