MacBook, defective by design banner

title:
Put the knife down and take a green herb, dude.


descrip:

One feller's views on the state of everyday computer science & its application (and now, OTHER STUFF) who isn't rich enough to shell out for www.myfreakinfirst-andlast-name.com

Using 89% of the same design the blog had in 2001.

FOR ENTERTAINMENT PURPOSES ONLY!!!
Back-up your data and, when you bike, always wear white.
x

MarkUpDown is the best Markdown editor for professionals on Windows 10.

It includes two-pane live preview, in-app uploads to imgur for image hosting, and MultiMarkdown table support.

Features you won't find anywhere else include...

You've wasted more than $15 of your time looking for a great Markdown editor.

Stop looking. MarkUpDown is the app you're looking for.

Learn more or head over to the 'Store now!

Wednesday, January 15, 2020

Here's Today Adventure in Enjoyable AngularJS...

I ran into some code from a project that looked give or take like this...

namespace.directive('caNavigation', function () {
    "use strict";

    return {
        restrict: 'E',
        scope: {
            inLinks: "@",
            collapseNav: "="
        },
        // ...
       
        controller:
            ["$scope", /* more jive */],
            function ($scope /* more jive */)
        {
            $scope.$watch('inLinks', function (value) {
                // omgwtfbbq?
                // ...


Well, that's @ and = stuff is pretty cryptic, especially since I hadn't used a directive in AngularJS before (Angular 2+, sure, but still learning ye olde AngularJS -- going backwards, I know. Don't forget I had my first run-in with Perl in 2014 [sic]).

Let's see what ye olde docs say:

[Having to have a separate controller to change direct scopes] is clearly not a great solution.
What we want to be able to do is separate the scope inside a directive from the scope outside, and then map the outer scope to a directive's inner scope. We can do this by creating what we call an isolate scope. To do this, we can use a directive's scope option:
...

Let's take a closer look at the scope option:
//...
scope: {
customerInfo: '=info'
},
//...
The scope option is an object that contains a property for each isolate scope binding. In this case it has just one property:
  • Its name (customerInfo) corresponds to the directive's isolate scope property, customerInfo.
  • Its value (=info) tells $compile to bind to the info attribute.

Good heavens, AngularJS. Not the most discoverable. Seems like in AngularJS in general there's lots of magic stringing. Perhaps I shouldn't be surprised, but that doesn't mean I like it. (Here I mean largely inline array notation, which is too hipster by half. I mean, I get that you don't what to lose information when you minimize, but then how about pass real references, not refer to objects by their original names? Sheesh.* )

But let's go a little deeper so that we know what the @ symbol means too.
The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the directive's element. These local properties are useful for aliasing values for templates. The keys in the object hash map to the name of the property on the isolate scope; the values define how the property is bound to the parent scope, via matching attributes on the directive's element:
  • @ or @attr - bind a local scope property to the value of DOM attribute. The result is always a string since DOM attributes are strings. If no attr name is specified then the attribute name is assumed to be the same as the local name. Given <my-component my-attr="hello {{name}}"> and the isolate scope definition scope: { localName:'@myAttr' }, the directive's scope property localName will reflect the interpolated value of hello {{name}}. As the name attribute changes so will the localName property on the directive's scope. The name is read from the parent scope (not the directive's scope).
  • = or =attr - set up a bidirectional binding between a local scope property and an expression passed via the attribute attr. The expression is evaluated in the context of the parent scope. If no attr name is specified then the attribute name is assumed to be the same as the local name. Given <my-component my-attr="parentModel"> and the isolate scope definition scope: { localModel: '=myAttr' }, the property localModel on the directive's scope will reflect the value of parentModel on the parent scope. Changes to parentModel will be reflected in localModel and vice versa. If the binding expression is non-assignable, or if the attribute isn't optional and doesn't exist, an exception ($compile:nonassign) will be thrown upon discovering changes to the local value, since it will be impossible to sync them back to the parent scope.
    By default, the $watch method is used for tracking changes, and the equality check is based on object identity. However, if an object literal or an array literal is passed as the binding expression, the equality check is done by value (using the angular.equals function). It's also possible to watch the evaluated value shallowly with $watchCollection: use =* or =*attr
If you've used Angular 2+ (what a failed naming scheme, btw. Why not "Angular.IO" after the website or just NGular or something so that googling this jive would be easier?), you can already see our steady progression to banana boxes.

But for now, what a mess.
  1. If a scope property is a string that starts with @, then we'll initialize that scope property with the directive's DOM attribute that matches what comes after the @.
    • That is, this is a one-way process. Props down, and set up events if changes need to go back up.
  2. If the prop starts with an =, we're doing the equivalent of Angular.IO's banana boxing, it appears.
    • So if we change whatever's in here, the parent is changing too.
Back to our original example now... We had a cryptic (to me) $scope.$watch('inLinks', function (value)..., and now we know what that means. @ means we've got a prop that $watch can check for changes, which, in this case, means:

  • The listener is called only when the value from the current watchExpression and the previous call to watchExpression are not equal (with the exception of the initial run, see below). Inequality is determined according to reference inequality, strict comparison via the !== Javascript operator, unless objectEquality == true (see next point)
  • When objectEquality == true, inequality of the watchExpression is determined according to the angular.equals function. To save the value of the object for later comparison, the angular.copy function is used. This therefore means that watching complex objects will have adverse memory and performance implications.

Guess that works. Let's ignore the =* shallow comparison jive for now. All we've really got is one prop called inLinks that will be passed... um... "in" that we'll update whenever it changes, probably after the page finishes loading and making some resource calls. And sure enough...

<li ng-repeat="subLink in link.SubLinks|filter: {ShowInNav: true}" ...

That means once we've loaded the correct navigational links for this context, we'll start pushing them into the DOM.

And our code makes sense. Much rejoicing.



* I actually kinda like AngularJS. It's one clean step away from the inefficient, overly-engineered land of enterprise development Bjarnason discusses and that at least the social convention surrounding Angular 2+ seems to require. But there are a few stupid* conventions like this one that really do AngularJS in conceptually.

* Sorry, I've tried to come up with a good synonym, but "stupid" fits here. My earlier use of "hipster" was my kind attept at an alternative, but this stuff really feels a little too much like a first-pass solution to be in a nice, mature templating library.

Labels: , ,


posted by ruffin at 1/15/2020 05:32:00 AM
0 comments
Tuesday, January 14, 2020

I'm not sure why I didn't look it up earlier, but here's how to exclude files from your Sublime Text 3 projects -- where the real benefit to me is that libraries won't pollute my search results.

Open up your Settings (Preferences menu, then Settings). In the pane that opens, your user settings will appear as a json file on the right.

If you don't already have these properties, add them with your desired values...

"folder_exclude_patterns": ["angular"],
"file_exclude_patterns": ["angular.js", "*.min.js"]

Thanks to here for getting me started with folder_exclude_patterns, and to Sublime for have settings where property names are as easy to guess as they were in this case.

Labels: ,


posted by ruffin at 1/14/2020 01:19:00 PM
0 comments
Thursday, January 09, 2020

I don't recall when I signed up for it, but I routinely get a newsletter from Baldur Bjarnason about software development with a focus on web development.

His new year's email includes some points on JavaScript frameworks that I'm suspicious I like because they so closely match mine.

But what they so clearly indicate is that web development has become a field whose outward appearance and, all too often, local practice, has been completely co-opted by the needs of the largest of enterprise corporations.

(Words in bullets are his. All emphasis is mine.)

  • Once you step outside of the social media bubble, however, vanilla JS and more ‘modest’ approaches like Svelte or Stimulus/TurboLinks seem to have reached critical mass in terms of sustainability.Irrespective of those newer trends, jQuery and PHP-driven, un-hydrated, old-fashioned server-side rendering still utterly dominate the web that people use.
  • Web dev driven by npm packages, frameworks, and bundling is to the field of web design what Java and C# in 2010s was to web servers. If you work in enterprise software [npm, framework, and bundling-through-transpilation driven patterns are] all you can see. Web developers working on CMS themes (or on Rails-based projects) using jQuery and plain old JS—maybe with a couple of libraries imported directly via a script tag—are the unseen dark matter of the web dev community.
  • What should worry you is that npm- and framework-driven web development feels just as painful as enterprise software dev because it is enterprise development.

He continues by comparing TypeScript -- and I think he means more specifically "enterprise TypeScript development", because there's lots to enjoy about TypeScript the language, nothing inherently evil about it -- to enterprise Java of 20 years ago.

  • TypeScript smells like Java.
  • The complexity of npm packages harkens back to painful Java packaging monstrosities.
  • JavaScript build systems are about as much fun as Java build systems, even though they are doing very different things.
  • Deployment, as implemented in the Kubernetes and Docker ecosystems, is exactly as hard to understand and use as its Java predecessors because those are their predecessors.

Then he has a few points that serve as a sort of manifesto for the non-enterprise developer.

  • Some of use work exclusively with SMBs (Small-/Medium-sized Businesses) and shouldn’t need to run the enterprise anti-productivity gauntlet. Our needs in terms of frameworks, bundling, and packages are very different from those working in enterprises with hundreds or thousands of employees.
  • It is not rational to expect [developers for small and medium-sized businesses] to be using enterprise-oriented tools and environments or to demand that we be happy about being saddled with your need for complexity.

I'd also ask people to take a close look at how they define what's an SMB project and what's an enterprise project. That is, defining projects by the size of the corporation that produces them isn't always -- heck, isn't usually -- the best metric.

Where something becomes "enterprise" is when you have hundreds of people working on the same project, in the same codebase. What npm/library-based bundling buys you is the ability to firewall smaller projects from each other. If someone writes a horribly inefficient page that no rendering library could solve without magic...


... your development process already includes a baked-in firewall to ensure that code doesn't adversely impact the rest of your system. You can stitch together completely independent projects easily, iff you really need to do that stitching.

But how big is your day-to-day work, really? How many of the problems "solved" by mass package import could have been solved reasonably well for your uses with a (think what's often derisively called a "NIH syndrome-induced") roll-my-own, homebrew solution?

Put another way, how many developers are really part of your specific product? 

If it's not hundreds, ask yourself how much time you'd save on developer ramp-up, maintenance, and new development if you too used "jQuery and plain old JS—maybe with a couple of libraries imported directly via a script tag". Even in a team of "just" 20-30, the resource savings from going just 25% faster (a conservative cost of doing framework-style development in my experience) are unbelievable.

I'm not sure I know when going "full framework" is the best idea. At a conference, some friends of mine (coworkers) managed to corner a guy who was then working at Google on AngularJS (before Angular 2+). We talked to him a bit about AngularJS' pain points and how they solved problems of large DOMs, as our Knockout.js-based system was getting crushed in our more dynamic, feature-rich UIs.

His basic comment was that you can't fix these issues easily, and there was no silver bullet. Inefficient or complex UIs are trouble no matter where you build them.

The problem here was browser performance, which by definition isn't an SMB or enterprise problem. It's a client-side rendering one. Frameworks don't fix these issues. "Full framework" dev doesn't provide solutions to day-to-day problems, it provides the passive coordination that allows the amalgamation of code from hundreds of developers working at once.

Is that really the state your team finds itself in? Why would you want it to be?

And then here's a final comment from Bjarnason about how the thinkspace of development has been dominated by the enterprises -- he does a good job in the balance of his new year's points discussing how we see so much enterprise-specific information because those enterprises have a vested interest in making that come to be.

  • The divide between what you read in developer social media and what you see on web dev websites, blogs, and actual practice has never in my recollection been this wide. I’ve never before seen web dev social media and forum discourse so dominated by the US west coast enterprise tech company bubble, and I’ve been doing this for a couple of decades now. The pre-2000 dot-com bubble comes close although that one came attached to an actual financial bubble and happened before social media had evolved into its current form.

Anyhow, it's a good, thought provoking post, and worth a full read.

Labels: , , , , ,


posted by ruffin at 1/09/2020 10:06:00 AM
0 comments
Sunday, January 05, 2020

How had I never learned this?

If, for example, you knew that the file had bob somewhere in the file, you would type:
dir *bob*.* /s
That's borderline embarrassing. I've even used tree /a /f > out.txt and then searched that text file.

Major Facepalm.

Labels: , , ,


posted by ruffin at 1/05/2020 09:46:00 AM
0 comments
Thursday, December 12, 2019

I over-complicated this.

I found some sublime-syntax files I wanted to install from here, and was used to copying those somewhere in Sublime Text 3's folder hierarchy.

No, dope, you can install via the package manager.

Ctrl-Shift-P, Install Package, HTML C#, profit.

Labels: , ,


posted by ruffin at 12/12/2019 02:16:00 PM
0 comments
Thursday, December 05, 2019

I've had an iPhone XR since essentially the release date, so I've been using it as my daily phone for over a year, upgrading from an iPhone SE.

This is enough time to realize that I really, truly, hate Face ID.

Maybe some day I'll screed on precisely why, but here's it in a nutshell: I just had a failed attempted Face ID of my leg. I'd used Siri a second before, and didn't close the screen while I carried it into the next room. It tried to Face ID maybe my face as I brought it down after reading Siri's response, then haptic'd again for another fail after I'd lowered it in my hand to beside my thigh.

This happened in my home.

It happens all the freaking time when I'm running too. Or when I'm driving and need the map to come back. Or when I'm walking around the house listening to podcasts or whatever and have had the phone open recently.

Face ID guesses it's time to ID me in all sorts of stupid situations where there's no face in sight. Argh.

And what happens when it fails because it tried to ID me when I didn't want it to? It makes me enter my passcode. My passcode is not a four digit number. I can't enter it safely when driving. I can't enter it easily when running. I hate entering it at home when the Face ID lock wasn't my fault.

(Let's not even get started with how many times Face ID doesn't realize it's me. That's at least once or twice a day, and sometimes more than five. And sometimes that locks the Face ID and I have to enter my passcode. 

And at some point I'm going to do a video of how it handles beards, aka the "ignore your chin" feature. I shaved a beard once and could unlock my iPhone by holding a scrunched towel over the bottom of my face.)

You know what I like? Touch ID. I like Touch ID because it only tries to ID me when I actively ask it to. It doesn't guess when I want to check my ID -- and guess wrong -- like Face ID.

I hope this iPhone SE 2 is a real thing, because I'll be one of the first in line to get an A13 chip with a Touch ID. The XR is nice, but the Face ID is infuriating.

Labels: , , , , ,


posted by ruffin at 12/05/2019 10:36:00 AM
0 comments
Thursday, November 21, 2019

From Bloomberg:

Apple Inc. is overhauling how it tests software after a swarm of bugs marred the latest iPhone and iPad operating systems, according to people familiar with the shift.

I believe I had that.



The worst part is that anyone with any experience in these things knew testing was broken within Apple years ago. My first post on it with that tag was from 2015 (and it looks like I missed blogging about one of my favorite QA fails from that year). Worse, it wasn't limited to a single product. The QA fails were systemic.

Look, if I had a good idea testing was broken from outside of Apple over four years ago, why the heck couldn't they tell they needed to head off this disaster asap from inside the company?

The worst part is that this description from Bloomberg, if accurate, makes it sound like the push is for code-side fixes. I don't hear that they're going to hire quality QA engineers to better test -- manually and with automation -- those daily builds.

There's a push to make the daily builds testable, which they weren't before, but hiding unready code behind featuregates shows that Apple doesn't yet really understand code management or how continuous deployment is supposed to work.

Again, from Bloomberg:

Test software got so crammed with changes at different stages of development that the devices often became difficult to use. Because of this, some “testers would go days without a livable build, so they wouldn’t really have a handle on what’s working and not working,” the person said. 

I feel for them. If this happens, QA should be given the power to say that all new development must stop and the current build needs fixing. In the world of Scrum, you don't get your points (aka "say you're done with your work") until it's tested and approved. Sounds like QA isn't getting the opportunity to say no at Apple. That's broken.

The truth is that QA is exceptionally undervalued as a whole in our industry. As this disaster shows, QA testers (and quality engineers) are very nearly literally worth their weight in gold. At 200 lbs, one's worth $4.7 million at today's gold prices. Not that far from the mark for Apple.

Labels: , ,


posted by ruffin at 11/21/2019 01:05:00 PM
0 comments
Wednesday, November 06, 2019

Note to self (from SO):

document.addEventListener("DOMContentLoaded", function() {
  // code...
});

Labels: , ,


posted by ruffin at 11/06/2019 09:37:00 AM
0 comments
Friday, November 01, 2019

The new AirPods Pro sound like (har har) the first in a line of game-changing AR devices to me. If you can take external sounds and manipulate them in way that removes the "noise" (har again) and emphasize what you personally find interesting, well, that's a whole new reality. No, it's a whole new approach to reality.

You could already use the old version of AirPods (AirPods Plain?) as a part-time, special-case hearing aid and emphasize sounds you wanted to hear as if your ears where in another location. This is similar, but so much more inventive. Take the sounds you want to hear, remove the ones you don't. That's step one, and it sounds like what Transparency Mode claims to do.

The next step -- and you've got the start of this with always-on Siri -- is when your devices start adding sounds to the filtered "noises from reality". Maybe your AirPods listen for sounds your own ears can't hear like your baby whimpering. Maybe they're connected to a baby monitor and, if the sounds are as small but as important as your baby turning over onto their stomach, it unmistakably alerts you.

Think about how neat it would be to have a subaural Siri, where your AirPods can hear what you're not quite vocalizing and change how your hearing works in the world. I had a taste of that dictating a message into my Apple watch once, where in a loud situation I was able to Siri my way to a message without anyone hearing me.

And that's just Siri taking input. That's child's play in the world of audio AR. Now imagine that's a means of interacting with "superhuman" hearing -- hearing what happens in another place or hearing sounds that were subaural or simply interacting with notifications as they happen. That's powerful. That's a new interface for computing that can fundamentally change the way we live.

Does that make sense? Step one is take out the noises we don't want. It's filtering. Step two is to insert noises over what's left that make our hearing more useful. That's Augmented Reality. Augmented Reality (AR) is what's important to our future -- not Virtual Reality that's a complete escape, but Augmented Reality, the addition and layering onto (or off of) what we already experience.

And that's interesting and exciting.

Labels: , ,


posted by ruffin at 11/01/2019 12:59:00 PM
0 comments
Sunday, October 27, 2019

I've run out of iCloud space for my Apple ID. It's not the first time, but this time is one of the worst.

I continually get bombarded now with what amount to advertisements asking for me to either buy more space now or, the other button says, "Not now", implying, "Sure, I'll do this later". There is no, "I realize I'm out of space and I'll handle it on my own danged time, thanks," option. There was a time a week or two ago where, no lie, I was getting notifications about being out of space every 10-15 minutes on my iPhone.

Combine that with all the stupid in-app advertisements for Apple Music any time I want to play a few albums I purchased there and it makes the "Use Edge" pop-ups in Windows look absolutely innocuous.

This badgering is not a mistake. And it's not a technical requirement. It's a marketing strategy.

What I really dislike is that, if the reason for all these annoyance notifications are popping up isn't directly tied to pushing services revenue at Apple (I'd like to think it's not, though it probably is), the fact that these market-driven notifications aren't being called and removed sure is. The culture of the company has shifted primarily to sales and revenue over the last, oh, I don't know, eight years.

Alternatives: Package up half of someone's photos into as many two gig zip files as it takes to get things off of iCloud and into deep storage. Maybe integrate with Dropbox, OneDrive, AWS Glacier, even Backblaze to move the files over so that your day-to-day phone operation can wake back up. And to keep those files in iCloud until you can confirm you have a good copy at the archival service.

Does that make sense? iCloud is, for people who have years and years of pictures, being used in more than one way: Both as active cloud storage for today and as archival storage for years of files. You shouldn't be forced to pay premium dollars for archival storage. Apple doesn't bless us with the distinction, and wants to charge for live storage no matter how often we access something.

The manual Photos clean-up process

Instead, when I run out of space -- essentially always because of photos -- I have to log into iCloud online, as it's got the only good interface for downloading images, select hundreds of pictures, start downloading, hope the download doesn't break, check that the zip works if it does download, then store them on my box while they upload to my preferred cloud service (but could be just making copies on another hard drive), and then tell iCloud.com to delete my images...


... wait for that command to "process", select a few hundred more images, and start again. And again. Until I've take several gigs off and have space in the iCloud again.


But there are certainly other options. iOS could ask if someone can backup their iPhone to a local machine, or back up only the most important parts of your iPhone to iCloud, not every app with every piece of data they care about. Even just an "appless" backup might be useful, especially when all this crap (like your email) is likely already in the cloud. I don't know. But stop asking me to spend $12, no, $36, no $120 each year just to use my phone.

What's a synonym for extortion? 

That is, the only solution Apple provides for you when you're out of iCloud space is for you to buy more iCloud space. That's broken. That's the wrong attitude. What is that space being used for? Are there smart ways to do these things another way?

And could we maybe stop shutting down services and bombarding users with notifications until we fork over the cash? Is that you, Mr. Soprano?

The amount of space you get in iCloud is quickly becoming as painful as the iPhone 5c with 8 gigs of local storage. It's a joke, and it's gotta be creating some negative "cust sat" for Mr. Cook. (I'll ignore that Macs still ship with 128 gig hard drives, and those drives are no longer user upgradeable.) Sure, you can say $3 a month for 200 gigs ain't much, but if I have to pay at least that if I'm a "loyal" customer with a long photo history, that's borderline extortion.

Help me use my phone. Stop selling me services.

Labels: , , , ,


posted by ruffin at 10/27/2019 12:13:00 PM
0 comments

Support freedom
All posts can be accessed here:


Just the last year o' posts:

URLs I want to remember:
* Atari 2600 programming on your Mac
* joel on software (tip pt)
* Professional links: resume, github, paltry StackOverflow * Regular Expression Introduction (copy)
* The hex editor whose name I forget
* JSONLint to pretty-ify JSON
* Using CommonDialog in VB 6 * Free zip utils
* git repo mapped drive setup * Regex Tester
* Read the bits about the zone * Find column in sql server db by name
* Giant ASCII Textifier in Stick Figures (in Ivrit) * Quick intro to Javascript
* Don't [over-]sweat "micro-optimization" * Parsing str's in VB6
* .ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture); (src) * Break on a Lenovo T430: Fn+Alt+B
email if ya gotta, RSS if ya wanna RSS, (?_?), ¢, & ? if you're keypadless


Powered by Blogger etree.org Curmudgeon Gamer badge
The postings on this site are [usually] my own and do not necessarily reflect the views of any employer, past or present, or other entity.