Tags: session

49

sparkline

Sunday, March 6th, 2022

A bug with progressive web apps on iOS

Dave recently wrote some good advice about what to do—and what not to do—when it comes to complaining about web browsers. I wrote something on this topic a little while back:

If there’s something about a web browser that you’re not happy with (or, indeed, if there’s something you’re really happy with), take the time to write it down and publish it

To summarise Dave’s advice, avoid conspiracy theories and snark; stick to specifics instead.

It’s very good advice that I should heed (especially the bit about avoiding snark). In that spirit, I’d like to document what I think is a bug on iOS.

I don’t need to name the specific browser, because there is basically only one browser allowed on iOS. That’s not snark; that’s a statement of fact.

This bug involves navigating from a progressive web app that has been installed on your home screen to an external web view.

To illustrate the bug, I’ll use the example of The Session. If you want to recreate the bug, you’ll need to have an account on The Session. Let me know if you want to set up a temporary account—I can take care of deleting it afterwards.

Here are the steps:

  1. Navigate to thesession.org in Safari on an iOS device.
  2. Add the site to your home screen.
  3. Open the installed site from your home screen—it will launch in standalone mode.
  4. Log in with your username and password.
  5. Using the site menu, navigate to the links section of the site.
  6. Click on any external link.
  7. After the external link opens in a web view, tap on “Done” to close the web view.

Expected behaviour: you are returned to the page you were on with no change of state.

Actual behaviour: you are returned to the page you were on but you are logged out.

So the act of visiting an external link in a web view while in a progressive web app in standalone mode seems to cause a loss of cookie-based authentication.

This isn’t permanent. Clicking on any internal link restores the logged-in state.

It is surprising though. My mental model for opening an external link in a web view is that it sits “above” the progressive web app, which remains in stasis “behind” it. But the page must actually be reloading, either when the web view is opened or when the web view is closed. And that reload is behaving like a fetch event without credentials.

Anyway, that’s my bug report. It may already be listed somewhere on the WebKit Bugzilla but I lack the deductive skills to find it. I’m not even sure if that’s the right place for this kind of bug. It might be specific to the operating system rather than the rendering engine.

This isn’t a high priority bug, but it is one of those cumulatively annoying software paper cuts.

Hope this helps!

Tuesday, January 18th, 2022

Installing progressive web apps

I don’t know about you, but it seems like everyone I follow on Twitter is playing Wordle. Although I don’t play the game myself, I think it’s pretty great.

Not only does Wordle have a very sweet backstory, but it’s also unashamedly on the web. If you want to play, you go to the URL powerlanguage.co.uk/wordle. That’s it. No need to download an app.

That hasn’t stopped some nefarious developers trying to trick people into downloading their clones of Wordle from app stores. App stores, which are meant to be curated and safe, are in fact filled with dodgy knock-offs and scams. Contrary to popular belief, the web is quite literally a safer bet.

Wordle has a web app manifest, which means you can add it to your home screen and it will behave just like a native app (although I don’t believe it has offline support). That’s great, but the process of adding a web app to your home screen on iOS is ludicrously long-winded.

Macworld published an article detailing how to get the real Wordle app on your iPhone or iPad. On the one hand it’s great to see this knowledge being spread. On the other hand it’s dispiriting that it’s even necessary to tell people that they can do this, like it’s a hidden nerdy secret just for power users.

At this point I’ve pretty much given up on Apple ever doing anything about this pathetic situation. So what can I do instead?

Well, taking my cue from that Macworld article, the least I can do is inform people how they can add a progressive web app to their home screen.

That’s what I’ve done on thesession.org. I’ve published a page on how to install The Session to your home screen.

On both Android and iPhone the journey to installing a progressive web app begins with incomprehensible iconography. On Android you must first tap on the unlabeled kebab icon—three vertical dots. On iOS you must first tap on the unlabeled share icon—a square with an arrow coming out of it.

The menu icon on Android. The share icon on iOS.

When it comes to mobile operating systems, consumer choice means you choose which kind of mystery meat to eat.

I’ve included screenshots to help people identify these mysterious portals. For iOS I’ve also included a video to illustrate the quest to find the secret menu item buried beneath the share icon.

I’ve linked to the page with the installation instructions from the site’s “help” page and the home page.

Handy tip: when you’re adding a start_url value to your web app manifest, it’s common to include a query string like this:

start_url: "/?homescreen"

I’m guessing most people to that so they can get analytics on how many people are starting from an icon tap. I don’t do analytics on The Session but I’m still using that query string in my start_url. On the home page of the site, I check for the existence of the query string. If it exists, I don’t show the link to the installation page. So once someone has installed the site to their home screen, they shouldn’t see that message when they launch The Session.

If you’ve got a progressive web app, it might be worth making a page with installation instructions rather than relying on browsers to proactively inform your site’s visitors. You’d still need to figure out the right time and place to point people to that page, but at least the design challenge would be in your hands.

Should you decide to take a leaf out of the Android and iOS playbooks and use mystery meat navigation to link to such a page, there’s an emoji you could potentially use: 📲

It’s still worse than using actual words, but it might be better than some random combination of dots, squares and arrows.

(I’m not really serious about using that emoji, but if you do, be sure to use a sensible aria-label value on the enclosing a element.)

Wednesday, January 12th, 2022

Media queries with display-mode

It’s said that the best way to learn about something is to teach it. I certainly found that to be true when I was writing the web.dev course on responsive design.

I felt fairly confident about some of the topics, but I felt somewhat out of my depth when it came to some of the newer modern additions to browsers. The last few modules in particular were unexplored areas for me, with topics like screen configurations and media features. I learned a lot about those topics by writing about them.

Best of all, I got to put my new-found knowledge to use! Here’s how…

The Session is a progressive web app. If you add it to the home screen of your mobile device, then when you launch the site by tapping on its icon, it behaves just like a native app.

In the web app manifest file for The Session, the display-mode property is set to “standalone.” That means it will launch without any browser chrome: no address bar and no back button. It’s up to me to provide the functionality that the browser usually takes care of.

So I added a back button in the navigation interface. It only appears on small screens.

Do you see the assumption I made?

I figured that the back button was most necessary in the situation where the site had been added to the home screen. That only happens on mobile devices, right?

Nope. If you’re using Chrome or Edge on a desktop device, you will be actively encourged to “install” The Session. If you do that, then just as on mobile, the site will behave like a standalone native app and launch without any browser chrome.

So desktop users who install the progressive web app don’t get any back button (because in my CSS I declare that the back button in the interface should only appear on small screens).

I was alerted to this issue on The Session:

It downloaded for me but there’s a bug, Jeremy - there doesn’t seem to be a way to go back.

Luckily, this happened as I was writing the module on media features. I knew exactly how to solve this problem because now I knew about the existence of the display-mode media feature. It allows you to write media queries that match the possible values of display-mode in a web app manifest:

.goback {
  display: none;
}
@media (display-mode: standalone) {
  .goback {
    display: inline;
  }
}

Now the back button shows up if you “install” The Session, regardless of whether that’s on mobile or desktop.

Previously I made the mistake of inferring whether or not to show the back button based on screen size. But the display-mode media feature allowed me to test the actual condition I cared about: is this user navigating in standalone mode?

If I hadn’t been writing about media features, I don’t think I would’ve been able to solve the problem. It’s a really good feeling when you’ve just learned something new, and then you immediately find exactly the right use case for it!

Sunday, October 31st, 2021

Four days

I had an extra long weekend recently. It was four days of being a culture vulture. It was also four days of ever-increasing risk assessment.

It began on Thursday morning with the first Salter Cane band practice in eighteen months. That was pretty safe—three of us in a room, reminding ourselves of how the songs go. I honestly thought it could’ve been a disaster and that I wouldn’t remember anything, but thanks to a little bit of last-minute revision the evening before, it actually went really well. And boy, did it feel good to plug in and play those songs again.

Later that day, Jessica went up to London. We spent that evening in the Royal Opera House, watching a ballet, The Dante Project. We wore masks. Not everyone else did.

Checked in at Royal Opera House. Ballet time! — with Jessica

The next day, the indoor gatherings continued. We went to the IMAX to see Dune. The opportunity was too good to pass up. It was wonderful! But again, while we wore masks for the duration, not everyone else did.

Checked in at ODEON BFI IMAX for Dune: The IMAX 2D Experience. D U N E — with Jessica

Still, I reckon the ventilation was reasonably good in both the Royal Opera House and the BFI’s IMAX cinema. But that evening we checked into the Clayton Crown Hotel in Cricklewood, venue for the Return To London Town festival of Irish traditional music.

Checked in at Clayton Crown Hotel. Return To London Town 🎶🎻 — with Jessica Checked in at Clayton Crown Hotel. Matt Molloy and Sean Keane 🎶 — with Jessica Checked in at Clayton Crown Hotel. Afternoon session 🎶☘️

That’s where we spent two days going to concerts, sessions, and workshops, all of them indoors. The music was great, and we had a lovely time, but I couldn’t help but feel a sense of nervousness throughout.

When we got back to Brighton, we both took lateral flow tests—thank goodness that these are freely available! We were both negative. We had dodged a viral bullet.

That was the last trip out of town we’ll be making for a while. But even for Brighton-based activities, this is the routine now: weigh up the risks, decide whether an activity is worth it, and if so, testing afterwards.

For example, the week after our trip to London, the Wednesday evening Irish music session at The Jolly Brewer pub started up again here in Brighton. It was one of the things I missed most during The Situation.

I wrote about this at the very start of the first lockdown:

I’m quite certain that one positive outcome of The Situation will be a new-found appreciation for activities we don’t have to do. I’m looking forward to sitting in a pub with a friend or two, or going to see a band, or a play or a film, and just thinking “this is nice.”

I certainly did find myself thinking “this is nice” during the session, which was as wonderful as I had remembered. But I was also thinking about ventilation, and distancing, and airflow. Like I said:

Risks. Benefits. Running the numbers. Making decisions. Trying to do the right thing. Trying to stay safe but also trying to live life.

Friday, July 9th, 2021

Thursday, June 3rd, 2021

Two decades of thesession.org

On June 3rd, 2001, I launched thesession.org. Happy twentieth birthday to The Session!

Although actually The Session predates its domain name by a few years. It originally launched as a subdirectory here on adactio.com with the unwieldly URL /session/session.shtml

A screenshot of the first version of The Session

That incarnation was more like a blog. I’d post the sheetmusic for a tune every week with a little bit of commentary. That worked fine until I started to run out of tunes. That’s when I made the site dynamic. People could sign up to become members of The Session. Then they could also post tunes and add comments.

A screenshot of the second version of The Session

That’s the version that is two decades old today.

The last really big change to the site happened in 2012. As well as a complete redesign, I introduced lots of new functionality.

A screenshot of the current version of The Session

In all of those incarnations, the layout was fluid …long before responsive design swept the web. That was quite unusual twenty years ago, but I knew it was the webby thing to do.

What’s also unusual is just keeping a website going for twenty years. Keeping a community website going for twenty years is practically unheard of. I’m very proud of The Session. Although, really, I’m just the caretaker. The site would literally be nothing without all the contributions that people have made.

I’ve more or less adopted a Wikipedia model for contributions. Some things, like tune settings, can only be edited by the person who submitted it But other things, like the track listing of a recording, or the details of a session, can be edited by any member of the site. And of course anyone can add a comment to any listing. There’s a certain amount of risk to that, but after testing it for two decades, it’s working out very nicely.

What’s really nice is when I get to meet my fellow members of The Session in meatspace. If I’m travelling somewhere and there’s a local session happening, I always get a warm welcome. I mean, presumably everyone would get a warm welcome at those sessions, but I’ve also had my fair share of free pints thanks to The Session.

I feel a great sense of responsibility with The Session. But it’s not a weight of responsibility—the way that many open source maintainers describe how their unpaid labour feels. The sense of responsibility I feel drives me. It gives me a sense of purpose.

The Session is older than any client work I’ve ever done. It’s older than any books I’ve written. It’s even older than Clearleft by a few years. Heck, it’s even older than this blog (just).

I’m 50 years old now. The Session is 20 years old. That’s quite a chunk of my life. I think it’s fair to say that it’s part of me now. Of all the things I’ve made so far in my life, The Session is the one I’m proudest of.

I’m looking forward to stewarding the site through the next twenty years.

Friday, January 1st, 2021

2020

In 2020, I didn’t have the honour and privilege of speaking at An Event Apart in places like Seattle, Boston, and Minneapolis. I didn’t experience that rush that comes from sharing ideas with a roomful of people, getting them excited, making them laugh, sparking thoughts. I didn’t enjoy the wonderful and stimulating conversations with my peers that happen in the corridors, or over lunch, or at an after-party. I didn’t have a blast catching up with old friends or making new ones.

But the States wasn’t the only country I didn’t travel to. Closer to home, I didn’t have the opportunity to take the Eurostar and connecting trains to cities like Cologne, Lisbon, and Stockholm. I didn’t sample the food and drink of different countries.

In the summer, I didn’t travel to the west coast of Ireland for the second in year in a row for the annual Willie Clancy festival of traditional Irish music. I didn’t spend each day completely surrounded by music. I didn’t play in some great sessions. I didn’t hear some fantastic and inspiring musicians.

Back here in Brighton, I didn’t go to the session in The Jolly Brewer every Wednesday evening and get lost in the tunes. I didn’t experience that wonderful feeling of making music together and having a pint or two. And every second Sunday afternoon, I didn’t pop along to The Bugle for more jigs and reels.

I didn’t walk into work most days, arrive at the Clearleft studio, and make a nice cup of coffee while chit-chatting with my co-workers. I didn’t get pulled into fascinating conversations about design and development that spontaneously bubble up when you’re in the same space as talented folks.

Every few months, I didn’t get a haircut.

Throughout the year, I didn’t make any weekend trips back to Ireland to visit my mother.

2020 gave me a lot of free time. I used that time to not write a book. And with all that extra time on my hands, I read fewer books than I had read in 2019. Oh, and on the side, I didn’t learn a new programming language. I didn’t discover an enthusiasm for exercise. I didn’t get out of the house and go for a brisk walk on most days. I didn’t start each day prepping my sourdough.

But I did stay at home, thereby slowing the spread of a deadly infectious disease. I’m proud of that.

I did play mandolin. I did talk to my co-workers through a screen. I did eat very well—and very local and seasonal. I did watch lots of television programmes and films. I got by. Sometimes I even took pleasure in this newly-enforced lifestyle.

I made it through 2020. And so did you. That’s an achievement worth celebrating—congratulations!

Let’s see what 2021 doesn’t bring.

Tuesday, December 22nd, 2020

SVGs in dark mode

I added a dark mode to my site last year. Since then I’ve been idly toying with the addition of a dark mode to The Session too.

As with this site, the key to adding a dark mode was switching to custom properties for color and background-color declarations. But my plans kept getting derailed by the sheet music on the site. The sheet music is delivered as SVG generated by ABCJS which hard-codes the colour in stroke and fill attributes:

fill="#000000" stroke="#000000"

When I was describing CSS recently I mentioned the high specifity of inline styles:

Whereas external CSS and embedded CSS don’t have any effect on specificity, inline styles are positively radioactive with specificity.

Given that harsh fact of life, I figured it would be nigh-on impossible to over-ride the colour of the sheetmusic. But then I realised I was an idiot.

The stroke and fill attributes in SVG are presentational but they aren’t inline styles. They’re attributes. They have no affect on specifity. I can easily over-ride them in an external style sheet.

In fact, if I had actually remembered what I wrote when I was adding a dark mode to adactio.com, I could’ve saved myself some time:

I have SVGs of sparklines on my homepage. The SVG has a hard-coded colour value in the stroke attribute of the path element that draws the sparkline. Fortunately, this can be over-ridden in the style sheet:

svg.activity-sparkline path {
  stroke: var(--text-color);
}

I was able to do something similar on The Session. I used the handy currentColor keyword in CSS so that the sheet music matched the colour of the text:

svg path {
  fill: currentColor;
}
svg path:not(stroke="none") {
  stroke: currentColor;
}

Et voila! I now had light-on-dark sheet music for The Session’s dark mode all wrapped up in a prefers-color-scheme: dark media query.

I pushed out out the new feature and started getting feedback. It could be best summarised as “Thanks. I hate it.”

It turns out that while people were perfectly fine with a dark mode that inverts the colours of text, it felt really weird and icky to do the same with sheet music.

On the one hand, this seems odd. After all, sheet music is a writing system like any other. If you’re fine with light text on a dark background, why doesn’t that hold for light sheet music on a dark background?

But on the other hand, sheet music is also like an image. And we don’t invert the colours of our images when we add a dark mode to our CSS.

With that in mind, I went back to the drawing board and this time treated the sheet music SVGs as being intrinsicly dark-on-light, rather than a stylistic choice. It meant a few more CSS rules, but I’m happy with the final result. You can see it in action by visiting a tune page and toggling your device’s “appearance” settings between light and dark.

If you’re a member of The Session, I also added a toggle switch to your member profile so you can choose dark or light mode regardless of your device settings.

Monday, December 21st, 2020

Web Audio API weirdness on iOS

I told you about how I’m using the Web Audio API on The Session to generate synthesised audio of each tune setting. I also said:

Except for some weirdness on iOS that I had to fix.

Here’s that weirdness…

Let me start by saying that this isn’t anything to do with requiring a user interaction (the Web Audio API insists on some kind of user interaction to prevent developers from having auto-playing sound on websites). All of my code related to the Web Audio API is inside a click event handler. This is a different kind of weirdness.

First of all, I noticed that if you pressed play on the audio player when your iOS device is on mute, then you don’t hear any audio. Seems logical, right? Except if using the same device, still set to mute, you press play on a video or audio element, the sound plays just fine. You can confirm this by going to Huffduffer and pressing play on any of the audio elements there, even when your iOS device is set on mute.

So it seems that iOS has different criteria for the Web Audio API than it does for audio or video. Except it isn’t quite that straightforward.

On some pages of The Session, as well as the audio player for tunes (using the Web Audio API) there are also embedded YouTube videos (using the video element). Press play on the audio player; no sound. Press play on the YouTube video; you get sound. Now go back to the audio player and suddenly you do get sound!

It’s almost like playing a video or audio element “kicks” the browser into realising it should be playing the sound from the Web Audio API too.

This was happening on iOS devices set to mute, but I was also getting reports of it happening on devices with the sound on. But it’s that annoyingly intermittent kind of bug that’s really hard to reproduce consistently. Sometimes the sound doesn’t play. Sometimes it does.

Following my theory that the browser needs a “kick” to get into the right frame of mind for the Web Audio API, I resorted to a messy little hack.

In the event handler for the audio player, I generate the “kick” by playing a second of silence using the JavaScript equivalent of the audio element:

var audio = new Audio('1-second-of-silence.mp3');
audio.play();

I’m not proud of that. It’s so hacky that I’ve even wrapped the code in some user-agent sniffing on the server, and I never do user-agent sniffing!

Still, if you ever find yourself getting weird but inconsistent behaviour on iOS using the Web Audio API, this nasty little hack could help.

Wednesday, December 16th, 2020

Audio

I spent the last couple of weekends rolling out a new feature on The Session. It involves playing audio in a web page. No big deal these days, right? But the history involves some old file formats…

The first venerable format is ABC notation. File extension: .abc, mime type: text/vnd.abc. It’s an ingenious text format for musical notation using ASCII. The metadata of the piece of music is defined in JSON-like key/value pairs. Then the contents are encoded with letters: A, B, C, etc. Uppercase and lowercase denote different octaves. Numbers can be used for note lengths.

The format was created by Chris Walshaw in 1997 when dial-up was the norm. With ABC, people were able to swap tunes on email lists or bulletin boards without transferring weighty image or sound files. If you had ABC software on your computer, you could convert that lightweight text file into sheet music …or audio.

That brings me to the second old format: midi files. File extension: .mid, mime-type: audio/midi. Like ABC, it’s a lightweight format for encoding the instructions for music instead of the music itself.

Think of it like SVG: instead of storing the final pixels of an image, SVG stores the instructions for drawing the image instead. The instructions in a midi file are like “play this note for this long on this instrument.” Again, as with ABC, you need some software to turn the instructions into sound.

There was a time when lots of software could play midi files. Quicktime on the Mac, for example. You could even embed midi files in web pages. I mean literally embed them …with the embed element. No Geocities page was complete without an autoplaying midi file.

On The Session, people submit tunes in ABC format. Then, using the amazing ABCJS JavaScript library, the ABC is turned into SVG on the fly! For years I’ve also offered midi files, generated on the server from the ABC notation.

But times have changed. These days it’s hard to find software that plays midi files. Quicktime doesn’t do it anymore. And you’d need to go to the app store on iOS to find a midi file player. It’s time to phase out the midi files on The Session.

I still want to provide automatically-generated audio though. Fortunately ABCJS gives me a way to do this. But instead of using the old technology of midi files, it uses a more modern browser feature: the Web Audio API.

The end result sounds like a midi file, but the underlying technique is more like a synthesiser. There’s a separate mp3 file for each note. The JavaScript figures out how long each “sample” needs to be played for, strings them all together, and outputs them with Web Audio. So you’ve got cutting-edge browser technology recreating a much older file format. Paul Rosen—the creator of ABCJS—has a presentation explaining how it all works under the hood.

Not only is there a separate short mp3 file for each note in seven octaves, but if you want the sound of a different instrument, you need samples for all seven octaves in that instrument. They’re called soundfonts.

Paul provides soundfonts for ABCJS. It’s a repo that was forked from this repo from Benjamin Gleitzman. And here’s where it gets small worldy…

The reason why Benjamin has a repo of soundfonts is because he needed to create midi-like audio in the browser. He wanted to do this for a project on September 28th and 29th, 2013 …at Science Hack Day San Francisco!

I was there too—working on my own audio-related hack—and I remember the excellent (and winning) hack that Benjamin worked on. It was called Symphony of Satellites and it’s still online along with the promo video. Here’s Benjamin’s post-hackday write-up from seven years ago.

It’s rare that the worlds of the web and Irish music cross over. When I got to meet Paul—creator of ABCJS—at a web conference a couple of years ago it kind of blew my mind. Last weekend when I set out to dabble with a feature on The Session, I certainly didn’t expect to stumble on a connection to Science Hack Day! (Aside: the first Science Hack Day was ten years ago—yowzers!)

Anyway, I was able to get that audio playback working on The Session. Except for some weirdness on iOS that I had to fix. But that’s a hack for another day.

Wednesday, October 14th, 2020

Saving forms

I added a long-overdue enhancement to The Session recently. Here’s the scenario…

You’re on a web page with a comment form. You type your well-considered thoughts into a textarea field. But then something happens. Maybe you accidentally navigate away from the page or maybe your network connection goes down right when you try to submit the form.

This is a textbook case for storing data locally on the user’s device …at least until it has safely been transmitted to the server. So that’s what I set about doing.

My first decision was choosing how to store the data locally. There are multiple APIs available: sessionStorage, IndexedDB, localStorage. It was clear that sessionStorage wasn’t right for this particular use case: I needed the data to be saved across browser sessions. So it was down to IndexedDB or localStorage. IndexedDB is the more versatile and powerful—because it’s asynchronous—but localStorage is nice and straightforward so I decided on that. I’m not sure if that was the right decision though.

Alright, so I’m going to store the contents of a form in localStorage. It accepts key/value pairs. I’ll make the key the current URL. The value will be the contents of that textarea. I can store other form fields too. Even though localStorage technically only stores one value, that value can be a JSON object so in reality you can store multiple values with one key (just remember to parse the JSON when you retrieve it).

Now I know what I’m going to store (the textarea contents) and how I’m going to store it (localStorage). The next question is when should I do it?

I could play it safe and store the comment whenever the user presses a key within the textarea. But that seems like overkill. It would be more efficient to only save when the user leaves the current page for any reason.

Alright then, I’ll use the unload event. No! Bad Jeremy! If I use that then the browser can’t reliably add the current page to the cache it uses for faster back-forwards navigations. The page life cycle is complicated.

So beforeunload then? Well, maybe. But modern browsers also support a pagehide event that looks like a better option.

In either case, just adding a listener for the event could screw up the caching of the page for back-forwards navigations. I should only listen for the event if I know that I need to store the contents of the textarea. And in order to know if the user has interacted with the textarea, I’m back to listening for key presses again.

But wait a minute! I don’t have to listen for every key press. If the user has typed anything, that’s enough for me. I only need to listen for the first key press in the textarea.

Handily, addEventListener accepts an object of options. One of those options is called “once”. If I set that to true, then the event listener is only fired once.

So I set up a cascade of event listeners. If the user types anything into the textarea, that fires an event listener (just once) that then adds the event listener for when the page is unloaded—and that’s when the textarea contents are put into localStorage.

I’ve abstracted my code into a gist. Here’s what it does:

  1. Cut the mustard. If this browser doesn’t support localStorage, bail out.
  2. Set the localStorage key to be the current URL.
  3. If there’s already an entry for the current URL, update the textarea with the value in localStorage.
  4. Write a function to store the contents of the textarea in localStorage but don’t call the function yet.
  5. The first time that a key is pressed inside the textarea, start listening for the page being unloaded.
  6. When the page is being unloaded, invoke that function that stores the contents of the textarea in localStorage.
  7. When the form is submitted, remove the entry in localStorage for the current URL.

That last step isn’t something I’m doing on The Session. Instead I’m relying on getting something back from the server to indicate that the form was successfully submitted. If you can do something like that, I’d recommend that instead of listening to the form submission event. After all, something could still go wrong between the form being submitted and the data being received by the server.

Still, this bit of code is better than nothing. Remember, it’s intended as an enhancement. You should be able to drop it into any project and improve the user experience a little bit. Ideally, no one will ever notice it’s there—it’s the kind of enhancement that only kicks in when something goes wrong. A little smidgen of resilient web design. A defensive enhancement.

Sunday, August 9th, 2020

Dream speak

I had a double-whammy of a stress dream during the week.

I dreamt I was at a conference where I was supposed to be speaking, but I wasn’t prepared, and I wasn’t where I was supposed to be when I was supposed to be there. Worse, my band were supposed to be playing a gig on the other side of town at the same time. Not only was I panicking about getting myself and my musical equipment to the venue on time, I was also freaking out because I couldn’t remember any of the songs.

You don’t have to be Sigmund freaking Freud to figure out the meanings behind these kinds of dreams. But usually these kind of stress dreams are triggered by some upcoming event like, say, oh, I don’t know, speaking at a conference or playing a gig.

I felt really resentful when I woke up from this dream in a panic in the middle of the night. Instead of being a topical nightmare, I basically had the equivalent of one of those dreams where you’re back at school and it’s the day of the exam and you haven’t prepared. But! When, as an adult, you awake from that dream, you have that glorious moment of remembering “Wait! I’m not in school anymore! Hallelujah!” Whereas with my double-booked stress dream, I got all the stress of the nightmare, plus the waking realisation that “Ah, shit. There are no more conferences. Or gigs.”

I miss them.

Mind you, there is talk of re-entering the practice room at some point in the near future. Playing gigs is still a long way off, but at least I could play music with other people.

Actually, I got to play music with other people this weekend. The music wasn’t Salter Cane, it was traditional Irish music. We gathered in a park, and played together while still keeping our distance. Jessica has written about it in her latest journal entry:

It wasn’t quite a session, but it was the next best thing, and it was certainly the best we’re going to get for some time. And next week, weather permitting, we’ll go back and do it again. The cautious return of something vaguely resembling “normality”, buoying us through the hot days of a very strange summer.

No chance of travelling to speak at a conference though. On the plus side, my carbon footprint has never been lighter.

Online conferences continue. They’re not the same, but they can still be really worthwhile in their own way.

I’ll be speaking at An Event Apart: Front-end Focus on Monday, August 17th (and I’m very excited to see Ire’s talk). I’ll be banging on about design principles for the web:

Designing and developing on the web can feel like a never-ending crusade against the unknown. Design principles are one way of unifying your team to better fight this battle. But as well as the design principles specific to your product or service, there are core principles underpinning the very fabric of the World Wide Web itself. Together, we’ll dive into applying these design principles to build websites that are resilient, performant, accessible, and beautiful.

Tickets are $350 but I can get you a discount. Use the code AEAJER to get $50 off.

I wonder if I’ll have online-appropriate stress dreams in the next week? “My internet is down!”, “I got the date and time wrong!”, “I’m not wearing any trousers!”

Actually, that’s pretty much just my waking life these days.

Sunday, June 28th, 2020

Hemimastigophora

Probably fewer than a hundred people in the world have seen what you’re looking at right now.

Jessica and I were taking turns at the microscope when we were told that.

Let me back up a bit and explain how we found ourselves in this this situation…

It all started with The Session, the traditional Irish music community site that I run. There’s a big focus on getting together and playing music—something that’s taken a big hit during this global pandemic. Three sections of the website are devoted to face-to-face gatherings: events (like concerts and festivals), sessions, and the most recent addition, trips.

The idea with trips is that you input somewhere you’re going to be travelling to, along with the dates you’ll be there. It’s like a hyper-focused version of Dopplr. The site then shows you if any events are happening, if there are any sessions on, and also if there are any members of the site in that locality (if those members have added their location to their profiles).

Last August, I added the trips I would be taking in the States. There’s be a trip to Saint Augustine to hang out with Jessica’s family, a trip to Chicago to speak at An Event Apart, and a trip to New York for a couple of days because that’s where the ocean liner was going to deposit us after our transatlantic crossing.

A fellow member of The Session named Aaron who is based in New York saw my trip and contacted me to let me know about the session he goes to (he plays tin whistle). Alas, that session didn’t coincide with our short trip. But he also added:

I work at the American Museum of Natural History, and if you have time and interest, I can provide you with vouchers for tickets to as many special exhibits and such as you’d like!

Ooh, that sounded like fun! He also said:

In fact I could give you a quick behind-the-scenes tour if you’re interested.

Jessica and I didn’t have any set plans for our time in New York, so we said why not?

That’s how we ended spending a lovely afternoon being shown around the parts of the museum that the public don’t usually get to see. It’s quite the collection of curiosities back there!

There’s also plenty of research. Aaron’s particular area was looking into an entirely different kingdom of life—neither animal, nor plant, nor fungus. Remarkably, these microscopic creatures were first identified—by a classmate of Aaron’s—by happenstance in 2016:

The hemimastigotes analyzed by the Dalhousie team were found by Eglit during a spring hike with some other students along the Bluff Wilderness Trail outside Halifax a couple of years ago. She often has empty sample vials in her pockets or bags, and scooped a few tablespoons of dirt into one of them from the side of the trail.

That’s like a doctor announcing that they’d come across a hitherto-unknown limb on the human body. The findings were published in the paper, Hemimastigophora is a novel supra-kingdom-level lineage of eukaryotes in 2018.

In the “backstage” area of the American Museum of Natural History, Aaron had samples of them. He put them under the microscope for us. As we took turns looking at them wriggling their flagella, Aaron said:

Probably fewer than a hundred people in the world have seen what you’re looking at right now.

Saturday, May 30th, 2020

Programming CSS to perform Sass colour functions

I wrote recently about moving away from Sass to using native CSS features. I had this to say on the topic of mixins in Sass:

These can be very useful, but now there’s a lot that you can do just in CSS with calc(). The built-in darken() and lighten() mixins are handy though when it comes to colours.

I know we will be getting these in the future but we’re not there yet with CSS.

Anyway, I had all this in the back of my mind when I was reading Lea’s excellent feature in this month’s Increment: A user’s guide to CSS variables. She’s written about a really clever technique of combining custom properites with hsl() colour values for creating colour palettes. (See also: Una’s post on dynamic colour theming with pure CSS.)

As so often happens when I’m reading something written by Lea—or seeing her give a talk—light bulbs started popping over my head (my usual response to Lea’s knowledge bombs is either “I didn’t know you could do that!” or “I never thought of doing that!”).

I immediately set about implementing this technique over on The Session. The trick here is to use separate custom properties for the hue, saturation, and lightness parts of hsl() colour values. Then, when you want to lighten or darken the colour—say, on hover—you can update the lightness part.

I’ve made a Codepen to show what I’m doing.

Let’s say I’m styling a button element. I make custom propertes for hsl() values:

button {
  --button-colour-hue: 19;
  --button-colour-saturation: 82%;
  --button-colour-lightness: 38%;
  background-color: hsl(
    var(--button-colour-hue),
    var(--button-colour-saturation),
    var(--button-colour-lightness)
  );
}

For my buttons, I want the borders to be slightly darker than the background colour. When I was using Sass, I used the darken() function to this. Now I use calc(). Here’s how I make the borders 10% darker:

border-color: hsl(
  var(--button-colour-hue),
  var(--button-colour-saturation),
  calc(var(--button-colour-lightness) - 10%)
);

That calc() function is substracting a percentage from a percentage: 38% minus 10% in this case. The borders will have a lightness of 28%.

I make the bottom border even darker and the top border lighter to give a feeling of depth.

On The Session there’s a “cancel” button style that’s deep red.

Here’s how I set its colour:

.cancel {
  --button-colour-hue: 0;
  --button-colour-saturation: 100%;
  --button-colour-lightness: 40%;
}

That’s it. The existing button declarations take care of assigning the right shades for the border colours.

Here’s another example. Site admins see buttons for some actions only available to them. I want those buttons to have their own colour:

.admin {
  --button-colour-hue: 45;
  --button-colour-saturation: 100%;
  --button-colour-lightness: 40%;
}

You get the idea. It doesn’t matter how many differently-coloured buttons I create, the effect of darkening or lightening their borders is all taken care of.

So it turns out that the lighten() and darken() functions from Sass are available to us in CSS by using a combination of custom properties, hsl(), and calc().

I’m also using this combination to lighten or darken background and border colours on :hover. You can poke around the Codepen if you want to see that in action.

I love seeing the combinatorial power of these different bits of CSS coming together. It really is a remarkably powerful programming language.

Wednesday, May 13th, 2020

Sass and clamp

CSS got some pretty nifty features recently. There’s the min() and max() functions. If you use them for, say, width you can use one rule where previously you would’ve needed to use two (a width declaration followed by either min-width or max-width). But they can also be applied to font-size! That’s very nifty—we’ve never had min-font-size or max-font-size properties.

There’s also the clamp() function. That allows you to set a minimum size, a default size, and a maximum size. Again, it can be used for lengths, like width, or for font-size.

Over on thesession.org, I’ve had some media queries in place for a while now that would increase the font-size for larger screens. It’s nothing crucial, just a nice-to-have so that on wide screens, the font is bumped up accordingly. I realised I could replace all those media queries with one clamp() statement, thanks to the vw (viewport width) unit:

font-size: clamp(1rem, 1.333vw, 1.5rem);

By default, the font-size is 1.333vw (1.333% of the viewport width), but it will never get smaller than 1rem and it will never get larger than 1.5rem.

That works, but there’s a bit of an issue with using raw vw units like that. If someone is on a wide screen and they try to adjust the font size, nothing will happen. The viewport width doesn’t change when you bump the font size up or down.

The solution is to mix in some kind of unit that does respond to the font size being bumped up or down (like, say, the rem unit). Handily, clamp() allows you to combine units, just like calc(). So I can do this:

font-size: clamp(1rem, 0.5rem + 0.666vw, 1.5rem);

The result is much the same as my previous rule, but now—thanks to the presence of that 0.5rem value—the font size responds to being adjusted by the user.

You could use a full 1rem in that default value:

font-size: clamp(1rem, 1rem + 0.333vw, 1.5rem);

…but if you do that, the minimum size (1rem) will never be reached—the default value will always be larger. So in effect it’s no different than saying:

font-size: min(1.rem + 0.333vw, 1.5rem);

I mentioned this to Chris just the other day.

Anyway, I got the result I wanted. I wanted the font size to stay at the browser default size (usually 16 pixels) until the screen was larger than around 1200 pixels. From there, the font size gets gradually bigger, until it hits one and a half times the browser default (which would be 24 pixels if the default size started at 16). I decided to apply it to the :root element (which is html) using percentages:

:root {
  font-size: clamp(100%, 50% + 0.666vw, 150%);
}

(My thinking goes like this: if we take a screen width of 1200 pixels, then 1vw would be 12 pixels: 1200 divided by 100. So for a font size of 16 pixels, that would be 1.333vw. But because I’m combining it with half of the default font size—50% of 16 pixels = 8 pixels—I need to cut the vw value in half as well: 50% of 1.333vw = 0.666vw.)

So I’ve got the CSS rule I want. I dropped it in to the top of my file and…

I got an error.

There was nothing wrong with my CSS. The problem was that I was dropping it into a Sass file (.scss).

Perhaps I am showing my age. Do people even use Sass any more? I hear that post-processors usurped Sass’s dominance (although no-one’s ever been able to explain to me why they’re different to pre-processers like Sass; they both process something you’ve written into something else). Or maybe everyone’s just writing their CSS in JS now. I hear that’s a thing.

The Session is a looooong-term project so I’m very hesitant to use any technology that won’t stand the test of time. When I added Sass into the mix, back in—I think—2012 or so, I wasn’t sure whether it was the right thing to do, from a long-term perspective. But it did offer some useful functionality so I went ahead and used it.

Now, eight years later, it was having a hard time dealing with the new clamp() function. Specifically, it didn’t like the values being calculated through the addition of multiple units. I think it was clashing with Sass’s in-built ability to add units together.

I started to ask myself whether I should still be using Sass. I looked at which features I was using…

Variables. Well, now we’ve got CSS custom properties, which are even more powerful than Sass variables because they can be updated in real time. Sass variables are like const. CSS custom properties are like let.

Mixins. These can be very useful, but now there’s a lot that you can do just in CSS with calc(). The built-in darken() and lighten() mixins are handy though when it comes to colours.

Nesting. I’ve never been a fan. I know it can make the source files look tidier but I find it can sometimes obfuscate what you’re final selectors are going to look like. So this wasn’t something I was using much any way.

Multiple files. Ah! This is the thing I would miss most. Having separate .scss files for separate interface elements is very handy!

But globbing a bunch of separate .scss files into one .css file isn’t really a Sass task. That’s what build tools are for. In fact, that’s what I was already doing with my JavaScript files; I write them as individual .js files that then get concatenated into one .js file using Grunt.

(Yes, this project uses Grunt. I told you I was showing my age. But, you know what? It works. Though seeing as I’m mostly using it for concatenation, I could probably replace it with a makefile. If I’m going to use old technology, I might as well go all the way.)

I swapped out Sass variables for CSS custom properties, mixins for calc(), and removed what little nesting I was doing. Then I stripped the Sass parts out of my Grunt file and replaced them with some concatenation and minification tasks. All of this makes no difference to the actual website, but it means I’ve got one less dependency …and I can use clamp()!

Remember a little while back when I was making a dark mode for my site? I made this observation:

Let’s just take a moment here to pause and reflect on the fact that we can now use CSS to create all sorts of effects that previously required a graphic design tool like Photoshop.

It feels like something similar has happened with tools like Sass. Sass was the hare. CSS is the tortoise. Sass blazed the trail, but now native CSS can achieve much the same result.

It’s like when we used to need something like jQuery to do DOM Scripting succinctly using CSS selectors. Then we got things like querySelector() in JavaScript so we no longer needed the trailblazer.

I’ve said it before and I’ll say it again, the goal of any good library should be to get so successful as to make itself redundant. That is, the ideas and functionality provided by the tool are so useful and widely adopted that the native technologies—HTML, CSS, and JavaScript—take their cue from those tools.

You could argue that this is what happened with Flash. It certainly happened with jQuery and Sass. I’m pretty sure we’ll see the same cycle play out with frameworks like React.

Monday, April 20th, 2020

geoTrad - Google My Maps

Well, this is a rather wonderful mashup made with data from thesession.org:

The distribution of Irish traditional tunes which reference place names in Ireland

Saturday, April 4th, 2020

A bit of Blarney

I don’t talk that much on here about my life’s work. Contrary to appearances, my life’s work is not banging on about semantic markup, progressive enhancement, and service workers.

No, my life’s work is connected to Irish traditional music. Not as a musician, I hasten to clarify—while I derive enormous pleasure from playing tunes on my mandolin, that’s more of a release than a vocation.

My real legacy, it turns out, is being the creator and caretaker of The Session, an online community and archive dedicated to Irish traditional music. I might occassionally mention it here, but only when it’s related to performance, accessibility, or some other front-end aspect. I’ve never really talked about the history, meaning, and purpose of The Session.

Well, if you’re at all interested in that side of my life, you can now listen to me blather on about it for over an hour, thanks to the Blarney Pilgrims podcast.

I’ve been huffduffing episodes of this podcast for quite a while now. It’s really quite excellent. If you’re at all interested in Irish traditional music, the interviews with the likes of Kevin Burke, John Carty, Liz Carroll and Catherine McEvoy are hard to beat.

So imagine my surprise when they contacted me to ask me to chat and play some tunes! It really was an honour.

I was also a bit of guinea pig. Normally they’d record these kinds of intimate interviews face to face, but what with The Situation and all, my chat was the first remotely recorded episode.

I’ve been on my fair share of podcasts—most recently the Design Systems Podcast—but this one was quite different. Instead of talking about my work on the web, this focussed on what I was doing before the web came along. So if you don’t want to hear me talking about my childhood, give this a miss.

But if you’re interested in hearing my reminisce and discuss the origin and evolution of The Session, have a listen. The chat is interspersed with some badly-played tunes from me on the mandolin, but don’t let that put you off.

Thursday, March 19th, 2020

Nice

Yesterday was Wednesday. Wednesday evening is when I play in an Irish trad session at The Jolly Brewer. It’s a highlight of my week.

Needless to say, there was no session yesterday. I’ll still keep playing tunes while we’re all socially distancing, but it’s not quite the same. I concur with this comment:

COVID-19 has really made me realize that we need to be grateful for the people and activities we take for granted. Things like going out for food, seeing friends, going to the gym, etc., are fun, but are not essential for (physical) survival.

It reminds of Brian Eno’s definition of art: art is anything we don’t have to do. It’s the same with social activities. We don’t have to go to concerts—we can listen to music at home. We don’t have to go the cinema—we can watch films at home. We don’t have to go to conferences—we can read books and blog posts at home. We don’t have to go out to restaurants—all our nutritional needs can be met at home.

But it’s not the same though, is it?

I think about the book Station Eleven a lot. The obvious reason why I’d be thinking about it is that it describes a deadly global pandemic. But that’s not it. Even before The Situation, Station Eleven was on my mind for helping provide clarity on the big questions of life; y’know, the “what’s it all about?” questions like “what’s the meaning of life?”

Part of the reason I think about Station Eleven is its refreshingly humanist take on a post-apocalyptic society. As I discussed on this podcast episode a few years back:

It’s interesting to see a push-back against the idea that if society is removed we are going to revert to life being nasty, brutish and short. Things aren’t good after this pandemic wipes out civilisation, but people are trying to put things back together and get along and rebuild.

Related to that, Station Eleven describes a group of people in a post-pandemic world travelling around performing Shakespeare plays. At first I thought this was a ridiculous conceit. Then I realised that this was the whole point. We don’t have to watch Shakespeare to survive. But there’s a difference between surviving and living.

I’m quite certain that one positive outcome of The Situation will be a new-found appreciation for activities we don’t have to do. I’m looking forward to sitting in a pub with a friend or two, or going to see a band, or a play or a film, and just thinking “this is nice.”

Monday, March 9th, 2020

41 hours in Galway

It was my birthday recently. I’m a firm believer in the idea that birthday celebrations should last for more than 24 hours. A week is the absolute minimum.

For the day itself, I did indeed indulge in a most luxurious evening out with Jessica at The Little Fish Market in Hove (on the street where we used to live!). The chef, Duncan Ray, is an absolute genius and his love for all things fish-related shines through in his magnificent dishes.

But to keep the celebrations going, we also went on a weekend away to Galway, where I used to live decades ago. It was a quick trip but we packed in a lot. I joked at one point that it felt like one of those travel articles headlined with “36 hours in someplace.” I ran the numbers and it turned out we were in Galway for 41 hours, but I still thought it would be fun to recount events in the imperative style of one of those articles…

A surprisingly sunny day in Galway.

Saturday, February 29th

The 3:30pm train from Dublin will get you into Galway just before 6pm. The train station is right on the doorstep of Loam, the Michelin-starred restaurant where you’ve made your reservation. Enjoy a seven course menu of local and seasonal produce. Despite the quality of the dishes, you may find the overall experience is a little cool, and the service a touch over-rehearsed.

You’ll be released sometime between 8:30pm and 9pm. Stroll through Eyre Square and down Shop Street to the Jury’s Inn, your hotel. It’s nothing luxurious but it’s functional and the location is perfect. It’s close to everything without being in the middle of the noisy weekend action. The only noise you should hear is the rushing of the incredibly fast Corrib river outside your window.

Around 9:30pm, pop ‘round to Dominick Street to enjoy a cocktail in the America Village Apothecary. It’s only open two nights a week, and it’s a showcase of botanicals gathered in Connemara. Have them make you a tasty conconction and then spend time playing guess-that-smell with their specimen jars.

By 10:30pm you should be on your way round the corner to The Crane Bar on Sea Road. Go in the side entrance and head straight upstairs where the music session will be just getting started. Marvel at how chilled out it is for a Saturday night, order a pint, and sit and listen to some lovely jigs’n’reels. Don’t forget to occassionally pester one of the musicians by asking “What was that last tune called? Lovely set!”

Checked in at The Crane Bar. Great tunes! 🎻🎶 — with Jessica

Sunday, March 1st

Skip the hotel breakfast. Instead, get your day started with a coffee from Coffeewerk + Press. Get that coffee to go and walk over to Ard Bia at Nimmos, right at the Spanish Arch. Get there before it opens at 10am. There will already be a line. Once you’re in, order one of the grand brunch options and a nice big pot of tea. The black pudding hash will set you up nicely.

Checked in at Ard Bia at Nimmos. Black pudding hash and a pot of tea — with Jessica

While the weather is far clearer and sunnier than you were expecting, take the opportunity to walk off that hearty brunch with a stroll along the sea front. That’ll blow out the cobwebs.

Galway bay. Galway bay.

When the cold gets too much, head back towards town and duck into Charlie Byrne’s, the independent bookshop. Spend some time in there browsing the shelves and don’t leave without buying something to remember it by.

By 1pm or so, it’s time for some lunch. This is the perfect opportunity to try the sushi at Wa Cafe near the harbour. They have an extensive range of irrestistable nigiri, so just go ahead and get one of everything. The standouts are the local oyster, mackerel, and salmon.

Checked in at wa cafe. Sushi — with Jessica

From there, head to Tigh Cóilí for the 2pm session. Have a Guinness and enjoy the tunes.

Checked in at Tigh Cóilí. Afternoon session — with Jessica

Spend the rest of the afternoon strolling around town. You can walk through the market at St. Nicholas Church, and check out the little Claddagh ring museum at Thomas Dillon’s—the place where you got your wedding rings at the close of the twentieth century.

Return the ring from whence it came!

If you need a pick-me-up, get another coffee from Coffeewerk + Press, but this time grab a spot at the window upstairs so you can watch the world go by outside.

By 6pm, you’ll have a hankering for some more seafood. Head over to Hooked on Henry Street. Order a plate of oysters, and a cup of seafood chowder. If they’ve got ceviche, try that too.

Checked in at Hooked. with Jessica

Walk back along the canal and stop in to The Salt House to sample a flight of beers from Galway Bay Brewery. There’ll probably be some live music.

Checked in at The Salt House. 🍺 — with Jessica

With your appetite suitably whetted, head on over to Cava Bodega for some classic tapas. Be sure to have the scallops with black pudding.

Checked in at Cava Bodega. Scallops with black pudding — with Jessica

The evening session at Tigh Cóilí starts at 8:30pm on a Sunday so you can probably still catch it. You’ll hear some top-class playing from the likes of Mick Conneely and friends.

Checked in at Tigh Cóilí. 🎶🎻 — with Jessica

And when that’s done, there’s still time to catch the session over at The Crane.

Checked in at The Crane Bar. 🎶🎻 — with Jessica

Monday, March 2nd

After a nice lie-in, check out of the hotel and head to McCambridge’s on Shop Street for some breakfast upstairs. A nice bowl of porridge will set you up nicely for the journey back to Dublin.

If you catch the 11am train, you’ll arrive in Dublin by 1:30pm—just enough time to stop off in The Winding Stair for some excellent lunch before heading on to the airport.

Checked in at The Winding Stair. Lunch in Dublin — with Jessica

Getting there

Aer Lingus flies daily from Gatwick to Dublin. Dublin’s Heuston Station has multiple trains per day going to Galway.

Wednesday, November 27th, 2019

Accessibility on The Session revisited

Earlier this year, I wrote about an accessibility issue I was having on The Session. Specifically, it was an issue with Ajax and pagination. But I managed to sort it out, and the lesson was very clear:

As is so often the case, the issue was with me trying to be too clever with ARIA, and the solution was to ease up on adding so many ARIA attributes.

Well, fast forward to the past few weeks, when I was contacted by one of the screen-reader users on The Session. There was, once again, a problem with the Ajax pagination, specifically with VoiceOver on iOS. The first page of results were read out just fine, but subsequent pages were not only never announced, the content was completely unavailable. The first page of results would’ve been included in the initial HTML, but the subsequent pages of results are injected with JavaScript (if JavaScript is available—otherwise it’s regular full-page refreshes all the way).

This pagination pattern shows up all over the site: lists of what’s new, search results, and more. I turned on VoiceOver and I was able to reproduce the problem straight away.

I started pulling apart my JavaScript looking for the problem. Was it something to do with how I was handling focus? I just couldn’t figure it out. And other parts of the site that used Ajax didn’t seem to be having the same problem. I was mystified.

Finally, I tracked down the problem, and it wasn’t in the JavaScript at all.

Wherever the pagination pattern appears, there are “previous” and “next” links, marked up with the appropriate rel="prev" and rel="next" attributes. Well, apparently past me thought it would be clever to add some ARIA attributes in there too. My thinking must’ve been something like this:

  • Those links control the area of the page with the search results.
  • That area of the page has an ID of “results”.
  • I should add aria-controls="results" to those links.

That was the problem …which is kind of weird, because VoiceOver isn’t supposed to have any support for aria-controls. Anyway, once I removed that attribute from the links, everything worked just fine.

Just as the solution last time was to remove the aria-atomic attribute on the updated area, the solution this time was to remove the aria-controls attribute on the links that trigger the update. Maybe this time I’ll learn my lesson: don’t mess with ARIA attributes you don’t understand.