OK, I figured it out.
The difference between the preconfigured outline view and the preconfigured source list (there isn't actually a NSSourceList, it's just a NSOutlineView with a different preconfiguration) is in the cell identifiers.
In the outline view, there is only one cell in each column, and the cell identifier is left at the default, which means "automatic". In this case, the cell identifier is actually the table column identifier, which is where you typically get the cell identifier from in your "outlineView(:viewFor:item:)" delegate method.
In the source list, there is only one column, but it has two cells (one for the group header and one for the detail row), and they come preconfigured with cell identifiers "HeaderCell" and "DataCell" respectively. Set up that way, you can't follow the usual pattern of using the column identifier, since neither cell matches the column identifier.
To fix this, you can just use the "HeaderCell" and "DataCell" identifiers directly in "outlineView(:viewFor:item:)". Or, you could delete the "DataCell" identifier in IB, which puts the behavior of the detail cell back to match the normal outline view case.
Note that in a source list — or in any NSOutlineView where you use group rows — you need to test for a table column of nil in "outlineView(:viewFor:item:)". If it's nil, you use the (or conceivably a) header cell identifier. If it's not, you use a (or the) data cell identifier or the table column identifier, depending on which strategy you've chosen to adopt.