tag:blogger.com,1999:blog-221209682024-03-18T05:48:22.544-04:00Jon Schneider's Tech BlogPress the Any key to continue! (Software development and technology)Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.comBlogger268125tag:blogger.com,1999:blog-22120968.post-34294436026541523552023-12-16T15:25:00.002-05:002023-12-16T17:57:30.928-05:00 The Jon Schneider Game of the Year awards: 2023<p> </p><p>Starting with my favorite video game that I played for the first time this year, and continuing on
with the rest of the top 10, these were my personal top games of 2023 (along with the platform(s) on which I played them)!</p><h2 style="text-align: left;">1. <a href="https://baldursgate3.game/">Baldur’s Gate 3</a> (Windows / Mac)</h2><p style="text-align: left;">There are so many facets to this game that, on their own, are exceedingly well done, and joined together, make for an incredible game. The adaptation of D&D 5E combat and mechanics. The overall story. The individual party members, and their stories. The smart way in which the game’s world and characters realistically respond to your actions. It speaks to BG3’s quality that it is the first long-form game in a very long time that I played through from start to finish -- and then immediately made a new character and started a second play-through.<br /></p><h2 style="text-align: left;">2. <a href="https://zelda.nintendo.com/tears-of-the-kingdom/">Zelda: Tears of the Kingdom</a> (Switch)</h2><p style="text-align: left;">It ALSO speaks to Baldur’s Gate 3's quality that when I played Tears of the Kingdom earlier in 2023, I felt that <i>it</i> was the best game I’d played in several years, and a lock for game of the year! A more-than-worthy sequel to <a href="https://zelda.nintendo.com/breath-of-the-wild/">Breath of the Wild</a> (my personal 2017 Game of the Year). Hyrule is once again super fun to explore -- this time with the additions of caves, the sky, the underground, and more things to do. The construction system managed to successfully achieve making me feel like “I’m not just a fantasy warrior, I'm a fantasy warrior <i>engineer</i>!” <br /></p><h2 style="text-align: left;">3. <a href="https://supermariobroswonder.nintendo.com/">Super Mario Bros. Wonder</a> (Switch)</h2><p style="text-align: left;">In my opinion, the best new 2D Super Mario game in 32 years(!!) (Super Mario World, SNES, 1991). The combination of more traditional-style Mario 2D gameplay with the “wonder” sequences in the various levels made for a great combined experience. The pseudo-multiplayer real-time “ghosts” of other players that you can see -- and to a limited extent, interact with, but very importantly, not in any toxic way -- was also a neat (optional) addition which I ended up being quite glad that I had decided to leave enabled.<br /></p><h2 style="text-align: left;">4. <a href="https://braceyourselfgames.com/cobalt-core/">Cobalt Core</a> (Steam Deck / Windows)</h2><p style="text-align: left;">This recently-released, spaceship-battle-themed roguelike deckbuilder came out of nowhere for me to claim this high spot on my crowded GOTY list this year. It adds to the traditional <a href="https://www.megacrit.com/">Slay-the-Spire</a>-like ability to block incoming damage an ability to move laterally to <i>dodge</i> incoming fire (or to take it on more-armored parts of your ship). Battles are quick and punchy. Even though Cobalt Core is mostly <i>not</i> mechanically similar to <a href="https://subsetgames.com/ftl.html">FTL</a>, it did give me some of the smartly-designed vibes of that game.<br /></p><h2 style="text-align: left;">5. <a href="https://www.infernax.com/">Infernax</a> (Steam Deck)</h2><p style="text-align: left;">A solid, 8-bit-style platforming action game in the tradition of Castlevania 2 (NES). The tight gameplay and fun progression were enough to keep me engaged straight through from start to credits.<br /></p><h2 style="text-align: left;">6. <a href="https://mintrocketgames.com/en/DaveTheDiver">Dave the Diver</a> (Steam Deck)</h2><p style="text-align: left;">A scuba-speargun-fishing main game mashed together with restaurant management sections, complete with a perfect-beer-pouring minigame? Why does this work so well? Once again, it's because all of the individual pieces are so solid! The charming character design and pixel art certainly don’t hurt, either. The first game in this year’s list that I haven’t actually finished yet: I played mostly in Early Access, decided to set it aside until the full 1.0 release -- and haven’t picked it back up quite yet due to the strength of all of the other new games on offer this year.<br /></p><h2 style="text-align: left;">7. <a href="https://octopathtraveler2.square-enix-games.com/en-us/">Octopath Traveler 2</a> (Switch)</h2><p style="text-align: left;">This year’s turn-based <a href="https://gaming.stackexchange.com/q/377559/3354">JRPG</a> comfort food. An interesting battle system, combined with a story that was decent enough to keep me playing straight through to the end -- albeit for only some of the characters, not all 8. As with the first series entry, my wish would be for better interaction between the 8 protagonist characters.<br /></p><h2 style="text-align: left;">8. <a href="https://www.mobiusdigitalgames.com/outer-wilds.html">Outer Wilds</a> (Steam Deck)</h2><p style="text-align: left;">After having this game on my to-play for a few years, I finally had a good way to play it when I acquired a <a href="https://store.steampowered.com/steamdeck">Steam Deck</a> in the early part of 2023. As I had heard about Outer Wilds prior to playing, this is a game worth playing without being spoiled on what it is all about! I will say to give it a try if a first-person, solo exploration game where you board and fly your own spaceship (while still in the first-person perspective) and explore a small solar system sounds appealing. I did admittedly eventually get stuck on how to progress further (and didn’t want to consult a guide) -- but not until after quite a few hours of really interesting exploration and discovery. <br /></p><h2 style="text-align: left;">9. <a href="https://tunicgame.com">Tunic</a> (Steam Deck)</h2><p style="text-align: left;">For me, Tunic created a vibe of being a kid back in the pre-Internet days, and having a new Nintendo cartridge to play -- but there's no real in-game help, and the instruction booklet that came with the cartridge (which in Tunic's case is discoverable in pieces within the game itself) is <i>mostly in Japanese</i> (a language I can't read) -- but the booklet's bits of English text, plus illustrations, allow for puzzling out the less obvious parts of game's mechanics and objectives. Add that to some overhead-view Zelda-like combat -- except tricky, with some <a href="https://en.wikipedia.org/wiki/Soulslike">Souls-like</a> aspects -- and Tunic ends up being pretty cool and unique experience.</p><h2 style="text-align: left;">10. <a href="https://www.pinixgames.com/2020/12/11/Alina/">Alina of the Arena</a> (Mac / Windows)</h2><p style="text-align: left;">A last-minute list addition! Another roguelike deckbuilder hybrid -- this time with <a href="https://subsetgames.com/itb.html">Into-The-Breach</a>-like tactical positioning, with a solo character you control facing off with one or more enemies on a small hex grid. </p><h2 style="text-align: left;">Honorable Mentions</h2><p style="text-align: left;">A few of the other games I played which were good, but didn’t make this year’s Top 10, in approximate descending order of where they would have appeared on the list, had there been room: <a href="https://apps.apple.com/us/app/retro-bowl-college/id1632904520">Retro Bowl College</a> (iPhone), <a href="https://diablo4.blizzard.com/en-us/">Diablo 4</a> (Windows), <a href="https://www.pinballfx.com/">Pinball FX</a> (Windows), <a href="https://batterystaplegames.com/">30XX</a> (Steam Deck), <a href="https://www.tinykingame.com/">Tinykin</a> (Steam Deck). <br /></p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-29673328466255701832023-07-23T13:56:00.003-04:002023-07-23T13:56:38.959-04:00How to fix timestamps on Mac Photos exported files<p>This is a "Remind my future self how to do this, but hopefully it'll be helpful for the rest of y'all too" post!</p><p>To change the timestamps on files exported from the Mac's Photos app to match the dates that the photos and/or videos were actually taken:</p><p>1. Install <a href="https://github.com/exiftool/exiftool">exiftool</a> if it isn't already installed:</p><p><code>brew install exiftool </code></p><p>2. One a a time, run these two commands from the terminal, from the directory where the files are located:</p><p><code>for file in *.jpeg; do touch -t "$(exiftool -p '$CreateDate' -d '%Y%m%d%H%M' "$file")" "$file"; done</code></p><p><code>for file in *.mov; do touch -t "$(exiftool -p '$CreationDate' -d '%Y%m%d%H%M' "$file")" "$file"; done</code></p><p>When those are done, each file's timestamp should match the actual date that the photo or video was taken. <br /></p><p>Any <code>The ExtractEmbedded option may find more tags in the media data</code> warnings can be ignored.<br /></p><h3 style="text-align: left;">Background </h3><p>When copying photos and/or videos from an iPhone to a Mac, the copied photos don't end up as individual files in the Mac's filesystem. Instead, they become part of the "Photos Library" on the Mac, in which all photos and movies are stored in a single "blob" file.</p><p>Fortunately -- for the purpose of copying and/or backing up photos elsewhere, on non-Apple computers or cloud storage -- the Mac's Photos app provides a capability to "export" photos and videos from the library as individual files. (This is accessed via File menu > Export.)</p><p>Two export options are provided: "Unmodified Originals" (which tend to have large file sizes); or as JPG, TIFF, or PNG files (for photos), and .mov files for videos (which produces smaller file sizes).</p><p>Unfortunately, the exported photo and image files have a timestamp (shown as "Date Modified" in Finder) of the time the export was performed -- not the time that each individual photo or video was actually taken.</p><p>For me, having the date shown for each file in Finder match the date that the photo/video was originally taken is a lot more useful. Hence, the procedure described earlier in this post to make that change.</p><h3 style="text-align: left;">"CreateDate" versus "CreationDate" <br /></h3><p>You may have noticed that in the two terminal commands above, the former uses the <a href="https://en.wikipedia.org/wiki/Exif">EXIF tag</a> "CreateDate", and the latter, "CreationDate".</p><p>For some reason -- for photos and videos exported using the Photos app on macOS Ventura 13.4, and originally taken on an iPhone running iOS 16.5 -- exported .jpeg and .mov files, respectively, have inconsistent sets of EXIF tags.</p><p>The EXIF tags on a paritcular file can be inspected using exiftool via a terminal command like:</p><p><code>exiftool -s my_photo.jpeg</code></p><p>For my exported .jpeg files, this produces output like (with irrelevant tags excluded):</p><p><code>CreateDate: 2023:07:04 09:51:12</code></p><p>There's no "CreationDate" tag present. <br /></p><p>For my exported .mov files, the output is like:</p><p><code>CreateDate: 2023:07:22 14:04:56</code><br /><code>CreationDate: 2023:07:04 13:39:20+02:00</code></p><p>So both CreateDate and CreationDate values are present; however, here, "CreateDate" is the timestamp of the Mac Photos app export, and CreationDate is the actual time the video was recorded.</p><p>I'm sure there are excellent reasons behind this seemingly-inconsistent state of affairs; I am not aware of what those might be. 😅 In any event, it was easy enough, one I investigated and figured out what was going on, to split the exiftool command into two separate parts, for the EXIF tags that are actually present and correct in the .jpeg and .mov files, respectively.</p><p>Credit for the original exiftool command that I adapted here goes to <a href="https://apple.stackexchange.com/a/337500/8255">Daniel Schofield on the Ask Different Stack Exchange site</a>.</p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-57748661346378107072023-07-21T18:13:00.000-04:002023-07-21T18:13:00.471-04:00Recommended for international travel in Switzerland: "Traverlers Wifi"<p>(Note: I have no affiliation with "Travelers Wifi," nor is this a paid post. I'm just a fan!)</p><p>My family and I were fortunate enough to be able to spent the past sixteen days traveling abroad in Switzerland. To cover us while there, our current cell provider, AT&T, wanted US $10 per person per day. For just one person, that of course would work out to $160; for the four of us with cell phones, we'd have gotten hit for $640. Eek!</p><p>My wife Melissa, an excellent hobbyist travel planner, researched, and landed on <a href="https://www.travelerswifi.com/">Travelers Wifi</a> as the solution. It's a cell-phone-sized device that provides cellular connectivity in Switzerland (specifically that one country), and allows up to 5 client devices at a time to connect via Wi-Fi to access the Internet.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4uKvxSyTpQctqyKEtdEVibXu8vxIba2NyScB770GRt1OzEfXr5TG4fuZmu_M-FfWJmyhtDol8Rhbk-3vIgpyfrJf5lXSgkQfmzvJGWTt_3_ECFOOMTpSHqXvIsZ76alhaOhhw421b_fm1a03XomeGBdPU4nvVWz76Y7v1pVpwSBXhj5wYkvwFPA/s277/TravelersWifi.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="Travelers Wifi device" border="0" data-original-height="277" data-original-width="153" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4uKvxSyTpQctqyKEtdEVibXu8vxIba2NyScB770GRt1OzEfXr5TG4fuZmu_M-FfWJmyhtDol8Rhbk-3vIgpyfrJf5lXSgkQfmzvJGWTt_3_ECFOOMTpSHqXvIsZ76alhaOhhw421b_fm1a03XomeGBdPU4nvVWz76Y7v1pVpwSBXhj5wYkvwFPA/s16000/TravelersWifi.png" /></a></div><br />The device cost €5.90 (a little over US $6) per day. Quite the cost savings over the up-to-$40-daily that we would have been spending!<p></p><p>Bottom line: It worked great! </p><p>The device connected with no issues and had good Internet speeds everywhere we went (Zurich, Lucerne, Lauterbrunnen and the Berner Oberland region, Vevey and environs, and Geneva). </p><p>Our device was good for about eight hours of battery life on a full (overnight) charge. That's not quite a full day of adventures, but it was not to worry: We had come prepared with a few external battery packs with USB-A outputs; connecting one of those to the Travelers Wifi device was more than enough to get it through an entire day. I'd recommend being similarly prepared to back up the Travelers Wifi device with an external battery.</p><p>Pick-up of the device in Zurich was trivial; Travelers Wifi has a dedicated storefront in the Zurich airport in the public (outside of security) area, and they were able to look up our online reservation (made in advance, prior to our trip) and hand over the device with no issues.</p><p>Return of the device was similarly easy; although the Geneva airport (from which we were departing) has no Travelers Wifi storefront, it does have several of the yellow "Die Post" mailboxes. The Travelers Wifi comes with a "self-addressed stamped envelope"; we just put the device and it's included cable and wall charger into that envelope, and dropped the resultant thin package into a mailbox at the airport. <br /></p><p>I'm happy to be able to cheerfully recommend <a href="https://www.travelerswifi.com/">Travelers Wifi</a> to anyone traveling abroad in Switzerland!<br /></p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-14669174152370100642023-06-22T10:16:00.001-04:002023-06-22T12:55:55.199-04:00The mystery of the broken JWT magic link login URLs on iPhone<p>My team at work was recently facing a problem where "magic link" login URLs being sent out via SMS (text message) were "broken" when received by iPhone users. Only part of the URL's <a href="https://en.wikipedia.org/wiki/Query_string">query string</a> portion was properly rendering as part of the link; the remainder -- despite not being separated by a space, or any <a href="https://stackoverflow.com/questions/1547899/which-characters-make-a-url-invalid">URL-invalid characters</a> -- was showing up as plain text:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2tpDTO0pSim5lzX3L6jjVMptISbDkaip62B7JbKIRQsMwybepzGwvqJh82aU9OgsNXNu3mO_MBIUy7TNxc8oACRMCxgyWE_kBod2fZpAc4Wg8wTwXKR8Q-z8w_wW7QuMQWxg_MO4MFCWWQ_loqrsEzeBbEbb783LV7stLaonrmxq-aWSDQmZH9w/s362/Screenshot%202023-06-22%20at%2010.04.28%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="217" data-original-width="362" height="192" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2tpDTO0pSim5lzX3L6jjVMptISbDkaip62B7JbKIRQsMwybepzGwvqJh82aU9OgsNXNu3mO_MBIUy7TNxc8oACRMCxgyWE_kBod2fZpAc4Wg8wTwXKR8Q-z8w_wW7QuMQWxg_MO4MFCWWQ_loqrsEzeBbEbb783LV7stLaonrmxq-aWSDQmZH9w/s320/Screenshot%202023-06-22%20at%2010.04.28%20AM.png" width="320" /></a></div><br />A <a href="https://postmarkapp.com/blog/magic-links">magic link</a> in this context is an URL that includes a secure, tamper-evident key which identifies the user, and allows them to log in to the application that sent the link, in lieu of having to enter a password. (This has security pros and cons; that linked article provides a nice summary.)<br /><p></p><p>My team is using <a href="https://jwt.io/">JWT</a> as the magic link key. JWTs are encoded into three portions, separated by period characters (remember that, it's important later!): A header; the message payload (including things like the user's ID, and the key's expiration time); and the signature verification.</p><p>In our case, only the first portion of the JWT value, the header, was being rendered by iPhone recipients of our SMS message as a part of the clickable hyperlink. The remaining two portions were showing up as plain text. This broke the magic link! While it still directed users to our site, it was unable to log them in.<br /></p><p>I spent the day yesterday performing an investigation into why this was happening. <br /></p><p>For starters, <a href="https://www.google.com/search?q=sms+maximum+length">asking Google about the maximum length of an SMS message</a> yields the answer "160 characters." Here in 2023, as far as regular users are concerned, this is no longer really true. (When's the last time you were composing a text message, and your phone stopped you from sending your message because it was longer than about <a href="https://developer.twitter.com/en/docs/counting-characters">half a Tweet</a>?) All modern providers use <a href="https://www.telemessage.com/what-is-message-concatenation/">"SMS concatenation"</a> to, behind the scenes, break a long SMS message into multiple parts, and then seamlessly stitch those parts back together into a single message for the recipient. </p><p>I hypothesized: Perhaps Apple's implementation of SMS concatenation doesn't work when the URL itself is longer than 160 characters (as our magic link login URLs including a JWT token are)? No; I was able to disprove this by sending myself a text message with such an URL; it arrived in one piece, no problems. </p><p>(As an aside, I started out doing these tests by using the web UI of my work's existing <a href="https://www.twilio.com/en-us">Twilio</a> account to send messages to my personal iPhone's number. This worked fine; I pretty quickly determined, though, that I could more expeditiously test by just using my Mac's Messages app to send messages to my own phone number. This produced the same results, as far as the received message ending up broken or not.)</p><p>Perhaps SMS concatenation doesn't work when the query string portion of the URL is longer than 160 characters? No; disproved by sending myself such a link, which once again was delivered in one piece, as expected.</p><p>Perhaps the problem is when a single query string key-value pair -- or just a query string value -- is longer than 160 characters? No; I was able to successfully send myself messages (using the string "1234567890" repeatedly as the query string value to achieve the target length) in with such query string values excess of <i>500</i> characters in length, no problems.</p><p>My testing went on like this. I was consistently able to reproduce the broken link behavior using an actual (Dev environment) magic login link; the behavior of any particular URL being broken or not appeared to be consistent/deterministic, at least. Further, by trimming down certain portions of that URL, the link <i>would</i> be correctly delivered in one piece. </p><p>By testing many message and URL variants, and recording for each one whether it succeeded or failed to deliver properly, along with the lengths of the various portions of the message text and the URL, I was finally able to pin down the problematic behavior. Here it is, in plain English:</p><p>For a given query string value: If that value contains any URL-valid punctuation characters (i.e. non-alphanumeric characters): If any portion (or "slice") of that query string value beyond the first portion, when separated/sliced by punctuation characters, is <b>302 characters</b> or longer, the URL will break (on Apple devices). If all such portions are <b>301 characters</b> or shorter, the URL will render correctly. </p><p>Recall that JWT values consist of 3 portions -- <i>separated by period characters</i>? This meant that if the token's 2nd (payload) or 3rd (signature) encoded portions were in excess of 301 characters, the resulting link would be broken when delivered to an iPhone. </p><p>(Notably: It's only Apple's handling of SMS messages, in their Messages / iMessage app on iPhone and on Mac, where links render as broken in this particular way. In my testing with Android clients, and with Google Voice, all links that I tested with were delivered correctly, regardless of length!)<br /></p><p>Here are a few examples of working and broken URLs (when received by an Apple client). To save space (and to make this post less ugly!), instead of actually spelling out URL portions of 300+ characters, I'll represent such portions with the number of characters in that portion. The following links, when delivered to and viewed on an iPhone, or in Apple's Messages app on a Mac:<br /></p><p><span style="font-family: Consolas;">https://example.com?key=400</span> (OK; there are no punctuation characters in the query param value)</p><p><span style="font-family: Consolas;">https://example.com?key=10.302</span> (BROKEN; the second portion of the query param value is longer than 301 characters)</p><p><span style="font-family: Consolas;">https://example.com?key=301.301_301</span> (OK; no portion of the query param value is longer than 301 characters)</p><p><span style="font-family: Consolas;">https://example.com?key=200~200.400</span> (BROKEN; the 2nd portion is ok, but the 3rd potion is longer than 301 characters)<br /></p><p></p><p><span style="font-family: Consolas;">https://example.com?key=400-50</span> (OK; only the first portion of the query param value is longer than 301 characters, and that doesn't manifest the problem) <br /></p><p><span style="font-family: Consolas;">https://example.com?key1=400&key2=400</span> (OK; the both query param values here consist only of "first portions", which don't manifest the problem)</p><p></p><p>To work around this problem -- and to produce links that are some what less nasty-looking on clients that render the entire URL -- I'm planning on making a pair of changes to our magic login tokens:</p><p>1. Reducing the payload content to "essential" values only. Namely, the user's email address, and an expiration date/time value. This will cut down on the middle "payload" portion of the JWT.</p><p>2. Using <a href="https://auth0.com/blog/rs256-vs-hs256-whats-the-difference/">HS256 instead of RS256</a> as the signing algorithm. For our specific application and usage scenario, HS256 will provide sufficient security; but HS256 signature values are significantly shorter in length. </p><p>All of the aforementioned testing was done in June 2023 using an iPhone 12 running iOS 16.5.1; and a MacBook Pro running MacOS Ventura 13.4. Perhaps Apple will address this issue in future software versions? (But if this particular bug isn't at the top of their priority list, I certainly can understand why not. ☺)<br /></p>Hopefully this post may be helpful to any of y'all out there who are researching why your SMS messages that include JWT magic link login URLs (or other long URLs including long query string values) being delivered to iPhone clients aren't rendering properly!<br />Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-74814170680079516582023-04-06T09:42:00.000-04:002023-04-06T09:42:45.182-04:00The Mystery of the NULL Values in a NOT NULL DATETIME MySQL Database Column<h4 style="text-align: left;"></h4><p>My development team at work yesterday picked up a task to investigate a error that was periodically showing up in our production logs:</p><p></p><p><code>NoMethodError: undefined method `>' for nil:NilClass</code></p><p>This is a common Ruby error that occurs when you try to call a method on a variable with a value of nil. In this case, the "method" in question was the greater-than operator, ">". </p><p>The line of code associated with the error in question was actually a conditional evaluation that involved 3 separate ">" evaluations, which obscured the exact variable that was the source of the error a bit. </p><p>Two of the three variables in question were attributes of an ActiveRecord model object whose associated database column was defined as NOT NULL. Those attributes were not being re-assigned after being read from the database, so we initially ruled them out as being the possible cause of the error.<br /></p><p>However, further investigation revealed that none of the three variables on the problematic line of code could -- in theory -- possibly ever be null. Faced with this, I decided to take a closer look at the actual data in the database.<br /></p><p>Our production environment is split into multiple MySQL databases. For purposes of conveniently being able to query customer data across databases all at once, we have an ETL process which extracts (non-sensitive) customer data from the source databases, and populates it all into a single central Snowflake SQL database. </p><p>The database structure of the relevant table (Users) was similar to this (simplified for brevity):</p><ul style="text-align: left;"><li>id: INT, primary key</li><li>first_name: VARCHAR(255)</li><li>last_name: VARCHAR(255)</li><li>updated_at: DATETIME, NOT NULL</li></ul><p>Temporarily putting common sense aside, I queried the snowflake database to see if any of the NOT NULL updated_at values were nevertheless null:</p><p><code>SELECT * FROM Users WHERE updated_at IS null</code></p><p>This returned 0 results, as expected. </p><p>Acting on a hunch, I tried searching for unexpectedly old records; this database has been in service since about 2009:</p><p><code>SELECT * FROM Users WHERE updated_at < '2005-01-01'</code></p><p>This query did produce some results! Out of the tens of thousands of records in the Users table, a few hundred records were returned whose updated_at date was '1970-01-01 00:00' -- a value equal to the <a href="https://en.wikipedia.org/wiki/Unix_time">start of the epoch in Unix time</a>. </p><p>At this point I questioned: Does Ruby on Rails and/or ActiveRecord somehow treat start-of-epoch date values of '1970-01-01 00:00' at nil? This seemed unlikely, but I tested it anyway, setting the updated_at value for an existing record in my local machine's copy of the database to that start-of-epoch value; and then reading the record into the corresponding ActiveRecord model object. Not unexpectedly, the updated_at attribute on the model did not end up with a nil value; it had the expected value of midnight on January 1, 1970.</p><p>I still felt like I might be onto something here, though. At this point, I wanted to inspect the data in the actual MySQL customer database. I don't have access to the live database, but I was able to access a read-only replica of the database.</p><p>Having connected to that MySQL database, I verified that the updated_at column was still defined at NOT NULL; it was. I then ran the same query to look for old values:</p><p><code>SELECT * FROM Users WHERE updated_at < '2005-01-01'</code></p><p>As before, this returned a few records -- about 300. However, I noticed that the actual updated_at values were different. This time, they were returned as '0000-00-00 00:00'. </p><p>Year "0", month "0", day "0" -- all nonexistent values. Feeling a bit of a chill, I re-ran my earlier query to look for records with null values:</p><p><code>SELECT * FROM Users WHERE updated_at IS null</code></p><p>MySQL returned <i>the same 300 records</i>. <br /></p><p></p><p></p><p>Cool, cool, coolcoolcool. The evidence indicated that (1) MySQL was allowing values of '0000-00-00 00:00' to be set on a DATETIME, NOT NULL column; and (2) despite the aforementioned NOT NULL restriction on the column, was evaluating such values as being null.</p><p>To close the loop, back on my local development machine's database, I set an existing record to the '0000-00-00 00:00' value -- which MySQL happily allowed -- and then, in the Rails console, populated a model object from the record. Sure enough: The updated_at attribute on the record had been assigned a value of nil.</p><h4 style="text-align: left;">Summary of findings</h4><p>(1) In a Rails application (and possibly in other languages / frameworks as well), it may not be safe to assume that a value read from a DATETIME, NOT NULL column on a MySQL database is actually guaranteed not to be null. MySQL allows a value of '0000-00-00 00:00' to be set in such columns (despite the NOT NULL restriction); such values are treated as null both my MySQL itself, and by Rails / ActiveRecord.<br /></p><p>(2) Although I'm not familiar with its inner workings, that the ETL process from the multiple production MySQL databases to consolidate data into a single Snowflake database couldn't 100.0% be trusted not to change data values. In this case, it silently converted MySQL DATETIME values of '0000-00-00 00:00' to values of '1970-01-01 00:00' <br /></p><h4 style="text-align: left;">Mitigations</h4><p>Doing a bit of additional reading after the fact, I found a <a href="https://stackoverflow.com/a/55358724/12484">StackOverflow post</a> that references a MySQL configuration mode of "NO_ZERO_DATE," which when set, prevents such "zero date values" from being set. </p><p></p><p><a href="https://dev.mysql.com/doc/refman/8.0/en/date-and-time-types.html">According to the MySQL documentation</a>, not setting that option may be "more convenient" and/or consume less space. Based on my team's experience here, though, I'd certainly be inclined to take advantage of that NO_ZERO_DATE setting whenever feasible!<br /></p><br />Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-59413555597556839612022-12-28T20:15:00.000-05:002022-12-28T20:15:31.219-05:00The Jon Schneider Game of the Year awards: 2022<p>Starting with my personal Game of the Year for 2022, and continuing on
with the rest of the top 10, these were my favorite games that I played
for the first time this year (along with the platform(s) on which I played them)!</p><div style="text-align: left;"><div style="text-align: left;"><h2>1. <a href="https://persona.atlus.com/p5r/?lang=en">Persona 5 Royal</a> (Switch) </h2></div></div><p>Probably the most expansive single-player RPG I've ever played, I was hesitant to dive into the this game given the expected time commitment, but I'm certainly glad I did! Engaging turn-based combat system, check. Interesting story, check. Realistic and sympathetic characters, check. Getting to explore (in a limited way) the districts of real-life Tokyo, a fun bonus. All wrapped up in a package that tries hard to be super stylish and cool -- and actually manages to pull it off!</p><h2 style="text-align: left;"><b>2. <a href="https://www.nintendo.com/ph/switch/az3h/">Xenoblade Chronicles 3</a> (Switch)</b> </h2><p>I enjoyed seeing the lore of the first two main games in the Xenoblade series woven together here into something completely different, yet still congruent with the original pairs of source material. A key innovation here was an RPG with a large party of player characters where some PCs aren't present during combat; all 6 (or 7) of them are present, contributing, and playable. The way the game handles, in consistent fashion with the game's world, a mixed-gender party of (essentially) late-teens / early-20s characters with romance between characters (mostly) <i>not</i> being a thing was also an enjoyable novelty. </p><h2 style="text-align: left;"><b>3. <a href="https://store.steampowered.com/app/1637730/Crystal_Project/">Crystal Project</a> (Steam / Mac / Windows)</b></h2><p>This game was a real treat for me, a fan of old-school Final Fantasy games and similar JRPGs. Layering challenging turn-based on top of exploration of a blocky Minecraft-like world worked remarkably well. Like Stardew Valley a few years back, Crystal Project is a game that managed to pleasingly surprise me on a number of occasions after I thought that I had what the game had to offer all figured out.</p><h2 style="text-align: left;"><b>4. <a href="https://playknotwords.com/">Knotwords</a> (iPhone)</b></h2><p>The best word game app I've ever played. It's Sudoku crossed with crossword puzzles in a really smart way. Wonderful UX design (which inspired a <a href="https://aggromagnetgames.com/vigil-rpg/">Vigil RPG</a> haptics update!). More-than-fair premium pricing model with no ads, premium currencies, or other typical mobile-game dark patterns. Multiple game modes, including daily and monthly puzzles. Knotwords' ability for the player to "recover" lapsed daily streaks is another innovation that should be universally copied. Highly recommended to fans of word games!</p><h2 style="text-align: left;"><b>5. <a href="https://gridpop.co/isle/">Isle of Arrows</a> (iPhone)</b> </h2><p>Another premium iOS game done right. Combines tower defense, a light puzzle/card game, and a cool aesthetic. I 100%'ed the campaign.<br /></p><h2 style="text-align: left;"><b>6. <a href="https://phoenotopia.com/">Phoenotopia Awakening</a> (Steam / Mac)</b> </h2><p>A smartly-designed, difficult-but-fair, cute pixel-art hybrid of Metroidvania and Zelda 2: The Adventure of Link gameplay styles, with great settings/locations and story.</p><h2 style="text-align: left;"><b>7. <a href="https://kirbyandtheforgottenland.nintendo.com/">Kirby Forgotten Lands</a> (Switch)</b> </h2><p>I played through this cute first 3D foray of Kirby through start-to-finish with my 10-year-old, with her taking on the role of Kirby, and me, Bandana Waddle Dee. I appreciate how this game caters to a variety of skill levels, with the the main story being well suited for the pair of us to complete, and also including a post-game suitable for experts.<br /></p><h2 style="text-align: left;"><b>8. <a href="https://www.chasmgame.com/">Chasm</a> (Steam / Mac)</b></h2><p>Like Phoenotopia, a pixel art melee Metroidvania game, whose weapon/combat system seems to have been particularly inspired by the more recent 2D games in the Castlevania series (see: next entry below!). Slightly thin on story, but solid enough in terms of level design and gameplay that I did complete the main story.</p><h2 style="text-align: left;"><b>9. <a href="https://www.konami.com/games/castlevania/advance_collection/us/en-us/">Castlevania Aria of Sorrow</a> (Switch port of Game Boy Advance game)</b> </h2><p>Played through this game which I'd never played in its original form as part of the Castlevania Advance Collection. I'd previously been most familiar with the more traditional series entries including the original Castlevania (NES) and Super Castlevania (SNES); hat tip to TouchArcade and freelance game writer <a href="https://twitter.com/ShaunMusgrave">Shaun Musgrave</a>, whose recent <a href="https://www.patreon.com/ShaunMusgrave">Patreon</a> ranking of all(!) of the Castlevania series games inspired me to give this a play-through. </p><h2 style="text-align: left;"><b>10. <a href="https://teamwoodgames.com/">Super Auto Pets</a></b> (Web / iPhone)</h2><p>This is the type of multiplayer-game that I tend to prefer these days: Quick and asynchronous, meaning low-stress! You start each game from scratch to build a team of up to 5 of animals, each with their own unique combat ability, and watch them engage in a series of brief auto-battles against teams built by other players who have done the same.<br /></p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-55722045904988411822022-10-26T09:25:00.001-04:002022-10-26T09:25:36.586-04:00Workaround: Input Lag with Nintendo Pro Controller playing Steam games on Mac<p>I was having a problem where, when playing the game <a href="https://phoenotopia.com/">Phoenotopia Awakening</a> on my MacBook using a Nintendo Switch Pro Controller connected to the Mac via Bluetooth, I was getting pretty significant "input lag": About half a second would elapse between me pressing a button on the controller, and the character reacting on the screen, making the game nigh-unplayable.</p><p>Oddly, this issue would only occur with my MacBook "docked" -- connected on my desk to my <a href="https://blog.jonschneider.com/2021/03/jons-five-monitor-work-from-home-desk.html">multiple monitors</a>, wired ethernet, and various other peripherals. When using the MacBook as a standalone laptop, the game and controller would play just fine.</p><p>Even in the "docked" configuration, however, outside of Steam, an <a href="https://gamepad-tester.com/">in-browser gamepad tester</a> showed that the controller was communicating with the MacBook with <i>no</i> lag. <br /></p><p>I found the following to be an effective workaround for the problem:</p><ol style="text-align: left;"><li>In Steam (not in "Big Picture" mode, and with the game not running), locate the game in your Library.</li><li>Right-click the game and select Properties.</li><li>On the Controller tab, select "Disable Steam Input".</li></ol><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7NkWuZulhm0u6G54Ih2pzmn8bQQj5gX2EalNwFlMt810FGaZggo142JsrY0FV5869aRR5iuiZRCaSyFABxslhIDwAD2jTElh35nfrLsDiZLClOoH2u58BOxjjhfoNNrDEdfrLSkC0nztETY8mjHUNDIi5X5bahpp1j-0zNEgZsLXw3and5d8/s888/Screen%20Shot%202022-10-26%20at%209.16.44%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img alt="Steam game properties dialog > controller tab" border="0" data-original-height="647" data-original-width="888" height="466" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7NkWuZulhm0u6G54Ih2pzmn8bQQj5gX2EalNwFlMt810FGaZggo142JsrY0FV5869aRR5iuiZRCaSyFABxslhIDwAD2jTElh35nfrLsDiZLClOoH2u58BOxjjhfoNNrDEdfrLSkC0nztETY8mjHUNDIi5X5bahpp1j-0zNEgZsLXw3and5d8/w640-h466/Screen%20Shot%202022-10-26%20at%209.16.44%20AM.png" width="640" /></a></div> Voila -- No more input lag with the Nintendo Switch Pro Controller, even when docked! <p></p><p> Hat tip to <a href="https://steamcommunity.com/discussions/forum/1/3395163747103389017/">"Toms33" for posting this solution</a> in a thread on the Steam Community forums. </p><p><br /></p><p><br /></p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-1773554332159187462022-08-26T17:07:00.000-04:002022-08-26T17:07:04.409-04:00TimeSnapper: The power of being able to replay the past<p>To get this disclaimer out of the way right out front: <a href="https://blog.jonschneider.com/2019/08/timesnapper-for-mac-is-now-available.html">I'm the author</a> of <a href="https://timesnapper.com/">TimeSnapper for MacOS</a>. But like the guy in the <a href="https://www.youtube.com/watch?v=xeFoLdeqG1I&t=53s">1980-something Hair Club for Men TV commercial</a>: I'm not just the author, I'm also a client! A real event just happened where TimeSnapper enabled me to share a key bit of information with my team at work that I hadn't been able to come up with otherwise.</p><p>About a month ago, I came across a mention from a co-worker of a book as being a great introduction to the FinTech space: <a href="https://anatomyoftheswipe.com/">"The Anatomy of the Swipe" (2020) by Admed Siddiqui</a>. At the time, I checked my local public library for a copy, but they didn't have one; so I took advantage of an awesome feature of the library, and put in a request via the library's website for the library to purchase a physical copy! They did so, and automatically put the book on hold for me! I picked up the book yesterday.</p><p>In a team meeting near the end of my work day yesterday, I mentioned the book, and promised to drop a link to the book in our Slack channel in the morning. I followed up on that this morning, intending to post the link along with the original post where I'd seen the book recommended internally.</p><p>I searched Slack for the earlier mention of the book that I remembered, and... nothing. </p><p>No search results for "anatomy of the swipe". Or for "anatomy" "swipe". Or for "ahmed siddiqui".</p><p>Although I thought I'd seen the book recommendation via Slack, maybe it had actually been via shared Google Doc...? Or an email? I searched those places too, and... again, nothing.</p><p>At this point, I was momentarily stumped. I wanted to provide credit where it was due and share the original book recommendation I had followed... but where was it? Obviously I hadn't just imagined it; I had gotten the name and author of this particular book, in order to request my local library to order a copy, from <i>somewhere</i>.</p><p>And then, inspiration struck: I've got TimeSnapper! TimeSnapper, if you're not familiar with it, is a utility program that runs in the background on your computer, and saves a snapshot of the screen every few seconds. It also provides a "Play Your Day Like a Movie" feature, where you can rapidly <a href="https://clipchamp.com/en/blog/video-scrubbing/">scrub</a> back and forth through an entire day's worth of these saved screenshots.</p><p>To get the time window for my TimeSnapper search, I went to my library's website, logged in, and and viewed the page with the status of my past purchase requests. Sure enough, it showed that my request for "The Anatomy of the Swipe" had been originally made on July 26, 2022 -- one month ago today.</p><p>I then fired up TimeSnapper's Play Your Day feature and navigated to July 26. Skimming through my day, I found the images of my visit to the library website where I'd initially keyed in my request for the book. Navigating a short way back in time from there, I saw myself visiting my company's Slack instance... and found the recommendation I had remembered -- not for the book itself, but a link to a <i>blog article</i> with a list of good FinTech introductory resources -- which is where the mention of the book has actually been located!</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRg3cmgTo8JMcv5XYdBWhJ7UlqfuakHOu5o94ueR_MtDri00-79V8L4cuETucH1U4N84hiQ8RtXQXBSacbtEBYMflqZNut1ELjcWvCTosJGwIiMzG6RuW4SE0rFecOjGpBTnE9Cb72QAw-4L-yB1gDi-K9FijSfVlJJW45oeJtvyMeXTdaPuY/s1150/Screen%20Shot%20-%20TimeSnapper%20Play%20your%20Day.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="794" data-original-width="1150" height="442" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRg3cmgTo8JMcv5XYdBWhJ7UlqfuakHOu5o94ueR_MtDri00-79V8L4cuETucH1U4N84hiQ8RtXQXBSacbtEBYMflqZNut1ELjcWvCTosJGwIiMzG6RuW4SE0rFecOjGpBTnE9Cb72QAw-4L-yB1gDi-K9FijSfVlJJW45oeJtvyMeXTdaPuY/w640-h442/Screen%20Shot%20-%20TimeSnapper%20Play%20your%20Day.png" width="640" /></a></div><p></p><p>Thanks to TimeSnapper, I was able to post a link that Slack post with the link to the FinTech blog post along with the link to the book itself.</p><p>Obligatory closing promotional line: If this ability to go back in time and observe what you were seeing on your computer in the past sounds like a superpower you'd like to gain for yourself, you can read more about TimeSnapper, and buy a copy for Mac or for Windows, on the <a href="https://timesnapper.com">TimeSnapper website</a>!<br /></p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-37453832150815564402022-04-29T17:02:00.002-04:002022-04-30T09:40:56.918-04:00Nervous Flyer + Gamer? Try Playing Mario Kart<p>Hi! I'm Jon, and I'm a nervous flyer. 👋 Always have been!</p><p>Don't get me wrong: I totally understand and agree that riding on a commercial airliner is generally one of the safest activities that one can engage in. It's a lot safer than driving a car, for example.</p><p>Against my will, though, while flying -- especially during takeoffs, landings, and during turbulence -- some primitive part of my brain insistently alerts me that I'm in danger. Even though at the same time, I know that I'm not. It's pretty weird and annoying.</p><p></p><p>Past attempts to cope with this by distracting myself with movies, reading, or playing video games generally haven't helped.</p><p>In recent years, though, I've found a workaround that, quite oddly, actually is fairly helpful: Playing Mario Kart!</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCvWkx5UTDzIMyYUiyMkWKlrYY1HgxnBmRfD0Xqm_-3kDVVyA4WGJhL_EOzUvUMr56GvcU3loOvEUX9uyRd8qlY21XasfEIo1FY67BL35qocRW1VdmAVeV8NIwV5bnyoGKteWNFtplRKv14rdQXs0xr6TmjAWb4plavwzyeGdu3OHIcFndKmU/s728/mario-mario-kart-8-deluxe-luigi-wallpaper-preview.jpg" style="margin-left: 1em; margin-right: 1em;"><img alt="Luigi in a kart on the "Sunshine Airport" course of Mario Kart 8 Deluxe" border="0" data-original-height="410" data-original-width="728" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCvWkx5UTDzIMyYUiyMkWKlrYY1HgxnBmRfD0Xqm_-3kDVVyA4WGJhL_EOzUvUMr56GvcU3loOvEUX9uyRd8qlY21XasfEIo1FY67BL35qocRW1VdmAVeV8NIwV5bnyoGKteWNFtplRKv14rdQXs0xr6TmjAWb4plavwzyeGdu3OHIcFndKmU/s16000/mario-mario-kart-8-deluxe-luigi-wallpaper-preview.jpg" /></a></div><br /><p></p><p>For whatever reason, while I'm playing a Mario Kart race, that primitive part of my brain that normally sounds the alarm as sensations of motion are happening while in flight, instead seems to interpret those sensations as motions that are happening <i>in the game</i> -- and the unpleasant "danger" feelings are largely ameliorated!<br /></p><p>I've found that this works for me both with the latest Mario Kart game, Mario Kart 8 for the Nintendo Switch, as well as with the older (and cheaper!) Mario Kart 7 for the Nintendo 3DS.</p><p>To maximize my time spent in races (instead of between races), as well as to enjoy the gameplay more, on my airplane ride Mario Kart 8 games, I've taken to using the "custom rules" game mode (found on the game's main menu under Single Player > VS Race):</p><ul style="text-align: left;"><li>Items: "Mushrooms Only." More opportunities to take those fun course
shortcuts that require a speed-boosting mushroom. And I don't miss having my racing interrupted by lightning bolts and blue shells! <br /></li><li>Race Count: Depending on the available time, I pick some high value, like 24 or 48. <br /></li><li>Course Selection: "Automatic - In order." </li></ul><p>Would this trick work with other racing games, too? Good question! Maybe?
Mario Kart is the only driver-perspective (or behind-driver-perspective)
racing game that I've spent more than a short time with over the past
few years. <br /></p><p>If you're a fellow nervous flyer who is also a gamer, consider giving Mario Kart a try next time you find yourself up in the air! (And don't forget to bring a USB battery pack to keep that Nintendo Switch charged!)<br /></p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-6538478607661455882022-04-23T15:22:00.003-04:002022-04-23T15:22:57.077-04:00Investigation and fix: A SQL Server stored procedure slowdown<h3 style="text-align: left;">What happened?</h3><p>At work this past week, I was alerted to a problem where a background job which processes incoming messages from a particular queue had slowed down dramatically. As a result, a significant delay had developed in providing certain event notifications to our customers. <br /><br />Our monitoring tooling showed that the culprit was a particular SQL Server stored procedure -- one that gets invoked as a part of processing each message from the queue. Execution time of that sproc had suddenly increased from near-instant to an average of 4 seconds, with spikes up to around 20 seconds. (The queue processing job is multi-threaded, so we were still processing about one message per second, but that was no longer keeping up with the incoming volume of messages during the busy hours of each day.)</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSSwEm73_pLsKPDIGSZqosqp9czkyM6XlrPxldqiTii5D6GesLYD1IoJYyPXLJ4Cjlf8Z4q2Ay0BTRWuoAnvaoeOFQlwUQnP9k8BZ_8pJbE4zgnH4aHntNofMaCMzc3eEm6vhGpV0lUUkhleNYN5j7Rr-_jL0FR_XEVmfyBUuGfnXUl2s6Jo0/s1058/crop.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="212" data-original-width="1058" height="128" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSSwEm73_pLsKPDIGSZqosqp9czkyM6XlrPxldqiTii5D6GesLYD1IoJYyPXLJ4Cjlf8Z4q2Ay0BTRWuoAnvaoeOFQlwUQnP9k8BZ_8pJbE4zgnH4aHntNofMaCMzc3eEm6vhGpV0lUUkhleNYN5j7Rr-_jL0FR_XEVmfyBUuGfnXUl2s6Jo0/w640-h128/crop.png" width="640" /></a></div><p><span style="color: #f3f3f3;"></span>No code deployments or changes to the environment had been made around the time of the initial slowdown; this problem had started occurring without an obvious root cause. </p><h3 style="text-align: left;">Our investigation<br /></h3><p>The stored procedure in question comprises about a dozen individual SQL statements and queries. It wasn’t immediately clear which individual statement in the sproc was responsible for the slowness.<br /><br />After a few initial investigative paths didn’t yield results, I decided to run a short trace on the live production database. The trace revealed that one particular SELECT query was consistently taking a long time to run during each run of the stored procedure. <br /><br />Interestingly: When I ran that same SELECT query SQL manually, it completed near-instantly. However, when I ran some minor variations on that SELECT query, they did take a long time to execute.</p><h3 style="text-align: left;">Execution plans and indexes<br /></h3><p>SQL Server internally generates an “execution plan” for each query that it runs, whereby it decides how it will go about performing the operation that the query's SQL calls for. The SQL Server Management Studio tool is able to display these execution plans in a nice graphical format. <br /><br />When the problematic SELECT SQL was run manually, the execution plan showed that it was – correctly – leveraging an existing index on the target table. That index had been originally added to support this specific query. It includes fields needed by the query’s lookup criteria.<br /><br />Looking back at the results of the trace, the problematics SELECT queries being run during execution of the stored procedure were being run using a different execution plan – not leveraging the custom-designed index at all! A full table scan was being run by SQL Server to gather the query results. As that table currently sits at a total row count of over 150 million, those queries were taking a while to run!</p><p>The specific SQL being run (simplified a bit here for readability) was:<br /></p><div style="text-align: left;"><code>SELECT TOP 1 ID<br />FROM myTable t<br />WHERE t.CreationDate > DATEADD(day, -30, GetDate())<br /> AND (t.Email = @Email OR t.PhoneNumber = @PhoneNumber)<br /> AND t.CompanyID = @CompanyID) <br />ORDER BY t.ID ASC</code></div><div style="text-align: left;"><br />The custom index includes the following fields:<br /><ul style="text-align: left;"><li> CompanyID </li><li> Email</li><li> CreationDate</li><li> PhoneNumber<br /></li></ul></div><p>Can you spot the problem?<br /><br />…<br /><br />Take a look at the ID field. That field isn’t part of the index; but it is being used in the SQL’s ORDER BY clause! And that clause, as it turns out, is used by the database as one of the factors in how it seeks for the single (TOP 1) row to return.<br /><br />Through its own inscrutable internal algorithms, prior to April, SQL Server had been using an execution plan that did leverage the custom index for this query, despite the presence of the ID field in the ORDER BY clause. After April, some tipping point in the database’s characteristics was evidently hit that made SQL Server decide to no longer use that index in its execution plan during runs of the stored procedure.<br /><br />The fix was to adjust the SELECT query to ORDER BY CreationDate, rather than by ID. This had the same logical effect with regard to selection of the returned row; and it got SQL Server to once again use our custom index while running the stored procedure, resulting in the resumption of speedy performance in processing of messages from the queue.</p><h3 style="text-align: left;">How did we verify that the fix worked?<br /></h3><ul style="text-align: left;"><li>We observed that new records continued to be inserted correctly (and now, a lot more quickly!) into the database table.</li><li>Execution times for the stored procedure went way down. We could see this both from the database itself, and from the our queue's web interface – the green line here is the rate of messages being processed off the queue, with the fix having been applied just after the 10:20 mark:</li></ul><div class="separator" style="clear: both; text-align: center;"><span style="border: medium none; display: inline-block; height: 100px; margin-left: 1em; margin-right: 1em; overflow: hidden; width: 390px;"><img height="100" src="https://lh5.googleusercontent.com/ZMXG-9oq2acWT4Ac4b8BYPb3IEm_5smvQfjqL-f1lyTN9kt8P7H1ySm24HJOBEBnNxwK1PpfYk-x_Kc5HEPlkex53S0EYWNdUXncN-fINkLwwYacv54fEW7-5BW4pPC4U3IQngzM" style="margin-left: 0px; margin-top: 0px;" width="390" /></span></div><p><span style="color: #f3f3f3;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"></span></span> </p><h3 style="text-align: left;">So what are the lessons here?</h3><ul style="text-align: left;"><li>Having the ability to determine which specific SQL statement was the root cause of the downstream slowness was key to being able to devise a fix. (The fact that our monitoring instrumentation only reported to the granularity of which stored procedure was running slowly -- but not which individual SQL statement within the sproc was slow -- made this trickier.)</li><li>The availability of multiple weeks of monitoring data from our instrumentation did make it quick and obvious to confirm that there had been a dramatic change in performance from one of our stored procedures, which was the cause of the problem.</li><li>When a particular SELECT statement is slow, ensuring that a properly-matching database index is in place – and that index is being applied to live queries in the expected manner – is a very good thing to check!</li></ul>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-18176530714549584482022-02-28T16:03:00.001-05:002022-02-28T16:03:32.019-05:00Tip: How to easily reorder bullet points in Google Docs<p>There's an easier way to reorder bullet points in <a href="https://docs.google.com/">Google Docs</a> than cutting and pasting!</p><ol style="text-align: left;"><li>Position the insertion point over the bullet point line you want to move. </li><li>Press Ctrl+Shift+⬆ (up arrow) to move the line up, or Ctrl+Shift+⬇ (down arrow) to move the line down.</li></ol><p>I’ve found that this works a lot more nicely than cutting the item to be
moved, and then pasting the item in the new location in the list (since
often, line breaks need to be fixed up after the new item is pasted
in).<br /></p><p>If you select multiple items in the bulleted list first, then the aforementioned keyboard shortcuts will move them all together, as a group. </p><p>This tip works for reordering or moving regular paragraphs, too, in addition to bullet points.</p><p>Want to do the same thing in Microsoft Word? Check out my (much!) earlier post, <a href="https://blog.jonschneider.com/2009/07/tip-how-to-easily-reorder-bullet-points.html">Tip: How to easily reorder bullet points in MS Word</a>, from 2009!<br /></p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com3tag:blogger.com,1999:blog-22120968.post-55479722013121304122021-12-26T10:53:00.001-05:002021-12-26T10:54:03.064-05:00My Game of the Year Awards: 2021<p>Dating back to my days as a kid, one of my favorite hobbies is computer and console gaming. Following the <a href="https://www.gamerswithjobs.com/node/1304067">Gamers With Jobs rules</a> -- only games that I played for the first time in 2021 are eligible to make the list -- here are my top 10 favorite new-to-me games of 2021 (along with the platform on which I played each game), in descending order:<br /></p><h2 style="text-align: left;"><strong>1. <a href="https://www.nintendo.com/games/detail/metroid-dread-switch/">Metroid Dread</a> (Nintendo Switch)</strong>
</h2><div class="field field-name-comment-body field-type-text-long field-label-hidden"><div class="field-items"><div class="field-item even" style="text-align: left;"><p><a href="https://en.wikipedia.org/wiki/Metroid_Fusion">After nearly 20 years</a>, we finally got a new 2D <a href="https://en.wikipedia.org/wiki/Metroid">Metroid</a> sequel. And it
does so many things right! Gameplay that deeply respects prior games in
the series, yet smartly blazes new territory. Beautiful and
finely-detailed graphics. Good map/level design. Tough but fair boss
battles. It's probably impossible for the greatness of <a href="https://en.wikipedia.org/wiki/Super_Metroid">Super Metroid</a> (SNES) to ever be matched again, but Dread is an excellent modern take
on the formula.</p>
<h2 style="text-align: left;"><strong>2. <a href="https://www.nintendo.com/games/detail/bravely-default-ii-switch/">Bravely Default 2</a> (Nintendo Switch)</strong> </h2>
<p>Bravely Default 2 takes the raw materials of JRPGs going all the way
back to the first <a href="https://en.wikipedia.org/wiki/Final_Fantasy">Final Fantasy</a> games, iterates and folds them back on
themselves many times, and ends up with this pretty nice piece of gaming
origami. The most important thing I look for in a JRPG is combat that
is fun, well-balanced, and (ideally) rewards smarts/creativity on the
part of the player, and Bravely Default 2 delivers.</p>
<h2 style="text-align: left;"><strong>3. <a href="https://finalfantasypixelremaster.square-enix-games.com/en_US/">Final Fantasy Pixel Remaster</a> (iPhone)</strong></h2>
<p>Speaking of the first Final Fantasy games: It was a treat this year
having in my pocket this update to the very first Final Fantasy
featuring a wonderful soundtrack, sharp graphics, and modernized and
rebalanced gameplay -- while still remaining faithful to the original. I
don't often blog about individual games, but I did do <a href="https://blog.jonschneider.com/2021/08/impressions-final-fantasy-1-pixel.html" title="https://blog.jonschneider.com/2021/08/impressions-final-fantasy-1-pixel.html">a compare-and-contrast between Final Fantasy Pixel Remaster and the NES original</a>.</p>
<h2 style="text-align: left;"><strong>4. <a href="https://www.themonstertrain.com/">Monster Train</a> (Windows)</strong></h2>
<p>With my primary computer these days being a Mac, I don't do a lot of
gaming on Windows, but I made an exception for Monster Train. It's a fun
and smart iteration on the <a href="https://www.megacrit.com/">Slay the Spire</a> deck-builder-battler genre.
(I was initially put off somewhat by the protagonists being the forces
of Hell, but it's fine; in Monster Train, the angels are a bunch of jerks,
and your team is fighting for self-preservation.)</p>
<h2 style="text-align: left;"><strong>5. <a href="https://www.nintendo.com/games/detail/xenoblade-chronicles-definitive-edition-switch/">Xenoblade Chronicles</a> (Nintendo Switch)</strong> </h2>
<p>I'm still midway through this expansive classic, and it might end up
higher on a hypothetical 2021-2022 combined list, but for now, here we
are! I had originally passed on this game, but the combination of my
teenage son recommending it to me as having "single-player MMO" combat,
plus wanting to learn more about Shulk from <a href="https://www.smashbros.com/en_US/">Smash Ultimate</a> convinced me
to give Xenoblade Chronicles a fair shake, and I'm glad I did. And I
really like the soundtrack!</p>
<h2 style="text-align: left;"><strong>6. <a href="https://atlus.com/etrian5/">Etrian Odyssey 5</a> (Nintendo 3DS)</strong></h2>
<p>I dusted off my 3DS again during a period where the aforementioned
teenage son was monopolizing the household Switch, and discovered that
he'd purchased a digital copy of Etrian Odyssey 5 (2017) at some point.
I'm only midway through, but the tough combat and old-school DIY mapping
using the 3DS's stylus are just as engaging as I remember from earlier
series entries.</p>
<h2 style="text-align: left;"><strong>7. <a href="https://www.nintendo.com/games/detail/new-pokemon-snap-switch/">New Pokemon Snap</a> (Nintendo Switch)</strong></h2>
<p>I had low expectations for New Pokemon Snap, but they were wildly
exceeded when I had a couple of weeks to give it a try on a check-out
from my local library. I think the thing I like most is the fact that
when you manage to get a great photograph, it's 100% due to your own
timing and skill -- not due in part to some huge passive buff from a
piece of equipped gear, or a lucky roll on some behind-the-scenes RNG.
I'm planning to purchase a copy of New Pokemon Snap for myself when it
goes on sale.</p>
<h2 style="text-align: left;"><strong>8. <a href="https://imageform.se/game/steamworld-quest/">Steamworld Quest</a> (iPhone)</strong></h2>
<p>This deck-builder-battler that's <em>not</em> a roguelike, but is
instead a party-based RPG, played really well on the iPhone platform,
even though it was originally designed for larger screens. The unique
battle system kept me engaged all the way through to the end of the
game.</p>
<h2 style="text-align: left;"><strong>9. <a href="https://roguebook.net/">Roguebook</a> (Mac)</strong></h2>
<p>Another fun riff on the deck-builder-battler-roguelike genre, adding a
map exploration mechanic that ends up working a bit like <a href="https://subsetgames.com/ftl.html">FTL</a>, where
you need to do all you can in a given area to power yourself up, before
taking on the area boss and advancing. A few remaining rough edges in
both minor UI bugs and gameplay balance issues don't hold Roguebook back
from being fun to play.</p>
<h2 style="text-align: left;"><strong>10. <a href="https://www.inklestudios.com/sorcery/">Sorcery! 4-game series</a> (Mac)</strong> </h2>
<p>Also available on iPhone, this isn't just a remaster of <a href="https://en.wikipedia.org/wiki/Steve_Jackson%27s_Sorcery!">the original 1980s gamebook series</a>, but a full remake, taking advantage of the
digital platform. A combat system that incorporates a bit of skill, and
isn't simply dice-rolling -- and smartly, gives the player the option to
immediately replay battles that didn't go so well -- adds to the fun.</p><h2>Honorable mention <br /></h2>
<p>Best new DLC: <strong><a href="https://smashbros.nintendo.com/buy/dlc/">Fighters Pass 2</a> for <a href="https://www.smashbros.com/en_US/">Super Smash Bros Ultimate</a> (Switch)</strong>.
I find it crazy how well this mashup of <a href="https://www.smashbros.com/en_US/fighter/">82 or so distinct playable characters</a> from dozens of different series actually works in practice.
My favorite gaming experience of 2021, eclipsing any of the new games
mentioned above, is playing 2v2 local-vs-online matches with the teenage
son. (The characters I mainly play are Samus and Ludwig von Koopa!)</p></div></div></div>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-86630280257034280112021-12-10T18:20:00.000-05:002021-12-10T18:20:16.761-05:00Kotlin: My first impressions, via days 1-3 of Advent of Code 2021<p>While working on Ruby solutions to <a href="https://adventofcode.com/2021">this year's Advent of Code programming challenges</a>, my attention was caught by one of the sponsored messages running in the site's sidebar, from JetBrains:</p><blockquote><span style="font-family: Consolas;">Get ready to jingle with Advent of Code in Kotlin! Have fun, learn new things, and win prizes. Believe in magic with Kotlin. Happy holidays!
<a href="https://jb.gg/AoC">https://jb.gg/AoC</a></span></blockquote><p dir="auto">That link redirected to a <a href="https://blog.jetbrains.com/kotlin/2021/11/advent-of-code-2021-in-kotlin/" rel="nofollow">post on the JetBrains / Kotlin blog</a> inviting developers to try out the Kotlin language via the Advent of Code (AoC) problems, and providing a nice <a href="https://github.com/kotlin-hands-on/advent-of-code-kotlin-template">Kotlin AoC GitHub template</a> as a starting point.</p>
<p dir="auto">I'd never worked with Kotlin before, but <a href="https://github.com/jschneid/advent-of-code-2019">I have used AoC to try out new programming languages before</a>. Serendipitously, I had some time available today, and so I decided to give it a go!</p><p dir="auto">In the time I had, I wound up solving <a href="https://github.com/jschneid/advent-of-code-2021-kotlin">the first 3 days of AoC 2021 in Kotlin</a>. Here's a quick write-up of my very first impressions of Kotlin!<br /></p><h3 style="text-align: left;">What I liked!<br /></h3><p>Good out of the box support in the IntelliJ IDE (which I downloaded for the first time today), including suggestions for more idiomatic Kotlin syntax. (Which makes sense, as JetBrains provides both the IntelliJ IDE and the Kotlin language itself!) I was saved from some instances of typical "trying to write language X as if it were language Y" newbie mistakes by warnings/suggestions in the IDE.<br /></p><p>IntelliJ displays, inline in loop declarations, whether the lower and upper bounds are inclusive of exclusive. This made it easy for me to understand whether, in a Kotlin <span style="font-family: Consolas;">until</span> loop over an array, I needed to use <span style="font-family: Consolas;">myArray.length</span> or <span style="font-family: Consolas;">myArray.length - 1</span> as the upper bound of the loop. (The former!)<br /></p><p>I found the <span style="font-family: Consolas;">data class</span> syntax to be a nice succinct and readable way for a method to return some related values. In a method in one of my Ruby AoC solutions where I was returning two values just wrapped together in an array, it was easy in the Kotlin equivalent to declare a one-liner data class, and then have my method return an instance of that class, to make what was being returned much more obvious to readers.</p><p>If ... else in Kotlin is an expression, not a statement. While looking up an equivalent for the typical <span style="font-family: Consolas;">? : </span>ternary expression syntax, I actually enjoyed discovering that <a href="https://stackoverflow.com/q/16336500/12484"><span style="font-family: Consolas;">? :</span> is actually not supported in Kotlin, and an inline if ... else should be used instead</a>.</p><p>Support for ".."-syntax ranges like in Ruby (e.g. <span style="font-family: Consolas;">(5..15)</span> to represent the set of integers 5 through 15, inclusive) was fun to see in a C-like language.<br /></p><p>The variable-declaration keywords <span style="font-family: Consolas;">val</span> and <span style="font-family: Consolas;">var</span>, similar to <span style="font-family: Consolas;">let</span> and <span style="font-family: Consolas;">const</span> in JavaScript ES6, to respectively declare immutable and immutable variables.<br /></p><p>The ability to pass a function "pointer" as a parameter to a function -- something both the Ruby and Kotlin variations of my Day 3 solution actually made use of.<br /></p><p> </p><h3 style="text-align: left;">What I maybe didn't like so much<br /></h3><p>I actually wasn't aware of this ahead of time, but Kotlin is built on top of Java. (Or perhaps it's more accurate to say that Kotlin, like Java, is a language that runs on the JVM?) The first time I became aware of this was when my program threw an exception... and there was a mix of Java and Kotlin code in the call stack. I have no idea to what extent aspects of Java poke their heads up while working in Kotlin... but it's something that I'd want to understand more about before committing to using Kotlin for a real project, versus other available languages where that kind of thing is much more of a non-issue.</p><p><a href="https://github.com/kotlin-hands-on/advent-of-code-kotlin-template">JetBrains' Kotlin template for AoC</a> helpfully included a <span style="font-family: Consolas;">check</span> call to test each problem's provided sample answer against the corresponding sample input. What wasn't so helpful was that a failure of the test just resulted in a generic <span style="font-family: Consolas;">java.lang.IllegalStateException : Check failed</span> -- with no accompanying mention of the check's expected or actual values. I had to manually add a <span style="font-family: Consolas;">print</span> statement to find out what the failure was. Maybe I was just missing something?<br /></p><p>I ran into some trouble using the built-in pow to raise 2 to the power of a particular variable:</p><ul style="text-align: left;"><li>It took me a little while to figure out why I was getting an <span style="font-family: Consolas;">Unresolved reference: pow</span>. I ended up googling for that exact phrase, while led me to discover that an <span style="font-family: Consolas;">import kotlin.math.* </span>is needed. Not surprising in retrospect, but it would have been nice to a a prompt in Kotlin's error message about an import/reference possibly being needed. (I believe C# does this, probably among others.)</li><li>The <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.math/pow.html"><span style="font-family: Consolas;">pow</span> method</a> (as of the current version, 1.6) can't be used on integers. After a little more confusion, I figured out that I had to use the literal <span style="font-family: Consolas;">2.0</span> (instead of just <span style="font-family: Consolas;">2</span>), and then manually convert the result back to an <span style="font-family: Consolas;">Int</span> after getting my result. Why make developers do that?<br /></li></ul><p></p><p> </p><h3 style="text-align: left;">Verdict</h3><p style="text-align: left;">Verdict? It's much too early for a verdict! I only worked with Kotlin for a few hours, on some toy problems, on my own! </p><p style="text-align: left;">It was fun, though, to get to play with a new language! Kudos to the Kotlin team at JetBrains for their combination of the Advent of Code sponsored message, <a href="https://blog.jetbrains.com/kotlin/2021/11/advent-of-code-2021-in-kotlin/">their blog post</a>, and the <a href="https://github.com/kotlin-hands-on/advent-of-code-kotlin-template">Kotlin Advent of Code GitHub template</a>, which taken together, were enough to get me to take a look at Kotlin!</p><p style="text-align: left;">And if you're a developer but have never taken a look at <a href="https://adventofcode.com/">Advent of Code</a> before, I strongly recommend checking it out! The event is extremely well crafted and executed each year. It's worth your time!</p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-57668297550088484372021-10-07T15:04:00.003-04:002021-10-07T15:04:24.599-04:00Git tip: Visual indicator of the 50-character git commit summary message limit<p>Although it's somewhat too short for my taste, I generally comply with <a href="https://stackoverflow.com/q/2290016/12484">the conventional wisdom of having the summary line of git commit messages be no more than 50 characters</a>.</p><p>Even using an editor that shows the current position of the insertion point (such as <a href="https://superuser.com/q/365320/6581">vim with <code>set ruler</code> enabled</a>), it's not readily discernible how much of the available 50 characters remains, relative to text that has been keyed in so far.</p><p>My solution to this has been to set up a <a href="https://thoughtbot.com/blog/better-commit-messages-with-a-gitmessage-template">".gitmessage" commit message template</a> that includes a comment showing the 50-character limit:</p>
<p style="border: 1px solid gray;"><code> <br /> <br /># |<-- 50 char summary line limit
</code></p><p>As long as my summary message ends before that vertical pipe character, I'm good!</p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-82634160502487557972021-09-03T12:37:00.001-04:002021-12-06T10:23:15.436-05:00New iOS app: Gong Sound<p>Would you like the ability to make your iPhone to emit a "gong" sound? Further, would you, ideally, like that gong sound to be accompanied by a low-fidelity, semi-realistic animation of an actual swinging gong? If so, then do I have the app for you!</p><p>Introducing <a href="https://apps.apple.com/us/app/id1579942956">Gong Sound</a>, available for free on Apple's App Store!</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://apps.apple.com/us/app/id1579942956" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="2521" data-original-width="1247" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmfEv82UrHgNq_ujZRUx5byWNkI5Ck9T1qRDmGDatbnqVkA2Ft9eWIC9B7bUWLydr0vQ4pc7zqbBxOsh7zBEda57WhVHImQZKCjp3TmYWfh64y6s-0K-j2bOIrBlEoRRZPQnt9AA/w198-h400/Gong+App+1.0.2+in+iPhone+frame.png" width="198" /></a></div><h3 style="text-align: left;">Amazing features</h3><p></p><ul style="text-align: left;"><li>On a tap, the app emits a "GONG!" sound! <a href="http://dogr.io/verygong/suchsound/muchfree/manyoscillation/wow">Wow</a>!</li><li>The gong swings back and forth -- in a physics-approximating, 7-frame animation! -- for a few seconds after being tapped!</li><li>iPad support!</li><li>Supports landscape (device held sideways) mode! <br /></li><li>High score counter! (With classic-arcade-like non-persistence!)</li><li>No ads! No in-app purchases! No data harvesting! No review nags! No network connectivity used or required! </li><li>FREE! <br /></li></ul><h3 style="text-align: left;">Wait... so it's just a gong app?</h3><p>Yep. 🙂 The Gong Sound app is just a "toy" project. I got the code written, and the <a href="https://pixabay.com/illustrations/gong-music-metal-sound-instrument-4327695/ ">art</a> and <a href="https://freesound.org/people/cdiupe/sounds/112507/">sound</a> assets adapted, for the initial 1.0 version in all of a couple of hours. (As compared to the dozens of hours that I spent on <a href="https://aggromagnetgames.com/desktop-journey/">Desktop Journey</a>, or the hundreds spent making <a href="https://aggromagnetgames.com/vigil-rpg/">Vigil RPG</a>!)<br /></p><p>At my company, teams strike a gong that we have in the office as (part of) a celebration of notable achievements. In these days of remote work, I figured: Why not give folks an easy way to "gong" from home which sounds better than banging pots and pans together, and isn't likely to <a href="https://www.youtube.com/">play an ad instead of the desired sound effect</a> when the "Play" button is pressed. </p><p>(And doesn't violate any of the items in that second-to-last bulleted list item above, in the fashion of most existing similar apps on the App Store.)<br /></p><a href="https://apps.apple.com/us/app/desktop-journey/id1530583648">Gong Sound</a> is a free download on iOS devices, so if you're reading this post on your iPhone: <a href="https://apps.apple.com/us/app/desktop-journey/id1530583648">Share and Enjoy</a>!Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-64841996191761873892021-08-05T00:30:00.004-04:002021-08-05T00:49:23.986-04:00Impressions: Final Fantasy 1 Pixel Remaster (iPhone)<p>Square Enix recently released "Pixel Remaster" versions of the first three games in the Final Fantasy series. Over the past four or so days, I purchased, played through, and completed the <a href="https://apps.apple.com/us/app/final-fantasy/id1492041278">iPhone version of Final Fantasy 1 Pixel Remaster</a>. </p><p>As a veteran of multiple playthroughs of the <a href="https://en.wikipedia.org/wiki/Final_Fantasy_(video_game)">original NES version of Final Fantasy 1</a>, I thought I'd share my impressions of this update to this classic game, focusing mostly on compare/contrast of the new pixel remaster version with the original.<br /></p><p>With the original Final Fantasy 1 having a high frequency of standard enemy encounters plus a very limited number of spell slots, my favorite party composition is Fighter, Black Belt (Monk), Thief, and Red Mage. That’s also the party that I used for my playthrough of the remaster.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEix1Fadehhi_AGNYpE2hksD9_1kjhX5l-1VjjzNjWcvU53Gs74eGHLTqxOOeZMIKlw0OE-eufDyH49-vL_eUxYsnbuYd91FnRGc6VheVWCnHcPt9vT5CVb9Uq_W_4LRFEgXO6zdYw/s2532/FF1_Pixel_Remaster.png" style="margin-left: 1em; margin-right: 1em;"><img alt="Final Fantasy 1 Pixel Remaster Battle: 6 ogres vs party of Fighter, Red Mage, Thief, and Monk" border="0" data-original-height="1170" data-original-width="2532" height="296" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEix1Fadehhi_AGNYpE2hksD9_1kjhX5l-1VjjzNjWcvU53Gs74eGHLTqxOOeZMIKlw0OE-eufDyH49-vL_eUxYsnbuYd91FnRGc6VheVWCnHcPt9vT5CVb9Uq_W_4LRFEgXO6zdYw/w640-h296/FF1_Pixel_Remaster.png" width="640" /></a></div><p></p><p style="text-align: center;"><i>All-caps character names for old times' sake!</i><b> <br /></b></p><p><b>Spoiler statement:</b> This article avoids spoilers about game plot /
events, and for the most part, about specifics of particular dungeons
and enemies. There are some mentions of particular character abilities
and spells. <br /></p><h3 style="text-align: left;">Quality-of-life improvements 💆<br /></h3><p>These are numerous! Some highlights that I noted during my playthrough:<br /></p><p><b>Auto-battle. </b>⏩ The remaster has a button available on the combat screen which causes the party continuously repeat whatever actions they performed last round, and also increases the speed at which the battle plays out. Both a convenience (particularly on mobile, where performing precise button taps multiple times every round to select actions is more annoying) and a time-saver for when the party is moving through an area of easily-winnable battles.</p><p><b>Tap to move. 👆 </b>The iPhone remaster takes nice advantage of the touch screen by providing an ability to tap a location on the screen to move there (as well as a traditional on-screen D-pad option). Movement speed when en-route to a tapped location is also significantly increased, which is a nice time-saver.</p><p><b>Save anywhere. </b>💾 One “quick save” slot is available which can be used in any location. Definitely a must-have feature in a mobile game like this — at least, for a game not featuring automatic continuous progress saving (like at least <a href="https://aggromagnetgames.com/vigil-rpg/">one other RPG native to the iOS platform I’m familiar with</a>!).</p><p><b>Unlimited inventory space for weapons and armor.</b> In the NES original, there was no shared inventory pool for weapons and armor. Instead, each of the four characters could hold four weapons and four pieces of armor — and once those 16 slots of each type were full, there was no picking up more until space was freed up. (This resulted in a sort of primitive encumbrance system!)</p><p>The remaster features a modern-style, unlimited-size shared inventory pool (similar to later Final Fantasy games). This ends up being a gameplay advantage as well as a quality-of-life improvement. There isn't, for example, a decision between keeping a helmet with the best armor rating, and a helmet with a lower armor rating but with a special ability usable in battle. Now you can equip the former, while using the latter as an item in battle out of the shared inventory pool.</p><p><b>Dungeon maps.</b> While in dungeons, a scrollable map of the current dungeon level is available — even portions that haven’t been explored yet. Handy for avoiding the dead end corridors that are a part of a number of the dungeons.</p><p><b>World map.</b> 🗺 It’s readily available now — no “<a href="http://www.fforigins.com/ff1/chap1/matoyav1.html">TCELES B HUSP</a>” required! Additionally the maps shows counts of the treasures and key items available in each visited location, and the number of each of those obtained. Handy for going for 100% completion! Speaking of which: <br /></p><p><b>Achievements.</b> 🏆 The remaster has them! I’m not sure they’re
integrated with the iOS Game Center achievements system, though. (I
can’t speak to the Steam or other versions of the game.)<b> <br /></b></p><p><b>No more invisible, fixed enemy encounters.</b> In the NES original, there were a number of spots/squares in dungeons -- often immediately in front of important treasure chests -- where a fixed enemy encounter would take place when entered. (Repeatedly, if you left the square and then returned!) This has been replaced with (1) those encounters being triggered via the relevant treasure chest being opened, or (2) the enemy being visible on the map, with the encounter initiating when the enemy is character is interacted with. (The "Hall of Giants" is implemented in the latter fashion.)<br /></p><p><b>Automatic use of key items.</b> 🗝 <span> </span>There were a few spots in the original game where you needed to open the menu and use a particular key item at a particular location in order to progress. That's smoothed out in the remaster, with the appropriate key item being used automatically when you arrive at the appropriate location. I can definitely see this feature saving a few new players a trip to a FAQ to figure out what they need to do next.</p><p style="text-align: left;"><b>Fast potion buying.</b> Buying 99 healing potions is quick and easy now! No need to visit a shop with healing potion of at the top of the list, and zone out for several minutes while mashing the A button (or using a controller with a turbo function!).<br /></p><h3 style="text-align: left;">Balance and Gameplay changes ⚖<br /></h3><p style="text-align: left;"></p><p>(📉 Difficulty nerf) <b>Better potions!</b> 🍾 The original Final Fantasy featured only three types of potions: HEAL (restoring 50 HP), PURE (removing poison), and SOFT (curing petrification). The remaster has those, and additionally makes available at stores Hi-Potions (restoring 150 HP), and more significantly, Phoenix Down (restoring a slain warrior to 1 HP). </p><p>The availability of Phoenix Down in particular is a game-changer, as it makes reviving slain warriors possible before the Life spell is available at white magic level 5, and also removes the limit on the number of revives that can be performed in a given dungeon delve being tied to the number of level 5 white magic slots available.</p><p>Ether potions (restores 1 spell slot of each level) are also available at shops, making it possible to restore spell slots while in dungeons -- something that was not possible at all in the original game.</p><p>There are also a few additional status-restoring items in the game, although I found that I didn’t really use those in my playthrough. (The enemies in the game’s first large dungeon are as poisonous as ever, though! I burned through some 20 or 30 antidote potions while in there.)</p><p>Finally, there are a just a few X-Potions (fully restores HP) and Elixirs (fully restores HP and MP). These came in handy for me at one particular spot in the late game, when the warriors have much more HP than a Hi-Potion is able to recover.</p><p>(📉 Difficulty nerf) <b>Lower gold costs / more gold rewards.</b> 💰 Costs at shops are lower in the remaster. I was always able to afford everything I wanted at shops, with no need to grind for gold at all, or otherwise save up. I even bought the expensive “steel armor” / “knight armor” that’s available for sale at a high price (about a third of the way through the game), which I don’t believe I ever did in the original game.</p><p>(📉 Difficulty nerf) <b>Faster leveling / easier foes?</b> Most enemies and encounters seemed less deadly in the remaster. For example, in the original, having the entire party get stun-locked for multiple rounds by groups of undead with paralysis attacks was a big concern in some mid-game dungeons. I never came close to having this happen in the remaster. </p><p>Further, in some spots in my playthrough of the remaster, my party felt over-leveled for the enemies I was facing. A particular undead mini-boss in the first half of the game went down especially easily (in the first combat round). Much of the second half of the game in general felt pretty easy difficulty-wise. The major dungeon after the “prove your courage” event in particular felt more like a victory lap than a challenge. (I do recall this to some extent being the case in the original game as well, however.)</p><p>(📈 Minor difficulty buff) <b>Multiple large enemies.</b> 👹 In the NES original game, fights were limited to a maximum of four large-size enemies (such as ogres) at a time, due to screen space constraints. In the remaster, although most fights stuck to the constraints of the NES original, I did have a handful of fights against 5 or 6 large enemies at one time, as pictured at the top of the article.</p><p>(📈 Difficulty buff) <b>The final boss.</b> 👿 Without getting into spoiler territory: With a well-prepared and properly-equipped party, the final boss in the NES original game could be fairly reliably defeated. In the remaster, although my party never had a wipe prior to the final boss, beating that last boss took me a good dozen or so attempts! (I do suspect that having a White Mage on the team might have made things significantly easier.) I was definitely glad for the ability to create a save immediately before the final boss fight in the remaster! </p><h3 style="text-align: left;">Bugs fixed 🐞🔨<br /></h3><p>This is almost certainly not a complete list, but here are a couple of fixes that I noticed that the remaster made over the original in my play through:<b> </b></p><p><b>The TMPR / Temper and SABR / Saber spells.</b> Evidently in the original game, these spells were buggy, and had no effect on combat at all! I verified that they do indeed work now, and are effective melee buffs — along with the FAST / Haste spell, which is a very effective melee buff in both the original game and the remaster. (Pro tip: In the remaster, these spells can all be stacked onto the same character for a greater effect, too!)</p><p><b>Darkness status.</b> 🕶 This is another one in the original game that didn’t seem to do anything at all. In the remaster, although I didn’t test exhaustively, it did appear to cause both warriors and enemies to deal fewer hits in melee. I actually did have my Red Mage use the black magic Darkness spell in a few combats against large groups of melee enemies.<br /></p><h3 style="text-align: left;">New bugs 😱<br /></h3><p>Unfortunately, I did notice a couple of new bugs introduced by the remaster! (At least in the 1.0 launch version. Maybe these will get corrected in a post-launch update?)<b> </b></p><p><b>Elemental protection buff spells miss!</b> 🔥 The NulShock / NulBlaze / NulFrost spells (formerly ALIT / AFIR / AICE) (reducing damage of the applicable element by half for the entire party) worked great and were effective when I first obtained them in the remaster. In particular, a battle with a fire-themed major boss was made much easier in my playthrough by the NulBlaze spell. </p><p>Unfortunately, when I tried using these spells in the late game, about 90% of the time, they resulted in “miss!” on each of my own party members, instead of the buff being applied! I assume there’s some bug where the warriors’ evade chance is being applied to these spells, as they would be to incoming enemy status-affecting spells. <br /></p><p><b>Monk “optimal” equipment. </b>👊 When pressing the “Optimal” button on the equip screen for my Monk (formerly “Black Belt”) character in the early parts of the game, the game equipped the monk with one of my nunchaku items — even past the point where the monk would work better fighting unarmed (dealing a bit less damage per hit, but more hits). It’s easy to see this issue tripping up new players, making the monk class less effective than it should be for them.<br /></p><h3 style="text-align: left;">Miscellaneous improvements ⚔<br /></h3><p><b>Music</b>. 🎶 The music is not just remastered versions of the original chiptune NES tracks, but actually re-recorded as orchestral tracks. They really are great-sounding. They are very true to the original versions. Some of the original tracks that were very tight loops in the NES original, such as the shopping music and menu music, have gotten new extended versions, which are also great.</p><p><b>Dungeon backgrounds.</b> 🌵 Each location has a new and nice-looking battle backdrop.</p><p><b>Spell animations. </b>💥 Each spell in the remaster has a unique and beautiful animation!</p><p><b>Dialog text. 💬 </b>All of the dialog text in the game has been retranslated and improved. (The protagonists are still completely silent, however.) I did notice and appreciate that the classic line very early on in the game, "I, Garland, will knock you all down!" was preserved as-is, though! <br /></p><p><b>Enemy names. </b>The enemies have been assigned names that are more true to (what I assume are) the Japanese originals, are better translations, or can fit now that name length constraints are relaxed somewhat. A few such changes off the top of my head:</p><ul style="text-align: left;"><li>IMP ➡ Goblin</li><li>MadPONY ➡ Crazy Horse</li><li>BONE ➡ Skeleton</li><li>CREEP ➡ Gigas Worm</li><li>SAHAG ➡ Sahagin <br /></li><li>KYZOKU ➡ Buccaneer</li></ul><p><b>In-game hints. </b>💡 At least in the early portions of the game, if you talk to the dancer NPC in the starting town, she'll give you a hint as to what you should be doing next, a feature which is new to the remaster. (Another NPC in the castle near the starting town will clue you in that the dancer will do this.)<br /><b></b></p><h3 style="text-align: left;">Things kept the same! 🛡<br /></h3><p><b>Character classes.</b> No new ones, and they work the same as before!</p><p><b>Magic.</b> 🪄 The system of a fixed count of available spell slots per level remains in place (instead of a more modern MP pool system). This keeps the system of needing to use potions instead of white magic for most healing outside of battle, and needing to use offensive black magic sparingly in general -- especially in the early game -- the same as it was originally.<br /></p><p>The available spells, what they do, and which character classes can learn which spells, are also unchanged.</p><p><b>Items.</b> The equippable items and key items available in the game, where they can be found, what they do, and which character classes can use which items, are almost entirely the same. </p><p>(A single exception that I noticed: The "silver sword" / "mithril sword" is no longer available for sale in the elf town. Getting into <i>why</i> it was probably removed would touch on spoiler territory!)</p><p><b>World map, towns, and dungeon layouts.</b> No changes that I spotted!</p><p><b>High random encounter rate.</b> Random encounters occur quite often in both versions of this game! The above-mentioned auto-battle feature definitely makes this more palatable in this current day and age, particularly for a mobile game.</p><p><b>Deadly enemies!</b> Some enemies that were particularly deadly in the original game remain so in the remaster! A particular type of enemy present in a mid-game ice-themed area with multiple abilities that can instant-kill individual warriors retains those abilities in the remaster. (The availability of Phoenix Down potions in the remaster does make the possibility of a warrior being taken down in that way a lot less scary!) </p><h3 style="text-align: left;">Overall recommendation: 👍<br /></h3><p>For anyone nostalgic for the NES original and looking for a solid premium RPG for their iPhone, I can easily recommend picking up this version of Final Fantasy!<br /></p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-37059803595815682772021-07-17T23:50:00.001-04:002021-07-17T23:50:28.246-04:00Hotels! Let me play stuff on my room's TV screen!<p>It has been my observation that successful hotel chains seem to always be on the lookout for new ways to positively differentiate themselves from their competition -- and thus, to attract more guests, and/or be able to charge higher rates. Such differentiating amenities range from the lavish -- such as building elaborate "pool complexes" on the hotel property -- to the simple -- a classic example being <a href="https://www.inc.com/shama-hyder/what-you-can-learn-from-doubletrees-famous-chocolate-chip-cookies.html">offering a warm cookie at check-in</a>.</p><p>In this spirit: Hey, hotel chains! Here's a simple "amenity" suggestion that would get me to book with you over your competition, all else (e.g. location and price) being approximately equal: You know those nice big flat-screen TVs you've put in each of your guest rooms? It would be super great if you'd let me use that nice screen with my gaming console to play a game, or with my laptop to watch a video -- instead of, say, actively blocking my ability to do so!</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvUzpTA7FCbaQULoIFD2d-ud6kdo6ZZUQIedfebGlLR9uDHV2297_cGtdClWE3-itVIdYVMRwxrZyNdeeTMCrtKZyu1kFJIFUabge92dbybTpniuuCt2avC3se5JLZEUpRi3IRfw/s2048/LEGO+Laptop.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="2048" data-original-width="1536" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvUzpTA7FCbaQULoIFD2d-ud6kdo6ZZUQIedfebGlLR9uDHV2297_cGtdClWE3-itVIdYVMRwxrZyNdeeTMCrtKZyu1kFJIFUabge92dbybTpniuuCt2avC3se5JLZEUpRi3IRfw/w300-h400/LEGO+Laptop.jpg" width="300" /></a></div><p></p><p style="text-align: center;"><i>Pictured: Not necessarily the optimal guest experience.</i></p><p style="text-align: left;">I was recently fortunate enough to be able to spend a couple of weeks traveling around Utah with my family, hiking in the national parks. Beautiful, and highly recommended! (Although if the forecast calls for 110<span class="ILfuVd"><span class="hgKElc"><b>°</b>F+ temperatures, I recommend heading out as early in the morning as you can manage!)</span></span></p><p style="text-align: left;"><span class="ILfuVd"><span class="hgKElc">Unfortunately, we had a lot better luck with the natural scenery than we did getting streaming video to work in our various hotel rooms' TVs. I had come on the trip prepared -- or so I thought -- with a laptop and an HDMI cable, so we would be able to stream a show in the evenings after our daily adventures (or in the hot afternoons between them!).<br /></span></span></p><p style="text-align: left;"><span class="ILfuVd"><span class="hgKElc">In the trip's best-case hotel, we had a TV where I was able to reach around to the back of the TV to get at an HDMI port, then access a hidden function of the remote to change the input source on the TV, managing to get the show being streamed via my laptop to play on the TV.</span></span></p><p style="text-align: left;"><span class="ILfuVd"><span class="hgKElc">In the worst-case hotels -- plural -- the TV's onboard HDMI ports had evidently been disabled entirely. Even though (peering behind the wall-mounted TVs) there were HDMI ports present on the TVs, there was no way to switch to them. The TVs were "hotel models" whose only physical control was an on/off button, and there was also no way to switch via the TV's remote controls.</span></span></p><p style="text-align: left;"><span class="ILfuVd"><span class="hgKElc">This resulted in some silly situations like the one pictured above, with the family sitting around the hotel room's TV to watch some <a href="https://www.fox.com/lego-masters/">LEGO Masters</a> -- with the TV uselessly powered off, while we huddled around the show on my laptop's small screen. </span></span></p><p style="text-align: left;"><span class="ILfuVd"><span class="hgKElc">While certainly a trivial problem in the grand scheme of things, it would nevertheless be quite a nice amenity to stay in a hotel which made it easy, instead of difficult or impossible, to play content on the room's TV screen from my laptop or gaming console, via an HDMI connection! (And I have to imagine that this would be cheaper and easier for hotels to implement than adding more "pool complexes" to their properties!)<br /></span></span></p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-19377512077356943362021-04-18T23:17:00.004-04:002021-04-18T23:17:34.701-04:00ClipEmoji.com Updated With New 2021 (Unicode 13.1) Emoji<p>My single-page website for quickly and easily getting a particular emoji character onto your clipboard for pasting into a document you're composing, <a href="https://ClipEmoji.com">ClipEmoji.com</a>, has been updated with the <a href="https://unicode.org/emoji/charts-13.1/emoji-released.html">new 2021 set of emoji from the Unicode Consortium</a>. New emoji include "face in clouds" and "heart on fire". <br /></p><p>As of the time of this writing, these newest emoji likely aren't available on your preferred platform yet, but when they are, ClipEmoji.com will be ready!</p><p>Incidentally, ClipEmoji.com isn't strictly needed anymore. The latest versions of both Windows and MacOS now include built-in <b>emoji pickers </b>that can be accessed from any place where you're composing text. The keyboard shortcuts to access them are <b>Win+Period (Windows) </b>and <b>Cmd+Ctrl+Space (Mac)</b>.</p><p>Still, I find myself still regularly using ClipEmoji.com while composing text on desktop platforms. You're welcome to use it, too! <a href="https://clipemoji.com/">Enjoy!</a> 😁<br /></p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com1tag:blogger.com,1999:blog-22120968.post-61286927015148034392021-03-12T09:34:00.003-05:002021-03-12T09:36:42.161-05:00Jon's Five-Monitor Work-From-Home Desk Configuration: 2021 Edition<p style="text-align: left;">Although many folks are working remotely these days due to the
Coronavirus pandemic, I've had longer than most to accumulate a nice desk configuration! For the past <i>seven</i> years now, I've worked remotely full-time from my home in Michigan.</p><p style="text-align: left;">Here's my current desk setup:</p><table cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: left;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4aCaiQkel-QhC4JwhXDITu0aFP_do7bh7KIzoTO8Udbebw-nLoCrobDf1wwZFa1XNAs66RwF8RQGS7lPivKalRA2UBBpsTW-ptN_OC8xaJLN9yc1wGZN-tInFlOIc9CoTOr2vSw/s744/5Monitors_202103_744.jpg" style="margin-left: auto; margin-right: auto;"><img alt="Five-monitor Mac desk configuration" border="0" data-original-height="422" data-original-width="744" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4aCaiQkel-QhC4JwhXDITu0aFP_do7bh7KIzoTO8Udbebw-nLoCrobDf1wwZFa1XNAs66RwF8RQGS7lPivKalRA2UBBpsTW-ptN_OC8xaJLN9yc1wGZN-tInFlOIc9CoTOr2vSw/s16000/5Monitors_202103_744.jpg" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;"></td></tr></tbody></table><p style="text-align: left;">My current primary computer, a 2019 16" MacBook Pro, is the screen on the far left. Of the other four monitors, two are connected directly to the MacBook Pro via USB-C cables; the remaining two are connected to my OWC 13 Port Thunderbolt 3 Dock -- that's the component sitting on the desktop behind my keyboard.</p><h3 style="text-align: left;">Delta since 2018<br /></h3><p style="text-align: left;">I didn't purchase and assemble this setup overnight! Instead, I've been able to add a part every year or so, building it up over time. This current setup is a nice improvement over <a href="https://blog.jonschneider.com/2018/04/my-five-monitor-macbook-pro-setup.html">my 2018 desktop configuration</a>:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCEQC6kUeZE0eVgytrOd9_Ix3CkNSWc0mS6GlOS-L83aRhyphenhypheniLASA5lGyBvrJpcSGrdkHOSxoSHbzbwhrxCKa9vPH4-KhpupZ_iH5lPIDnxeBzob138OpZEFmkicvbyeYrKxzU4WA/s739/5Monitors_739.jpg" style="margin-left: 1em; margin-right: 1em;"><img alt="My 2018 monitor configuration" border="0" data-original-height="471" data-original-width="739" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCEQC6kUeZE0eVgytrOd9_Ix3CkNSWc0mS6GlOS-L83aRhyphenhypheniLASA5lGyBvrJpcSGrdkHOSxoSHbzbwhrxCKa9vPH4-KhpupZ_iH5lPIDnxeBzob138OpZEFmkicvbyeYrKxzU4WA/s16000/5Monitors_739.jpg" /></a></div><p style="text-align: left;">The current setup replaces that smaller rightmost monitor with a new portrait orientation monitor; the remaining monitors are the same, just repositioned.</p><h3 style="text-align: left;">Application layout<br /></h3><p style="text-align: left;"></p>One reason I've opted for this setup of multiple monitors, as opposed to a single ultra-large monitor -- in addition to having had the ability to slowly purchase and assemble the setup over a period of years -- is that the multi-monitor setup lends itself really nicely to having a particular application always be placed at a particular location.<p style="text-align: left;">It is a really nice luxury to just be able to just look at a particular screen to get a view of a given application, instead of having to Cmd+Tab or otherwise search through multiple windows to find an application that I'm looking for. </p><p style="text-align: left;">Here's how I typically have my applications arranged in this current setup:<br /></p><p style="text-align: left;"></p><p style="text-align: left;"></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIPimq2c6Ge4Uwdj3xW20s65ZlCYRi9SlvjJ0ROroFxsQ-XNCfpDsW3Sx8EaAPk2qgdNlhNSqXjgEaOk-NNVYqnq71E-IEPzcXOtYMyQezbfXIDEGqmAD-bb9x-q4yksCfV8ukqw/s744/5Monitors_202103_744_Annotated.jpg" style="margin-left: 1em; margin-right: 1em;"><img alt="5-monitor application arrangement" border="0" data-original-height="422" data-original-width="744" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIPimq2c6Ge4Uwdj3xW20s65ZlCYRi9SlvjJ0ROroFxsQ-XNCfpDsW3Sx8EaAPk2qgdNlhNSqXjgEaOk-NNVYqnq71E-IEPzcXOtYMyQezbfXIDEGqmAD-bb9x-q4yksCfV8ukqw/s16000/5Monitors_202103_744_Annotated.jpg" /></a></div><h4 style="text-align: left;"><b>Leftmost monitor</b></h4><p style="text-align: left;"></p><p style="text-align: left;">As mentioned above, this is the MacBook itself. I have it up on a stand, both to better match the vertical positions of my other monitors, and to get a little bit of desk real estate back to store some small items under the stand. <br /></p><p style="text-align: left;"></p><p style="text-align: left;">I typically have my (multi-tab) iTerm2 terminal window running on this monitor, as well as a music player in "mini" mode. </p><p style="text-align: left;">I've been using the Mac App Store free app <a href="https://apps.apple.com/us/app/miniplay-for-spotify-itunes/id936243210?mt=12">MiniPlay</a> for the latter recently, and it's been working really nicely. In addition to being very similar in size and UI layout to my own <a href="https://jonschneider.com/SchneidersEleven.html">"Schneider's Eleven" minimal skin for Windows Media Player</a> that I previously used on a daily basis when I primarily developed on a Windows machine, it seamlessly supports both music player apps that I typically use, Music (formerly iTunes) and Spotify.</p><h4 style="text-align: left;"><b>Left portrait-orientation monitor</b></h4><p style="text-align: left;">This is where I view and write code! As I've <a href="https://blog.jonschneider.com/2015/12/developer-tip-orient-your-monitor.html">blogged previously</a>, I find it really productive to be able to view a long swath of code at once on the tall monitor.<br /></p><p style="text-align: left;"> (An exception is when I'm pairing with someone over screen-sharing software, since most of my peers don't have a portrait-orientation monitor. In that case, I typically drag the IDE over to my primary monitor.)</p><h4 style="text-align: left;"><b>Bottom landscape-orientation monitor</b></h4><p style="text-align: left;">This is my "primary monitor". I keep the Mac's Dock on this monitor. I typically have my main web browser window open on this monitor. </p><p style="text-align: left;">I'm currently composing this blog post on this monitor. <a href="https://clipemoji.com/">😁</a></p><h4 style="text-align: left;"><b>Top landscape-orientation monitor</b></h4><p style="text-align: left;">As mentioned in my 2018 post (and more visible in my 2018 image above), this monitor is mounted on a pole anchored to my desk via a WALI Extra Tall Monitor Desk Mount. </p><p style="text-align: left;">Sometimes I've got nothing happening on this monitor -- when I'm sitting, I have to crane my neck just slightly to look up at it -- but when I need a place to put some secondary window like a database/SQL query window, a code diff, or just another browser window, it's another place I can do so without obscuring any of my other open apps!</p><h4 style="text-align: left;"><b>Right portrait-orientation monitor</b></h4><p style="text-align: left;">This is where I keep my communications windows. The bottom portion is dedicated to Slack while I'm at work. I use the top portion for other communications apps like Messages and WhatsApp.<br /></p><h4 style="text-align: left;"><b>Phone stand</b><br /></h4><p style="text-align: left;"></p><p style="text-align: left;">The small, portrait-orientation "screen" in front of my MacBook is an
iPhone 12. I finally caved and bought myself a new phone, replacing my
venerable iPhone 6S. Despite the <a href="https://blog.jonschneider.com/2021/01/controversy-corner-wired-earbuds-vs.html">lamentable lack of headphone jack technology</a>, I have been otherwise been enjoying the improved battery life and memory on the new device.<br /></p><p style="text-align: left;">The
phone's "stand" is a magnetic wireless charger from Choetech, which
takes advantage of the new MagSafe technology in the 12. (The phone's
position while on the stand doesn't actually obscure any of the area of
the MacBook monitor behind it when I'm sitting at my desk, as it appears
to do in this photo.)</p><p style="text-align: left;">The app that you can see running on the phone is my own free App Store app, <a href="https://aggromagnetgames.com/desktop-journey/">Desktop Journey</a>. I developed it because I wanted my phone to be doing something useful while sitting and charging in its stand on my desk. I figured an at-a-glance "dashboard" view of the time, my next calendar event, my current Reminders app reminders, the weather, and occasional prompts to stand up and stretch or do a few reps of an exercise would be ideal. </p><p style="text-align: left;">After failing to find such an app anywhere on the App Store, I decided to build it myself, and created Desktop Journey! It's a completely free and ad-free app (except for the weather functionality, to help cover my costs for the non-free weather API that I'm using to query global weather data), and collects no personal data. <a href="https://apps.apple.com/us/app/desktop-journey/id1530583648">Free Desktop Journey download on the App Store</a>.<br /></p><h4 style="text-align: left;"><b>Small top-left screen</b> <br /></h4><p style="text-align: left;">The last remaining little screen, at the top-left, is a <a href="https://www.raspberrypi.org/">Raspberry Pi</a> running <a href="https://pi-hole.net/">Pi-Hole</a>,
a new addition to my home network, a Christmas gift from my wife
Melissa this past winter. With my local high-speed Internet monopoly
having started imposing a bandwidth cap -- which my family regularly bumps
up against, just through normal everyday usage -- I'm happy to take advantage of
the opportunity to save some bandwidth by declining to send outgoing
telemetry and requests for ads from my local network.</p><h4 style="text-align: left;"><b>Wallpaper</b></h4><p style="text-align: left;">The image is of Peyto Lake at Banff National Park, which my family had the good fortune to be able to visit in 2019. Banff -- along with the other nearby national parks in Canada, Jasper and Yoho -- is the most beautiful place I've ever visited. I hope to be able to go back and visit again someday!</p><p style="text-align: left;">As MacOS still lacks built-in support for spanning an image across multiple monitors, I used the App Store paid app <a href="https://apps.apple.com/us/app/multi-monitor-wallpaper/id504284434?mt=12">Multi Monitor Wallpaper</a> to generate this wallpaper.<br /></p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-62388227255541077212021-02-19T08:52:00.005-05:002021-02-19T08:55:18.331-05:00Easy one-handed Control Center and Notification Center access on iPhone 12/11/X<p> I finally opened up my wallet and replaced my aging iPhone 6S -- fare ye well, <a href="https://blog.jonschneider.com/2021/01/controversy-corner-wired-earbuds-vs.html">headphone jack</a> -- with a new iPhone 12. It's my first purchase of a brand new iPhone since <a href="https://blog.jonschneider.com/2013/12/upgrade-impressions-iphone-5s-vs-ipod.html">buying an iPhone 5S, all the way back in 2013</a>. (I had bought the 6S secondhand, via a seller on <a href="https://swappa.com/">Swappa</a>.)</p><p>While I'm finding the 12 to be a nice upgrade, one big usability regression jumped out at me in the early going: The difficulty of activating Control Center. (That's the screen where you can adjust volume and brightness, enable/disable wi-fi and Bluetooth, and so forth.) </p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9dZZY9cTHk87Cx_uwpJoIG7H2x8iaqQpQfYoN2yzljhF8_VPyc9joe98QueWHFR3Mq-8SzFE9GZEoxJOj5a26FBOnWsJP3h5W7KH8cmXRM7tNFY-p0TW26bYmuySO_TE-VfD_fg/s1581/IMG_704BBD4B3F06-1.jpeg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1581" data-original-width="1170" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9dZZY9cTHk87Cx_uwpJoIG7H2x8iaqQpQfYoN2yzljhF8_VPyc9joe98QueWHFR3Mq-8SzFE9GZEoxJOj5a26FBOnWsJP3h5W7KH8cmXRM7tNFY-p0TW26bYmuySO_TE-VfD_fg/s320/IMG_704BBD4B3F06-1.jpeg" /></a></div><p>On the 6S, activating Control Center was easily done while holding the phone in one hand, via an easy swipe up from the bottom of the screen. On the 12 -- which has no Home button -- that gesture now navigates to the Home screen. Control Center is now activated via a swipe down from the top-right corner of the screen. </p><p></p><p>The 12's larger size makes that gesture difficult to perform one-handed. Compounding the problem, although I'm right-handed, <a href="https://twitter.com/JonSchneider/status/561186084562825216">I use my left hand</a> when operating the phone one-handed -- so that top-right corner is unreachable without substantially shifting my grip on the phone (which increases the risk of fumbling it).</p>With its generally very good support for accessibility features, I figured Apple would provide a simple option somewhere in Settings to swap that top-right-corner gesture for the top-left-corner. Unfortunately -- at least as of iOS 14 -- <a href="https://discussions.apple.com/thread/250837585">there is no such option</a>.<h2 style="text-align: left;">The solution<br /></h2><p>To the rescue comes a configurable accessibility gesture <a href="https://www.tempertemper.net/blog/ios-14s-back-tap-a-better-way-to-access-control-centre">pointed out by @temptertemper</a>: The "Double Back Tap". Under Settings → Accessibility → Touch → Back Tap, you can set a firm "tap tap" performed on the <i>back</i> of your phone to perform one of a number of options -- including bringing up Control Center!</p><p>That double-tap is easily done with an index finger, while still securely holding the phone in a one-handed grip. In the early going, I've found the double-back-tap to be a much easier-to-perform alternative to the stock swipe-down-from-top-right-corner gesture.</p><p>Triple-back-tap is also assignable from the same place in Settings. I've assigned that to bring up Notification Center, which is also by default activated by a swipe down from the (distant) top of the phone screen.</p><p>Thanks to <a href="https://www.tempertemper.net/">@tempertemper</a> for this great suggestion, which I expect to be a nice quality-of-life improvement in daily use of my new iPhone! <br /></p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com3tag:blogger.com,1999:blog-22120968.post-55191512936625820182021-02-15T16:44:00.000-05:002021-02-15T16:44:56.025-05:00Interview: Amanda Schneider Milne, Software Developer / Manager / Executive<p><span style="font-size: small;"><span style="font-family: inherit;"> </span></span></p><p dir="ltr" id="docs-internal-guid-c57517e3-7fff-7dc7-5bfe-84839fb3df5f" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;">I’m super proud of my mom, Amanda Milne! She was among the first to earn a degree in Computer Science from the University of Michigan, has worked in the field of software development her entire career, and worked her way up to become president of her own custom software development company here in Michigan, and later on, a Senior Vice President of a Fortune 500 company.<br /></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: center;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><span style="border: none; display: inline-block; height: 400px; overflow: hidden; width: 400px;"><img height="400" src="https://lh4.googleusercontent.com/I_JBY1MgrnV3vaSDYSKCmbpPcdBqZgHi3_BHf75JmS1hqsBnafdWNLk0FXO_znQO7STWS7LDumylUgns2hWV2RzUXClodbjRT04DPAvs8RRt0l_G7b8z9c7-nQRB5d2N5szEdbnT" style="margin-left: 0px; margin-top: 0px;" width="400" /></span></span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">This is an interview with Amanda about her early years in software development, back in the 1970s and 1980s, and about one project in particular that I remembered her doing from my days as a grade school kid.</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">You were among the first Computer Science graduates at the University of Michigan, right?</span></span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">I was! I graduated from Michigan with a Computer Science Degree in 1974. The Computer Science degree program was fairly new back then. We had no textbooks, just mimeographed handouts (which I wish I’d kept!). </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Nearly all of our work was done on an IBM System/360 Mainframe Computer, primarily by submitting our code on punched cards via a remote card reader. I took one class on a DEC PDP-8 Mini Computer, and I learned to build logic boards by soldering chips in an elective Electrical Engineering course. The programming languages were primarily Fortran and Assembler. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Only 43 of us graduated with a Computer Science degree from U of M in ‘74. Fun Fact: I graduated without ever actually seeing the IBM System/360 Computer!</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">What did you do after graduation?</span></span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">I worked as a Programmer for various companies (and customer accounts). </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">My first assignment was to single-handedly write the real time process control code to run a custom carburetor test stand for Oldsmobile. The computer was a16-bit Modcomp mini computer with 16K of memory, and no disk! When I started that project, I knew nothing about Modcomp computers, their operating system, their language -- or what a carburetor was! </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">I wrote the code in an Assembler level language, and loaded my compiled code into the machine via paper tape. When I found a bug in my code, I wrote the fix, de-compiled it by hand, converted it to hexadecimal, and keyed the fix into memory using 16 binary switches. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Writing process control code was fun because I could actually see my code working as it opened and closed mechanical valves, and turned stepping motors. I was only 21, but I did successfully complete the project.</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">That’s pretty amazing! As a developer these days, there are so many layers of indirection between code we write, and what’s actually happening at the hardware level. You actually got to add instructions to memory directly, with no steps in between! (And without Google or StackOverflow to help you know what to do!)</span></span></span></span></p><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">After that, you worked at a startup called Custom Software Services, right? What was your role there?</span></span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">In 1981, Custom Software Services, a programming consultancy startup in the Detroit area, hired me to work part-time on a two-month project. I only had 3 years of programming experience at the time, because I took a 4-year break when you and Jeremy were born. My assignment was to single-handedly develop a daily Profit and Loss system for a local restaurant chain on an IBM Series/1 Computer (a 16-bit mini computer with the EDX Operating System), using a low level language called EDL. At the time, CSS had only one employee. After the project was completed, I stayed on to work on other projects. After 4 years, I went full time as a programmer. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Highland Superstores was a big appliance sales company back in the 1980s, right? What was the project that you did for them?</span></span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Highland Superstores was a national chain of 100 appliance stores selling TVs, electronics, washers, dryers and refrigerators. In 1987, the Internet did not yet exist. Highland Superstores wanted a networked Point of Sale (POS) system in all of their stores, and headquarters. The system would track their inventory, daily sales, gross margins, commissions, and so forth.</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Highland Superstores wanted a system to enable the salespeople on the floor to negotiate pricing, view how a lowered price negatively affected their own commission, check accurate up-to-date store inventories, sell the items, and schedule delivery. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Before this system, a salesperson had to leave the customer (or make a phone call) to check inventory in the back of the store. Salespeople had no idea if the next closest store had the item in stock that his customer desired. It was important to Highland to have up-to-date inventories of ALL of their stores available in each store. With this feature, a salesperson would be able to sell the item, and send the customer to the next closest store to pick it up. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">The POS system in each store had to communicate and update the headquarters computer every 15 minutes. It also had to download inventory updates from the other stores every 15 minutes.</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">How did IBM get involved in this project?</span><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> </span></span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Highland Superstores had spent 2 years working with another local software company to develop the POS system using PC LANs, but it wasn’t working and Highland was getting frustrated. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Rick Teague, Highland’s IBM Salesperson, told them that putting an IBM System/36 in each store, all communicating with an IBM System/38 at their headquarters, would work. The hardware was up to the job.</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">With no Internet, how did this kind of communication work? Dial-up modems?</span></span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Yes, dial-up modems. The POS system had to dial the headquarters IBM System/38 every 15 minutes, establish a connection, upload the local store inventory, and download updated inventories from the other 99 stores.</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">You mentioned Highland was getting frustrated? What was the issue with the other company’s in-progress implementation?</span></span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Well, they couldn’t get their PC LAN system to work. If I remember correctly, their software could perform the in-store functionality, but it had a great deal of trouble getting the communications with headquarters to work properly and quickly.</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">So how did you, and CSS, get involved in this project?</span><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> </span></span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">The IBM System/36 hardware solution was a good idea, but there was no POS software to run on that platform. Since Highland Superstores had been paying another software company, they weren’t willing to fund another software development project on a different platform.</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Rick said, “If I can get your POS system up and running in one of your stores on an IBM System/36, will you buy 100 System/36s?” Highland Superstores said maybe, provided they didn’t have to spend any money for the trial, and that the system would be up and running in the pilot store in 90 days! </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Highland Superstores also made it clear they were keeping their focus and money on the PC LAN-based solution. The new IBM System/36-based solution would have to out-perform a solution that had a two-year head start! </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">IBM approached several software companies in the Detroit area, and asked if they would be willing to develop the POS system for free, demonstrate that it worked, and then hope to get paid afterwards. They all said “No”... except my boss, Don George, who owned Custom Software Services.</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Our company had about 15 employees at the time, business was slow, and we were in danger of going under. Don was an ex-IBM Systems Engineer, so he knew the guys in the local IBM office. He sat down and had some serious discussions with them about the project, and then he decided to take on the risk and tackle the project. I got involved when my boss asked me to manage the project.</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">That does sound like quite a risk! What was it like working on a project, and leading a team, that would have a zero payout for everyone involved if it wasn’t successful? </span></span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">It was extremely challenging. We couldn’t afford to pay a team of developers to write the software, so Don came up with a creative idea to share the risk. He offered several local developers a double-or-nothing deal. If they signed on with our project, we’d track all of their hours, and then pay them double their going hourly rate at the end -- if it was successful and Highland Superstores purchased it. If not, they’d get zero. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Surprisingly, he found 7 developers who were willing to take his offer, but they all had to keep their day jobs. So, they worked on our POS project nights and weekends. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">And you were in charge of managing this? How did you approach that?</span><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> </span></span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">I was 34 years old, and this was my very first project management assignment. The first thing I did was to read 3 books on project management. I knew that the project would be very difficult, and that I’d have to use every bit of education, new knowledge, guts, and perseverance I had! </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">As it turned out, it was even more stressful, and challenging that I’d first thought. The project lasted 5 months, not 3. At the 90 day mark, we had accomplished enough that we earned another month or two as a grace period. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">The developers and I were all working 2 shifts, 7 days a week. Over that summer, none of us had any family time, or vacation time. There were many days when I came home late at night, dropped my clothes on the floor and fell into bed. Then, the next morning, I showered, grabbed clean clothes and was gone again. We worked 2 shifts, 7 days a week. The good news is that as the project progressed, everyone had more skin in the game. The developers saw their potential earnings mount, and became more and more unwilling to walk away. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">When we had the code (in the RPG language) running on a remote IBM System/36, IBM installed a System/36 in one of the Highland Superstores (for a free trial), and we spent about a month troubleshooting and debugging the software in a live store environment.</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">That must have been exhausting! How about the flip side? Was there a best part about the project?</span></span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Yes! No scope creep! This was the only software development project I’ve ever worked on with zero scope creep. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">We didn’t realize at first, how important this would be, but it was one of the biggest contributors to the project’s success. There was no scope creep because Highland Superstores had already invested 2 years in hammering out the functionality of the system. All we had to do was design and develop our code to exactly produce the same POS interface, files and results. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Also, we were all working for free, so we had added incentive to stick to the exact specs. There were many, many times during the project that someone involved would suggest a change or improvement, and my answer was always ‘No’. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">The project team was all men… except for you! What was that like?</span></span></span></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> </span></span></span></p><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: center;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><span style="border: none; display: inline-block; height: 392px; overflow: hidden; width: 624px;"><img height="392" src="https://lh3.googleusercontent.com/yfUvQCLBvq47vyasZ-r__FG6OeAPeZNV77rpCXalCIhgBWQfmu-1iU_S7e_FNmBJ3njjJmXwmrMNIiqpL8qlnqajizYPjz3j8SwZbPGIXdiBV-2mZZEqtULG4lYElYMzuKMGv-H1" style="margin-left: 0px; margin-top: 0px;" width="624" /></span></span></span></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: center;"><span style="font-size: x-small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Amanda (far right), her team, her boss (Don, far left), and a few IBMers at the project kickoff meeting.</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">I don’t know what it was like for the other people involved. I can only tell you that I really didn’t think much about being the only woman, except on two occasions. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">The first time was at the beginning of the project. I knew I would be managing a team of all men, and that I had never managed a project of this scale, scope or importance before. I knew I had to establish credibility - and authority right from the start. So, I called a Project Kick Off Meeting and I invited all of the developers, and the local IBMers. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">At the meeting, I laid out the project plan, the timeline, the assignments, and the project ground rules. After the meeting, one of the IBMers told me it was the best kickoff meeting he’d ever attended.</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: center;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><span style="border: none; display: inline-block; height: 431px; overflow: hidden; width: 624px;"><img height="431" src="https://lh4.googleusercontent.com/0ECOwJi7mtXFB6Zwzl2Icz4q8PBo5NdYrRYwHsxPW5E9yjbkBZ-7sun9eXlmOZRnMjuzd6JEPRwivtxgaQswL4ogskBXU7wKGwDhxh9fnAMTH01X1U4TosFYuE3czrINFQX91cfs" style="margin-left: 0px; margin-top: 0px;" width="624" /></span></span></span></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: center;"><span style="font-size: x-small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Amanda with 2 IBMers and John, a fellow employee</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">The second time it mattered to me that I was the only woman, was during the roll-out phase of the project. At that point, our software had been awarded the contract, and I had a team working at the Highland Superstore headquarters to help install the software in all of their stores. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">We were all working long hours, and it was very stressful - getting the hardware and software up and running in 100 stores nationwide before the Christmas shopping season. One evening after most people had gone home for the day, one of the IBM Managers got into a heated argument with one of the Highland Superstores Managers -- they were yelling and screaming at each other so loudly, someone called security to stop them before they started hitting each other. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">A week or two later, I raised my voice in a meeting, and I was accused of ‘being emotional’!! No one ever said the screaming men were emotional, but I was accused of being emotional, which I thought was very unfair.</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">That’s super lame. I’m very glad there is generally more awareness in corporate environments in general, and in the software development industry in particular, about the unfairness (and lameness) of those kinds of double-standards. I’m hopeful that if either of my daughters get into the industry when they grow up, the state of things will be such that it won’t even cross their minds to be concerned about that kind of issue.</span></span></span></span></p><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Were there any particularly memorable challenges during the project?</span><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> </span></span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Lots! At one point, a certain bug was making us all crazy. I solved it when I noticed -- on the old dot matrix print-outs we used at that time -- that there was a letter O where a 0 (zero) was supposed to be in the code. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">The worst was when my lead developer, who was working on the critical path, decided to walk off the project when we were about ⅔ through development. That surprised me, and freaked me out. Especially since, although I was a developer, I didn’t know RPG, and wasn’t able to jump in and take over his code myself. Luckily, another of the developers on the team agreed to step up and successfully took over his portion of the code. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Another time, in the middle of the project, Northwest Airlines Flight 255 crashed shortly after takeoff from Detroit Metropolitan Airport killing all six crew members and 148 of its 149 passengers -- including a family member of one of our software developers. We were all horrified, but we soldiered on. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">What wisdom would you share with people working on software projects today?</span></span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Make sure to keep an open mind, open communications, and always hear both sides of the story. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">I remember one time, the customer came to me with “evidence” that one of my developers had deliberately caused a significant problem in the code. Upon seeing the evidence, I too became very upset, and thought about firing him on the spot. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">But, I calmed myself down and went to talk with the developer in question. I asked him to tell me about the day, and code in question -- just tell me what happened. His description threw a completely new light on the situation, and made it clear that he was not trying to sabotage the project. I was then able to explain it to the customer, and keep the project going. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Another lesson is one I learned was from Rick, the IBM salesperson. One evening, after a team meeting at my home, we were in my driveway talking about the odds that Rick had to surmount to get this project started. With great persistence, Rick had spent months and months calling on Highland Superstores before they even began to consider his pitch, and a possible IBM-based solution. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">During our talk. Rick picked up a basketball and started bouncing it while he walked down my long, curved, sloped driveway. When he neared the street he said, “You have to be optimistic. You’ll never get a parking spot closest to the mall entrance if you don’t drive down the first row. You have to take a shot.” Then, without turning around, with his back to the basketball hoop, he shot the basketball one-handed, over his head -- and the ball swooshed through the basket!! I will never forget that moment!</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">How did it all turn out in the end?</span></span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Well, it was a true horse race right to the end. After 5 months of working grueling hours, Highland Superstores made the decision to scrap the PC LAN-based system, and purchase ours! </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: center;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><span style="border: none; display: inline-block; height: 468px; overflow: hidden; width: 624px;"><img height="468" src="https://lh6.googleusercontent.com/KIU5xzAfheyT-fhxOqU8VJ36iBJUmvGP3bwMFaBfIt2ScpHfVXSlueSQh-sjcso9PWOn6LMQgk5l8mCnIIeJUnF2YcuDyXF5GltuxNrwAQydJ_BWuq2C1M0DjFQOL3t2NKZF-g-F" style="margin-left: 0px; margin-top: 0px;" width="624" /></span></span></span></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: center;"><span style="font-size: x-small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Testing the software in a live store environment</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">We were ecstatic. We all celebrated with an impromptu party. Over the following months, Highland Superstores successfully installed an IBM System/36 and our software in all 100 stores. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Rick, the IBM Salesperson was handsomely rewarded with commissions on all of those machines. My boss, Don paid each of the software developers double their hourly rate for every hour they worked on the project. </span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">And me? Don promoted me to VP of the company, gave me a $25,000 bonus, a new car, a Caribbean sailing vacation, and the opportunity to purchase a minority stake in his company -- which I did!</span></span></span></p><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">I’m so impressed that you were able to do all of that! I’m super proud to have been able to follow in your footsteps, in having a career creating software to solve problems for people!</span></span></span></span></p><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><br /></span></span></span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #9fc5e8;"><span style="font-size: small;"><span style="font-family: inherit;"><span style="background-color: transparent; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Thanks, Mom, so much for taking the time to do this interview and share this part of your story!</span></span></span></span></p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com6tag:blogger.com,1999:blog-22120968.post-44961697440558059172021-02-07T22:45:00.001-05:002021-02-07T22:45:17.626-05:00Why Not Take a Break?<p>Here's a graphic that I whipped up today, which I thought would be fun to occasionally post on social media sites, to appear in folks' feeds as they scroll though posts:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjT2uItE2iQIJVOiDcL7NZjz39qP71N5EZIzkJ5X0ggBG6b1Vnqr7IItHhvPPtGtzaGNC_xqEVsIUWtOR-rr63VTMvu4xCoR0xuje96MC51P03ufS3895BOe2XumiZiDp1QzWJlTg/s480/WhyNotTakeABreak.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="Why not take a break? You can stop scrolling this feed by pressing your phone's Home button." border="0" data-original-height="288" data-original-width="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjT2uItE2iQIJVOiDcL7NZjz39qP71N5EZIzkJ5X0ggBG6b1Vnqr7IItHhvPPtGtzaGNC_xqEVsIUWtOR-rr63VTMvu4xCoR0xuje96MC51P03ufS3895BOe2XumiZiDp1QzWJlTg/s16000/WhyNotTakeABreak.png" /></a></div><p>I was amused by the contrast between Nintendo including <a href="https://www.google.com/search?q=wii+sports+take+a+break&safe=strict">the original version of this image </a>in a couple of their games -- most notably Wii Sports -- and the hypothetical likelihood of any of the big social media companies inserting some content like this into their users' feeds.</p><p>(Anecdotally, I've heard that Facebook, at least, tends to favor implementation of new features that <i>increase </i>the amount of time that their users spend scrolling through their feeds -- of which something like this, encouraging their users to stop scrolling and take a healthy break, would be the opposite!)</p><p>It was pointed out by my wife that modern phones -- including the one that I photoshopped into the image in place of the original Wii Remote -- do not actually have home buttons. As the proud carrier of an iPhone 6S, this admittedly didn't even cross my mind. I'll just leave it there as an intentional <span>anachronism</span>, hearkening back to the time of the Wii's popularity. 🤷♂️ <br /></p><p>In any event, feel free to share this image onto your own social media feeds, should you be so inclined!<br /></p><p><br /></p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-77514636895224535342021-01-22T10:33:00.002-05:002021-01-22T10:33:53.702-05:00Can we make Cmd+Z undo Cmd+V (only)?<p>Hey developers! If you're fortunate enough to be doing greenfield development on a new text editing app or component, I have a quick feature request for you!</p><p>In all existing text editors and text fields that I'm aware of, if you do the following:</p><ol style="text-align: left;"><li>Key in some text.</li><li>Append another piece of text via a press of Cmd+V (Ctrl+V on Windows) to paste from the clipboard.</li><li>Press Cmd+Z (Ctrl+Z on Windows) to undo.</li></ol><p>What happens is that <i>both</i> the pasted text, <i>and</i> some or all of the keyed-in text, get removed.</p><p>What I'd <i>like</i> to have happen is for only the pasted text to be removed. The keyed-in text can be removed upon additional Cmd+Z press(es).</p><p>I bump into this problem quite frequently when I Cmd+V to append something to some text I'm in the middle of composing. I see that the wrong thing was on the clipboard and got added to the text, reflexively hit Cmd+Z to try and undo that mistake -- and now I have two problems, because some of the text that I had keyed in got removed along with the incorrect pasted-in value.<br /></p><p>It would be super fun to have this no longer be an issue in the text editors of the future. Thanks in advance! 👍<br /></p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-43364109168933650142021-01-18T11:26:00.003-05:002021-06-26T10:00:32.600-04:00Controversy corner: Wired earbuds vs Airpods<p>This is a "just for fun" post on the pros and cons of traditional wired earbuds vs <a href="https://www.apple.com/airpods/">Apple AirPods</a> for everyday use. </p><p></p><p>Hat tip to my brother <a href="https://www.personalfinanceclub.com/">Jeremy</a> for inspiring me to finally put this post together. He was recently <a href="https://www.instagram.com/personalfinanceclub/">seen on Instagram</a> rocking some sweet wired earbuds, even though he's retired, and has sufficient discretionary funds to buy himself AirPods, if he so chose!</p><div align="left" dir="ltr" style="margin-left: 0pt;"><table style="border-collapse: collapse; border: medium none; table-layout: fixed;"><colgroup><col></col><col></col><col></col></colgroup><tbody><tr style="height: 0pt;"><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><br /></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">Wired Earbuds</span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">Apple AirPods</span></p></td></tr><tr style="height: 0pt;"><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">Price tag 💸</span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">✅ From around </span><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">US $10-15 💰</span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">US $159+ 💰💰💰💰💰💰💰💰💰💰</span></p></td></tr><tr style="height: 0pt;"><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">Ease of switching between multiple devices (e.g. iPhone 📱 and MacBook 💻) </span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">✅ Plug them in</span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">Fiddle with the Bluetooth settings menu</span></p></td></tr><tr style="height: 0pt;"><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">Troubleshooting pairing issues / charging issues / audio issues<br /></span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">✅ 100% reliable</span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;"><a href="https://support.apple.com/en-us/HT209581">Seldom</a>, <a href="https://support.apple.com/en-us/HT209506">hopefully...?</a></span></p></td></tr><tr style="height: 0pt;"><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">Charging ⚡</span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">✅ Never needs charging</span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">Up to 5 hours listening per charge; requires charging case + Apple lightning cable or wireless charging mat (</span><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">💰</span>)</span></p></td></tr><tr style="height: 0pt;"><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">Anxiety when they get lost 😰</span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">✅ Shrug and buy a new pair</span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">High</span></p></td></tr><tr style="height: 0pt;"><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">Works with </span><a href="https://www.nintendo.com/switch/" style="text-decoration: none;"><span style="-webkit-text-decoration-skip: none; background-color: transparent; color: #1155cc; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration-skip-ink: none; text-decoration: underline; vertical-align: baseline;">Nintendo Switch</span></a><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">, the treadmill TV at the gym, the entertainment systems on airplanes</span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">✅ Plug them in</span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">No; can work around with Bluetooth adapter (</span><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">💰</span>)</span></p></td></tr><tr style="height: 0pt;"><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">Battery lifespan 🔋<br /></span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">✅ Unlimited<br /></span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><a href="https://www.cultofmac.com/614998/airpods-battery-life-wear-out/"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">2-3 years</span></a></p></td></tr><tr style="height: 0pt;"><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">Audio quality 🎶</span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">✅ Great</span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">✅ Great</span></p></td></tr><tr style="height: 0pt;"><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">Can simultaneously charge device </span><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">🔌 </span>and listen </span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">✅ Yes (on devices with a 3.5mm jack, like <a href="https://blog.jonschneider.com/2016/09/why-no-iphone-7-35mm-audio-jack-is-deal.html">my iPhone 6S</a>)</span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">✅ Yes</span></p></td></tr><tr style="height: 0pt;"><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">Risk of inadvertently getting yanked or knocked out of ears 👂</span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">Wires can get caught when doing chores</span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">✅ Minimal </span></p></td></tr><tr style="height: 0pt;"><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">Works with newer iPhones</span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">Dongle needed 🙄</span></p></td><td style="border-bottom: solid 1pt; border-color: currentcolor; border-left: solid 1pt; border-right: solid 1pt; border-style: solid; border-top: solid 1pt; border-width: 1pt; overflow-wrap: break-word; overflow: hidden; padding: 5pt; vertical-align: top;"><p dir="ltr" style="line-height: 1.2; margin-bottom: 0pt; margin-top: 0pt;"><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline;">✅ Yes 🍎</span><span style="background-color: transparent; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 700; text-decoration: none; vertical-align: baseline;">💰</span></p></td></tr></tbody></table></div><p><br />Looks like AirPods win in a landslide! Let's all throw away our inexpensive, never-needs-charging, high-fidelity earbuds, and buy AirPods! 😜<br /></p><p>For clarity, I am indeed perfectly aware that the ship has (for the most part)
sailed on this debate. Apple, at least, doesn't seem likely to release a
new phone ever again with 3.5mm headphone jack technology, when they could sell
the folks buying that phone $160 AirPods instead. I stand by my entitlement to my
(unpopular) opinion on this topic regardless. 😁</p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0tag:blogger.com,1999:blog-22120968.post-26665349883247124432021-01-08T15:05:00.002-05:002021-01-08T15:08:44.294-05:00Configuring git to prevent commits of temporary/debug code<p>Developers, have you ever embarrassingly pushed a commit that inadvertently included temporary debug log/print statements, or other code that you intended to remove before committing? I have!</p><p>I wondered if it was possible to add some keyword to a code file -- for example, <span style="font-family: Consolas;">DONOTCOMMIT</span> -- which, if present, would cause git to automatically reject the commit. This could be added as a comment alongside any temporary debug code, to make it impossible to forget to remove that code before committing.</p><p>It turns out that it is possible to do this -- and have it apply automatically to all of your local git repositories -- using a <a href="https://git-scm.com/docs/githooks">git pre-commit hook</a>.<br /></p><h2 style="text-align: left;">Quick instructions</h2><p>(The specifics in these instructions apply to Unix-like filesystems, including Mac OS.) <br /></p><p>1. If it doesn't already exist, create the directory: <span style="font-family: Consolas;">~/.git_templates/hooks/ </span><br /></p><p>2. If it doesn't already exist, in that <span style="font-family: Consolas;">hooks</span> directory, create a text file named: <span style="font-family: Consolas;">pre-commit</span> (no extension).</p><p>3. Add the following content to the <span style="font-family: Consolas;">pre-commit</span> file:</p><p><span style="font-family: Consolas;"><span class="pl-c"><span class="pl-c">#!</span>/bin/bash</span></span></p><p><span style="font-family: Consolas;"><span class="pl-c"></span>FOUND=$(git diff-index --cached -U0 HEAD -- | grep DONOTCOMMIT | wc -l) <br /></span></p><p><span style="font-family: Consolas;">if [[ $FOUND -gt 0 ]]; then<br /> echo "pre-commit hook: DONOTCOMMIT detected, commit not allowed"<br /> echo "(enforced from: ~/.git_templates/hooks/pre-commit)"<br /> exit 1<br />fi</span><br /></p><p>4. To apply that pre-commit hook to all git commit operations on your local machine, from the terminal, run:</p><p><span style="font-family: Consolas;">git config --global core.hooksPath ~/.git_templates/hooks</span></p><h2 style="text-align: left;">References<br /></h2><p>Credit to <a href="https://github.com/mudrd8mz">https://github.com/mudrd8mz</a> for the <a href="https://gist.github.com/mudrd8mz/2828474">pre-commit hook code</a>.</p><p>Credit to <a href="https://stackoverflow.com/users/6309/vonc">https://stackoverflow.com/users/6309/vonc</a> for the <a href="https://stackoverflow.com/a/37293198/12484">core.hooksPath global git config command</a>.</p><p><br /></p>Jon Schneiderhttp://www.blogger.com/profile/05718316809087214442noreply@blogger.com0