<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: John Pham</title>
    <description>The latest articles on Forem by John Pham (@johnphamous).</description>
    <link>https://forem.com/johnphamous</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F54096%2Fd0614a97-3d8d-43d1-a29e-a6f315b11314.jpg</url>
      <title>Forem: John Pham</title>
      <link>https://forem.com/johnphamous</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/johnphamous"/>
    <language>en</language>
    <item>
      <title>Building a Reverse Proxy with Cloudflare Workers</title>
      <dc:creator>John Pham</dc:creator>
      <pubDate>Thu, 11 Nov 2021 06:10:39 +0000</pubDate>
      <link>https://forem.com/johnphamous/building-a-reverse-proxy-with-cloudflare-workers-4eom</link>
      <guid>https://forem.com/johnphamous/building-a-reverse-proxy-with-cloudflare-workers-4eom</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;One of the features we have on &lt;a href="https://highlight.run"&gt;Highlight&lt;/a&gt; is the ability to &lt;a href="https://docs.highlight.run/comments"&gt;create a comment on a session&lt;/a&gt;. The cool thing about these comments is they have a spatial property. A comment is created at a specific location on the screen. This allows users to call out things in the session with the comment's location rather than writing something like "the blue button in the top right corner below the red button...".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5x6UBYC1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://pham.codes/images/blog/highlight-creating-comment.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5x6UBYC1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://pham.codes/images/blog/highlight-creating-comment.gif" alt="" width="840" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While creating a comment, you can also tag your individual team members or Slack channels. When you do this, Highlight will send a preview of your comment's text along with a screenshot of the screen you commented on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HIGafGt0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://archbee.imgix.net/XPwQFz8tul7ogqGkmtA0y/gsYvSrZkPZwf-M-P75jKp_brandbird-1.png%3Fauto%3Dformat%26ixlib%3Dreact-9.1.1%26w%3D2400%26h%3D1350%26dpr%3D1%26q%3D75" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HIGafGt0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://archbee.imgix.net/XPwQFz8tul7ogqGkmtA0y/gsYvSrZkPZwf-M-P75jKp_brandbird-1.png%3Fauto%3Dformat%26ixlib%3Dreact-9.1.1%26w%3D2400%26h%3D1350%26dpr%3D1%26q%3D75" alt="" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Under the hood, we use &lt;a href="https://html2canvas.hertzen.com/"&gt;html2canvas&lt;/a&gt; to get the screenshot. I go over more on why in the Alternative Solutions section.&lt;/p&gt;

&lt;p&gt;At a high level, &lt;code&gt;html2canvas&lt;/code&gt; creates an image by recreating the DOM in a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;. We then get a base64 representation of the &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; to use.&lt;/p&gt;

&lt;p&gt;If the screen we're creating a screenshot of has external resources like images, &lt;code&gt;html2canvas&lt;/code&gt; might not be able to load them due to CORS restrictions. When external resources are blocked, the places where the external resources are on the screen are blank in the screenshot.&lt;/p&gt;

&lt;p&gt;For Highlight, this problem is pretty common because our customer's sessions are recorded off the Highlight origin. Most of the external resources Highlight tries loads will probably be blocked by the browser due to CORS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reverse Proxy to the Rescue
&lt;/h2&gt;

&lt;p&gt;So our problem is the CORS restrictions. We can use a reverse proxy to get around the problem.&lt;/p&gt;

&lt;p&gt;Instead of making a direct request from the Highlight app to the cross-origin resource, we'll make a request to a Highlight proxy which will make the request to the cross-origin resource, then return the response. To the browser, the requested resource is on the same origin so the cross-origin resource is loaded successfully!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MiHA78UI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pham.codes/images/blog/reverse-proxy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MiHA78UI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pham.codes/images/blog/reverse-proxy.png" alt="" width="880" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To implement the reverse proxy, we chose to go with Cloudflare Workers for the following reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Independent scaling from our main app API&lt;/li&gt;
&lt;li&gt;We don't have to worry about infrastructure&lt;/li&gt;
&lt;li&gt;Fun opportunity to try the new shiny toy on a non-mission critical code path&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Boilerplate for workers.&lt;/span&gt;
&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fetch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Gets the URL that will be proxied from `url` search parameter.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resourceToProxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Respond to the requesting caller with the response of the proxied resource.&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resourceToProxy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when you call &lt;code&gt;html2canvas&lt;/code&gt;, you can pass the URL to the proxy server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;html2canvas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#player&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://path_to_proxy.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// This is the address to your Cloudflare Worker.&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Do stuff with the canvas.&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Alternative Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Using &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;'s &lt;code&gt;drawImage()&lt;/code&gt; on a &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;For technical reasons, the video you see isn't actually a video. If you inspect the page on Highlight, you won't find a &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; tag. Instead, you'll find an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At a high level, the video you see is a reconstructed DOM that has changes applied to it as the video plays. Because the video isn't an actual &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt;, we couldn't go with this approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using a headless browser with &lt;code&gt;getDisplayMedia()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;We could spin up a headless browser that takes the screenshot asynchronously. This would be a costlier project in terms of engineering effort and maintenance. In the long term, this will probably be what we end up doing.&lt;/p&gt;

&lt;p&gt;There are some performance implications when using &lt;code&gt;html2canvas&lt;/code&gt; on deep DOM trees. In the ideal world, we offload this work from the client to the server.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Sending Slack Messages with Images using Go</title>
      <dc:creator>John Pham</dc:creator>
      <pubDate>Fri, 24 Sep 2021 23:05:40 +0000</pubDate>
      <link>https://forem.com/johnphamous/sending-slack-messages-with-images-using-go-23ao</link>
      <guid>https://forem.com/johnphamous/sending-slack-messages-with-images-using-go-23ao</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I was building a feature on &lt;a href="https://highlight.run" rel="noopener noreferrer"&gt;Highlight&lt;/a&gt; that allows you to create a comment on a video. This comment is special because it has not only the author and the text but also the video's x-coordinate, y-coordinate, and current time. You can create a comment by clicking anywhere on the video at any point during playback.&lt;/p&gt;

&lt;p&gt;When writing a comment, you can tag individuals or Slack channels. When you create a comment that has tags, then the things that are tagged will get a notification in Slack.&lt;/p&gt;

&lt;p&gt;The first version of notifications we shipped only showed the comment's author and text. This wasn't ideal because since the comment is related to the video's coordinates and time, any context in the comment text is lost. By adding a screenshot, we bring the whole context of the comment to the notification. Now the recipient doesn't have to go into Highlight to get the full context, they'll have all of it with the notification.&lt;/p&gt;

&lt;p&gt;Here's what I ended up with:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpham.codes%2Fimages%2Fblog%2Fslack-message-with-image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpham.codes%2Fimages%2Fblog%2Fslack-message-with-image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/slack-go/slack" rel="noopener noreferrer"&gt;Slack Go SDK&lt;/a&gt; is a community SDK and not officially maintained by Slack. This means it doesn't get the same care or attention in terms of documentation and code examples.&lt;/p&gt;

&lt;p&gt;It took me a bit to figure out how to send a Slack message with an image so I'm hoping this blog will save you time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;From the browser, I'm sending the image as a base64 image. This isn't required. I'm using base64 instead of a file because of other reasons.&lt;/p&gt;

&lt;p&gt;Here's a simplified version of the code I ended up using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/base64"&lt;/span&gt;
    &lt;span class="s"&gt;"errors"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/slack-go/slack"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;SendSlackAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;taggedSlackUsers&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;commentText&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base64Image&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;slackClient&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;slack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SLACK_ACCESS_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c"&gt;// For every tagged user, join the channel and send the message.&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slackUser&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;taggedSlackUsers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;slackUser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebhookChannelID&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c"&gt;// The Slack API handles:&lt;/span&gt;
            &lt;span class="c"&gt;// 1. Joining a channel the bot is already a member of&lt;/span&gt;
            &lt;span class="c"&gt;// 2. Joining a Slack user&lt;/span&gt;
            &lt;span class="c"&gt;// Because of this, we can skip checking for this in our application code.&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;slackClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JoinConversation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slackUser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebhookChannelID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"failed to join slack channel"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;slackClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PostMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slackUser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebhookChannelID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MsgOptionBlocks&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"error posting slack message via slack bot"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// We need to write the base64 image as a png on disk to upload to Slack.&lt;/span&gt;
    &lt;span class="c"&gt;// We create a unique file name for the image.&lt;/span&gt;
    &lt;span class="n"&gt;uploadedFileKey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"slack-image-%d.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnixNano&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="n"&gt;dec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StdEncoding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DecodeString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;base64Image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to decode base64 image"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadedFileKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to create file on disk"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dec&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to write file on disk"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to sync file on disk"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// We need to write the base64 image to disk, read the file, then upload it to Slack.&lt;/span&gt;
    &lt;span class="c"&gt;// We can't send Slack a base64 string.&lt;/span&gt;
    &lt;span class="n"&gt;fileUploadParams&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;slack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileUploadParameters&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Filetype&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"image/png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Filename&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Upload.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c"&gt;// These are the channels that will have access to the uploaded file.&lt;/span&gt;
        &lt;span class="n"&gt;Channels&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;uploadedFileKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;slackClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UploadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileUploadParams&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"failed to upload file to Slack"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;uploadedFileKey&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadedFileKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to remove temporary session screenshot"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Considerations
&lt;/h2&gt;

&lt;p&gt;The above code will result in 2 messages being sent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;For the comment text&lt;/li&gt;
&lt;li&gt;For the uploaded image&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ideally, we only send 1 message with the image attached. The Slack API doesn't allow you to do this unless you are attaching the image with a URL. This means the image has to exist somewhere on the internet already.&lt;/p&gt;

&lt;p&gt;In the above code, we send the messages then we upload the images. What if we upload the images first then use the URLs for the uploaded images to attach to each message?&lt;/p&gt;

&lt;p&gt;That will work but will lead to this behavior:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The image is posted in the channel&lt;/li&gt;
&lt;li&gt;The comment is posted in the channel with the attached image&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This isn't the experience I wanted. The code as-is is closer to the desired experience of showing the comment, then the image to provide context.&lt;/p&gt;

&lt;p&gt;The Slack API doesn't provide a way to upload a file "silently". Each upload to a channel will result in a message with a preview of the uploaded content.&lt;/p&gt;

</description>
      <category>go</category>
      <category>slack</category>
      <category>api</category>
    </item>
    <item>
      <title>Forming habits by taking advantage of the consistency and commitment biases</title>
      <dc:creator>John Pham</dc:creator>
      <pubDate>Fri, 17 Sep 2021 02:56:21 +0000</pubDate>
      <link>https://forem.com/johnphamous/forming-habits-by-taking-advantage-of-the-consistency-and-commitment-biases-32pc</link>
      <guid>https://forem.com/johnphamous/forming-habits-by-taking-advantage-of-the-consistency-and-commitment-biases-32pc</guid>
      <description>&lt;h2&gt;
  
  
  Forming Habits
&lt;/h2&gt;

&lt;p&gt;There's lots of research and studies that show creating a habit can take anywhere between 21 days to 8 months. I'm too lazy to cite them, you can find them yourselves (if you find studies that prove the counterpoint, please share!).&lt;/p&gt;

&lt;p&gt;For the past couple of years, I've been working on a few daily habits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Adding and reviewing my Anki cards&lt;/li&gt;
&lt;li&gt;Practicing Chinese&lt;/li&gt;
&lt;li&gt;Walking 11,000 steps&lt;/li&gt;
&lt;li&gt;Intermittent fasting&lt;/li&gt;
&lt;li&gt;Lifting weights&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most habit trackers are made for you to check off the habit after you've completed it. This means, unless you have the habit scheduled, you are unlikely to do it. I've found a way to use the consistency and commitment biases to help me do my daily habits.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Consistency Bias
&lt;/h2&gt;

&lt;p&gt;Humans have a bias to staying consistent. If you've told someone or even yourself you'll do something, the odds of you doing it is a lot higher if you hadn't told it. Even when it acts against our best interest, we tend to be consistent with our prior commitments, ideas, thoughts, words, and actions.&lt;/p&gt;

&lt;p&gt;There are many reasons for this: a big one for me is to appear "right". If I tell someone I'm going to do something, otherwise, I'll be in an awkward situation. Being or appearing "right" is better than appearing "wrong".&lt;/p&gt;

&lt;h2&gt;
  
  
  The Commitment Bias
&lt;/h2&gt;

&lt;p&gt;The commitment bias is very similar to the consistency bias. The difference is that the commitment bias is a subset of the consistency bias. The commitment bias applies to things you've committed to while the consistency bias applies to your beliefs, ideas, and actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to take advantage of the biases for your habits
&lt;/h2&gt;

&lt;p&gt;Okay so how do we use the consistency and commitment bias to form habits?&lt;/p&gt;

&lt;p&gt;Instead of checking off a habit &lt;em&gt;after&lt;/em&gt; you've completed it, you should check off the habit at the start of the day &lt;em&gt;before&lt;/em&gt; you've completed it.&lt;/p&gt;

&lt;p&gt;By doing this, you're committing yourself to do the habit. If you don't do the habit by the end of the day, you will have to face yourself being inconsistent by un-checking the completed habit.&lt;/p&gt;

</description>
      <category>productivity</category>
    </item>
    <item>
      <title>Enabling brotli compression in Go and Chi</title>
      <dc:creator>John Pham</dc:creator>
      <pubDate>Tue, 07 Sep 2021 23:17:48 +0000</pubDate>
      <link>https://forem.com/johnphamous/enabling-brotli-compression-in-go-and-chi-4ban</link>
      <guid>https://forem.com/johnphamous/enabling-brotli-compression-in-go-and-chi-4ban</guid>
      <description>&lt;h2&gt;
  
  
  Why Brotli?
&lt;/h2&gt;

&lt;p&gt;Brotli is &lt;a href="https://caniuse.com/brotli"&gt;supported on most modern browsers&lt;/a&gt;. For browsers that don't support Brotli, the compression method will fall back to gzip or deflate based on the request's &lt;code&gt;Accept-Encoding&lt;/code&gt; header. &lt;strong&gt;Enabling Brotli compression is an easy performance win&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Brotli is usually more effective at compressing compared to gzip and deflate. When I enabled Brotli compression for &lt;a href="https://highlight.run"&gt;Highlight&lt;/a&gt;, I saw response sizes decrease around 40% with no latency increases (for the most part requests were faster!).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Code Changes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Installing Dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install the Chi, older versions of Chi don't support Brotli&lt;/span&gt;
go get &lt;span class="nt"&gt;-u&lt;/span&gt; github.com/go-chi/chi

&lt;span class="c"&gt;# Install the package that will do the Brotli compression&lt;/span&gt;
go get &lt;span class="nt"&gt;-u&lt;/span&gt; gopkg.in/kothar/brotli-go.v0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Enabling Brotli Compression
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;r := chi.NewMux()
// /* means to compress all content types that can be compressed.
compressor := middleware.NewCompressor(5, "/*")
compressor.SetEncoder("br", func(w io.Writer, level int) io.Writer {
    params := brotli_enc.NewBrotliParams()
    params.SetQuality(level)
    return brotli_enc.NewBrotliWriter(params, w)
})
r.Use(compressor.Handler)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;"/*"&lt;/code&gt; means to compress all content types that can be compressed. These are the supported types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;defaultCompressibleContentTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"text/html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"text/css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"text/javascript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"application/javascript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"application/x-javascript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"application/atom+xml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"application/rss+xml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"image/svg+xml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it, congrats on the easy performance win for you and your users!&lt;/p&gt;

</description>
      <category>go</category>
      <category>performance</category>
    </item>
    <item>
      <title>How we create animated product stories</title>
      <dc:creator>John Pham</dc:creator>
      <pubDate>Fri, 13 Aug 2021 18:18:33 +0000</pubDate>
      <link>https://forem.com/highlight/how-to-create-animated-product-stories-2bcd</link>
      <guid>https://forem.com/highlight/how-to-create-animated-product-stories-2bcd</guid>
      <description>&lt;p&gt;At Highlight, we make it our goal to add sparkles to our product updates when we can. One way we do this is by sharing product updates with dynamic animations rather than with static images. This allows us to tell the story of a product update in its entirety; something that we wouldn't be able to with just an image. Here are some examples:&lt;/p&gt;

&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vix-8vhI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://res.cloudinary.com/deawsvb92/video/upload/e_loop:3/v1628825600/blog/How%2520to%2520create%2520animated%2520product%2520stories/Kapture_2021-08-12_at_20.33.04_tyjumi.gif" width="800" height="265"&gt;

The full images can be seen &lt;a href="https://twitter.com/highlightrun/status/1414602196168155139" rel="noopener noreferrer"&gt;here&lt;/a&gt;, &lt;a href="https://twitter.com/highlightrun/status/1385287321071677445" rel="noopener noreferrer"&gt;here&lt;/a&gt;, and &lt;a href="https://twitter.com/highlightrun/status/1396843932953104385" rel="noopener noreferrer"&gt;here&lt;/a&gt;.




&lt;p&gt;We use these animated stories when sharing to &lt;a href="https://twitter.com/highlightrun" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, &lt;a href="https://www.linkedin.com/company/highlightrun" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, and directly to customers.&lt;/p&gt;

&lt;p&gt;These animated stories are not a new concept. We were heavily inspired by others like &lt;a href="https://twitter.com/github/status/1407731478096756739" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and &lt;a href="https://retool.com/#component-wrapper" rel="noopener noreferrer"&gt;Retool&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We'd like to share how we create our animated stories.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A screen recorder (We use QuickTime Player on Mac)&lt;/li&gt;
&lt;li&gt;A video editor that supports keyframes (We use Adobe After Effects)

&lt;ul&gt;
&lt;li&gt;After Effects is overkill for creating these. Here are some alternatives (we haven't tried all of these):

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.descript.com/screen-recording" rel="noopener noreferrer"&gt;descript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.blender.org/features/video-editing/" rel="noopener noreferrer"&gt;Blender&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shotcut.org/" rel="noopener noreferrer"&gt;Shotcut&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. "The Script"
&lt;/h2&gt;

&lt;p&gt;Plan out the story you want to show. You should be able to record the story in less than 15 seconds. We shoot for animated stories that are 5 seconds. We usually get a 5 second animated story from a 15 second recording after cuts and &lt;a href="https://www.movavi.io/how-to-speed-ramp-en/" rel="noopener noreferrer"&gt;speed ramps&lt;/a&gt; (speeding up the video).&lt;/p&gt;

&lt;h3&gt;
  
  
  Tips
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Record the beginning and ending so you can create a looping video (no real reason other than it's oddly satisfying)&lt;/li&gt;
&lt;li&gt;Use a mouse instead of a trackpad (we're more accurate and have smoother motion with a mouse)&lt;/li&gt;
&lt;li&gt;Make sure you're not recording any sensitive information&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Editing
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Import your recording into your video editor&lt;/li&gt;
&lt;li&gt;Set the video canvas resolution (we use 500x500 if we're lazy)

&lt;ul&gt;
&lt;li&gt;This depends on where you want to show the animated story. Different platforms UIs are optimized to render media at different sizes. You can read &lt;a href="https://sproutsocial.com/insights/social-media-image-sizes-guide/" rel="noopener noreferrer"&gt;this guide for sizes&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Optional: Set your recording layer to 3D mode if you want to do any 3D effects.&lt;/li&gt;
&lt;li&gt;Add speed ramps

&lt;ul&gt;
&lt;li&gt;Speed ramps are used to speed up portions of your video. I usually speed up the video by 200% (because we're slow at recording mouse movements and keyboard input). We also will sometimes go up to 400% depending on what's on-screen.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Add keyframes

&lt;ul&gt;
&lt;li&gt;We use scale, position, opacity, and rotation often.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Export to .gif and .mp4.

&lt;ul&gt;
&lt;li&gt;Why both? We prefer using .mp4 where possible and .gif as a fallback. Most platforms give you analytics like view count for .mp4s but not .gifs. .mp4s are generally a lot smaller size-wise compared to .gifs too.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;Here's what our video editor looks like after adding keyframes.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/LfjOPJWCUZk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo
&lt;/h3&gt;

&lt;p&gt;Here's a recording of how we edited an animated story.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/81nPdgExm5w"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Tip
&lt;/h3&gt;

&lt;p&gt;There's no "right" way to edit your video. Every edit is different depending on what's recorded. Some "plots" for the stories will be pretty stale. In those cases, we will add "sparkles" by playing with perspective. A simple rotation will take a boring pan and add a little more wow.&lt;/p&gt;

&lt;p&gt;Here's an example: the left is without a 30° tilt and the right is with a 30° tilt.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fonuf14d1en9la294avw7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fonuf14d1en9la294avw7.gif" width="1658" height="798"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Aaand that's pretty much it! Hope this helps you add some sparkles to your product updates.&lt;/p&gt;

&lt;p&gt;If you end up creating animated stories, I'd love to see them! You can reach me on Twitter &lt;a href="https://twitter.com/johnphamous" rel="noopener noreferrer"&gt;@JohnPhamous&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Until next time, ✨&lt;/p&gt;




&lt;p&gt;Thanks to &lt;a href="https://twitter.com/jsjoeio" rel="noopener noreferrer"&gt;jsjoeio&lt;/a&gt; for reviewing the draft!&lt;/p&gt;

</description>
      <category>startup</category>
      <category>tutorial</category>
      <category>design</category>
    </item>
    <item>
      <title>Hiding Microsoft Teams status icons</title>
      <dc:creator>John Pham</dc:creator>
      <pubDate>Tue, 23 Jun 2020 04:20:12 +0000</pubDate>
      <link>https://forem.com/johnphamous/hiding-microsoft-teams-status-icons-ehh</link>
      <guid>https://forem.com/johnphamous/hiding-microsoft-teams-status-icons-ehh</guid>
      <description>&lt;p&gt;Microsoft Teams shows your coworkers current status from available, busy, to away. I can see the utility in knowing this but for myself, I'd rather not know.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9KDTAOiV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/SRXTt8f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9KDTAOiV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/SRXTt8f.png" alt="Microsoft Teams status icon"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From working remotely, I've found asynchronous communication to be the best way to do most of my communication. The status icons changes the way I chat. For example, if I send a message to someone that is available I could sit there waiting for an instant response. Take the status away and my expectation that the person will respond soon goes away.&lt;/p&gt;

&lt;h1&gt;
  
  
  Removing the status icons
&lt;/h1&gt;

&lt;p&gt;Teams makes an API call to get the status of your coworkers. The URL that it hits is &lt;code&gt;http://presence.teams.microsoft.com&lt;/code&gt;. If we can block those requests, we'll stop seeing the status icons.&lt;/p&gt;

&lt;p&gt;We can use &lt;a href="https://www.telerik.com/fiddler"&gt;Fiddler&lt;/a&gt; to block requests to a specific URL. Because Fiddler works on all OS', we can do the following steps across all the devices we use.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.telerik.com/download/fiddler"&gt;Install Fiddler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Open the AutoResponder tab&lt;/li&gt;
&lt;li&gt;Check "Enable rules"&lt;/li&gt;
&lt;li&gt;Check "Unmatched requests passthrough"&lt;/li&gt;
&lt;li&gt;Click "Add rule"&lt;/li&gt;
&lt;li&gt;In the "Request URL Pattern" input, enter &lt;code&gt;http://presence.teams.microsoft.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In the "Local file to return or *Action to execute" input, choose "*drop"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With that done, Teams will no longer show the status icons.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---3AwdiYR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/K12aX7b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---3AwdiYR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/K12aX7b.png" alt="Microsoft Teams with disabled status icons"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Extending this to other applications
&lt;/h1&gt;

&lt;p&gt;If you'd like to do this for other applications, you can use Fiddler to see all the network requests an application makes. Once you find the API endpoint the application makes, you can make a rule to drop the request.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>tools</category>
    </item>
    <item>
      <title>Debugging Checklist</title>
      <dc:creator>John Pham</dc:creator>
      <pubDate>Tue, 28 Apr 2020 18:55:00 +0000</pubDate>
      <link>https://forem.com/johnphamous/debugging-checklist-4blp</link>
      <guid>https://forem.com/johnphamous/debugging-checklist-4blp</guid>
      <description>&lt;p&gt;We've all been there. You're working on fixing a bug and have been at it for hours. You're jumping around files, looking at call stacks, and randomly changing code hoping it'll fix the bug. I've been down this rabbit hole many times and it didn't help that I didn't have a framework for debugging.&lt;/p&gt;

&lt;p&gt;Recently I read &lt;a href="https://www.goodreads.com/book/show/3938178-debugging"&gt;Debugging: The 9 Indispensable Rules for Finding Even the Most Elusive Software and Hardware Problems&lt;/a&gt; by &lt;a href="https://twitter.com/daveagans"&gt;Dave Agans&lt;/a&gt;. The book is a short read that teaches you the rules through stories. The 9 rules are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Understand the system

&lt;ul&gt;
&lt;li&gt;Does this function handle edge cases?&lt;/li&gt;
&lt;li&gt;Are dates suppose to be in a specific format or timezone?&lt;/li&gt;
&lt;li&gt;Will data ever be null?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Make it fail

&lt;ul&gt;
&lt;li&gt;Find reproduction steps to make it fail&lt;/li&gt;
&lt;li&gt;The shorter the reproduction steps are, the better&lt;/li&gt;
&lt;li&gt;Can you write a test case to cause the failure? If so add it to your test suite&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Quit thinking and look

&lt;ul&gt;
&lt;li&gt;Don't just think/guess about what could be causing the bug, find it!&lt;/li&gt;
&lt;li&gt;Your guesses can help you focus your search&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Divide and conquer

&lt;ul&gt;
&lt;li&gt;Just like in algorithms, divide and conquer algorithms are faster than linear algorithms&lt;/li&gt;
&lt;li&gt;Is the bug from the backend? Database? Frontend? A library?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Change one thing at a time

&lt;ul&gt;
&lt;li&gt;Don't change multiple things then observe if the bug persists&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Keep an audit trail

&lt;ul&gt;
&lt;li&gt;Write down what changes you've tried and what the effects are if any&lt;/li&gt;
&lt;li&gt;This is helpful if you need to pull in other people because you can easily reference what you've already tried&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Check the plug

&lt;ul&gt;
&lt;li&gt;Validate your assumptions&lt;/li&gt;
&lt;li&gt;Is there data in the database? Can it ever be null?&lt;/li&gt;
&lt;li&gt;99.999% uptime is still not 100%&lt;/li&gt;
&lt;li&gt;Is your development/test environment the same as production?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Get a fresh view

&lt;ul&gt;
&lt;li&gt;Reach out for help&lt;/li&gt;
&lt;li&gt;Write/talk out loud your problem, &lt;a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging"&gt;rubber duck debugging&lt;/a&gt; helps&lt;/li&gt;
&lt;li&gt;Take a break and come back with fresh eyes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If you didn't fix it, it ain't fixed

&lt;ul&gt;
&lt;li&gt;If the bug "disappears" without any fixes applied, it isn't fixed&lt;/li&gt;
&lt;li&gt;The bug can only occur when the stars are aligned&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As you read through the rules, you might think they're obvious things you already do while debugging. The best things are often times obvious in retrospect.&lt;/p&gt;

</description>
      <category>debugging</category>
    </item>
    <item>
      <title>What the COBOL?</title>
      <dc:creator>John Pham</dc:creator>
      <pubDate>Sat, 11 Apr 2020 23:06:42 +0000</pubDate>
      <link>https://forem.com/johnphamous/what-the-cobol-4gc7</link>
      <guid>https://forem.com/johnphamous/what-the-cobol-4gc7</guid>
      <description>&lt;p&gt;The &lt;strong&gt;CO&lt;/strong&gt;mmon &lt;strong&gt;B&lt;/strong&gt;usiness-&lt;strong&gt;O&lt;/strong&gt;riented &lt;strong&gt;L&lt;/strong&gt;anguage (COBOL) is a compiled programming language first introduced in 1959. COBOL is not a language you would reach for when building new software however it is still used by many systems today. These are huge systems that keep the world running. Think of systems that handle banking transactions for example. Banking itself has such complex business logic that rewriting the COBOL code to a "modern" language would be a huge undertaking.&lt;/p&gt;

&lt;p&gt;So why learn COBOL? COBOL isn't exactly the language taught or sought after today. If you look at today's landscape, you'll find C++, Javascript, Rust, and many other "modern" languages. The number of COBOL developers is decreasing but there are systems that still need to be maintained. Being a COBOL developer could be a very lucrative job.&lt;/p&gt;

&lt;p&gt;Let's now take a look at COBOL at a high level.&lt;/p&gt;

&lt;h2&gt;
  
  
  COBOL versions
&lt;/h2&gt;

&lt;p&gt;The COBOL language has 3 main versions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;COBOL-85&lt;/li&gt;
&lt;li&gt;COBOL-2002&lt;/li&gt;
&lt;li&gt;COBOL-2014&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a COBOL developer, you can expect most of your development will be done in COBOL-85.&lt;/p&gt;

&lt;h2&gt;
  
  
  GnuCOBOL
&lt;/h2&gt;

&lt;p&gt;GnuCOBOL is one of the more popular COBOL compilers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing on Windows
&lt;/h3&gt;

&lt;p&gt;Follow the instructions on &lt;a href="https://sourceforge.net/p/open-cobol/wiki/Install%20Guide/"&gt;GnuCOBOL's SourceForge page&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing on Ubuntu
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-install gnucobol
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Installing on MacOS
&lt;/h3&gt;

&lt;p&gt;We will be using &lt;a href="https://brew.sh/"&gt;Homebrew&lt;/a&gt; to install GnuCOBOL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;gnu-cobol
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  File extensions
&lt;/h2&gt;

&lt;p&gt;COBOL programs can have the following file extensions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;.cobc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.cbl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.cob&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.cpy&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The extensions used will depend on the coding practices set by the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hello world!
&lt;/h2&gt;

&lt;p&gt;Let's create a new file called &lt;code&gt;hello-world.cbl&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IDENTIFICATION DIVISION.
PROGRAM-ID. HELLO-WORLD.
PROCEDURE DIVISION.
DISPLAY 'Hello, world'.
STOP RUN.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've written our first COBOL program! Let's compile and run it now. In a terminal, do the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cobc &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; hello-world.cbl &lt;span class="c"&gt;# Creates an executable called hello-world&lt;/span&gt;
./hello-world &lt;span class="c"&gt;# Runs the executable&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Hello, world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it for now. In future posts we'll take a deeper look in how to write COBOL.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Build a better email verification experience</title>
      <dc:creator>John Pham</dc:creator>
      <pubDate>Thu, 26 Dec 2019 23:44:38 +0000</pubDate>
      <link>https://forem.com/johnphamous/build-a-better-email-confirmation-experience-3mp7</link>
      <guid>https://forem.com/johnphamous/build-a-better-email-confirmation-experience-3mp7</guid>
      <description>&lt;p&gt;You find a new application and you're excited to use it. You go through the account creation process but are blocked from using the application until you verify your email. This is a pain point in the user journey and can be the point where potential users stop using your application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/8hFJfKMGbbwCQ/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/8hFJfKMGbbwCQ/giphy.gif" alt="Frustrated user"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's avoid our user from doing that. In this article we'll be focusing on how we can change the experience around email verification in on-boarding experiences.&lt;/p&gt;

&lt;h1&gt;
  
  
  Interrupting the on-boarding experience
&lt;/h1&gt;

&lt;p&gt;The on-boarding experience is one of the most crucial experiences your users will have. If the experience is painful, then users are more likely to exit. Our goal as creators should be to create experiences that delights and brings value to the user.&lt;/p&gt;

&lt;p&gt;Most on-boarding experiences need you to verify your email before you use the application. You should consider allowing the user to use the smallest set of features that will entice and keep the user engaged. Requiring the user to step outside of your application, to their email client, find the email, and then verify their account adds friction. If the user isn't enticed during the previous experience then what's keeping them from moving forward?&lt;/p&gt;

&lt;p&gt;There are security and resource implications with allowing the user to use your application before verifying their account. This is where you need to come up with policies such as sandboxing, account cleanups, and rate limiting to protect you and your other users.&lt;/p&gt;

&lt;h1&gt;
  
  
  Reducing the friction
&lt;/h1&gt;

&lt;p&gt;So we need the user to verify their account at the end of the day. We can tell the user to go to their inbox and verify from there. That's a lot of steps and your competitors are most likely requiring the user to do the same thing. This is your chance to differentiate yourself from the competition.&lt;/p&gt;

&lt;p&gt;Here's the general experience:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;1. Go through account creation journey
2. Prompt to verify account by clicking link in email
3. Open new tab
4. Go to email provider
5. Find email
6. Click the verification link
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's look at how we can improve the experience. From the initial account creation step, we know their email address. What if we can provide a link to their email provider from our experience? Here's how the new experience would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;1. Go through account creation journey
2. Prompt to verify account by clicking link in email, provide a link to email provider
&lt;/span&gt;&lt;span class="gd"&gt;-3. Open new tab
-4. Go to email provider
&lt;/span&gt;&lt;span class="p"&gt;5. Find email
6. Click the verification link
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2 steps less may not seem like a lot but it could be the difference between successfully or unsuccessfully on-boarding a new user.&lt;/p&gt;

&lt;h2&gt;
  
  
  The technicals
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fi6r68zbh4c24y9snr9y5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fi6r68zbh4c24y9snr9y5.png" alt="Format of an email"&gt;&lt;/a&gt;&lt;/p&gt;
Format of an email address



&lt;p&gt;How can we achieve this? Let's look at a few scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 1
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;User signs up with &lt;a href="mailto:ada@gmail.com"&gt;ada@gmail.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;We want to provide the user with a link to Gmail&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Scenario 2
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;User signs up with &lt;a href="mailto:turing@outlook.com"&gt;turing@outlook.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;We want to provide the user with a link to Outlook&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Okay, this problem looks pretty simple. We can look at the domain of an email to determine which email provider to link to. We'll end up with a mapping like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;Email Provider&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;outlook.com&lt;/td&gt;
&lt;td&gt;&lt;a href="https://outlook.office.com" rel="noopener noreferrer"&gt;https://outlook.office.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gmail.com&lt;/td&gt;
&lt;td&gt;&lt;a href="https://gmail.com" rel="noopener noreferrer"&gt;https://gmail.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;But wait! What about edge cases?&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 3
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;User signs up with &lt;a href="mailto:grace@hopper.com"&gt;grace@hopper.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;We want to provide the user with a link to ???&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We haven't considered the case where the user is using a custom email domain. We can't determine which email provider the user is using from only looking at their email address' domain.&lt;/p&gt;

&lt;p&gt;For this example, let's assume hopper.com uses Gmail as their email provider. How can we determine that? We can use MX records! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/MX_record" rel="noopener noreferrer"&gt;Mail exchanger records (MX records)&lt;/a&gt; tell the internet which server handles sending and receiving emails for a domain. For our example, hopper.com would have a MX record telling the internet that Gmail handles it's emails.&lt;/p&gt;

&lt;p&gt;In our backend, we can leverage existing tools to look up the MX records for a domain. If you're on a Nix* OS, you can test this out by using &lt;code&gt;host&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[I] ➜ host hopper.com
hopper.com has address 198.179.227.105
hopper.com mail is handled by 5 aspmx.l.google.com.
hopper.com mail is handled by 10 alt1.aspmx.l.google.com.
hopper.com mail is handled by 15 alt2.aspmx.l.google.com.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By looking up the MX records, we now cover all cases. If you're looking to add this to your project, here are a few ways to do it: &lt;a href="https://github.com/martyndavies/legit" rel="noopener noreferrer"&gt;Node&lt;/a&gt;, &lt;a href="https://gist.github.com/afair/2480159" rel="noopener noreferrer"&gt;Ruby&lt;/a&gt;, and &lt;a href="https://github.com/cisagov/trustymail" rel="noopener noreferrer"&gt;Python&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapping up
&lt;/h1&gt;

&lt;p&gt;We've learned that we can improve our email verification process by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Allowing users to use our application in a non-destructive way before they verify their email&lt;/li&gt;
&lt;li&gt;Reduce the friction for users going to their email to click on the verification link&lt;/li&gt;
&lt;/ol&gt;




&lt;h1&gt;
  
  
  A problem for you
&lt;/h1&gt;

&lt;p&gt;There's another pain point with email verifications. You are asking your user to go to their inbox and find an email from you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario 1
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;User gets prompted to verify email&lt;/li&gt;
&lt;li&gt;User immediately goes to their inbox&lt;/li&gt;
&lt;li&gt;Your email is &lt;em&gt;most likely&lt;/em&gt; at the top of their inbox&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Scenario 2
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;User gets prompted to verify email&lt;/li&gt;
&lt;li&gt;User &lt;strong&gt;doesn't&lt;/strong&gt; immediately go to their inbox&lt;/li&gt;
&lt;li&gt;Your email is most likely &lt;code&gt;n&lt;/code&gt; emails down in their inbox&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F87traccrw3aydmvhzlpc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F87traccrw3aydmvhzlpc.jpg" alt="Unread emails"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In both scenarios, your user could get distracted by other emails and forget about verifying their account.&lt;/p&gt;

&lt;p&gt;Is there a way to solve this problem? Here's a hint: query parameters.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>discuss</category>
      <category>ux</category>
    </item>
    <item>
      <title>Fast track yourself through hackathons with APIs</title>
      <dc:creator>John Pham</dc:creator>
      <pubDate>Wed, 25 Dec 2019 06:07:13 +0000</pubDate>
      <link>https://forem.com/johnphamous/fast-track-yourself-through-a-hackathon-with-apis-38l2</link>
      <guid>https://forem.com/johnphamous/fast-track-yourself-through-a-hackathon-with-apis-38l2</guid>
      <description>&lt;p&gt;You're at a hackathon, have a stellar team, and a really cool idea but only 24-36 hours to work on it. Your team could implement everything from scratch. If your goal is to learn then do that! If you want to test an idea then you should leverage APIs and focus on building what makes your idea unique.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's an API?
&lt;/h2&gt;

&lt;p&gt;An API is an Application Programming Interface. Think of this as a function you can call from your application to do work such as: identifying puppies in an image and storing the location of your favorite coffee shops in a database.&lt;/p&gt;

&lt;p&gt;It doesn't matter if you're building a web application, mobile app, or virtual reality game. APIs are platform agnostic so you can use them no matter what you're building.&lt;/p&gt;

&lt;p&gt;Most APIs are either completely free or paid once you past a certain usage. For most hackathon projects, you'll stay in the free tiers. If your project becomes an overnight success then you'll have to start looking at the paid tiers.&lt;/p&gt;

&lt;p&gt;There are pretty much APIs that do anything you could want! You can find APIs in &lt;a href="https://github.com/public-apis/public-apis"&gt;this list&lt;/a&gt;, the &lt;a href="https://education.github.com/pack"&gt;GitHub Student Developer Pack&lt;/a&gt;, and from your hackathon's sponsors.&lt;/p&gt;




&lt;p&gt;Here's a general workflow you can use during a hackathon:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Identify a feature&lt;/li&gt;
&lt;li&gt;Find an API to help implement that feature&lt;/li&gt;
&lt;li&gt;Find documentation and code examples for the API&lt;/li&gt;
&lt;li&gt;Add the API to your application&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now let's walk through an example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I want to build an application to show Pokemon&lt;/li&gt;
&lt;li&gt;I've found the &lt;a href="https://pokeapi.co/"&gt;PokéAPI&lt;/a&gt; that can provide me the data&lt;/li&gt;
&lt;li&gt;Found the &lt;a href="https://pokeapi.co/docs/v2.html"&gt;documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;I can add the API by creating my own client or using a wrapper library&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's take a deeper look at steps 3 and 4.&lt;/p&gt;

&lt;h2&gt;
  
  
  The documentation
&lt;/h2&gt;

&lt;p&gt;Before you choose to use an API, make sure it fulfills your requirements. Next you'll want to look at the documentation and ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Are there a lot of code examples?&lt;/li&gt;
&lt;li&gt;Are all the parameters defined?&lt;/li&gt;
&lt;li&gt;How much can I use this API before I have to start paying?&lt;/li&gt;
&lt;li&gt;Is there a community around the API? Slack, Reddit, Twitter, StackOverflow, etc.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are all signs of a well-maintained API. A well-maintained API means that if you run into a problem, you can usually find someone to help.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the API
&lt;/h2&gt;

&lt;p&gt;If you don't know how REST and Request Methods works, please read &lt;a href="https://code.tutsplus.com/tutorials/code-your-first-api-with-nodejs-and-express-understanding-rest-apis--cms-31697"&gt;Understanding REST and REST APIs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Request Methods are how your application will communicate with APIs. Everyone programming language has it's own way of using Request Methods.&lt;/p&gt;




&lt;p&gt;Going back to our example, we want to get show Pokemons in our application. From reading the documentation, there is the &lt;a href="https://pokeapi.co/docs/v2.html#pokemon"&gt;&lt;code&gt;/api/v2/pokemon&lt;/code&gt;&lt;/a&gt; endpoint.&lt;/p&gt;

&lt;p&gt;The documentation shows which Request Method to use (&lt;code&gt;GET&lt;/code&gt;), what parameters we can use (&lt;code&gt;{id or name}&lt;/code&gt;), and what the expected response is.&lt;/p&gt;

&lt;p&gt;Before using this API directly in our application, let's test the API using a GUI tool. The benefits of using GUI tools includes iterating quickly without having to rebuild your application and having code exported from the GUI which you can use directly in your application.&lt;/p&gt;

&lt;p&gt;For this example we will be using &lt;a href="https://www.getpostman.com/"&gt;Postman&lt;/a&gt;, but you can also use &lt;a href="https://insomnia.rest/"&gt;Insomia&lt;/a&gt; and &lt;a href="https://postwoman.io"&gt;Postwoman&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;With Postman, we want to make a &lt;code&gt;GET&lt;/code&gt; request to &lt;code&gt;https://pokeapi.co/api/v2/pokemon&lt;/code&gt; endpoint. Clicking send with make the request, we'll then see Pokemon data below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QIxMoClQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/EpqwWUE.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QIxMoClQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/EpqwWUE.jpg" alt="Postman"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What if we want to lookup a specific Pokemon? Say Charizard? From the documentation, all we have to do is add the name of the Pokemon we want to lookup to the endpoint: &lt;code&gt;/pokemon/charizard&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sZV7AeI8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://imgur.com/adPQQs8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sZV7AeI8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://imgur.com/adPQQs8.jpg" alt="Postman"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've successfully made API calls to get all the Pokemon and a specific Pokemon! Let's add this to our application now. A handy feature of Postman is it allows you to export your request to code.&lt;/p&gt;

&lt;p&gt;You can access this feature by clicking Code then select the language you want to export to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kN-M6S8a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://imgur.com/Aq6d7rJ.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kN-M6S8a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://imgur.com/Aq6d7rJ.jpg" alt="Postman"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapping up
&lt;/h1&gt;

&lt;p&gt;You've learned how to leverage APIs during hackathons, how quickly iterate using a GUI tool, and export code. With this, you can focus on building what makes your project unique during the hackathon.&lt;/p&gt;

</description>
      <category>hackathon</category>
      <category>api</category>
      <category>beginners</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Notes on working at NASA JPL vs. Microsoft</title>
      <dc:creator>John Pham</dc:creator>
      <pubDate>Thu, 31 Oct 2019 00:21:57 +0000</pubDate>
      <link>https://forem.com/johnphamous/notes-on-working-at-nasa-jpl-vs-microsoft-4oah</link>
      <guid>https://forem.com/johnphamous/notes-on-working-at-nasa-jpl-vs-microsoft-4oah</guid>
      <description>&lt;p&gt;I wanted to sum up what it's like working at a research facility vs. a big tech company. Note that my experience isn't the same for everyone at both organizations. There will always be nuances based on factors such as team, role, and seniority.&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/johnphamous" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DUxr4L89--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--7hJPhj1N--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/54096/d0614a97-3d8d-43d1-a29e-a6f315b11314.jpg" alt="johnphamous"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/johnphamous/what-it-s-like-working-at-nasa-jet-propulsion-laboratory-2eln" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;What it's like working at NASA Jet Propulsion Laboratory&lt;/h2&gt;
      &lt;h3&gt;John Pham ・ May 24 '19 ・ 4 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#career&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;This is going to be a series of small posts covering the following areas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Development Workflow&lt;/li&gt;
&lt;li&gt;Work Life Balance&lt;/li&gt;
&lt;li&gt;Resources/Benefits&lt;/li&gt;
&lt;li&gt;Community/Culture&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Happy to talk about other areas folks would like to hear about!&lt;/p&gt;




&lt;h1&gt;
  
  
  The Development Workflow
&lt;/h1&gt;

&lt;p&gt;Some questions to answer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How does the team function?&lt;/li&gt;
&lt;li&gt;How are requirements formed? Who does it?&lt;/li&gt;
&lt;li&gt;How is the tech stack decided?&lt;/li&gt;
&lt;li&gt;How is testing done?&lt;/li&gt;
&lt;li&gt;What's the scope of your job?&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  At NASA JPL
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Team
&lt;/h4&gt;

&lt;p&gt;The way I define team here are the people I worked with on a daily basis for the project I was on. The team I was only had about 9 people:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2 front-end focused software engineers&lt;/li&gt;
&lt;li&gt;4 back-end focused software engineers&lt;/li&gt;
&lt;li&gt;1 program manager&lt;/li&gt;
&lt;li&gt;3 testers&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Requirements
&lt;/h4&gt;

&lt;p&gt;The program manager converts conversations with our end users (scientists, operations, and engineers) to requirements. Requirements manifest as tickets in Jira. The tickets would be assigned to a person based on the type of work (frontend/backend). We'd then have a 2 week sprint to finish the work. Work was measured with points based on the Fibonacci sequence.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tech Stack
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Backend mostly Java with some in Node with Typescript&lt;/li&gt;
&lt;li&gt;Frontend is Angular&lt;/li&gt;
&lt;li&gt;Data contracts were written in &lt;a href="https://json-schema.org/"&gt;JSON Schema&lt;/a&gt; which allowed us to export type definitions for Java and Typescript&lt;/li&gt;
&lt;li&gt;Database was MongoDB&lt;/li&gt;
&lt;li&gt;Code is versioned with GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Testing
&lt;/h4&gt;

&lt;p&gt;Engineers were not expected to write their own tests. Once developers felt their code was ready to be tested, a ticket would be created and assigned to a tester to test the code. Testers would write their test cases and if cases failed, they would let the developer know. The developer would then fix their code and pass it along to the tester again. This loop would continue until no more bugs were found.&lt;/p&gt;

&lt;p&gt;This was a pattern I saw more among the senior engineers. It's a pattern that didn't really sit well with many and would cause headaches for engineers, testers, and program managers. Engineers could work on their feature for a month+ before handing it off to a tester for a first pass and the tester could be given little context on what it's suppose to do.&lt;/p&gt;

&lt;p&gt;Other engineers were open to writing their own tests to supplement those written by testers.&lt;/p&gt;

&lt;p&gt;The testing requirements were mandated by the US Government but the implementation was up to the testers. Some groups were fine with just unit tests and others required a bigger test suite that included integration, smoke, and E2E tests.&lt;/p&gt;

&lt;h4&gt;
  
  
  Job Scope
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;When you join JPL, you join a team that focuses on a specific area&lt;/li&gt;
&lt;li&gt;You join project(s) which compose of people across teams&lt;/li&gt;
&lt;li&gt;Your time is split between projects based on a %&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because of these 2 points, the type of work you do can either be narrowly or widely scoped. &lt;/p&gt;

&lt;p&gt;Say you join a frontend team. You would then be assigned to projects that need frontend help. If you wanted to get experience with backend, you could start working on backend tasks for that project. Down the line you could join a project as a backend dev or switch to a backend focused team.&lt;/p&gt;




&lt;h1&gt;
  
  
  Now off to Washington
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Xra9Nqtg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/http://giphygifs.s3.amazonaws.com/media/Btn42lfKKrOzS/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Xra9Nqtg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/http://giphygifs.s3.amazonaws.com/media/Btn42lfKKrOzS/giphy.gif" alt="Person on a plane"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  At Microsoft
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Team
&lt;/h4&gt;

&lt;p&gt;The way I define team here are the people that are in the same organization.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;61 software engineers&lt;/li&gt;
&lt;li&gt;26 program managers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference here is that software engineers are expected to work across the stack. Individuals do pick up preferences for frontend/backend/infrastructure.&lt;/p&gt;

&lt;h4&gt;
  
  
  Requirements
&lt;/h4&gt;

&lt;p&gt;Our organization works on a breath of products/services (projects) used internally and externally. Program managers are assigned project(s) and create requirements from market research, customer engagements, and logs. Requirements manifest themselves as features in Azure DevOps. &lt;/p&gt;

&lt;p&gt;Software engineers also join projects and can be on multiple projects.&lt;/p&gt;

&lt;p&gt;Big picture goals are decided on a quarterly basis. At the start of each sprint, engineering managers, engineers, and program managers go through the backlog together and pull features in. Features are weighted based on how many engineering hours.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tech Stack
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Backend is .NET&lt;/li&gt;
&lt;li&gt;Frontend is Angular (maintaining) and React (new development)&lt;/li&gt;
&lt;li&gt;Databases are Azure managed databases (MySQL, Cosmos, etc.)&lt;/li&gt;
&lt;li&gt;Code is versioned and deployed with Azure DevOps&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Testing
&lt;/h4&gt;

&lt;p&gt;Software engineers are expected to test their own code. Tests are ran locally and also as part of the CI/CD process on Azure DevOps.&lt;/p&gt;

&lt;h4&gt;
  
  
  Job Scope
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;When you join Microsoft, you join a team that focuses on a specific area&lt;/li&gt;
&lt;li&gt;Your team works on different projects, you're expected to be able to move between all of them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most teams are full stack. They also manage the operations, telemetry, and support for their projects.&lt;/p&gt;

&lt;p&gt;If you prefer to have a specialization, you can communicate that with your manager and they'll be happy to get you work in your area of choice.&lt;/p&gt;

&lt;p&gt;A lot of work leverages work done by other teams. For example, the platform that ingests application logs is managed by a different team. Our team doesn't have to standup our own instance or recreate the wheel.&lt;/p&gt;

</description>
      <category>career</category>
    </item>
    <item>
      <title>Checklist for Starting a New Job in a New State</title>
      <dc:creator>John Pham</dc:creator>
      <pubDate>Thu, 13 Jun 2019 05:52:18 +0000</pubDate>
      <link>https://forem.com/johnphamous/checklist-for-starting-a-new-job-in-a-new-state-3m6</link>
      <guid>https://forem.com/johnphamous/checklist-for-starting-a-new-job-in-a-new-state-3m6</guid>
      <description>&lt;p&gt;Starting a new job is exciting. Moving to another state is exciting too but can also be daunting. I hope you find this checklist useful for when you find yourself in this situation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is written under the assumption that you're flying to your new place and packing light&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you move
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/mwWSwdrfjzO7e/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/mwWSwdrfjzO7e/giphy.gif" alt="moving"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find a place to live

&lt;ul&gt;
&lt;li&gt;Crash a friend's, AirBnb, or Corporate Housing while you search. This should take about a week to do. (Pick the best place after you've seen 37% of the places you want to visit, it's the &lt;a href="https://en.wikipedia.org/wiki/Optimal_stopping"&gt;most optimal choice&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Get copies of your medical records

&lt;ul&gt;
&lt;li&gt;Visit your doctor, dentist, and anyone else who you've seen in the past and get physical or digital records. The records will be useful for your new health care providers.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Cancel utilities/services/memberships you won't be using&lt;/li&gt;
&lt;li&gt;Request for mail forwarding with &lt;a href="https://moversguide.usps.com/mgo/disclaimer"&gt;USPS&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;If you're expecting any mail at your old address, you can get it forwarded to your new address&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Change the billing address with your banks&lt;/li&gt;
&lt;li&gt;Change the mailing address with the IRS with a &lt;a href="https://www.irs.gov/forms-pubs/about-form-8822"&gt;Form 8822&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Ensures you receive all correspondence and tax refunds as soon as possible&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Once you get there
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/3oxRmwOLK6OLi9IFI4/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/3oxRmwOLK6OLi9IFI4/giphy.gif" alt="arriving"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Register to vote

&lt;ul&gt;
&lt;li&gt;Some States let you do it online or when you apply for a driver license&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Get a driver license or an &lt;a href="https://www.dhs.gov/enhanced-drivers-licenses-what-are-they"&gt;Enhanced Driver License&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;The latter can be used as proof of citizenship and act as a limited passport&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Sign up for utilities

&lt;ul&gt;
&lt;li&gt;Includes: phone, internet, gas, water, electricity&lt;/li&gt;
&lt;li&gt;Check with the place you're staying, your employer, and any organizations you belong to if they have deals&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Establish Legal Residency (&lt;a href="https://en.wikipedia.org/wiki/Domicile_(law)#United_States"&gt;Legal Domicile&lt;/a&gt;) in your new state allows you to:

&lt;ul&gt;
&lt;li&gt;Avoid paying income taxes for 2 states&lt;/li&gt;
&lt;li&gt;California has income taxes, Washington doesn't, so you won't be subject to income taxes&lt;/li&gt;
&lt;li&gt;Qualify for in-state tuition for universities&lt;/li&gt;
&lt;li&gt;Power to vote for the local and state government&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Buy used when you can

&lt;ul&gt;
&lt;li&gt;You can find great pieces of furniture for the fraction of the retail price on the used market&lt;/li&gt;
&lt;li&gt;If you live near a university or large workplaces, check if they have garage sales&lt;/li&gt;
&lt;li&gt;Buying used is not only a good economically, but also good for the environment&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Plan your commute during the hours you will be commuting. Google Maps and &lt;a href="//citymapper.com"&gt;CityMapper&lt;/a&gt; are some tools you can use.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  After your first week at work
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/5GoVLqeAOo6PK/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/5GoVLqeAOo6PK/giphy.gif" alt="excited"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign up for your employer's 401(k) and Health Savings Account

&lt;ul&gt;
&lt;li&gt;If your employee has 401(k) matching, this is basically free money&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Checkout the perks/discounts your employer offers&lt;/li&gt;
&lt;li&gt;Find new health care providers&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Once you're settled in
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/Zw97g5eB1mBji/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/Zw97g5eB1mBji/giphy.gif" alt="cozy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign up for a gym/fitness club&lt;/li&gt;
&lt;li&gt;Participate in your companie's &lt;a href="https://www.investopedia.com/terms/e/espp.asp"&gt;Employee Stock Purchase Plan&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Contribute to your individual retirement account (Set contributions to occur on an automated schedule)&lt;/li&gt;
&lt;li&gt;Explore your new place! 

&lt;ul&gt;
&lt;li&gt;Hikes, classes, food scene, and new hobbies!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Thanks to Sam and Bill for some advice&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
