Thursday, January 07, 2021

Fallback Fonts in XAML

I have to admit that I feel foolish for not knowing about this before. Let me tell you about the power of fallback fonts in XAML so you can avoid the ignorance I lived in for so long. I'm doing this in UWP code but the same applies to WPF.

Let's say you want the following displayed (with XAML) as simply as possible.

I [filled heart] You [smiley face] [red lips] [music notes]

The "I" and the "You" are rendered with the "Segoe Script" font.

The "filled heart", "smiley face", and "music notes" are from the "Segoe MDL2 Assets" font.

The "red lips" are the "kiss mark" emoji.


Off the top of your head, how do you render this?

You might put multiple TextBlocks next to each other (probably in a Horizontally-oriented StackPanel) and assign different FontFamily values to each one.

Or, you might use multiple Runs within a single TextBlock and assign different FontFamily values to each one.


But, let me tell you there's a way to do this with a single TextBlock and no runs!

The XAML looks like this:

<TextBlock>I &#xEB52; You &#xED54; 💋 &#xE189;</TextBlock>

To get the script font I set the FontFamily to "Segoe Script" and I get this:

I [unrendered character] You [unrendered character] [red lips] [unrendered character]
Note that the red lips / kiss mark are rendered as a glyph even though they are not in the "Segoe Script" font. This is due to a built-in feature of XAML text rendering. The underlying text rendering system is doing a lot of work to try and avoid try and display all the characters. When the specified character isn't in the defined font it tries to use a value from the `ContentControlThemeFontFamily` resource. This is a list of fonts that it looks in for something to display the appropriate glyph. By default, this includes "Segoe UI" and "Segoe UI Emoji". It's the second of these that means that emojis are rendered in most places without any extra effort needed.

So, we're part of the way there by specifying a single FontFamily.

FontFamily="Segoe Script"

But what about the glyphs from the "Segoe MDL2 Assets" font?
Well, just as in CSS it's possible to specify multiple fonts/typefaces to use as a fallback, the same is possible with XAML.
Why I never thought of this before I don't know.

FontFamily="Segoe Script,Segoe MDL2 Assets"

In my head, I always thought of the HTML/CSS specification of multiple fonts as applying all or nothing to the text. If the first specified font is available use that for all the text, if it's not available use the next font, etc.

I don't know if HTML/CSS rendering is cleverer than that (please comment below, if you know) but the XAML text renderer is smarter than just using the same font for everything. It uses the specified list (family) of fonts on a character by character basis.

If it can't render a character in the first defined font, it will look to the next in the family and if none provide the character it will look to the ContentControlThemeFontFamily resource. Only if none of these fonts have a glyph for the character do you get the placeholder for a missing glyph.

This is a simplified explanation as the real solution considered different languages too. Don't ever think displaying text is simple. ;)



I discovered all this after going down a rabbit hole investigating how to avoid characters not displaying. I started by looking at intercepting what is displayed on the screen and then checking all the characters there are in the font family specified. (I didn't even consider multiple font-family values.) That lead me to investigate how to detect binding changes which led me down a side path looking at how the `x:bind` markup extension creates and reflects binding changes.

I wanted to know when any text in an app isn't rendered correctly so that the app could be updated to support the fonts/glyphs it needed so users can always see the characters they expect.

Now knowing about fallback fonts, I think a simpler solution is to make sure I include multiple fonts in the default FontFamily used within apps.



Thursday, December 31, 2020

Validate everything - why I no longer trust my supermarket

photo of a receipt highlighting entry: EASY PEELERS LSE 88.297 kg @ £1.95/kg  £172.18

The above is from the receipt when visiting a supermarket earlier in the year.
Yes, it's an entry for 88.297 KiloGrams of loose fruit at £1.95 per KG, coming to a total of £172.18!

That's almost 200 pounds and the weight of a grown adult!
It's also (to the best of my memory) more than I've ever spent in a supermarket at one time.


tldr1: Don't blindly trust ML/AI algorithms without adding extra data validation.
tldr2: Double check your receipts!


So, how did this happen?

During the lockdown, I made my once-a-week trip to the supermarket for essentials.

I used the "Smart Shop" app on my phone to scan and pack items as I shopped.
There are several possible benefits to using the app, but, to me, the biggest was reduced time with a cashier when we should be doing "social distancing."

The following is an explanation of a specific issue I've observed with the app.
I'm realistic enough to know the app won't be perfect, especially with my high standards.
There are some other issues that I regularly encounter (most times, I use the app), but I don't think they are worth exploring here. (If you're interested, these include the "scan" button disappearing and not allowing me to scan other products, and the scan button being unresponsive, leading me to press it again, which leads to the scan mode briefly being displayed before automatically closing again.)

I was doing my weekly shop, navigating my way around the store, and got to the confectionary aisle. I was choosing some treats for my children and saw something I thought my wife would like. I picked it up, scanned it. I wasn't paying attention to the app but heard the positive "beep" of a successful scan and put it in the trolley without looking at the screen.
A few minutes later, I noticed the running balance was significantly larger than what was reasonable based on my trolley. I was only shopping once a week, so spending a bit more than I had in the past, but this was way beyond anything reasonable.
I was able to scroll back through the list of everything I'd purchased and found the rogue entry. Based on the items before and after it in the list, I worked out what item was missing based on what I'd put in my trolley and where the erroneous entry had come from.
Cautiously I tried scanning the item for my wife again, and this time it showed up correctly. 
I tried removing the incorrect entry, but the app wouldn't let me without scanning it again--which was obviously impossible.
When I had gathered all my shopping and was ready to pay, I found a staff member to explain what had happened. They tried to remove the entry but required managerial approval because of the high price of the item. (Aside: why is managerial approval needed to void an item that doesn't require managerial approval to purchase?)
The item was eventually removed, and I paid for my shopping. Nobody in the store was interested in finding out more about how or why this had happened, and so I went home. (I'll save my rant on front-line customer service workers who aren't interested in escalating technical issues for another time.)

This experience weighed on my mind and left me with two big questions.
  1. How did this item end up on my list?
  2. How is this item even possible?
In reverse order.

How is this item possible?

The entry is for a product the weight of a grown adult.
The scales in the store don't support that much.
It's not physically possible to balance that many items on the scales.
How is this deemed valid?
It's a tremendous amount, so it should have raised an exception.
Why is there no default flagging or limits on the weight and total price of an individual item?
There was a check when removing the item, so why not on adding it?
Perhaps there's an implied check based on what the scales can measure, so a check wasn't deemed necessary. If the scales/printer can't print a barcode label for an item that heavy or expensive, why is it necessary to validate the value?
As we shall see, the system is based on the fact that it relies on accurate scanning, and my experience is that what is in a barcode and what the software reads can be different.


The troublesome item has what I'll refer to as a 'self-weighed barcode.' (It's a few years since I worked in a supermarket, and I don't know the terminology they use.)
These self-weighed barcodes contain two parts: an identifier for the product and the price of this particular item. This is how the correct price is charged without creating an entirely unique barcode for every weighed item.
This system is at risk of abuse, but that abuse is presumably deemed acceptable. If you've ever heard of someone weighing and printing the label for a small handful of grapes and then sticking the label on a bag containing a much larger number, you'll understand the risk. But, most people are honest, and so the amount lost by the supermarkets in this way is less than they save by not having to have everything preweighed or paying for staff to weigh them.

So, hopefully, you can see what the system thought I'd scanned, but...


How did this item end up on my list?

Or, as a bigger, more general question, how did a different item from what I'd scanned show up on my list?


Firstly, let's consider if this matters.

If it's a different product.

The store's inventory management will be out. They may think they've sold more (or less) of products. They probably allow for small discrepancies, so this may not be a problem. Whether 200 pounds of fruit counts as a small discrepancy, I can't say.

For the individual making the purchase, there are two potential consequences. Firstly they will have no proof of purchase of the product they put in their trolley/bag and so won't be able to return it or get assistance/replacement if there's a problem. The second consequence is if they are asked to verify that their shopping contents match their receipt. This may be a security check or a random check applied to all users of self-scanning systems. At best, this might require the rescanning of all items or, at worst, may be seen as an attempt at shoplifting. Good luck proving the issue was with their software if you're accused by them of shoplifting.

What if this affects an age-restricted product?
Customers may have to get their purchases checked unnecessarily. But, potentially more concerning is people buying age-restricted products without appropriate verifications being made. If my discovery has found a flaw in the supermarket's software, could this be exploited to purchase age-restricted products by people below that age? What are the legal implications of this?

If the prices are different.

If the price charged is less than it should be:
  • The store will lose money and may try and accuse you of deliberately underpaying. It might be hard for them to prove guilt, but it'll be harder for you to prove innocence. At any rate, it has the potential to be an unwelcome and stressful exchange. Again, a small discrepancy may not be worth the company's effort to change their systems to address.
  • If you're undercharged, then you gain. Bonus.

But if the price is higher than it should be...
  • The company won't mind. For them, the occasional over or undercharged item will probably cancel out.
  • Legislators might be interested, but I don't know enough about this area to say for sure.
  • For the individual, this could be a big deal. If, as in my case, it's a large difference, this could have a big impact on a person's finances. A large discrepancy is likely to be spotted, but multiple smaller differences might not be. What if you were regularly being overcharged?

How many times has this happened before, but the price difference wasn't large enough for me to notice? - It's a worrying thought.


So, what's happening when the wrong product/price show up?

Let's start by looking at the process as a whole. Here's how it's supposed to work.
  1. I scan the product with the app on my phone.
  2. The app decodes the image to identify a barcode.
  3. The data (barcode value) is sent to the server.
  4. The server responds with the product and price.
  5. Repeat for each product.
  6. At the end, the device/app sends all data to the till.
  7. I pay at the till. - Done.

The possible causes of the issue are either in the app, on the server, or in communication between them.
  • If it's a communication issue, it could be one of data corruption or a man-in-the-middle attack causing data corruption. I don't think either of these is the cause of the issue I encountered.
  • It could be that the server sent the response to the wrong device. Perhaps somewhere else, someone did scan the product that showed up on my device, and the messages got mixed up. Perhaps someone else scanned all that fruit and was only charged for some chocolate. This again seems extremely unlikely.
  • Perhaps the server was misconfigured, and it thought the barcode for the chocolate was the expensive fruit. That I scanned the chocolate again and it showed up correctly suggests this isn't the case. Of course, an invalid server configuration could have been corrected between the two times I scanned, but I think this is very unlikely.
I think the problem was with the software in the app that "reads" the barcode.
The app is running on my phone. My phone doesn't have a dedicated barcode scanner, but it does have a camera. The app uses the camera to take an image (well, it probably scans frames of video) and looks for a barcode image from which it can extract the value. (Years of building software that prints and scans barcodes have taught me a thing or two about how this works ;)

I think the "barcode scanner" software extracted the wrong value from the image/barcode, and it just happened to be something that the server considered valid.

I think this is what happened because when using the app, I regularly (it probably happens every other week) "scan" an item and receive an error message that the item is not recognized. I can "scan" the barcode again, and it goes through correctly. Is it that the barcodes are added to the system between the times I scan them? Or, is it more likely that the app sends invalid data because it "misread" the barcode?
Based on my knowledge of barcodes and image recognition, I think the app is "reading" the barcodes incorrectly. 
Don't get me wrong, I think the image recognition is impressively good. Sometimes it even reads barcodes that my knowledge of barcodes and laser scanners surprises me as I thought they'd be unreadable.

But, I know that the "barcode scanner software" isn't "reading" the barcode the same way that a laser scanner does. It uses AI (a machine-learning-based algorithm) to work out the best guess for an image's barcode value.

In this instance, I think I was "lucky" and encountered an incorrect barcode read that happened to be a value the server recognized but had a price difference enough for me to notice. This then combined with my knowledge of client-server software and barcode scanning to make an informed guess about what happened.


What could the supermarket have done to prevent this?

I have a few recommendations:
  • Continue to invest in improved accuracy of barcode "scanning" based on phone cameras.
  • Add validation for extremes of weight and price when reading values from 'self-weighed barcodes.'
  • Ensure encrypted connections between the app and server to avoid accidental corruption, modification during transmission, or responding to a different client than the sender.
  • Introduce a process for automatically logging and investigating exceptional values or errors.


What I'm doing because of this?
  • I now double-check everything I scan.
  • I verify the contents of my receipt matches what I put in my basket/trolley.
Not doing these could lead to me being overcharged and/or charged for products I didn't buy.


Bonus validation issue I found with their software for everyone who has read this far.
After several months of shopping with their phone app to scan items as I go round the store, I've discovered that there are a few aisles where the network signal is feeble. This leads to a potential issue with the app communicating with the server.

I regularly see the busy/progress spinner being displayed for longer than elsewhere in the store in these aisles. On one occasion, the delay was significantly longer than I'd encountered previously. It stopped me from scanning further items, and because I'm inclined to wonder when software misbehaves, I started counting seconds until it finished what it was doing. 
I counted for about a minute (I know I'm bad at estimating seconds, and it's not a skill I see value in spending time improving) but more concerning was that the list of products now displayed that I'd scanned two of the item I'd only scanned once. Was this another instance of the app scanning something other than I had? No, here's what I think happened.
I think the app sent the details to the server but didn't get a response. It then automatically retried (timeout and retry periods are typically either 60 or 100 seconds--both of which could have been met in this instance) and sent the request again. I think the server received both requests, but the response to the first request was not received. When the server sent the second response, it included the total number of that item that had been scanned, but this was actually the number of requests it had received, and due to the automatic retry, these weren't the same.
Having seen many, many codebases, I know it's common for developers to include automatic retry logic but not account for the server receiving a request but the response being lost.

I find it very disappointing when a large, multi-million-pound business has software that doesn't account for basic and expected connectivity scenarios.


If you're wondering which supermarket this was, why not leave a guess in the comments. ;)


Thursday, November 19, 2020

A festive introduction to Visual Studio Extensions

This article is part of Festive Tech Calendar 2020. An online event organized by the tech community with content created by many kind individuals around the world.


Visual Studio is highly extensible. This article will show you two simple ways to extend it by adding additional content to the editor. What you add is up to you. It could be something practical, or it could be something fun, like this:

Screenshot of Visual Studio editor with holly in the corners, and images of Santa, Christmas trees, and snowflakes among the text

While there are many festive images included in the above screenshot, they are added in two ways.

The holly and mistletoe are Viewport Adornments. The smaller images among the text are Intra-text Adornments.

This post has three parts.

  1. Instructions on how to create an extension that adds these festive adornments to the editor.
  2. Examples of how that knowledge can be used to create more practical extensions.
  3. A video showing how to modify or extend the festive editor code.

Creating a festive editor extension

The code for this extension can be found at https://github.com/mrlacey/FestiveEditor.

If you don't want the code but simply want to install it, you can download it from the marketplace.

Creating a project

To create an extension for Visual Studio, you must have the 'Visual Studio extension development' workload installed. This option is available when installing or modifying an installation of Visual Studio.

The Visual Studio Installer showing the extension development workload option

We'll start by creating an Empty VSIX Project within Visual Studio.

Empty VSIX Project option in the Create a new project dialog
Let's call this project FestiveEditor.

Adding a viewport adornment

The first things to add to the extension are the holly and mistletoe images.

I'm using images from SweetClipArt.com, but you could use any images you want.

mistletoe with red ribbon Holly with berries

For simplicity, I've created versions of the holly image rotated for each corner it will be displayed in. 
The images are in a folder called Images, and I've set the Include in VSIX property to True so that they'll be distributed with the extension.

partial screenshot showing images in solution explorer and the properties window

Firstly, we'll create an adornment that will display the mistletoe. We do this by adding an item to the project of the type Editor Viewport Adornment. Let's call it MistletoeAdornment.cs.

Add New Item dialog showing Editor Viewport Adornment option

This adds two files and several references to the project.

Solution Explorer showing new files and references

MistletoeAdornmentTextViewCreationListener.cs is responsible for the creation of the image (adornment) that we add to the editor. Because this class will create all of the adornments that we add, let's give it the more generic name of FestiveEditorTextViewCreationListener.

This file does three things.

  1. It determines which editors it applies to.
  2. It defines the visual layer to which the images will be added.
  3. It creates a class that displays the image.

Determining which editors the adornment applies to.

The class implements and exports the interface IWpfTextViewCreationListener. The ExportAttribute tells Visual Studio, via MEF (the Managed Extensibility Framework) that this class wants to listen to TextView creation events. By specifying the ContentType as "text" and the TextViewRole as PredefinedTextViewRoles.Document Visual Studio knows to add this to all editors that allow the editing of text documents.

Defining the visual layer that will host the images

The AdornmentLayerDefinition is a private field but is accessible within the extension as it is exported via MEF. By specifying the name of "FestiveAdornments," we will reference it in other classes. By specifying that it comes after the predefined Text layer, it will appear on top of all the content in the file/editor. The editor has three built-in layers: text, caret, and selection. If we wanted everything in the layer to appear under the editor's other contents, we could specify the Order as coming Before the Selection.

Creating the class that displays the image

The final part of this class is the implementation of the TextViewCreated method. Visual Studio calls this and passes a reference to the IWPFTextView interface. We'll pass this to the control that displays the mistletoe image.

Displaying the mistletoe image

The MistletoeAdornment class handles the creation and positioning of the mistletoe image.

When the class is created, it:

  • creates the BitmapImage class that will display the png file.
  • specifies which layer to add the adornment to.
  • subscribes to LayoutChanged events of the WpfTextView so that we can redraw it in the middle of the top of the screen whenever the window size changes.

If we debug the project, a new experimental instance of Visual Studio is opened with our extension installed. If you then open any document file, you will see the mistletoe added in the middle, at the top of the screen.

screenshot of Visual Studio showing the mistletoe adornment

Displaying the holly images

We now need to create four new classes to create the four holly image. We could use one class that creates and positions multiple images, but as the classes are so simple, I find it easier to debug and maintain them as separate classes.

We'll call these classes TopLeftHollyAdornment, TopRightHollyAdornment, BottomLeftHollyAdornment, and BottomRightHollyAdornment.

These classes can all be the same as MistletoAdornment.cs but with three differences:

  1. The fields containing the height and width with which to display the images.
  2. The name of the file used when creating the BitmapImage.
  3. The code to position the images.
The code for the positioning of the images is this.

The repetition in these classes makes them a suitable candidate for abstracting the commonality, but this is left as an exercise for you if you'd like to do this.  

We now need to tell the creation listener to create the new classes.

When this is debugged, we see all four images.

Visual Studio editor with holly in each corner and mistletoe at the top

Adding Intra-text adornments

We can now add additional images between some of the text in the editor.

We'll start by adding some images to the Images folder and again setting the Include in VSIX property to true.

Christmas treesanta facesnowflakesnowmanpresent

The aim is to put these images next to words of between three and seven characters in length.

The extensibility workload includes an item for creating an Editor Text Adornment. However, it is not suitable for the complexity of our scenario.

Instead, we'll take inspiration from one of the VSSDK Extensibility Samples.

Specifically, we'll reuse the IntraTextAdornmentTagger and RegexTagger classes.

Our code will use two taggers. A tag is a way of associating some data with a location (or span) in the text. A Tagger is responsible for creating tags.

We need the first tagger to look through the text to find places to add the images. It will use a regular expression to find those locations and so will be based on the RegexTagger class.

The second tagger will be responsible for drawing creating tags that show where to draw the image adornments. This will be based on the IntraTextAdornmentTagger class.

Tagging where to put the images

The first thing we need to do is to tell Visual Studio that we will provide an ITagger that will produce FestiveImageTags. This is done with the FestiveImageTaggerProvider.

Next, we need to define the FestiveImageTagger. Most of the work this class does is inherited from RegexTagger, but we need to specify the Regular Expression (Regex) to identify where we want to put the tags/images.

The final thing we need to create the tags is the tag definition itself. FestiveImageTag implements the ITag interface, but this is used purely for identification and does not contain any functionality. The only thing the tag needs is the "term" or word matched by the regular expression.

If we ran the code now, we wouldn't see anything. This is because (most) tags by themselves do not cause any UI to be displayed. (Notable exceptions to this are the OutliningRegionTag and the ErrorTag, but we are not using them here.)

Putting images where there are tags

As with creating the tagger earlier, we first need a provider to tell Visual Studio to create the tagger. FestiveImageAdornmentTaggerProvider tells Visual Studio that in text documents, it will create a FestiveImageAdornmentTagger that will create IntraTextAdornmentTag where there are FestiveImageTag tags.

FestiveImageAdornmentTagger gets most of its logic by inheriting from IntraTextAdornmentTagger and specifies how to place a FestiveImageAdornment where there is a FestiveImageTag.

FestiveImageAdornment is just an Image with a bit of extra logic where we specify the image's path to use, which is done based on the length of the term.

This might seem complicated, but it allows functionality to be clearly separated into different classes.
In summary, the process works like this:

  1. The FestiveImageTaggerProvider tells Visual Studio to create a FestiveImageTagger for all text documents, and in turn, that will create FestiveImageTags.
  2. The FestiveImageTagger watches the changing text and adds a FestiveImageTag to each location in the document where we want to add an image.
  3. The FestiveImageAdornmentTaggerProvider tells Visual Studio to create a FestiveImageAdornmentTagger for all text documents and that will create tags of type IntraTextAdornmentTag where there are FestiveImageTags. (As created separately.)
  4. The FestiveImageAdornmentTagger creates (and updates) instances of FestiveImageAdornment within the document, in the location specified in the FestiveImageTag, and these are the displayed images.

The result of all this is that festive images will be added within the text of a document.

Visual Studio editor showing all the adornments created in this article


Practical uses of editor adornments

I have previously made several extensions (for more serious purposes) that use the techniques shown above. 

Real extensions using viewport adornments

These two extensions extend the editor by adding adornments to the viewport.

Show Selection

This extension displays the start and end positions of the current text selection in the top right-hand corner of the window.
The need to know this is niche but the ability to create an extension that displays this when needed, has saved hours of trying to manually determine how the location of specific points within a file.

Visual Studio editor window showing the selected position in the corner adornment

Get it from the Visual Studio Marketplace or view the source on GitHub.

Watermark

This extension allows the display of a customizable, text-based watermark on the editor. This is useful for ensuring that specific information (such as a name, URI, or email address) is always available to someone else viewing the screen. It is intended for use in demonstrations at conferences/meetups or while streaming. The content, size, colors, and position of the text are all configurable.
The watermark (my Twitter handle - @mrlacey) is highlighted in a red oval in the image below.

The Visual Studio editor showing a watermark

This extension is available from the Visual Studio Marketplace, and the code is on GitHub.

Real extensions using text-based adornments

These three extensions include f

Comment Links

This extension makes it possible to navigate between files of any type or language by placing a prefix of "Link:" before the filename in the comment. When this is done it adds a button that, when clicked, will navigate to that file. It can also jump to specific line numbers or instances of a specific piece of text within the file.
This was created to make it possible to easily navigate within projects that use different languages and so Visual Studio's built-in functionality for navigating to types doesn't work.

partial screenshot showing a button added into a comment

This extension is available from the Visual Studio Marketplace, and the code is on GitHub.

String Resource Visualizer

This extension displays the value of localized strings next to where they're used in source code. It works by identifying the use of values from a resource file (.resx or .resw) and displays the localized value above the usage. Configurable options support showing the default translation of that of a specific culture. This extension exists to help developers confirm that they are using the right value in the right place. It works with the ResourceManager and the ASP.NET Localizer.

This extension is slightly different from the above as it places the adornment above the text.

screenshot showing the values of string resources being displayed above where they're used

Examples of resources being shown when used via the ASP.NET Localizer

This extension is available from the Visual Studio Marketplace, and the code is on GitHub.

Const Visualizer

This is a companion extension to the String Resouce Visualizer. Rather than displaying resources, it displays the values of constants above the places where they're used.
This can be helpful to verify that the value of the constant you are using has the value you expect or intend.

screenshot showing examples of constants being displayed

This extension is available from the Visual Studio Marketplace, and the code is on GitHub.


Extending the Festive Editor code

There are lots of ways you can extend the above code. I've demonstrated a couple of examples in this video


Congratulations on making it this far. If you try extending or customizing this code for your own purposes I'd love to see the results. Share them in issues on GitHub or via Twitter.

If you're interested, you can find all the extensions I've made on the Marketplace.



Saturday, October 24, 2020

Rethinking open-source priorities - what is my time worth to me?

One day into trying to fix an issue I wondered if I was using my time wisely. Two days after that, and when I'd finally found the solution, I'm certain I hadn't been using my time wisely.

⏰⏲⏱

Cliche
Photo by CharClarPhoto - https://www.flickr.com/photos/charclarphoto/42437342612/

One of the things I like about contributing to open-source projects is that I can choose my priorities.

My thinking goes or rather went, that if I'm giving my time for "free" I should get to choose what I work on. I'm starting to think this isn't the best for me, or the projects I'm contributing to.

Back to the scenario, I started with.

One day into debugging the issue (an automated UI test that would fail periodically for no obvious reason) and I considered giving up. There were two things that kept me going:

  • I was really keen to understand the underlying issue.
  • I'd committed to fixing the bug. 
The bug was reported by a member of the project's core team and I'd created the test originally. I felt a personal responsibility to get it working. The bug was also something I (originally) thought I could fix quickly. Having the core team (who are paid to work on the project) spend their time on this didn't feel like a good use of their time.

Reflecting back on this, I was making a few (common?) false assumptions:
  • My time was worth less than those people paid to work on the project.
  • My time doesn't matter as it's just an open-source contribution. (I'm not being forced to do this.)
  • Because I'd started it I need to finish it.
  • Any external contribution to an open-source project is valid.
If this had been paid work, I would have stopped before the end of the first day working on this and said it wasn't worth the effort. If this had been my own open-source project I would probably have stopped then too. It wasn't worth the effort to spend more time trying to fix the issue. Having to occasionally rerun a test because of a false negative wasn't worth spending multiple days of effort. That time could have been used on something much more productive.
I would have been appalled to hear of someone being paid to work on the problem keep going for so long. So, why did I think it was ok for me to keep spending my unpaid time on it?

If you've read this far and think I was foolish and should have known better and stopped sooner. I agree.
Clearly, I need to reflect on this and better prioritize what I work on.

However, I think this highlights some important questions for people who maintain, lead, manage. or otherwise, influence people who contribute to projects. I'm thinking particularly of open-source projects but I suspect the same applies to closed projects within an organization too.
  • How do you ensure that all contributors are making the best use of their time?
  • How do you ensure an external contributor is doing something that is a good use of their time?
  • How do you prevent external contributors from becoming demoralized when stuck on tricky problems?
  • If you want external contributors, what are you doing to attract and keep them?

I think these questions matter because:
  • For paid workers, you want to ensure that the money spent on their time is being used effectively and efficiently.
  • For volunteers, you want to value and respect the gift of their time and ensure it is being used wisely.
  • For everyone, you want to make sure that no-one is stuck on something they should re-evaluate, abandon, or ask for help with.
    Previously I would have relied on people to speak up when they're stuck but this experience demonstrates that I don't always do this. So I shouldn't expect this from others.
  • For everyone involved, you want them to feel valued, appreciated, and that they're making valuable useful contributions.
    Some paid contributors will be happy to turn up, do something, and get paid without thinking about what they're doing and the wider consequences. But I expect having such people on a project that seeks open-source contributions can be a turn-off for would-be volunteer contributors.

Managing projects with both paid and volunteer contributors can be very tricky. I wonder if there are lessons from charities or other organizations that have both paid staff and volunteers which would be useful for those creating software this way?

If you have any thoughts on this, I'd love to hear them.
They could be on how you prioritize and know when to stop, re-evaluate, and when to keep going. Or maybe you have some thoughts about how project leaders/maintainers/managers can ensure that all contributors (paid or not) are making the best use of their time. Both personally and with regards to contributing to a project.

For me, the lesson I'm going to try and take forward is to think about giving my time to an open-source project and then focusing on what the best thing is I can do with that time. I'm going to try and put that time decision before I commit to fixing a bug or adding a feature.

post-script.
Yes, I did finally solve the issue. It was a timing issue (as they often are) relating to a light-dismiss dialog not always being closed. It's interesting, to me, to know this but was it worth two days of my time to find this out? Probably not. Would I have felt bad charging for my time to fix this if I was being paid? Yes.

Friday, October 09, 2020

6 tips for contributing to Open Source

 The following is based on a presentation I gave on the September 2020 WinUI community call.


Here are my tips

  1. Don't start with a Pull Request
  2. Follow the project's conventions
  3. Assume positive intentions
  4. Explain why. Don't say "should"!
  5. Focus
  6. Everything helps


Don't start with a Pull Request

If you're doing anything more complicated than fixing a typo or small spelling mistake, don't start with a pull request.
Engage with the project maintainers before you start doing any work.

You don't want to spend time on something that isn't wanted or that someone else is already working on. Nor do you want to build a solution that doesn't fit with what else is going on with the project.
It may also be beneficial to discuss possible ways to make the change, fix the bug, or add the proposed feature. You'll want to make the best use of your time, and maintainers or other people working on a project may have ideas and suggestions to help you.

You can "engage" with the appropriate people by creating an issue (if it's something new), commenting on an issue (if there is already on related to the work you want to do), commenting on a discussion, message board, email list, chatroom, or wherever else is appropriate for that project.
Aim to try and work the same way as others on that project already do.

Follow the project's conventions

Most projects include a document on how to contribute. Most of them include a note about not starting with a pull request. But there's more in there too. If a project has one, be sure to read the contributing.md document and follow what's written there.
If a project doesn't have a guide to contributing, ask how to get involved. And, maybe suggest a guide to contributing too.

As a general rule, code is easier to understand (and therefore review and maintain) if consistent in style, structure, and conventions. Anything you contribute should look like it belongs with the rest of the codebase.
Consistency applies to documentation too. It can be distracting for the reader when documentation uses differing tenses, styles, terminology, etc.

Many projects also use templates and checklists when creating issues and submitting pull requests. Always strive to provide the information and complete the desired tasks.

This isn't being done to be difficult.

Assume positive intentions

Projects ask for information because it helps. They're not asking for it to be difficult.
 
Project maintainers don't want you to waste your time. Also, they want to make the best use of their time. They ask for specific information to evaluate and accept a change or understand an issue. They don't do it to waste your time.

This point goes both ways.
Project maintainers need to remember that when an issue is raised with minimal details, it's not because someone is being difficult or lazy. They may not have been able to provide more information. They may not even speak the same language. 
Be kind.

Explain why. Don't say "should"!

If raising an issue, explain why you want a change, or how a new feature will help you. This helps others understand and allows appropriate prioritization.

Never say, "you should make it do X" without explaining why. 
I'm sure your opinions and ideas are great, but you need to do more than state them.
Is this currently causing you a problem? Or is it just something you think would be a "nice to have"? Why you're asking for or suggesting something can be essential to know.

Focus

And if you're raising issues about problems or suggesting new features, create separate issues for each problem or idea.

Breaking work down into discrete areas can help with understanding, prioritization, and associating changes.

It's much easier to triage and manage work when an issue only lists one problem.
It's easier to review a change that fixes one bug or adds one feature.

It's also useful when discussions remain focused.
Depending on the project, it may or may not be appropriate to have side discussions in issues or comments. I want to be more involved with several open-source projects, but I can't filter out all the seemingly unrelated discussions and so am unable to follow all the relevant discussions that occur.
Follow the conventions of the project regarding side discussions. 

Everything helps

The success of very few projects is predominantly decided by the code. Many other factors play their part, and you can contribute to these too.


This includes writing, expanding, or translating documentation. - There are projects I contribute to where they don't need other people writing code, but I can make a valuable contribution by writing docs.

The same goes for testing. - There are projects where others are paid to write code, but I can come in as an external contributor and make significant, valuable contributions by creating tests.


Again, there are projects where I add value by asking questions.

  • What about people who are using this for the first time?
  • Did you consider X?
  • Why did you do it that way?

This can be informal or as part of a code review.


Bringing different perspectives and experiences helps create something useful to more people and a wider variety of people.


Everything helps.


You can also support projects in other ways. For examples, see https://rapidxaml.dev/support


Financial support can be great if you can provide it, but not every project needs it.

Even if you can't provide a financial contribution, there are ways to support projects you use and value.

  • Leave a star on GitHub.
  • Leave a review or a star rating.
  • Spread the word through a tweet, blog post, stream, video, or whatever you use.


Feedback can be really encouraging for people investing in the creation of open-source projects and is a reminder that their effort is valued and appreciated.


Show you care. Show your support.