<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Eddie Hinkle</title>
    <link>https://eddiehinkle.com/</link>
    <atom:link href="https://eddiehinkle.com/feed.xml" rel="self" type="application/rss+xml" />
    <description>Engineering leader, iOS developer, and writer in the Washington, DC metro area.</description>
    <language>en</language>
    <lastBuildDate>Thu, 01 Jan 1970 00:00:00 GMT</lastBuildDate>
    <item>
      <title>The Product Delivery Paradox: the framework to help engineers simplify estimates and trade offs.</title>
      <link>https://eddiehinkle.com/2023/01/10/1/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2023/01/10/1/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>Product delivery is simple and complex, all at the same time. The issue is that we always want 3 dimensions of delivery. We want it: Quickly, Bug-free, All the &quot;important&quot; features. The problem is that you can only deliver on 2 of the 3 at the same time.</description>
      <content:encoded><![CDATA[<p>Product delivery is simple and complex, all at the same time. The issue is that we always want 3 dimensions of delivery. We want it:</p>
<ul>
<li>Quickly (Time dimension)</li>
<li>Bug-free (Quality dimension)</li>
<li>All the “important” features (Scope dimension)</li>
</ul>
<p>The problem is that you can only deliver on 2 of the 3 at the same time.</p>
<h2>If you want a lot of features quickly, you have to sacrifice quality.</h2>
<p>Because if you are trying to do a lot of things quickly, the only thing you can adjust is the build quality.</p>
<p>Do you write automated tests? Work through all the edge cases and scenarios? Odds are, when you are rushing (which you are if you’re trying to do a lot of features fast), you will miss things, and the end result will feel less refined or have strange bugs.</p>
<p>The problem with this approach is that many of the issues are hidden from the view of anyone who isn’t looking from the technical aspect. Because of that, this is the angle that Product Managers tend to lean into.</p>
<h2>If you want a lot of high-quality features, it will take a long-time.</h2>
<p>Software Engineers love nerding out on tech.</p>
<p>Let’s make things reusable! What kind of crazy scenarios might happen? Does someone have 10 words in their name? Thinking through technical excellence and building a fully working piece of software is one of the best spots for an engineer to be in.</p>
<p>But the problem is, without modifications, the natural inclinations of most software engineers is how we got waterfall software development that goes over budget and over deadlines.</p>
<h2>The best approach is a small number of high-quality features that can be done quickly.</h2>
<p>This is what agile was meant to be before it became a buzzword—it’s how you deliver and iterate on magical experiences.</p>
<p>It’s the most challenging part of software design and development, but it’s essential to make the software the best it can be.</p>
<p>Limit the number of features, think through edge cases, and write automated tests. But because you limit the number of features, you keep the scope small, and you can ship software quickly and reliably.</p>
<p>So the next time you’re pulled into a discussion on what will be built and when it’ll be ready, bring the product-delivery paradox triangle with you.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Most software engineers start by thinking about the code. Instead, start with the why so you solve the right problem the first time.</title>
      <link>https://eddiehinkle.com/2023/01/07/1/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2023/01/07/1/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>Every software engineer has that moment. You are assigned a feature requirement. It could be a Jira ticket or a sticky note. You first start brainstorming if this is possible, how you would achieve it, and how much time it might take. But before you start thinking through the technical implementation, you need to step back and ask &quot;why&quot;?</description>
      <content:encoded><![CDATA[<p>Every software engineer has that moment.</p>
<p>You are assigned a feature requirement. It could be a Jira ticket or a sticky note. You first start brainstorming if this is possible, how you would achieve it, and how much time it might take.</p>
<p>But before you start thinking through the technical implementation, you need to step back and ask &quot;why&quot;?</p>
<h2>Asking &quot;why&quot; saves you time because it prevents you from building the wrong solution.</h2>
<p>Often by nature, there are some technical assumptions built into a requirements ticket.</p>
<p>However, those writing software requirements sometimes don&#39;t clearly understand what is and isn&#39;t possible. There may be a better solution that is either faster or more user-friendly that has not been thought of or assumed to be impossible. As the software engineer building the feature, understanding why a requirement exists allows you to validate that the best solution (given all the variables) is being selected.</p>
<p>As a software engineer, you are the technical subject matter expert (yes, even if you&#39;re new).</p>
<h2>Understanding &quot;why&quot; opens up alternative ways to build the feature.</h2>
<p>Even if the requirements you&#39;re given are correct, there are many assumptions you may make based on what is or isn&#39;t listed in the ticket.</p>
<p>It is so important to never make an assumption. Assumptions lead to faulty technical decision-making. This feature may be a stop-gap and will need to change in 6 months. You save yourself and your company time by asking questions and unlocking those things that other stakeholders may not think to volunteer. This complete understanding of the problem and solution allows you to make the right technical trade-offs.</p>
<p>You aren&#39;t really doing your whole job as a software engineer if you are just building what people ask rather than what people need.</p>
<h2>After asking &quot;why&quot; enough times, you&#39;ll build an understanding and intuition about your customers and their needs.</h2>
<p>The sweet spot for software engineers is when they understand their customers and their needs deeply.</p>
<p>When that happens, engineers can suggest ideas that product managers or designers might not have considered. This collaboration between engineering, product, and design makes unique software exist. So embrace being a collaborative, product-focused engineer.</p>
<p>Ask &quot;why&quot; and make your product better.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How does this continuous learning thing work?</title>
      <link>https://eddiehinkle.com/2022/09/23/1/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2022/09/23/1/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>So here&apos;s the thing, no matter how you learned software development, either in a degree program, through self-reaching, or by using a bootcamp-style program; one big risk is that what you learned yesterday isn&apos;t necessarily going to help you tomorrow.</description>
      <content:encoded><![CDATA[<p>So here’s the thing, no matter how you learned software development, either in a degree program, through self-reaching, or by using a bootcamp-style program; one big risk is that what you learned yesterday isn’t necessarily going to help you tomorrow.</p>
<p>Tech is consistently changing. In fact, even just skills in general fall away. According to <a href="https://www2.deloitte.com/content/dam/Deloitte/global/Documents/About-Deloitte/central-europe/ce-global-human-capital-trends.pdf">Deloitte’s 2017 Global Human Capital Trends report</a>, the half life of a skill is five years. That’s it. If you don’t keep learning and growing, your skills will continue to reduce over the years.</p>
<p>The solution is continuous learning. It’s a fancy phrase, you probably hear it around in Agile groups or fancy business seminars. But what does it mean for you, and how can it help you level up your game?</p>
<p>I want to share a framework to think about how you approach continuous learning, as well as some tips on what kinds of resources you can use and some methods to encourage yourself to keep at it!</p>
<h2>4 E’s of Continuous Learning</h2>
<p>The framework I want to share is the 4 E’s of Continuous Learning: Education, Experience, Exposure, and Environment.</p>
<p><strong>Education</strong> is most commonly what you think of when think about learning. This is a discrete event and it a starts and stops. It’s your online courses, tutorials, YouTube videos, etc. This is definitely an essential part of continuous learning, routinely reading or watching new materials that will expose you to other points or view or new technologies. But continuous learning is not just about traditional forms of education.</p>
<p><strong>Experience</strong> is what happens over the course of your regular working hours. Are you investigating a new potential technology for the company? Trying to figure out how you all can implement a CI/CD pipeline to make your deployments easier? </p>
<p>If you aren’t having opportunities to learn while doing your work, try to look for new projects where you can volunteer to investigate something or take charge or a project that is a bit outside your normal wheelhouse. This allows your learning to be more than just watching or reading, but really getting your hands dirty.</p>
<p><strong>Exposure</strong> is about the relationships on which you learn. Do you routinely discuss what you are learning with your co-workers? Do you have other friends in the tech industry that work at different companies? What about engaging with other developers on Tech Twitter or having a mentor?</p>
<p>These are all ways that you get exposed to learning in a different way. That’s because we are relational people and our brain is hardwired to retain information presented by others. By ensuring that you have routine exposure to others’ thoughts and ideas in the tech industry through personal relationships, you are learning even when you don’t realize it!</p>
<p><strong>Environment</strong> is the final form of learning. Over time you work with different technologies at work. Maybe your company is using JavaScript and they decide to do a migration to TypeScript? The tools and technologies that you interact with during your job creates the environment for your learning. Do you use CI/CD? What is the tech stack at your job? What software is used for version control, BitBucket, GitHub, GitLab, or some homegrown monstrosity?</p>
<p>If there are teams or projects that use tech you don’t get to experience in your normal job, see if there is an opportunity to shift into those projects, even for a short time. If your company isn’t consistently moving its tech stack and platform forward, you could be falling behind the industry. If you have been at your job long enough that you are no longer learning from the environment, and your company doesn’t seem to be progressing, it may be time to look for another job.</p>
<h2>Many ways to learn</h2>
<p>As you can tell, there are so many ways to learn. Traditional education, Experience in your daily work, Exposure to different people and ideas and the Environment in which you operate. These pieces all work together to ensure that you are consistently and consistently learning. If most of these avenues aren’t actually providing new learning, then you’ll likely stagnate.</p>
<p>So how do you ensure you have a healthy dose of learning? There’s many ways but here’s some suggestions.</p>
<h3>6 Tips for Learning Resources</h3>
<ol>
<li><em><strong>Read a tech article daily</strong></em>. Use Twitter, Matter or Daily.Dev. Even just reading a single interesting article every work day will only take a couple of minutes but it will guarantee exposure to new ideas.</li>
<li><em><strong>Read 4-6 books a year</strong></em>. Make sure you mix up the topics. some topics could be: languages, frameworks, agile methodologies. Include a book from a parallel field like UX design or project management. Also include books on what they refer to as soft skills. </li>
<li><em><strong>Take a course once a year</strong></em>. Something like Epic React or CSS for JS.</li>
<li><em><strong>You learn better when you teach</strong></em>. So teach something routinely, either in person at meetups, on your blog or on YouTube. The great part is that people give you feedback which ends up being more exposure.</li>
<li><em><strong>Build yourself a professional network of peers using Twitter or LinkedIn</strong></em>. Build relationships, understand what they are doing at their companies.</li>
<li><em><strong>Get yourself a mentor</strong></em>. Either ask someone that you know of, or use a resource like ADPList or MentorCruise.</li>
</ol>
<h3>5 ways to motivate yourself</h3>
<p>It’s easy to procrastinate or forget to incorporate learning regularly. So I have some suggest methods to keep yourself on track.</p>
<ol>
<li><p><strong>Create a learning calendar</strong>.<br>Plan out what topics or things you want to learn each month. By giving themes to a month of learning it helps you focus and allows the resources to build on each other. CSS September, anyone? If you do time-blocking, make sure you actually have a set time in your time-blocks for each day.</p>
</li>
<li><p><strong>Choose your learning materials and sources wisely</strong>.<br>Anyone can record a video or post a tweet thread. What is their background, what makes them trust worthy? The higher quality the content you’re consuming, the more you’ll get out of it and the more you’ll want to continue.</p>
</li>
<li><p><strong>Gamify the learning progress</strong>.<br>Rewarding yourself for every completed book or course will subconsciously motivate you to keep learning.</p>
</li>
<li><p><strong>Get a mentor, like I mentioned earlier</strong>.<br>There can be an over-emphasis on courses and videos with everything we have available on the internet. To really grow you need someone more experienced that can give guidance and career advice. Mentors can offer encouragement and instill confidence in a way that a course can’t. </p>
</li>
<li><p><strong>Participate in learning groups</strong>.<br>The power of community in learning is immeasurable. Why do universities and bootcamps all teach in classes or cohorts? More than just learning it is the collective sharing of ideas and perspectives that leads to continuous learning and growth. So find some type of community to talk in. A book club, Twitter space, or Discord server.</p>
</li>
</ol>
<h2>Just keep learning</h2>
<p>If you remember one thing from this email it’s this: if we aren’t learning, we’re stagnating. That’s a phrase I’ve had on my resume for years, because I believe it. I’ve had a huge personal focus on learning throughout the entire career... really, my entire life. Your mind is like water, if sits too long, it smells. But if you keep it running, you have a nice refreshing stream.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Creating auto-complete types without restricting values in TypeScript</title>
      <link>https://eddiehinkle.com/2022/09/14/1/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2022/09/14/1/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>Our library is in TypeScript and we wanted to provide IDE suggestions for the values of a parameter, but we couldn&apos;t actually restrict the value of that parameter. This is because 90% of the time, the value would be a couple of obvious values, however the other 10% of the time, we had no way to predict what the value would be!</description>
      <content:encoded><![CDATA[<h2>The problem</h2>
<p>In my current role, we build internal libraries for use by our front-end developers. For a new library we&#39;re building we needed to do something
I hadn&#39;t ever done before. Our library is in TypeScript and we wanted to provide IDE suggestions for the values of a parameter, but we couldn&#39;t
actually restrict the value of that parameter. This is because 90% of the time, the value would be a couple of obvious values, however the other 10%
of the time, we had no way to predict what the value would be!</p>
<h2>The challenge</h2>
<p>You would think that the solution would be simple. But it wasn&#39;t.</p>
<p>If you are familiar with TypeScript the initial problem seems easy. We need to declare a list of insects that you can use. This is easily accomplished
through defining a Union Type.</p>
<pre><code>type Insect = &#39;scorpion&#39; | &#39;cockroach&#39; | &#39;caterpiller&#39;;
</code></pre>
<p>The thing that&#39;s missing is that if I find a new insect and try to use it, I can&#39;t:</p>
<pre><code>const foundInsect: Insect = &#39;bee&#39;;
</code></pre>
<p>That would cause an error.</p>
<p>Well, there is a type which would accept any of these strings, right?</p>
<pre><code>const foundInsect: string = &#39;bee&#39;;
</code></pre>
<p>This would work, but we lose our IDE suggestions because now the IDE thinks we should just add <em>any</em> string value.</p>
<p>The next assumption is, if we have our Insect type as a union, why don&#39;t we just include the string type? That&#39;s one of
the first things I thought to try!</p>
<pre><code>type Insect = &#39;scorpion&#39; | &#39;cockroach&#39; | &#39;caterpiller&#39; | string;

const foundInsect: Insect = &#39;bee&#39;;
</code></pre>
<p>At first look, this seems like it should work. We define our list of approved values and we also allow it to be a string.</p>
<p>This won&#39;t throw an error, but it turns out ... we still lost our IDE suggestions!</p>
<p>It turns out that </p>
<pre><code>type Insect = &#39;scorpion&#39; | &#39;cockroach&#39; | &#39;caterpiller&#39; | string;
</code></pre>
<p>gets simplified down to</p>
<pre><code>type Insect = string;
</code></pre>
<p>which means no IDE suggestions for us!</p>
<p>That is when I discovered <a href="https://github.com/microsoft/TypeScript/issues/29729">this issue on the TypeScript GitHub repo</a>, where this problem
has been discussed at length. </p>
<p>The problem is, we need to say that the final segment, &quot;string&quot;, includes all strings <em>except</em> the ones we&#39;ve defined. By doing that, TypeScript understands
that it needs to hold on to the original literal string types that we define up front.</p>
<p>The solution, provided by <a href="https://github.com/spcfran">@spcfran</a> and <a href="https://github.com/manuth">@manuth</a> is:</p>
<pre><code>type LiteralUnion&lt;T extends string | number&gt; = T | Omit&lt;T, T&gt;;
</code></pre>
<p>Which works wonders!</p>
<h2>The solution</h2>
<p>All together you end up with:</p>
<pre><code>type LiteralUnion&lt;T extends string | number&gt; = T | Omit&lt;T, T&gt;;

type Insect = &#39;scorpion&#39; | &#39;cockroach&#39; | &#39;caterpiller&#39;;

const foundInsect: LiteralUnion&lt;Insect&gt; = &#39;bee&#39;;
</code></pre>
<p>The first type, LiteralUnion is what&#39;s called a Generic. It needs a secondary type to be fully defined. In that definition, the letter T
represents whatever type you provided.</p>
<p>So LiteralUnion takes another type called &quot;T&quot; which can extend either a string or a number. Then LiteralUnion defines itself as either: the type, or everything
that doesn&#39;t match the type.</p>
<p>That&#39;s a bit abstract, so let&#39;s dive a little deeper.</p>
<p>The second type, Insect is just what we&#39;ve been working with. It&#39;s a Union type, so it says that a value can be &quot;A or B or C&quot;. In this case we&#39;re saying
the value can be <code>scorpion</code> OR <code>cockroach</code> OR <code>caterpiller</code>.</p>
<p>When we combine these two types: <code>LiteralUnion&lt;Insect&gt;</code> we&#39;re saying that the generic type for LiteralUnion is going to be Insect.
so <code>T = &#39;scorpion&#39; | &#39;cockroach&#39; | &#39;caterpiller&#39;</code>. Which means that ultimately when the LiteralUnion&#39;s type is: <code>T</code> OR <code>OMIT T</code>, we essentiall get:
<code>type LiteralUnion = &#39;scorpion&#39; | &#39;cockroach&#39; | caterpiller&#39; | OMIT&lt;string, &#39;scorpion&#39; | &#39;cockroach&#39; | &#39;caterpiller&#39;&gt;</code>, which is exactly what we need
in order for TypeScript to hold on to all the types for them to be used by the IDE to suggest them as options!</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to make a plan for your career</title>
      <link>https://eddiehinkle.com/2022/09/10/1/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2022/09/10/1/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>You can let your career be blown around by the winds of happenstance or you can set a course toward the island of your dreams. I wish I had planned out my career earlier.</description>
      <content:encoded><![CDATA[<p>I spent a large chunk of my career making the worst mistake for career development: I didn’t have a plan.</p>
<p>A lot of people have a plan for their life. Being a software engineer wasn’t the clear plan for me for a long time. Programming started as a hobby for me and then was a side hustle in college. Ultimately I thought I would end up doing something in cyber forensics or cyber security. But I just kind of made decisions as they came up. I quit college because I was bored, worked full-time at the Apple Store, and moved to Maryland to live with my parents at 24. Not exactly what every 24-year-old plans on doing.</p>
<p>But then I applied and got a job as a Geospatial Software Engineer, it turns out, that all of my side hustles from college had created a pretty great resume, which was nice since they didn’t give much money!</p>
<p>As new opportunities came up, I decided about them, but it all felt very random until I was at a company that had a horrible manager. When that manager had to step down due to personal reasons, I realized it was an amazing opportunity. I hadn’t planned on management, I hadn’t planned much of anything in advance. But I felt I could do better at managing the team than he did. So I applied internally, got the position, and then realized I needed to find out how to manage people.</p>
<p>I grew and thrived in that role, but it also made me realize my career could be more than just working in “Senior Engineer” type positions at random companies for the next 20-30 years, which is what I had just assumed without thinking much about it. Instead, I could actually do important jobs that made an impact and even work at big or well-known companies.</p>
<p>This started the most exciting part of my career. I’ve done people management for several years, building a team from 2 people to 8 people, and ended up moving on because I knew I wanted to get into big tech. I’m at Glassdoor now and loving it! I never would have imagined I would be on a trajectory to lead and manage engineering teams much less as part of big well-known tech companies. But my realization that I was now a manager in charge of other people’s careers made me start to actually think about careers and career progression.</p>
<h2>Have a long-term idea and a short-term plan.</h2>
<p>You need both short-term and long-term plans. Not having a short-term plan will slow your progress and take longer to get where you want to, but a short-term plan without a long-term idea 💡 means every year might have you headed in a different direction rather than making strategic moves towards your ultimate goal.</p>
<p>That is why it’s important to pair both your short-term plan, which allows you to make intentional choices, while your long-term idea keeps you from getting sidetracked on something that seems good but will take you down a different path.</p>
<p>That’s easier said than done! No worries, let’s talk briefly about 3 essential steps that can help you make a basic plan for your career.</p>
<h2>1. Start with the end in mind</h2>
<p>“What do you want to be when you grow up?” is a question that adults ask kids but that makes no sense. There are so many possibilities, how is a kid supposed to know!</p>
<p>So how can I expect you to plan 5 years ahead?!?</p>
<p>At least that’s what several people asked when I tweeted about having a career plan.</p>
<p>The key is to figure out where you want to be or what you want to do. There is a Japanese idea called Ikigai, which is about a person’s reason for being or purpose.</p>
<p>This framework, Ikigai, is a great way to start brainstorming where you want to be at the pinnacle of your career. The key is finding what sits in the center of four overlapping areas of your life:</p>
<ul>
<li>What you can be paid for</li>
<li>What you are good at</li>
<li>What you love</li>
<li>What the world needs</li>
</ul>
<p>Obviously you may not be able to get a job in 5 years that fits in the center of all four of those pieces, but if you identify what the center looks like, your 5 year idea can move you closer there than you are today.</p>
<p>In my view, it’s best to actually try to move more into the circles in a clock-wise function. This means you start with what you can get paid for. It’s important to take care of your Provision. Then you can double down and get good at it, embracing Proficiency. Once you’ve showed that, you typically open up new doors to shift into what you love, using your Passion. Finally, at the pinnacle of your career the hope is that you’ll be able to be in a position that helps provide something the world needs and thus have an Impact.</p>
<h2>2. Choose one significant change over the next year that gets you closer</h2>
<p>We often over-estimate what we can do in shorter time periods but we under-estimate how that compounds in the longer time periods. So the goal should be to accomplish one major change in a year. Something that requires several steps and moving parts and that makes a meaningful step towards your 5 year goal.</p>
<p>For example, think about things like:</p>
<ul>
<li>Is there a big promotion you’re close to?</li>
<li>Is there a necessary skill you’re missing or have very little experience with?</li>
<li>Is there some area that you haven’t gotten to work in?</li>
</ul>
<h2>3. Define 4 milestones that will get you to your 1 year plan</h2>
<p>A year is both quick and a long time. It’s quick in terms of your life and a decade, however it’s a long-time in the day to day. To bridge that gap, you should set shorter goals, I recommend quarterly goals, that are smaller steps that can get you to your year long plan.</p>
<p>If you are working to get a promotion, then your goals should focus on ensuring you’ve been part of projects or other things that you’ll be able to use in your promotion packet or discussion.</p>
<p>If you’re trying to expand a skill, you’ll probably want to learn about it, then find a project to use it in. You’ll want to be sure and have a period where someone can give you feedback, because that’s the only way to learn and grow, and then implement that feedback actively.</p>
<p>Whatever your plan for this next year, it’s likely something that can be broken down into smaller chunks that can be tackled on a quarterly basis.</p>
<p>Don’t experience the same mistake I did, choose to invest in your future by setting up an action plan for your career today! Figure out where you are at in the Ikigai framework, and figure out what the next area you want to grow into is.</p>
<p>Provision, a job that pays? Proficiency, becoming good at your job? Passion, a job you love? Impact, a job that provides for a need in the world?</p>
<p>Choose where you want to be in 5 years and what that might look like. Make a plan for where you want to be at the end of this year and then set quarterly goals to get yourself there!</p>
<p>Just remember by developing an intentional plan, you are ensuring consistent growth. Consistent growth is the key to long-term success!</p>
<p>I’d love to hear your questions or your goals!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Hard Reset for Autonomous SmartDesk 2</title>
      <link>https://eddiehinkle.com/2019/08/08/1/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2019/08/08/1/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>TL;DR: If your Autonomous SmartDesk 2 won&apos;t lower all the way, it needs a Hard Reset, and the instructions are at the end of this article to reset it.</description>
      <content:encoded><![CDATA[<p><strong>TL;DR: If your Autonomous SmartDesk 2 won&#39;t lower all the way, it needs a Hard Reset, and the instructions are at the end of this article to reset it.</strong></p>
<p>Every day when I end my workday, it have my Autonomous SmartDesk 2 raise to the lowest level that will allow my chair to be pushed under it completely to save on floor space when I&#39;m not working. This typically isn&#39;t a big deal, but yesterday my power went out and my desk decided at it&#39;s half-elevated height, it was at the bottom of the desk. In order to optimize it&#39;s operation, it keeps digital track of when it&#39;s at the lowest height, which means if it gets reset while it&#39;s elevated, it gets confused. So while I was googling, solutions I <a href="https://gist.github.com/benjamincharity/691d8f5cab36c7a41ddfb0ba86cd1c89">came across the GitHub gist</a> of a support email provided to another customer when they had a similar issue.</p>
<p>Because I don&#39;t want to have to depend on the gist existing in the future if I ever need it, I&#39;ve decided to copy the instructions here as they worked flawlessly! If you stumble upon this, I hope they help you as well!</p>
<h2>Reset Steps</h2>
<ul>
<li>Unplug the desk for 20 seconds.</li>
<li>Plug it back in, wait a full 20 seconds.</li>
<li>Hold down both the up and down arrow buttons until either the desk lowers all the way and beeps OR 20 seconds pass.</li>
<li>Release both buttons.</li>
<li>Hold the down button until the desk lowers and beeps one more time or 20 seconds pass. It is during this stage that if it is raised too high, it will lower until it finds the bottom and beeps.</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Thinking through Reactive Programming using RxJS</title>
      <link>https://eddiehinkle.com/2019/07/10/3/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2019/07/10/3/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>State can be a hard, difficult and bug inducing thing to track and manage. The best way to simplify state is to reduce it. Because of that, at my job, we try to embrace using Observable streams whenever we can. The difference between imperative programming and reactive (using Observables) programmin…</description>
      <content:encoded><![CDATA[<p>State can be a hard, difficult and bug inducing thing to track and manage. The best way to simplify state is to reduce it. Because of that, at my job, we try to embrace using Observable streams whenever we can. The difference between imperative programming and reactive (using Observables) programming is that in imperative programming you have to continually add a lot of logic around state and determining when and how state changes and making sure that state is reflected properly in the UI.</p>
<p>Reactive programming, focuses on streams of data (in our case called Observables). These streams of data can come from anywhere: mouse event, keyboard event, network request or even a timer running in the background. These streams can trigger events, return data, combine streams, split streams and transform existing data into new shapes and forms. But these streams (much like real life streams of water) have singular sources that are far away up at the top of the mountain. As such, when you are down stream, you aren’t having to make sure the water keeps running, you just install a hydroelectric damn to power your house.</p>
<p>Here is an example, that powers an auto-complete or auto-search function:</p>
<pre><code class="language-typescript">this.autoResults$ = merge(of(undefined), fromEvent(this.searchField.nativeElement, ‘input’))
  .pipe(
    map(e =&gt; e !== undefined ? e.target.value : undefined), // Get value from input box
    debounceTime(350), // Prevent too many queries back to back
    distinctUntilChanged(), // Don’t run duplicate requests
    tap(() =&gt; { this.loading = true }), // show loading mask, this has to be in curly braces to avoid returning any data 
    switchMap(searchTerm =&gt; this.service.search(searchTerm)), // Search for terms
    tap(() =&gt; { this.loading = false }), 
    shareReplay(1) // Prevent different subscribers from creating duplicate parallel async requests
  );
</code></pre>
<p>The item above starts by merging two Observable streams. The first, is an Observable stream that will emit a single value: <em>undefined.</em> This is for one reason, and one reason alone. On the component loading, we want to immediately kick off a search for all the unfiltered results. So in the example above, undefined means we don’t have any search terms and we just want ALL of the results. It loads on component load, but if we enter keys into the search field, it will automatically update. We don’t have to assign any logic to watching the input field and changing a search term state when it’s been modified and then determining when and how to launch the search logic after the search term state is modified.</p>
<p>Instead, it’s a single stream that starts with events being emitted from the search field, and then all of our logic is built alongside that stream. To simplify things, If we ONLY want search results to show if we have entered search results, then it would look like this:</p>
<pre><code class="language-typescript">this.autoResults$ = fromEvent(this.searchField.nativeElement, ‘input’)
  .pipe(
    map(e =&gt; e !== undefined ? e.target.value : ‘’), // Get value from input box
    filter(e =&gt; e.length === 0) // Don’t proceed if the string is empty
    debounceTime(350), // Prevent too many queries back to back
    distinctUntilChanged(), // Don’t run duplicate requests
    tap(() =&gt; { this.loading = true }), // show loading mask, this has to be in curly braces to avoid returning any data
    switchMap(searchTerm =&gt; this.service.search(searchTerm)), // Search for terms
    tap(() =&gt; { this.loading = false }),
    shareReplay(1) // Prevent different subscribers from creating duplicate parallel async requests
  );
</code></pre>
<p>Here, we start with an event, the search field’s input. We then pipe it through a series of filters, actions and transformations.</p>
<p><strong>Step 1: Map</strong>. we convert the data object from a keyboard event into the value of the search field. If the event is undefined for some reason, we get an empty string.</p>
<p><strong>Step 2: Filter</strong>. We filter based on the length of the string. We only want to search IF there are characters in the string. If we wanted we could make this set a minimum character limit to 2 or 3 so you don’t search after just 1 or 2 characters.</p>
<p><strong>Step 3: Debounce</strong>. To prevent firing a bunch of requests WHILE we are typing the term, we only let the last emitted value through within a given time period. So in this case if during 200ms you type “Hello”. There were 5 emitted values in the stream.</p>
<p><em>H</em></p>
<p><em>He</em></p>
<p><em>Hel</em></p>
<p><em>Hell</em></p>
<p><em>Hello</em></p>
<p>In this instance, at 350ms, a single value is emitted, which is the last value received: “Hello”.</p>
<p><strong>Step 4: DistinctUntilChanged</strong>. Suppose your search term is “Hello”. The results are showing. If you remove the last two characters “lo”, and then within 350ms you will have 4 new values emitted, but only one will make it past the Debounce: “Hello”. This is the same term you already have! Because of that, DistinctUntilChanged will prevent us from running the search logic on the same value.</p>
<p><strong>Step 5: Tap</strong>. Tap is essentially to just run an action based on the data coming in. You can use it for all sorts of things: Logging, Analytics, or this case, updating a loading UI.</p>
<p><strong>Step 6: SwitchMap</strong>. With a Switch Map we are switching from one observable stream to another in its same place. So in this instance, we are replacing the search term observable with the results from the search service observable.</p>
<p><strong>Step 7: Tap</strong>. This tap isn’t called until the step before it is complete, which means when we get here, we have loaded all data from the network, so we can hide our loading UI.</p>
<p><strong>Step 8: ShareReplay</strong>. Typically every Observable subscriber is subscribing to their own independent stream of data. Sometimes that is good, but in the regard of a network request like this that is always going to be the same data, it’s better to share the Observable among various subscribers. The “Replay” part of this is the fact that it will return the last value emitted to any late subscribers. This means if one subscriber starts following the stream and the search results happen, but after they are returned another component subscribes to the stream, it will get the last search result that happened (as well as any future results).  </p>
<p>This is just a brief overview of one type of reactive/Observable pattern, but it&#39;s one I found helpful and thought I would share.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Swift Question: Swift Package error when relying on AuthenticationServices</title>
      <link>https://eddiehinkle.com/2019/06/19/3/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2019/06/19/3/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>Okay, so for anyone reading that is familiar with Swift/Apple development and more importantly, with Xcode 11 beta.</description>
      <content:encoded><![CDATA[<p>Okay, so for anyone reading that is familiar with Swift/Apple development and more importantly, with Xcode 11 beta.</p>
<p>I am building a Swift Package using Xcode 11 beta. It&#39;s called <a href="https://github.com/EdwardHinkle/IndieWebKit">IndieWebKit</a>, but the issue I&#39;m running into is one of the classes relies on AuthenticationServices because it uses OAuth 2.0 and Apple rejects any app that doesn&#39;t use ASWebAuthenticationSession for OAuth 2.0</p>
<p>So here is the issue, when build the package and run it&#39;s tests, it errors out because it says &quot;Library Not Loaded&quot; for AuthenticationServices.</p>
<p>Below are what I&#39;ve implemented to try to get it to work:</p>
<p>I added <code>#if canImport(AuthenticationServices)</code> around <code>import AuthenticationServices</code> to try to avoid importing it if it&#39;s not available. I&#39;ve also set the Swift package platforms to <code>macOS(v10_15)</code> and <code>iOS(v12)</code>.</p>
<p>I also tried adding <code>@available(iOS 12.0, macOS 10.15, *)</code> at the beginning of the function that utilizes ASWebAuthenticationSession.</p>
<p>In the test suite, I DO NOT call the function that uses ASWebAuthenticationSession within the tests.</p>
<p>Despite all of this, I can ONLY run my tests if I have commented out the import and the usage of ASWebAuthenticationSession. This works for now while I&#39;m doing initial development, but eventually I need to be able to run these tests AND have the package contain the ASWebAuthenticationSession call.</p>
<p>I believe the reason it&#39;s doing this is because I&#39;m still on macOS 10.14 (which doesn&#39;t have AuthenticationServices), but I feel like if I&#39;m add these various &quot;available if and can import&quot; statements, I should be able to run tests that don&#39;t run the function that requires those services.</p>
<p>Any thoughts or ideas would be super helpful. Thanks!</p>
]]></content:encoded>
    </item>
    <item>
      <title>On System-Wide Keyboard Shortcuts in iPadOS</title>
      <link>https://eddiehinkle.com/2019/06/13/6/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2019/06/13/6/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>Jason Snell in talking about his impressions of iPadOS 13</description>
      <content:encoded><![CDATA[<p>Jason Snell <a href="https://sixcolors.com/post/2019/06/ipados-13-the-wish-list-meets-reality/">in talking about his impressions of iPadOS 13</a></p>
<blockquote>
<p>Users can’t assign systemwide keyboard shortcuts...Arbitrating keyboard shortcuts between the system and individual apps is hard</p>
</blockquote>
<p>When I was thinking about this, I had to ask myself, how does Apple make a non-complicated system wide-keyboard shortcut? I think the answer is to follow the &quot;Add to Siri&quot; path.</p>
<p>In iOS 12, when an app has a feature that can be invoked by Siri, Apple supplies a button the developer can add to their UI that brings up a system controlled UI that allows the user to speak a phrase for Siri to save as the &quot;incantation&quot; of that feature. In iOS 13, if you can&#39;t verbally talk, you can type your Siri incantation and later you can speak that incantation to summon that feature. So why not take a similar approach where there is a system defined button and UI that allows a developer to show the user they can make this functionality able to be activated via a keyboard shortcut. Of course, in reality, you end up needing to make that code available to be run independently of the app if it&#39;s a system keyboard shortcut because your app might be closed. If that&#39;s the case, you end up building an extension, and then you&#39;ve basically built a SiriUI extension.</p>
<p>So now I&#39;m wondering if the answer to system keyboard shortcuts is really just a different way to access single step Siri Shortcuts?</p>
]]></content:encoded>
    </item>
    <item>
      <title>Why Xcode for iPad isn’t (and will never be) here...</title>
      <link>https://eddiehinkle.com/2019/06/08/2/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2019/06/08/2/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>For awhile now, me and many other people have been wanting Xcode for iPad. There have been vague mentions of Xcode for iPad would be okay if it “only supported swift initially” and such things. I was disappointed that Xcode for iPad didn’t arrive this year and probably won’t ever arrive. Now I think…</description>
      <content:encoded><![CDATA[<p>For awhile now, me and many other people have been wanting Xcode for iPad. There have been vague mentions of Xcode for iPad would be okay if it “only supported swift initially” and such things. I was disappointed that Xcode for iPad didn’t arrive this year and probably won’t ever arrive. Now I think I know why.</p>
<p>I believe in the not too distant future, we will get an app, it could be an updated Swift Playgrounds or a completely new app. For now I believe no matter the origin it will be marketed as something new, something like “SwiftCode”.</p>
<p>Swift came out several years ago, and it&#39;s been slowly building into a stable platform for apps. Little did we know, it would be the platform for the future of Apple development. SwiftUI was released this year at WWDC which is entirely new paradigm for developing apps. Why would Apple build Xcode for UIKit, when they have known for years that it&#39;s not the future of their platforms? Instead I believe SwiftUI is the beginning of an important transition. One step of that will be a completely new IDE that will sometime in the future (read 10-20 years) completely replace Xcode in the Apple ecosystem.</p>
<p>SwiftCode will be built using Swift and SwiftUI to the core, built for a cross platform experience of development and it will let go of all the old ways of programming. Brent Simmons <a href="https://inessential.com/2019/06/07/the_next_era_ends_the_swift_era_begins">wrote a great piece talking about the difference between the NeXT Era and the Swift Era</a>, I completely agree. This new development tool when released will live alongside Xcode for awhile allowing people who are all in on the Swift era to develop on any platform they wish, and Xcode will continue to exist for the foreseeable future for backwards compatibility of old development workflows. It&#39;ll take awhile before everything can use SwiftUI and the new Apple development tool chain, but I believe an app like SwiftCode built to run on iPad and Mac, and who knows, maybe even iPhone, will be the future of Apple development.</p>
<p>That is why Xcode for iPad hasn&#39;t and never will be released. They had to finish the foundations first, the Swift toolchain, Swift Package Manager, and SwiftUI. All the pieces in place so that they can release an IDE custom built for that ecosystem: SwiftCode.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Summer 2019 Premieres: The 100 Season 6 on CW</title>
      <link>https://eddiehinkle.com/2019/05/24/7/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2019/05/24/7/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>Well, this was a turn around! My wife and I quit watching last year (Season 5) of The 100 halfway through. When our son was born we didn&apos;t have as much free time for watching tv and we weren&apos;t enjoying the direction that Season 5 was taking. It felt like they had removed everything that we had enjoy…</description>
      <content:encoded><![CDATA[<p>Well, this was a turn around! My wife and I quit watching last year (Season 5) of <a href="https://www.cwtv.com/shows/the-100/">The 100</a> halfway through. When our son was born we didn&#39;t have as much free time for watching tv and we weren&#39;t enjoying the direction that Season 5 was taking. It felt like they had removed everything that we had enjoyed from each character and the storyline itself was pointless. Nothing interesting or intriguing. With many of our shows having their Spring Finales, our tv schedule freed up and we decided to finish up Season 5. After pulling off that bandaid, we were pleasantly surprised with the final minutes of Season 5. Everyone is put into medical comas and when they wake up they are informed that the Earth is no longer inhabitable and that it&#39;s been over 200 years and their spaceship has reached a new planet that had been under investigation as a replacement for earth.</p>
<p>Thus starts Season 6. The characters we know and love (granted, with some baggage from the last season) return as they descend to this new planet, very similarly to the original first season. A fresh canvas where they are able to mix the intrigue of new cultures and people that we know nothing about with some of the historical lore that they have established over the course of the last 5 seasons. It makes the show feel fresh, and my wife and I quickly binged the first couple of episodes and caught up to all the shows within a day or two. We are eagerly awaiting the next episodes and are very excited about the direction that The 100 has taken.</p>
<p>I almost wonder if sometime during Season 5 they had a change of writing staff. It reminds me a lot of <a href="https://www.cwtv.com/shows/arrow/">season 7 of Arrow</a>, which after having a rough patch felt like a flashback to the original season of Arrow after switching the show runner and an overhaul of the writing staff. Either way, whomever decided the make the changes to The 100, they&#39;ve breathed a breath of fresh air! We are looking forward to seeing how Season 6 of The 100 turns out!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring 2019 Retrospective: The Village Season 1 on NBC</title>
      <link>https://eddiehinkle.com/2019/05/24/3/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2019/05/24/3/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>I just finished the finale of Season 1. The Village was an interesting take on a common trend lately. It seems that many shows are trying to follow on the success of This is Us. In Spring 2018, There was a show on NBC called Rise that I didn&apos;t get to watch before it got cancelled at the end of its f…</description>
      <content:encoded><![CDATA[<p>I just <a href="https://eddiehinkle.com/2019/05/23/13/watch/">finished the finale of Season 1</a>. <a href="https://www.nbc.com/the-village">The Village</a> was an interesting take on a common trend lately. It seems that many shows are trying to follow on the <a href="https://eddiehinkle.com/2017/09/30/9/article/">success of This is Us</a>. In Spring 2018, There was a show on NBC called <a href="https://www.nbc.com/rise">Rise</a> that I didn&#39;t get to watch before it got cancelled at the end of its first season. From the previews, I could tell it was going for an emotional drive which is the core success of This is Us. This past fall, ABC released a new show called <a href="https://eddiehinkle.com/2018/09/30/27/article/">A Million Little Things</a>, which my wife and I enjoyed a lot but in the premiere I had this to say about it:</p>
<blockquote>
<p>Unlike This Is Us, which starts out very warm and fuzzy, A Million Little Things took the opposite approach and starts out very depressing. In fact 40 minutes into the 45 minute show my wife and I were almost ready to give up on the show because it was very depressing and didn’t have a clear direction. In the final 5 minutes, it flips on its head with some touching moments and some startling revelations that sets this show up for a very interesting season ahead.</p>
</blockquote>
<p>A Million Little Things had its ups and downs, but it had a lot of downs. This spring, NBC tried again with The Village. The Village started out the opposite of A Million Little Things. Where A Million Little Things went for a start in the emotionally sad and slowly built up, The Village went for an emotional high start.</p>
<p>The common issue though with both is that they try to lay out all of their cards up front. For me, what was special about This is Us was that it started out as a pretty normal show that at the end of the premiere it revealed that these people were all a family, and that Jack, was actually a flashback and he was the father of the other people you were watching. At the end of episode 2, it chose to begin to up the stakes by revealing that Jack, is no longer alive in present day. The mystery begins, what happens to Jack and how did the family get to where they are. Over the course of the last 3 seasons they slowly reveal various things like people having depression, alcoholism, eating disorders, struggling to be parents, marital challenges and even the <a href="https://eddiehinkle.com/2017/11/25/2/article/">challenges of pregnancy</a>. These things, however, were not dished out in one or even two episodes.  </p>
<p>The challenge with all of these This is Us &quot;copycat&quot; shows, they want to reveal all of their interesting ideas in the first one or two episodes as if to say &quot;look at all of our interesting and emotionally gripping characters&quot;. It feels heavy handed and it makes it difficult to really get interested in the characters as people rather than as representatives of various human ailments and issues.</p>
<p>In Season 1 of The Village, it explores being a single parent, unexpected pregnancy, illegal immigration, battling cancer, PTSD, loss of a child, finding love after losing a marriage partner, and dealing with family issues. These are all very interesting ideas but at times it feels like these issues speak louder than the characters themselves, which is hard to overcome. But I had to say, an aptly named finale episode title &quot;I Have Got You&quot;, was the best episode of the entire season. Is it worth it to watch an entire season of 10 episodes just for the build up of a wonderful single episode finale? Maybe. Maybe it&#39;s because now that I&#39;m a father, parental moments in tv shows can tear my heart out, but remembering the final episode of Season 1 doesn&#39;t have an inner emotional reaction. So I guess after 9 episodes of challenges, they nailed the final episode. If it&#39;s renewed for Season 2, I&#39;ll probably watch it, because the finale has given me faith that these characters might be finding their place after a slow start.</p>
<p>Should you watch it? I guess the question is if you have the patience to allow the characters to find their footing over the course of the first season.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Remark Development Log: Starting with the Kit</title>
      <link>https://eddiehinkle.com/2019/05/19/9/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2019/05/19/9/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>This is the first post about Remark, my upcoming Social Reader that is replacing my previous app, Indigenous. In my last log about Indigenous I talked about taking the lessons I’ve learned during Indigenous and structuring Remark better from the ground up.</description>
      <content:encoded><![CDATA[<p>This is the first post about Remark, my upcoming Social Reader that is replacing my previous app, Indigenous. <a href="https://eddiehinkle.com/2019/05/08/5/article/">In my last log about Indigenous</a> I talked about taking the lessons I’ve learned during Indigenous and structuring Remark better from the ground up.</p>
<p>First things first, I have multiple apps I’m planning on building on iOS (Remark, Chronicler, and Anthologist), not to mention the Indiepaper for iOS app that I built but that needs native IndieAuth before Apple will approve it for distribution in the App Store. Needless to say, neither I (nor anyone else) should have to keep building the IndieWeb building blocks in Swift over and over again. So the first thing I’m doing is taking code for client implementations of IndieAuth, Micropub and Microsub, and putting it into a generic Swift framework that can be included in any Swift application. Apple so often calls it’s frameworks with a “Kit” suffix, so I chose to call this <strong>IndieWebKit</strong>, for obvious reasons.</p>
<p>Lately, I&#39;ve had a renewed focus and appreciation for testing. So I bought a new book, <a href="http://www.amazon.com/dp/B01891X7S8/?tag=eddiehinkle-20">Test-Driven iOS Development with Swift</a>. I&#39;m currently reading through that book and looking through the IndieAuth, Micropub and Microsub specs and building tests for the proper functioning of the framework.</p>
<p>My hope is that by making this IndieWebKit a test-driven framework with all the IndieWeb building blocks for client apps, it will be much easier, reliable and frequent that we will see Swift apps built using IndieWeb building blocks.</p>
<p>As I&#39;m working on this core foundation, not just of Remark, but of all of my IndieWeb compatible apps, I&#39;m continuing to think through the features and User Experience of Remark. But more on that in a future post!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Final Indigenous Log: The Future of the App</title>
      <link>https://eddiehinkle.com/2019/05/08/5/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2019/05/08/5/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>Over a year ago, I was working on Indigenous, the first app I&apos;ve released in the App Store. It was a great experience but it originally started as a native share sheet extension. From there, more Micropub features were added and then as Microsub was announced, that was built in as well. Ultimately i…</description>
      <content:encoded><![CDATA[<p>Over a year ago, I was working on Indigenous, the first app I&#39;ve released in the App Store. It was a great experience but it originally started as a native share sheet extension. From there, more Micropub features were added and then as Microsub was announced, that was built in as well. Ultimately it turned into half-social reader half-Micropub client. This split role has made the code and app structure very complicated. Also the name Indigenous is a challenge as well, as it doesn&#39;t describe the purpose of the app at all. I liked the name initially because it was the native experiment for the IndieWeb (and I think it has worked great!), but the time has come where the app needs a name that will explain it&#39;s use and purpose for people who might not know about the IndieWeb. In fact, that means the app needs a clear purpose for it to have a clear name.</p>
<p>Going forward, Indigenous will be broken into different apps rather than trying to make a monster app that does everything. I have had three goals of the app up to now:</p>
<ul>
<li>A fun social reader</li>
<li>A Micropub app for posting to blogs</li>
<li>A reading app for long-form saved articles</li>
</ul>
<p>These are three very different use cases and they need different UIs and different approaches. So today I&#39;m going to talk about three new apps that I&#39;m planning on developing as open source apps for iOS and macOS (using the unnamed <a href="https://sixcolors.com/link/2019/02/gurman-marzipan-roll-out-a-three-year-process/">Marzipan</a> effort). This might take awhile to develop, but in the end, I think it will allow me to develop features faster, with a better UI and better overall apps. I call them the Abode Suite.</p>
<h2>Remark</h2>
<p><a href="https://eddiehinkle.com/tag/remark">Remark</a> is the first of the three Abode Suite apps that will be released and is the project I am currently working on. Remark will be the replacement of the <strong>fun social reader</strong> aspect of Indigenous. It&#39;s name is built on the idea that we read things that people are talking about and saying and we respond <em>or remark</em> on them. This will be primarily a Microsub app with Micropub sprinkled in just enough to allow you to post short notes, replies or reactions. Think of this as an IndieWeb Twitter-like app. I am avoiding most UI work for now because with WWDC 2019 approaching UI is not something you want to spend much time on. The goal is for this app to have a beta sometime during the iOS 13 beta cycle, and release either at the same time as iOS 13 or later this year after iOS 13 is released. When it is released it should support being used on both iPad and Mac but how optimized it is for both of those platforms may be minimal at launch.</p>
<h2>Chronicler</h2>
<p><a href="https://eddiehinkle.com/tag/chronicler">Chronicler</a> will be the replacement for the <strong>Micropub app for posting to blogs</strong> feature of Indigenous. I&#39;ve wanted to add support for offline authoring of posts, editing/updating posts, and more tools for long-form posting or posting things such as audio and video from Indigenous. My goal is for Chronicler to be a super easy and focused app that allows you to write long-form writing, but also do things like post podcast episodes, short video snippets as well as view and edit previously published posts using some of the new Micropub queries that have been brainstormed recently. This app will be the second app worked on, once Remark is complete and won&#39;t have any Microsub involvement.</p>
<h2>Anthologist</h2>
<p><a href="https://eddiehinkle.com/tag/anthologist">Anthologist</a> will be the third app of the Abode Suite trio, being a <strong>reading app for long-form saved articles</strong>. This app will be based on Microsub and won&#39;t be optimized for short-form content (although there won&#39;t be a technical restriction against it). This app will likely allow you to choose from 1 (or more) channels within your Microsub account to store offline in the app for reading. This could be a channel of saved articles or it could be a channel with some long-form RSS subscriptions. Either way, the focus will be on showing a subset of your Microsub account rather than showing all of your channels. The goal is to hide away the short ephemeral content that might distract you and instead create an environment like Kindle or Apple Books that allows you to get lost in the content of what you are reading rather than getting distracted by social engagement. I have not decided if this will support Micropub <em>at all.</em> Instead, it will likely rely on the share sheet to share the url of the article to the Chronicler share sheet in order to do most writing / reacting. That said, as it progresses, I&#39;m open to feedback and change. This will be the final of the three apps released, so this might be awhile before I get to it, but it&#39;s something I always wanted to build into Indigenous but it never felt like it fit.</p>
<h2>Thank You</h2>
<p>I appreciate everyone that has beta tested Indigenous and downloaded the public version of Indigenous in the iOS App Store. It&#39;s been exciting to release my first official iOS app to the public and I hope that this change in direction will only improve the quality of the apps I&#39;m making available.</p>
<p>I&#39;d love to hear feedback. I&#39;m going to be recreating the issues in the Indigenous repo in the three new repos for the apps. If you have suggestions or feedback on any of the specific apps feel free to create issue in their respective GitHub repos: <a href="https://github.com/EdwardHinkle/remark">Remark</a>, <a href="https://github.com/EdwardHinkle/chronicler">Chronicler</a>, <a href="https://github.com/EdwardHinkle/anthologist">Anthologist</a>. You can also just write a reply on your website and respond to this post!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Ranking the Marvel Cinematic Universe Movies</title>
      <link>https://eddiehinkle.com/2019/04/22/1/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2019/04/22/1/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>With Avengers: Endgame about to happen and serve as a conclusion to the incredibly long story arc around the threat of Thanos, I thought I would celebrate by ranking all the Marvel movies in the MCU up til now.</description>
      <content:encoded><![CDATA[<p>With Avengers: Endgame about to happen and serve as a conclusion to the incredibly long story arc around the threat of Thanos, I thought I would celebrate by ranking all the Marvel movies in the MCU up til now.</p>
<p>My goal is to do this is two ways: I want to group them into 4 tiers: First Tier (Amazing), Second Tier (Enjoyed), Third Tier (Okay), Fourth Tier (Reluctant / Disappointed). I also want to actually just rank them within their tiers/the entire order.</p>
<p>It’s been a long time since I have watched some of these movies, so it might be a bit affected by memory, and at some point I should probably do a watch through of all the movies and rank them at the end of each movie, so it would be more fresh. But for now, here it is:</p>
<h2>First Tier</h2>
<ol>
<li>Captain Marvel</li>
<li>Avengers: Infinity War</li>
<li>Spider-Man: Homecoming</li>
<li>Captain America: Civil War</li>
</ol>
<h2>Second Tier</h2>
<ol start="5">
<li>Doctor Strange</li>
<li>Thor: Ragnarok</li>
<li>Black Panther</li>
<li>Ant-Man</li>
<li>The Avengers</li>
<li>Iron Man</li>
<li>Thor</li>
<li>Captain America: The First Avenger</li>
<li>Iron Man 3</li>
</ol>
<h2>Third Tier</h2>
<ol start="14">
<li>Iron Man 2</li>
<li>Avengers: Age of Ultron</li>
<li>Captain America: The Winter Soldier </li>
<li>Thor: The Dark World</li>
</ol>
<h2>Fourth Tier</h2>
<ol start="18">
<li>The Incredible Hulk</li>
<li>Guardians of the Galaxy, Vol 1</li>
<li>Guardians of the Galaxy, Vol 2</li>
</ol>
<h2>Unranked</h2>
<ul>
<li>Ant-Man and The Wasp</li>
<li>Avengers: Endgame</li>
</ul>
<p>I have yet to watch both Ant-Man and the Wasp and Avengers: Endgame. I will update this post after I watch Ant-Man and the Wasp, as well as Avengers: Endgame… my current prediction is that Ant-Man and the Wasp will be in the Second Tier and Avengers: Endgame will be in the First Tier.</p>
<p>How do you rank the Marvel Cinematic Universe movies?</p>
<p><em><strong>UPDATE:</strong> After more consideration, Vol. 1 and Vol. 2 of Guardians of the Galaxy have been swapped putting Vol. 1 above Vol. 2.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>IndieWebCamp Online 2019</title>
      <link>https://eddiehinkle.com/2019/03/11/10/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2019/03/11/10/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>So this past weekend, I helped host IndieWebCamp Online 2019. It was a really fun weekend, if a little unorthodox. I think the camp was successful and enjoyed and yet had learn-able take-aways for the next online camp as well as ideas for single topic sessions which is a bridge somewhere between an…</description>
      <content:encoded><![CDATA[<p>So this past weekend, I helped host <a href="https://indieweb.org/2019/Online">IndieWebCamp Online 2019</a>. It was a really fun weekend, if a little unorthodox. I think the camp was successful and enjoyed and yet had learn-able take-aways for the next online camp as well as <a href="https://indieweb.org/ad-hoc_sessions">ideas for single topic sessions</a> which is a bridge somewhere between an virtual IndieWeb Meetup (aka Homebrew Website Club) and a full IndieWebCamp. The benefit of single topic sessions is extremely low overhead while still moving discussions forward in various topical areas without having to wait for a full IndieWebCamp.</p>
<p>We had 11 sessions, each one happened in their own time slot. That&#39;s a bit unorthodox for an IndieWebCamp but it allowed some of the sessions to spread out and have completely different people at some than others based on interest and timezones (yes, timezones are hard). Interestingly the sessions ended up in three groups, 4 &quot;Friday&quot; sessions, 3 late-night Wordpress sessions and 4 &quot;Saturday&quot; sessions. Although due to timzones those days are a bit blurry.</p>
<p>I really enjoyed all the sessions. A couple that really stood out were the <a href="https://indieweb.org/2019/Online/autoauth">AutoAuth</a> and <a href="https://indieweb.org/2019/Online/groups">Groups</a> sessions. In fact, inspired by the AutoAuth session, that&#39;s what my Create Day project was on! For Day 2 of the camp, I worked on adding AutoAuth to my site. AutoAuth is a proposed extension to the IndieAuth specification that allows IndieAuth to happen between software without the active intervention of the site owners. The goal of AutoAuth is to allow access control to posts like how Facebook allows you to make a post with only specific users able to see it or how Twitter allows you to have a private account.</p>
<p>First I was able to add the right scopes to my authorization endpoint</p>
<p><img src="https://eddiehinkle.com/media/c7b60a648c394d00303cefb1a49a82d0.png" alt=""></p>
<p>which I defined as &quot;Allow a third party application to request the ability to read other people&#39;s content&quot;. It can probably be improved but essentially it allows my <a href="https://indieweb.org/reader">Social Reader</a> to grab private posts on my behalf so that I can read them in my app.</p>
<p>I also implemented protected posts on my website and did some testing. I added two people to the &quot;access list&quot; (which I call audience) of a private test post. I then tested visiting that page as an unidentified user, as me, as a person on the access list and as a person I know is NOT on the access list. It worked correctly, for an unidentified user, it presents the correct HTTP headers for AutoAuth so a reader knows there is content there if people are identified. However the page itself doesn&#39;t say anything about the existence of a post to an unidentified user or a user that is not on the access list.</p>
<p><img src="https://eddiehinkle.com/media/46d86e585451c9787882bc5b55b42e81.png" alt=""></p>
<p>It&#39;s kind of a strange error message from when I had a static site that had to rebuild. I need to adapt that, but the key is it works! Then when either I or a person on the access list visits the url, you get a different page completely!</p>
<p><img src="https://eddiehinkle.com/media/f220a3d0824b72a2f6f81212b93242c1.png" alt=""></p>
<p>Surprise! There is a post there! In the future, I could even do things like restrict the location of the post to people that I know or other similar things. AutoAuth opens up the doorway to a whole interesting world of semi-private information.</p>
<p>I am not actually finished with the project, I have some more stuff I need to finish as far as how my site communicates with other people&#39;s sites to verify the person is who they say they are, etc. But it&#39;s an exciting start and sometimes starting is the hardest part!</p>
<p>I&#39;m hoping to get this work wrapped up in the next week so I can start testing it. Plus, if I get it working and start creating private posts for people on the IndieWeb, it might encourage them to start adding AutoAuth to their projects as well!</p>
]]></content:encoded>
    </item>
    <item>
      <title>rsvp-to-indieweb-online.html</title>
      <link>https://eddiehinkle.com/2019/02/28/19/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2019/02/28/19/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>I&apos;m RSVPing &lt;span class=&quot;p-rsvp&quot;yes&lt;/span to &lt;a href=&quot;https://2019.indieweb.org/online&quot; class=&quot;u-in-reply-to&quot;IndieWebCamp Online 2019&lt;/a</description>
      <content:encoded><![CDATA[<p>I&#39;m RSVPing <span class="p-rsvp">yes</span> to <a href="https://2019.indieweb.org/online" class="u-in-reply-to">IndieWebCamp Online 2019</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>fixed-button.html</title>
      <link>https://eddiehinkle.com/2019/02/20/28/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2019/02/20/28/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>&lt;a class=&quot;btn btn-primary btn-xl text-uppercase js-scroll-trigger&quot; href=&quot;tel:(845) 377-3275&quot;Call Me To Get Started&lt;br(845) 377-3275</description>
      <content:encoded><![CDATA[<p><a class="btn btn-primary btn-xl text-uppercase js-scroll-trigger" href="tel:(845) 377-3275">Call Me To Get Started<br>(845) 377-3275
</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>original-button.html</title>
      <link>https://eddiehinkle.com/2019/02/20/27/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2019/02/20/27/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>&lt;a class=&quot;btn btn-primary btn-xl text-uppercase js-scroll-trigger&quot;Call Me To Get Started&lt;br&lt;/a</description>
      <content:encoded><![CDATA[<p><a class="btn btn-primary btn-xl text-uppercase js-scroll-trigger">Call Me To Get Started<br></a>
<a href="tel:(845) 377-3275">(845) 377-3275</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>rings.html</title>
      <link>https://eddiehinkle.com/2019/02/13/8/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2019/02/13/8/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>&lt;!-- thanks to https://codepen.io/webslingerm/pen/GXxxVx --</description>
      <content:encoded><![CDATA[<!-- thanks to https://codepen.io/webslingerm/pen/GXxxVx -->
<style type="text/css">
@-webkit-keyframes RingProgress {
  0% {
    stroke-dasharray: 0 100;
  }
}

@keyframes RingProgress {
  0% {
    stroke-dasharray: 0 100;
  }
}
.ActivityRings {
  height: 100%;
  width: 100%;
}
.ActivityRings .ring {
  -webkit-transform-origin: 50%;
          transform-origin: 50%;
}
.ActivityRings .completed {
  -webkit-animation: RingProgress 1s ease-in-out forwards;
          animation: RingProgress 1s ease-in-out forwards;
  stroke-linecap: round;
}
.ActivityRings circle {
  fill: none;
}

.ring-move .background {
  stroke: rgba(197, 63, 61, 0.2);
}
.ring-move .completed {
  stroke: #c53f3d;
}

.ring-exercise .background {
  stroke: rgba(148, 213, 90, 0.2);
}
.ring-exercise .completed {
  stroke: #94d55a;
}

.ring-stand .background {
  stroke: rgba(112, 190, 215, 0.2);
}
.ring-stand .completed {
  stroke: #70bed7;
}
.ring-container {
    background: black;
    width: 200px;
    height: 200px;
}
</style>

<div class="ring-container">
<svg class="ActivityRings" viewBox='0 0 37 37'>
    <g class="ring ring-move" style="transform: scale(1) rotate(-90deg);">
        <circle stroke-width="3" r="15.915" cx="50%" cy="50%" class="background" />
        <circle stroke-width="3" r="15.915" cx="50%" cy="50%" class="completed" stroke-dasharray="<?= {{move}} ?>, 100" />
    </g>
    <g class="ring ring-exercise" style="transform: scale(0.75) rotate(-90deg);">
        <circle stroke-width="4" r="15.915" cx="50%" cy="50%" class="background" />
        <circle stroke-width="4" r="15.915" cx="50%" cy="50%" class="completed" stroke-dasharray="<?= {{exercise}} ?>, 100" />
    </g>
    <g class="ring ring-stand" style="transform: scale(0.5) rotate(-90deg);">
        <circle stroke-width="6" r="15.915" cx="50%" cy="50%" class="background" />
        <circle stroke-width="6" r="15.915" cx="50%" cy="50%" class="completed" stroke-dasharray="<?= {{stand}} ?>, 100" />
    </g>
</svg>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Passive Tracking &gt; Active Tracking</title>
      <link>https://eddiehinkle.com/2019/01/11/30/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2019/01/11/30/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>In the most recent episode of Two Dads Talking, Jonathan and I talk about tracking our information and whether we are using active or passive applications. Jonathan is using mostly passive applications that collect his behavior and translate that into posts on his website. I, on the other hand, spen…</description>
      <content:encoded><![CDATA[<p>In the <a href="https://twodads.fm/2019/01/10/episode-im-not.html">most recent episode of Two Dads Talking</a>, Jonathan and I talk about tracking our information and whether we are using active or passive applications. Jonathan is using mostly passive applications that collect his behavior and translate that into posts on <a href="https://cleverdevil.io">his website</a>. I, on the other hand, spend way too much time tracking all sorts of information: <a href="https://eddiehinkle.com/watched/">what I watch</a>, <a href="https://eddiehinkle.com/listened/">what I listen to</a>, <a href="https://eddiehinkle.com/tag/checkin/">where I go</a>, not to mention <a href="https://eddiehinkle.com/tag/health">what I eat and drink</a>. Essentially everything I log is manual, so in 2019 I have a goal to move as much as I can to automated and that which can&#39;t be automated to defer to a time when I have free time.</p>
<p>Step one is my listen posts. Currently after every podcast I listen to, I have to remember to activate the share sheet in my Podcast app (Castro) and share it with my app, Indigenous, which will share the url to my website and I will process it for the episode&#39;s information. Jonathan shared in the episode that he came up with a script that can pull in his play history from his Overcast account and publish it to his website. It hasn&#39;t been approved by Overcast&#39;s owner yet and he ran into some issues so he&#39;s waiting on those to be resolved but it&#39;s do-able. Because of this <a href="https://eddiehinkle.com/2019/01/11/7/article/">I switched from Castro back to Overcast</a> yesterday and am looking forward to setting up a similar system so that I can have listen posts be passively tracked just by being played in my podcast player of choice.</p>
<p>Step two are my watch posts. Currently, every time I watch something and want to track it, I have to run a command line script which require me connecting to my server, running a snippet from an app on my iPhone and entering the episode number that I just watched. It&#39;s fairly low maintenance but it still requires more thought than I would like. I initially thought I wouldn&#39;t be able to <a href="https://cleverdevil.io/2019/-huzzah-i-now-have-my-trakt">use Trakt like Jonathan</a> because I track who I watched it with. I realized, I already use an app to track what I <strong>want</strong> to watch that is compatible with Trakt, so I can set my site up to automatically import watch posts from Trakt, and then after the fact I can update <strong>who</strong> I watched it with, thus combining my two goals of 2019: automate and defer. This takes me from opening an app, connecting to my server, selecting the right snippet and entering the episode number into opening an app, tapping on the checkmark for the right show in my watch list. Then, later, I can sit down and add who I watched each show or movie with since I&#39;m not likely to forget that.</p>
<p>I don&#39;t have a plan for the rest of my items: ate, drank, checkins, trips, etc. but I&#39;m going to be thinking through them. Right now I have an app that automatically tracks my location everywhere and it logs when I&#39;ve been in one location for more than 10 minutes and my speed, so I&#39;m thinking I can build something that can analyze that information and present suggestions as to where I might need to checkin and how I might have traveled (walking, driving, biking, etc.) and then just provide the ability for me to modify that information if anything is incorrect and publish it to my site.</p>
<p>Additionally, there is no easy way to automatically track my food, but I can either work on making sure the app I use is as seamless and quick as possible to enter it and finish it, or I can try to defer it by creating an interface that makes it really easy for me to enter a entire day&#39;s worth of meals at one time at the end of the day and then submit that.</p>
<p>Any thoughts? Do you track information? Which do you track actively and which do you track passively?</p>
]]></content:encoded>
    </item>
    <item>
      <title>Switching from Castro back to Overcast</title>
      <link>https://eddiehinkle.com/2019/01/11/7/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2019/01/11/7/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>As mentioned on the recent episode of Two Dads Talking, I found myself really enjoying Castro, but wanting to shift back to Overcast. I finally figured out a workflow that works for me on Overcast! I have three playlists: Queue (manual), Priority (Smart), All (Smart).</description>
      <content:encoded><![CDATA[<p>As mentioned on the <a href="https://twodads.fm/2019/01/10/episode-im-not.html">recent episode of Two Dads Talking</a>, I found myself really enjoying Castro, but wanting to shift back to Overcast. I finally figured out a workflow that works for me on Overcast! I have three playlists: Queue (manual), Priority (Smart), All (Smart).</p>
<p>Queue is a manual playlist that I can add podcast episodes to and I always listen to episodes from the queue.</p>
<p>Priority is a smart playlist that lists episodes from my most important podcasts I follow.</p>
<p>All episodes is a smart playlist that includes all podcasts but excludes the podcasts that are in the priority smart playlist.</p>
<p>So my workflow is I listen to podcasts in the Queue, however when the queue runs low, I check my priority smart playlist and from there I can delete an episode I’m not interested in, or add that episode to the queue if I’m ready to listen to it. When I add it to the queue I also remove it from the smart playlist so that I know anything in the smart playlist doesn’t exist in the queue yet.</p>
<p>The best thing is that I can move individual episodes from priority to queue OR priority to all episodes if I feel like I don’t want to lose an episode but I don’t want it clogging up my priority list. The multiple playlists allow for more flexibility than I had with Castro while still allowing me the spirit of what I had in Castro a type of filtering system so I don’t feel I have to listen to every episode of a podcast.</p>
<p>The benefits of switching back to Overcast are also that <a href="https://cleverdevil.io">Jonathan</a> wrote a script that I can use to automatically import listen posts to my website rather than having to do it manually. Also, I’m looking forward to being able to try out Overcast’s Apple Watch app, not to mention I really appreciate Marco’s design aesthetic and attention to detail.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Story-telling and character perspectives in the show &quot;The Affair&quot;</title>
      <link>https://eddiehinkle.com/2019/01/10/11/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2019/01/10/11/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>I&apos;ve been watching the TV show &quot;The Affair&quot; because it has one of my favorite actors and I have to say their story telling approach is so interesting in season one! Every episode is split into two parts, each part is from one of two people&apos;s narrative perspective.</description>
      <content:encoded><![CDATA[<p>I&#39;ve been watching the TV show &quot;The Affair&quot; because it has one of my favorite actors and I have to say their story telling approach is so interesting in season one! Every episode is split into two parts, each part is from one of two people&#39;s narrative perspective.</p>
<p>The really interesting thing about this perspective shift is that sometimes it covers different events, but sometimes it overlaps the same events that happened in the other part, however in retelling the same event it will have inconsistencies between each person&#39;s perspective on how the event happened. Sometimes it&#39;s something small like one person or the other started the conversation, or they were chatting in a slightly different room. Other times it is huge inconsistencies like specific things literally never happen in one viewpoint or someone&#39;s personality is completely different. For example, the person whose perspective it is, is almost always more docile and the other person is more aggressive. It&#39;s really intriguing and makes me wonder if the &quot;truth&quot; of some of these things will ever be revealed or if it will always be a he-said she-said mystery.</p>
<p>So often stories are told in such standard and conventional ways, it really excites me to see stories told in different ways. This tv show is such a great rendition of the classic story-telling tactic of <a href="https://en.wikipedia.org/wiki/Unreliable_narrator">the unreliable narrator</a>.</p>
<p>It&#39;s an interesting show and I&#39;m wondering if they are going to change things up in the following seasons or keep the same story-telling approach. If it&#39;s the later, will they introduce new perspectives or continue to tell the entire show through these two characters. Only time will tell! Thankfully there are already 4 seasons out so I won&#39;t have to wait to find out.</p>
]]></content:encoded>
    </item>
    <item>
      <title>PHP&apos;s Relevance</title>
      <link>https://eddiehinkle.com/2018/11/30/9/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2018/11/30/9/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>(This post was written as an assignment for my CSIS 316 class as a short ~300 word analysis of PHP vs newer programming languages)</description>
      <content:encoded><![CDATA[<p><em>(This post was written as an assignment for my CSIS 316 class as a short ~300 word analysis of PHP vs newer programming languages)</em></p>
<p>PHP has been the bedrock of web development for decades. It was actually the first server-side language I learned how to program, 15 years ago. It has historically been one of the most dependable server-side programming languages, but the question is does it still hold that place in today&#39;s fast changing, ever growing market of programming languages?</p>
<p>PHP has a lot of things going for it, historical reliance, a large base of programmers who know and program it, server support in essentially every shared hosting plan available, as well as being the foundational language in two of the most popular Content Management Systems available (Wordpress and Drupal). Ease of use for newcomers is also key to its success. Once PHP is integrated with the web server it rarely if ever has to be managed or restarted and individual PHP scripts are run automatically by the PHP server service.</p>
<p>There is a lot of competition in today&#39;s programming market. Many of them have better tooling, are more exciting to work in and have great features that help with reliability such as typed variables, native multi-threading and speed of processing. But are these improvements worth the cost of the reliability and dependability of PHP? For me, that depends on the use case. If the application isn&#39;t going to have anyone experienced with managing it routinely, PHP is the best route to go because of the ability to easily deploy it on shared hosting environments by just uploading the scripts, while other languages such as Node.js, Swift, Go and others require an executable to be run and to ensure it remains up in the event of a crash, there needs to be a secondary server that restarts the application. If you need modern tooling with robust features such as typed variables and multi-threading it can help to use a more modern language rather than trying to use new libraries that make PHP more modern.</p>
<p>Ultimately I think it comes down most of the time to personal preference. There are likely few cases where any specific server-side language is going to drastically change things unless the application is frequently under a large load or has a lot of intensive data processing tasks.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Added Locally Stored Webmentions</title>
      <link>https://eddiehinkle.com/2018/11/26/13/article/</link>
      <guid isPermaLink="true">https://eddiehinkle.com/2018/11/26/13/article/</guid>
      <pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate>
      <description>The main part of my newwwyear 2019 goal is storing webmentions locally and displaying them on my website again, as well as adding moderation support.</description>
      <content:encoded><![CDATA[<p>The main part of <a href="https://eddiehinkle.com/2018/11/15/5/article/">my newwwyear 2019 goal</a> is storing webmentions locally and displaying them on my website again, as well as adding moderation support.</p>
<p>It&#39;s coming along nicely. So far, I&#39;ve imported all existing webmentions from webmention.io and stored them locally alongside my post storage. So if my post is stored in <code>/2018/11/15/5/post.md</code> then my webmentions would be stored in <code>/2018/11/15/5/mentions/</code> each webmention is stored as a <code>.json</code> file with the source url being the filename. This allows for one webmention per file and if I want to load all the webmentions for a specific post, I just open up all the files in the mentions directory. So far, I&#39;m really liking this and how well it&#39;s working.</p>
<p>If it&#39;s a homepage mention, it will save the webmention in <code>/mentions/homepage/</code> and if it&#39;s not a known post, right now it will collect the webmention in <code>/mentions/unknown</code>. Again, all of these webmentions are stored as individual files. If I recieve an updated webmention, it overwrites the file, so I get the updated information.</p>
<p>Besides just downloading all existing webmentions, I have also added support so that when webmention.io receives a new webmention, it pings my server and my server will go ahead and download that webmention and save it locally. This means that I am now <strong>only</strong> relying on webmention.io to deliver my webmentions, not for any long-term storage.</p>
<p>Finally, I&#39;ve added support for displaying webmentions on post pages. I used to have this when my website was powered by Jekyll, but when I moved to a dynamic website, I didn&#39;t bother adding that functionality back until I could do it the way I wanted it. Now that I have my local storage I was able to go back to displaying them. Below is a screenshot of the webmention displays on one of my posts:</p>
<p><img src="https://eddiehinkle.com/media/fca8860d7037e12732a5346cbbcd700f.jpeg" alt=""></p>
<p>I&#39;m still trying to tweak exactly how I want things, but I am pretty happy with things for a first pass. I am avoiding &quot;metrics numbers&quot; so instead of display any totals, I am trying to just display the content. Instead of showing 5 &quot;likes&quot; I would just display 5 👍 and of course each <a href="https://indieweb.org/reacji">reacji</a> will display on it&#39;s own as well.</p>
<h3>Displaying Responses on Feeds</h3>
<p>I am not currently displaying mentions for posts when they display on feed pages. I am thinking I might just display a row of emoji representing that last 10 responses. If they are reacji, it would display the reacji, for the rest I would translate the response types to the following emojis: likes 👍,  replies 💬, repost ♻️, bookmark 🔖 , listen 🎧, mentions 💭. It would show a recent snapshot of activity on a post without giving in to numbers.</p>
<h3>Moderating Webmentions</h3>
<p>I also am not doing any moderation of posts currently, every webmention automatically gets added and displayed. I can delete a webmention, of course, but there are protections against spam appearing until I delete it. I outlined <a href="https://eddiehinkle.com/2018/11/03/17/article/">how I want to deal with moderation</a>, the 2nd level connections are going to be a bit tricky so my goal is to first just start with immediate connections as always accepted, others unlisted until I moderate. Then I&#39;ll need to create a mute/block list and have incoming webmentions check that list and respond appropriately.</p>
<h3>Receiving Webmention Deletes</h3>
<p>Finally, if someone sends a webmention delete (a HTTP 410 Gone response from a webmention source url) right now I don&#39;t think my website will do anything, in fact, I don&#39;t even know if webmention.io supports them. I would like to be able to accept and process webmention deletes. My plan is not to actually erase a webmention that is &quot;deleted&quot; but instead, to set the visibility to private. I can of course, manually delete any private webmentions that I don&#39;t want any more.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
