What happens when you delete ~/.Trash folder on macOS

I have done some stupid stuff and managed to delete the ~/.Trash folder on my Mac. The result was surprising:

To fix this you can type this in the shell:

mkdir ~/.Trash

This is clearly bad user experience. Somebody not knowing how to fix this will just assume that the trash stopped working. Even worse, they might miss the message and just click on Delete.

What should macOS do in this case, is to silently restore the trash folder and keep on humming.

The new RCS standard is just better SMS, and that is a bad thing

Google has managed to herd device manufacturers and telcos to support a new standard for messaging: Rich Communication Services (RCS). In short it is “SMS, but good”, with support for images and other rich media. After gChat, Hangouts, Google+, Meet, Allo or Duo, we can be skeptical on whether it will be adopted.

But I think it will. In order to understand why, let’s look at iMessage. When iMessage launched people already had means to communicate with their families, colleagues and friends: WhatsApp, Skype, Hangouts, Facebook WeChat… SMS usage varied country by country, depending on whether the service was paid per message. RCS will be integrated just like iMessage: when you send a message to a phone which supports RCS, it will be used instead of SMS.

When iMessage was introduced I had sporadic use of SMS and I knew few people with iPhones. But as that number grew, so did the amount of blue bubbles. Feature-wise iMessage was comparable to other platforms. What makes the Messages1 app stand out is that it is present by default and cannot be deleted.

In a similar fashion, the Android Chat will replace SMS. RCS is not a new silo; it is the long needed update to an outdated system.

RCS–the messaging app killer

So many single purpose apps!

Each social network starts with an original idea but in the end the users will stay if it has a compelling messaging component. To build such a service, it is necessary to know your connections. Initially it was possible to borrow the social graph from Twitter and Facebook, but they have closed the pipes. New players piggy-back on the phone’s contact list to create a rudimentary graph of connections that is tied to telephone numbers. This is a great advantage for RCS it already has access to your contact list as it is your new default messaging service.

As people upgrade to RCS, they will discover that they do not need to have Messaging App X for “that one person”. As nobody wants to have a folder full of instant messaging apps, many will be replaced by RCS by erosion. When you replace the application you use for one person by RCS, they have one fewer reasons to use it. As times goes on, only the very large networks will survive.

What is the bad news?

When people left SMS for other services they have got encryption for free. Some services provided end-to-end encryption2, some only at the transport level. With the exception of Signal, few people choose messaging services based on privacy — many do not even know what it means for them.

With RCS, this feature will disappear and many will lose the protection provided by encryption without knowing it. When SMS was created, security was not a thing in personal communication. Anybody who got hold of your phone could suck all of its information.

But our phones did not know everything about us back then. Today, there are lot of actors that would like to get their hands on them sweet messages: criminals, governments and criminal governments.

What can be done?

Nothing. We will see how this will play out but I fear that after RCS is entrenched, many messages will be floating around in plain text. I hope that the current secure services will solve problems that RCS can not, providing a compelling reason to stay off it.


  1. The app used for iMessage and SMS, the equivalent of the future Android Chat. ↩︎

  2. End to end encrypted messages (Signal, WhatsApp, iMessage), guarantee that nobody, including the platform provider, can read your messages. ↩︎

Why Does Eventail Have So Many Settings?

In general, I do dislike applications which have many settings. It might be a little bit surprising that Eventail falls into this category. If you look at the whole un-cropped UI it might be a perfect fit for the sword-like iPhone 20.

In design, I try to abide to this rule:

“If you want to add an option, you need to decide on the default value. Once you have chosen the default, keep it and remove the option.”

With Eventail, I have broken this rule in two ways: On top of having many options, Eventail’s defaults are not even what I would recommend people to use.1

The perfect app

eventail-settings

The ultimate goal of Eventail is to be a perfect calendar widget for the masses. As I have argued in my article about e-mail clients: everybody’s definition of perfect is different. As such, I have defined two ground rules about the widget which will stay unbroken.

  • The app uses iOS calendar API, I will not be making any custom clients to manage CalDav or Exchange servers.
  • The app stays a simple and nice to look at widget. There are already a lot of great calendaring apps.

The first rule dictates limits on the features I can implement. For example there is no way I could handle Exchange categories or Google custom event colors in the current iOS (11.3).

The second rule might look at odds with many settings, but it is the widget itself that must stay simple. My stats show that most people who download Eventail launch the application once or twice. This is either because the app is crap, or because they set it up the first time they launch it and stash it in a dump folder. I choose to believe in the second case since this is how I intended people to use the app.

The complexity of settings dissipates after you have customized the widget to suit your needs. Tap and a detailed view opens, tap again and you are back. Two states is all there is. This is why the widget has (unlike other apps) a prominent preview of how will it look on top.

Behind the scenes—options that got axed

Although this means that Eventail will grow in complexity, it will not have all of them, the features.

In the 2.2 version, I have added a thin left border on the events in the default theme. Initially, this was a separate theme because I did not want to get e-mails from people unhappy with the change. In the end I have decided to risk the hit. The new theme is more iOS-like and it makes the choice of the theme simple–just decide whether titles or calendars are more important.

New icons for events were initially optional, there was a fallback to spheres for everything. Again, other applications use spheres all the time, but I wanted the invitations to be distinct. There may be more icons coming for other things. For example would it not be great to see a group of two people when there are just two people in the event?


  1. I have opted for the switch options to be all in the positive tone: “display this,” “enable that”. For the sake of consistency and discoverability, they are all turned on by default. I would recommend to hide the empty days though. ↩︎

Eventail 2.2

After two months of coding I am pleased to announce a new version of Eventail.

This release brings some new features and a lot of polish into the settings screen.

Eventail Screenshots

The app now has some more personality, the preview is more prominent and looks like a preview too!

Colors

iPhone X users will be, hopefully, delighted by the new Black theme, which gives you a pure black background.

Black Theme

There are some good news for people who organize their life by colors too. A new Vivid color scheme puts accent on the calendar color and makes it easy to navigate through the day if you do a lot of context switching.

Black Theme

Weekends can get their own color too! A subtle background color indicates where the week ends.

Black Theme

Tweaks

The events now have a bold-colored stripe on the left, to highlight the calendar they are in.

Event invitations have distinct icons, it is easy to distinguish between invitations and own events; and also to see the RSVP status at a glance.

New Day View Icons

And more

I have squashed a number of bugs, and worked on the pestering memory issues. In most cases the widget should no longer fail to load, and the troubleshooting options will resolve the rest.

Without further ado, find Eventail on the App Store

Why there is no perfect email application

I have written this thought after listening to the [Golden Cortex episode][cortex-golden], however I forgot to remove the draft tag on it so it was never published. The latest [episode on Triggers and email][cortex-triggers] had made me re-discover it.

There is no perfect e-mail application because everybody has a different definition of perfection. Bland as it might seem, this is what makes it not viable to make an application that would be considered even remotely good by everyone.

Problems such as note taking or shopping list making are simple. Even with large competition, you can take the risk and develop an application that will work in some novel way. However, e-mail is complicated, which means that the application would have to be complex. It would be costly to develop and thus would have to be expensive, reducing the pool of potential clients.

Complex problem will only have good enough1 solutions because making a perfect solution would not be economically viable.


  1. It is important to note that perfection is not additive. If your application has every feature under the sun it means it will be less than desirable to many. [cortex-golden]: https://itunes.apple.com/fr/podcast/cortex-50-golden-anniversary/id1001591696?i=1000385024209&l=en&mt=2&at=1010lIXq [cortex-triggers]: https://itunes.apple.com/fr/podcast/cortex-66-triggers-creating-behaviour-that-lasts/id1001591696?i=1000406937726&l=en&mt=2&at=1010lIXq ↩︎

Eventail 2.0

After a few months of development, I am proud to announce a new version of my iOS Calendar Widget App Eventail.

I am really happy how it turned out and invite you to try it, it is free.

Eventail Screenshots

Find Eventail on the App Store

Zoom out any page on mobile Safari

Just now I wanted to publish a new version of my app to testers. This is to be done on the iTunes connect website. I only had my phone, but according to Steve “iOS has full web experience” so this should not be a problem.

However, iTunes connect does not work exactly well on mobile. This is because the zoom level of the page is fixed on the “background” while the dialogs are in front. The result is that the button Submit is inaccessible.

After some amount of looking for a solution I have found several bookmarklets that should unlock the zoom. None of them worked. So I made my own solution.

Bookmark this page in Safari and change its URL to this:

javascript:(function(){document.getElementsByTagName("body")[0].style.zoom =0.5;})()

Then, on a page which has elements outside of the viewport, launch the bookmark from the bookmark menu. The page will be twice as small, but more of it will fit!

10 Laws of UX, digested

After reading the article on 10 Laws of UX and the associated comments on Hacker News, I have decided to try a shot at reformatting the page to a more legible format. In the form of a blog post—because I do not think that every idea on the internet needs its own domain.

I provide my own interpretation of the laws in the titles, they are not in the original article.

1. If a function needs to be accessed often and/or quickly, make the button big

The time to acquire a target is a function of the distance to and size of the target.

– Paul Fitts

Fitt’s Law on Wikipedia

2. Only provide choices when a good default does not exist

The time it takes to make a decision increases with the number and complexity of choices.

– William Edmund Hick and Ray Hyman

Hick’s Law on Wikipedia

3. Respect the platform’s conventions and interface guidelines

Users spend most of their time on other sites. This means that users prefer your site to work the same way as all the other sites they already know.

– Jakob Nielsen

Jakob’s Law on Nielsen Norman Group

4. Do not stuff too much detail into a small space

Law of Prägnanz:

People will perceive and interpret ambiguous or complex images as the simplest form possible, because it is the interpretation that requires the least cognitive effort of us.

The Laws of Figure/Ground, Prägnanz, Closure, and Common Fate on Interaction Design

5. Put actions that do similar things together

Law of Proximity:

Objects that are near, or proximate to each other, tend to be grouped together.

N.B.: The recent missile alert fiasco is a good example of why this is important

6. Reduce the number of things your users have to remember

The average person can only keep 7 (plus or minus 2) items in their working memory.

– George Miller

Miller’s Law on Wikipedia

7. Make tasks short, simple and with set deadlines

Any task will inflate until all of the available time is spent.

– Cyril Northcote Parkinson

Parkinson’s Law on Wikipedia

8. Put the important things at the beginning or at the end

Users have a propensity to best remember the first and last items in a series.

Serial Position Effect on Wikipedia

9. Removing features from your product may result in users not being able to achieve some goals

Tesler’s Law, also known as The Law of Conservation of Complexity, states that for any system there is a certain amount of complexity which cannot be reduced.

Larry Tesler’s Law of Conservation of Complexity on Wikipedia

10. Make the most important thing stand out

The Von Restorff effect, also known as The Isolation Effect, predicts that when multiple similar objects are present, the one that differs from the rest is most likely to be remembered.

Von Restorff Effect on Wikipedia

Alternatively: If you want to stand out from competition, find a feature which is always the same and make it different.

Bonus law

Do not put a leading zero on numbers that do not have to be aligned.

How I discovered how iOS calendar app chooses colors by digging into the icloud.com JavaScript code

While making an iOS calendar app, I needed to find out how Apple calculates background and text colors when displaying events in the Calendar app. I wanted to use the same algorithm for my display, in order to integrate well with the OS.

colors-iphone

I was hoping for some displayBacgkroundColor property on EKCalendar object or something similar, but it does not seem to exist. I almost decided to start approximating their algorithm while I realized one thing—the icloud.com site has a calendar “application”. The event colors there seem to follow the same logic as those in the Calendar app. They are not strictly the same but it could provide some insight into how the color switching works.

colors-icloud

iCloud.com being a modern web application, it runs a bunch of client side JavaScript. Since it is possible to add events, change calendar colors and so on, I assumed that the code which calculates the text and background colors for events must be somewhere in the publicly available JavaScript. There was a small possibility that they pre-computed the colors on the server but this seemed unlikely.

Homing in on the function

I decided to use Safari for this. It seems kind of appropriate and I am quite familiar with its web developer tools. I open the iCloud website, open the calendar page and look into what kind of JavaScript files are loaded.

Among others, this page loads mainly two big JavaScript files, both called javascript-packed.js. The other files are all called javascript-strings so I assume these load the localization files or something like that.

My first hypothesis was that the function is called when a color of an already present event is changed. This might happen when, for example, the calendar to which the event belongs is changed.

Luckily Safari can display minified JavaScript files in indented mode. However searching for functions for strings like “update” gave me too many results.

Looking at the HTML code of an event, I can observe that its color is inside an inline style attribute and that it changes when I change the calendar. This is good news, as the inspector lets me to put a breakpoint on that.

In the inspector I find the event element, right click on the div in the tree structure and set a breakpoint on Attribute Modified

set-breakpoint

Now I click on the event in the app, change the calendar… and voilà.

callstack

Walking up the call stack

At the very end of the call stack I can see that a property is assigned a value of #a66110. This is actually the text color for this calendar (I checked this in the inspector). My hope is that it will be calculated somewhere up the stack.

1 style: Assign #a66110 as as the color property of a CSS declaration
2 anonymous: Function that that applies the CSS style when called
3 access: This function is a sort of trampoline
4 css: This function generates the anonymous function in step 2, it does get the #a66110 as a parameter 'n'
5 updateProperty: calls the css function, and also gets the #a66110 as a parameter 'r'

When I get to the 6th level I finally hit something interesting. This is where the value #a66110 is created (inside a function called _update:

6 _update: This function seems to update the whole event, its title, state and most importantly, colors
p = t.get("textColor")

The get method seems to be doing something akin to the following (I did not really dig into this):

object.prototype.get = function(propName) {
	return call(this, this.properties[propName]);
}

I any case, the color value is derived from object t and its property textColor.

Finding the ‘Event’ object

I look into the t object’s properties and see this:

A cursory glance at the properties confirms to me that this is an ‘Event’ object. It has the usual ones such as owner, isAccepted and so on.

Intuitively this line then, in the _update function, does what we were searching for: Get the text color for an event and change the elements css representation to reflect that.

p = t.get("textColor"), this.updateProperty(n, o, ".text", p, "color")

I dig into this textColor property. I search the JavaScript file for it and look over the few results I get.

Within a piece of code which starts with Cal.Collection = CoreCal.Collection.extend({ I find this (among the other event properties):

textColor: function() {
    if (this.get("isBirthdayCalendar")) return "#4d5765";
    var e = this.get("_hexColor");
    return Cal.colorController.computeTextColor(e)
}.property("_hexColor").cacheable(),
backgroundColor: function() {
    if (this.get("isBirthdayCalendar")) return "#cfd3d9";
    var e = this.get("_hexColor");
    return Cal.colorController.computeBackgroundColor(e)
}.property("_hexColor").cacheable(),

Cal.colorController.computeTextColor(e) and Cal.colorController.computeBackgroundColor(e) and in both cases e is a color… finally I have struck gold!

Compute text and background color

The function iCloud uses to compute the text color is this:

computeTextColor: function(r) {
    r || CW.fatalError("Cannot provide a text color without a starting color"), r = r.toLowerCase();
    var i = this.get("specialColors"),
        s = SC.convertHexToHsv(r),
        o = s[0],
        u = s[1],
        a = s[2],
        f;
    if (i.isLight(o, u, a)) f = i.light.text;
    else if (i.isDark(o, u, a)) f = i.dark.text;
    else {
        var l = a - n,
            c = i.isGray(o, u, a),
            h = c ? 0 : Math.max(u, e);
        f = SC.convertHsvToHex(o, h, Math.max(l, t))
    }
    return f
},

By putting a breakpoint on the f = ... line I can deduce the constants n = 0.35, e = 0.5 and t = 0.3.

As for the background color:

computeBackgroundColor: function(e) {
    if (!e) {
        SC.error("Cannot provide a background color without a starting color");
        return
    }
    e = e.toLowerCase();
    var t = this.get("specialColors"),
        n = t.backgrounds[e];
    if (n) return n;
    var r = SC.convertHexToHsv(e),
        i = r[0],
        o = r[1],
        u = r[2];
    if (t.isLight(i, o, u)) n = t.light.background;
    else if (t.isDark(i, o, u)) n = t.dark.background;
    else {
        var a = t.isGray(i, o, u) ? 0 : s;
        n = SC.convertHsvToHex(i, a, t.bgBrightness(i, o, u))
    }
    return n
},

And the contents of the specialColors are here (comments are from my investigation of constants):

specialColors: {
    light: {
        main: "#dddddd",
        text: "#a8a8a8",
        background: "#f8f8f8"
    },
    dark: {
        main: "#1a1a1a",
        text: "#000000",
        background: "#bababa"
    },
    titles: {
        "#cc73e1": "#b14bc9",
        "#65db39": "#49bf1f",
        "#ffcc00": "#e0ac00",
        "#ff9500": "#ff7f00"
    },
    backgrounds: {
        "#a2845e": "#e0d3c1"
    },
    isLight: function(e, t, n) {
        var s = Cal.colorController;
        return n > r && t < i // r = 0.6, i = 0.1
    },
    isDark: function(e, n, r) {
        return r < t // t = 0.3
    },
    isGray: function(e, t, n) {
        return t < i // i = 0.1
    },
    bgBrightness: function(e, t, n) {
        return Math.min(n * 1.5, o)
    }
},

Then there is a bunch of color conversion functions that work in a pretty standard way.

  • convertHsvToHex takes a R, G, B triplet and returns a [H, S, V]
  • convertHexToHsv takes an array of three elements [H, S, V] and returns an array [R, G, B]
  • two regexes to check if a colors is in the HTML rgb(r, g, b) or #RRGGBB format
  • parseColor which converts either of the two formats and returns a #RRGGBB representation
  • expandColor which takes the #RRGGBB color and returns an array of [R, G, B]
  • toColorPart which takes a number in 0-255 range and transforms it to hex representation

I am not going to detail these functions here, but I have uploaded them here if you would like to look:

Original Apple Conversion Functions

Rewriting the functions into more human readable format

These functions were of course at least partly generated and minified. So I have rewritten them into a digestible format.

First, I have written a colorTools object which has the properties from the specialColors object on iCloud.

const colorTools = {
  light: {
    text: "#a8a8a8",
    background: "#f8f8f8"
  },
  dark: {
    text: "#000000",
    background: "#bababa"
  },
  backgrounds: {
    "#a2845e": "#e0d3c1"
  },
  isLight: function(h, s, v) {
    return v > 0.6 && s < 0.1
  },
  isDark: function(h, s, v) {
    return v < 0.3
  },
  isGray: function(h, s, v) {
    return s < 0.1
  },
  bgBrightness: function(h, s, v) {
    return Math.min(v * 1.5, 1)
  },
  /* color space conversion removed for brevity */

This object is then used as a helper for the two functions I was actually searching for in the beginning.

Text Color

If the color is too light or too dark then use a pre-computed text color. Otherwise cap the saturation to 0.5 (for grayish colors just set it at 0) and reduce the value by 0.35.

function computeTextColor(rgb) {
  const [h, s, v] = colorTools.convertHexToHsv(rgb);  
  if (colorTools.isLight(h, s, v)) {
    return colorTools.light.text;
  } else if (colorTools.isDark(h, s, v)) {
    return colorTools.dark.text;
  } else {
    const rs = colorTools.isGray(h, s, v) ? 0 : Math.max(s, 0.5);
    const rv = v - 0.35;
    return colorTools.convertHsvToHex(h, rs, Math.max(rv, 0.3))
  }
}

Background color

In a similar fashion, use a pre-computed colors if the background is too light or dark. Set the saturation to 0 for grayish colors.

function computeBackgroundColor(rgb) {
  if (colorTools.backgrounds[rgb]) {
    return colorTools.backgrounds[rgb];
  }
  const [h, s, v] = colorTools.convertHexToHsv(rgb);
  if (colorTools.isLight(h, s, v)) {
    return colorTools.light.background;
  } else if (colorTools.isDark(h, s, v)) {
    return colorTools.dark.background;
  } else {
    let rs = colorTools.isGray(h, s, v) ? 0 : s;
    return colorTools.convertHsvToHex(h, rs, colorTools.bgBrightness(h, s, v));
  } 
}

This was fun. Now, the colors on iCloud website are not strictly the same than in the iOS app, but that requires just a bit of tweaking with the constants.

You can download the complete JavaScript file I have extracted here:

Apple Calendar Colors

Some final words

There you have it. In the end I was actually surprised that it was pretty easy to extract these functions from the code. I expected the minified/obfuscated code to be quite hard to read. Interestingly, most function names did not get mangled. I suppose this is due to the fact that they are called through strings in some part of the framework Apple uses.

Of course, later I have converted these functions into Swift UIColor extensions so I can use them in my application.

I might publish them here sometime later, but if you are interested ask me on Twitter.

Thoughts about spam notifications in iOS

I try to keep my notifications minimal. Only those which are important should get through.

But when I receive notifications like this my blood boils:

Spam notification from Prime Video, for an episode I have already watched.

Amazon is not the sole culprit. When I received several notifications from The Fork I have confronted them on Twitter. They have promised to do something about it, and then a few days later I was spammed again… with the same notification.

The Fork is guilty too

Although this is forbidden by Apple App Store Review Guidelines, Design section 4.5.3, big firms do not care.

I think the main problem comes from the fact that these cost nothing and are invisible to apple. I think we should be able to report these to Apple, in the same way it is done for spam e-mail.

Where to put the report button?

Putting a button on each notification would be an overkill, it would be easy to hit it with a fat thumb.

It could go into the slide over or 3D touch menu in the notification center. But the problem is that if a notification pops up while the phone is unlocked, and you tap it by reflex, it gets a free pass. These are maddening.

My idea would be to put a section inside the Privacy tab. This view would display last few notifications you have received, with an option to report them to Apple and an option to ignore notifications with the same text from this application. At first, this would not have much impact, but over time Apple could use this data to pressure App publishers and maybe even start training Siri to act as a spam filter.