Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider handling feature identifier changes or aliases #91

Open
ddbeck opened this issue Mar 10, 2023 · 15 comments
Open

Consider handling feature identifier changes or aliases #91

ddbeck opened this issue Mar 10, 2023 · 15 comments

Comments

@ddbeck
Copy link
Collaborator

ddbeck commented Mar 10, 2023

Inspired by discussion on #89.

If the feature set is based on real-world usage by web developers, then some features may need to change identifiers from time to time or accommodate multiple known names. Some possible scenarios:

  • A feature is folded into a closely related feature. For instance, subgrid is subsumed by grid. At least one feature is removed and the definition of another feature changes, triggering some sort of versioning event (either at the data or package level).
  • A feature has some name but official or popular usage goes in another direction or multiple directions (e.g., es6 and es2015).
  • A feature is named erroneously (e.g. felxbox) and we ship with the erroneous name.

Some questions emerge:

  • How do we communicate such changes to consumers? (e.g., as release notes, as structured data, or something else)
  • How much should we assist consumers in following changes? (e.g., do we maintain a formerly-known-as list for each feature? do we duplicate data access under multiple names?)
  • How long should we assist consumers in following changes? (e.g., do we follow a deprecation schedule for outgoing features? do we maintain historic names in perpetuity?)

The answers to some of these questions might influence how we handle versioning (if we understand versions to be a kind of identifier for a feature).

We ought to resolve this issue before a 1.0 release.

@foolip
Copy link
Collaborator

foolip commented Mar 13, 2023

Thoughts on the different cases:

A feature is folded into a closely related feature. For instance, subgrid is subsumed by grid. At least one feature is removed and the definition of another feature changes, triggering some sort of versioning event (either at the data or package level).

This case is different from the others, it's about the scope of the feature changing in addition to the name. This is a very long term problem, but I think we'll be better off trying to maintain a stable tree structure and only changing the properties of the nodes of the tree. Inverting parent-child relationships would be especially complex to understand and communicate I think.

A feature has some name but official or popular usage goes in another direction or multiple directions (e.g., es6 and es2015).

For this I think we should have a concept of renaming using aliases, if we don't pick the best name first. Such aliases could remain long term if they don't get in the way.

Technically, I think this requires that we have a features.find() method that understands aliases, so that consumers that have used the original name continue to work.

A feature is named erroneously (e.g. felxbox) and we ship with the erroneous name.

If we want to get rid of error, or if we've accidentally used up a good name, I think we need a concept of deprecation and removal. We'd mark features as deprecated and make that visible in the API and release notes, and then remove them in a future major release bump.

@foolip
Copy link
Collaborator

foolip commented Jun 16, 2023

My concrete proposal here is a new alias property which is a string. In case we make multiple mistakes for the same feature we'll need an array of strings, but it's not certain that we ever will.

The API can wait until later, and might also including looking features up by BCD and caniuse identifiers.

@ddbeck
Copy link
Collaborator Author

ddbeck commented Jun 16, 2023

#202 stakes out the optional alias property for at least the simple case of renaming something.

@foolip
Copy link
Collaborator

foolip commented Mar 25, 2024

There's some additional discussion in #729. @romainmenke points out that is might refer to multiple things, and I sketched out how I think we could handle alias reuse (removal followed by later addition) if we really have to in the future.

@romainmenke
Copy link
Contributor

The alias approach does offer a way for a person to intervene and resolve issues by hand.
But it is a cumbersome process. You would have to scan the entire feature set and try to find the entry that has the old identifier in an alias field.

Maybe all features could have a stable machine readable id?
Something like a uuid. This would never change, even if a feature is renamed.

Having such an id would make it easier for 3rd parties (like myself) to reference web-features without having to worry too much about churn.

@foolip
Copy link
Collaborator

foolip commented Mar 26, 2024

Would a "get feature" helper that follows aliases make this easier to deal with? That would also console.warn when an alias was used to make it more likely it is noticed.

I'm a little bit hesitant on an ID which is guaranteed to never change, because it opens up new questions. If a feature needs to be split, which one gets to keep the ID? And with long term evolution you also have a problem akin to Ship of Theseus, does it ever become a new feature?

@romainmenke
Copy link
Contributor

Would a "get feature" helper that follows aliases make this easier to deal with?

This helps with discovery and it slightly reduces the amount of work to handle changes.
It is better than only having the alias field :)

@ddbeck
Copy link
Collaborator Author

ddbeck commented Jan 16, 2025

This came up in the WebDX call today. There was a request for more examples (from me), especially actual historic ones. Some semantics we'll need to deal with:

  • Renames with no underlying meaning (i.e., a typo like Fix typo in file name #2484).
  • Aliases with no underlying meaning, to deal with important "also known as" cases. For example, imagine we had a referrer alias to a referer feature.
  • Renames because of significant feature name/API changes. For example, at one time array-group might have been plausibly called grouptomap or something—we'd want the option to give it a new, more commonly used name.
  • Merges. For example, someday we'll fold array-group into array. It should be possible to safely reference that "inner" feature of array (e.g., perhaps by making array-group a pointer to some inner structure of array that retains the shape of array-group).
  • Tombstoning features that didn't go anywhere. Caniuse has tons of these, like https://caniuse.com/jpeg2000. Edit: we now have a good example as import-assertions from Add feature for discouraged import assertions #2565.
  • Reserved for future use. For instance, a browser is implementing a feature (but it's not in nightlies or betas yet and not in BCD), wishes to refer to its eventual feature ID, and we want there to be a way to verify that the ID is reserved, but not publish any real data for it yet until it gets closer to release. It will (eventually, hopefully) become a real feature or get tombstoned.

There was also some discussion of features that evolved a lot, such as a CSS feature with changing name and syntax. I don't have a good example for this kind of thing and would welcome more thought on that.

@tidoust
Copy link
Member

tidoust commented Jan 16, 2025

One (identified) example is the font-stretch CSS property that is now a legacy alias name for font-width. I suppose the feature will get renamed at some point in the future. That probably fits in your "aliases" category, but not so much as "also known as", rather as "used to be known as".

@foolip
Copy link
Collaborator

foolip commented Feb 14, 2025

Reading through #91 (comment), I agree that those are the important cases. Simplified further, we can talk about features that are gone with no replacement, and features that are gone but there's one or more other features that took its place.

Some mocked YAML to show what we might do in practice:

#777 merely changed an identifier because we liked another better. Tools could follow the rename without looking closely. The fonts.yml tombstone would look like:

# no name or description, it would just be duplicated
tombstone:
  reason: 'Renamed to match the @font-face syntax and avoid the overly generic "fonts" identifier.'
  redirect: font-face

#1089 split a feature into several others. Some uses of it could be replaced with a more specific feature, others with multiple features, it can't be sorted out automatically. But sites like webstatus.dev could suggest which features to look at instead. The custom-elements.yml tombstone would look like:

name: Custom elements
description: Custom elements are HTML elements with behavior that you define.
spec: https://html.spec.whatwg.org/multipage/custom-elements.html
caniuse: custom-elementsv1
tombstone:
  reason: Split into more specific features and a group.
  maybe_you_want: # needs better name
    - autonomous-custom-elements
    - customized-built-in-elements

#1000 removed a feature where the replacement is quite different, it's not a simple rename. A sanitizer.yml tombstone would look like:

name: Sanitizer API
description: The Sanitizer API sanitizes untrusted strings of HTML, Document and DocumentFragment objects. After sanitization, unwanted elements or attributes are removed, and the returned objects can safely be inserted into a document's DOM.
spec: https://wicg.github.io/sanitizer-api/ # not linted!
tombstone:
  reason: Removed from Chrome, superseded by Unsanitized HTML parsing methods.
  # nothing machine readable pointing to the Unsanitized HTML parsing methods feature

I haven't given an example of a merge here, but I think that'll just be one of the first two forms. For cases that can be redirected automatically use the first form, and for other cases use the second.

@ddbeck WDYT?

@jcscottiii would something like this work for webstatus.dev if we published it in the package?

@jcscottiii
Copy link
Contributor

The first two cases would be very helpful for webstatus.dev. The last case seems similar to the discouraged field. How do you imagine the third case would differ from the discouraged field?

Another question I have: Would you continue to calculate baseline status for these features marked with tombstones?

@captainbrosset
Copy link
Contributor

Another question I have: Would you continue to calculate baseline status for these features marked with tombstones?

I think we would have to. At least for the redirect case. If a feature changes name/ID (and perhaps its syntax changes, but the principle of the feature mostly remains the same), then we'd want to tombstone the old one and redirect to the new one in a way that makes it easy for consumers of the package to move in their own time (or, never).

@foolip
Copy link
Collaborator

foolip commented Feb 19, 2025

The last case seems similar to the discouraged field. How do you imagine the third case would differ from the discouraged field?

@jcscottiii The main difference I'm thinking is that webstatus.dev wouldn't show the Sanitizer API since another feature has replaced it, but would show discouraged features, I assume.

Would you continue to calculate baseline status for these features marked with tombstones?

Terrific question. I was imagining that we'd trim the YAML down to something minimal, but @captainbrosset makes a very good point that preserving everything allows consumers to migrate in their own time. So maybe we should consider the tombstone purely an extra field in terms of the schema, not a replacement for other information as I was thinking.

@ddbeck
Copy link
Collaborator Author

ddbeck commented Feb 25, 2025

Thanks to Philip for the concrete proposal and the discussion based on it. After reading through this and trying to apply the proposal to my examples #91 (comment), I think I’ve got a few questions/ideas for this, but I think we’re getting to close to what we need.

Highlights for my long comment:

  • I think we should name this stuff to be clearer to consumers about how we want them to use it. I think it would be nice to distinguish between dead ends and two kinds of redirects. I did some bikeshedding.
  • For merges, we’ll need an answer for Allow references to useful subsets of large features #2676, but it sounds like we have a grasp on what the redirection part could look like.
  • I think a lot of use cases I had previously imagined for renames are actually discouraged features that point to a new feature. I’m not certain whether discouraged data and redirects should coexist.

Types of tombstones and redirects

After trying to apply Philip’s idea to the situations I described in #91 (comment), I think there are three distinct types of tombstones/redirects.

True tombstones

This is when we don’t think developers should see/care about the feature at a given ID, at all. We’re holding it because we don’t want it to be used for something else. In HTTP terms, a 410 Gone or 403 Forbidden.

A tombstoned feature has no (published) support information, name, or description. I'd expect consumers to basically ignore these entries.

It’s a little bit of a misnomer, but a tombstone could be used to reserve an ID for future use too. If we wanted to clean up the terminology, we could call these “reserved” or something.

Use cases: a feature that we added erroneously or prematurely; an ID reserved for future use.

True redirects

This is when we don’t think developers should see/care about the redirect itself, just the data at the other side of that redirect. In HTTP terms, a 301 Moved Permanently.

For consumer convenience (per #91 (comment)), true redirects could have support information, names, and descriptions, pulled from its redirect target—though I expect consumers to do something about duplication in that case. If that data ever gets too cumbersome, we could periodically do breaking changes that force consumers to actually follow the redirects.

Use cases: a feature was entered with a typo in the ID; an alias; developers started referring to a feature by a new name and we want the ID to match; a feature merged into another (provided we have an “inner feature” reference; see #2676).

Informational redirects

This is when the redirect itself has some informational value for developers. This would be any case where silently redirecting would be unhelpful or incomplete. In HTTP terms, a 300 Multiple Choices, I guess.

For these cases, I'd expect the consumer make some kind of decision about how to show that redirect. Maybe they offer the user a choice, they pick one but offer a "did you mean…?" option, or they make an editorial decision.

Interestingly, we already do this in a way, with a discouraged feature’s alternatives.

Use cases: one feature replaces another; we split a feature in two or more pieces; discouragement with one or more alternatives.

Schema bikeshedding

Given the three types above, I think it would be nice if we had different ways of marking them for consumers, so our intent is clearer. I ended up coming up with these examples, to exercise the full range:

# oops.yml
# accidentally committed a test file
reserved:
  reason: deleted

# some-very-early-proposal.yml
reserved:
  reason: future use

# numeric-seperators.yml
# our typo
redirect:
  reason: typo
  to: numeric-separators

# array-includes.yml
redirect:
  reason: merged
  to: array/array-includes

# referrer.yml
# the spec's typo
redirect:
  reason: alias
  to: referer

# custom-elements.yml
disambiguate:
  reason: feature split
  choices:
    - autonomous-custom-elements
    - customized-built-in-elements

# import-assertions.yml
# we already do this, minus the reason
discouraged:
  according_to: 
  reason: evolved into
  alternatives:
    - json-modules

Things I’m still worried about

  • When is a feature discouraged (with an alternative) versus redirected? I kinda like how we handled a case like import assertions/attributes—it feels more faithful to reality than a true redirect.

    I think in the case of “early spec changes with new name and semantics” discouragement is probably better for consumers like the Baseline web component, so an old blog post’s banner makes sense given its original context. But maybe minor name changes with no change in semantics shouldn’t get that treatment? I’m having a hard time coming up with examples that would help me decide this.

  • Do we need to slim down discouraged to cover only the fact of discouragement and use redirects to handle the alternatives more generically? It seems slightly odd that us splitting a feature gets a different linking mechanism than when we mark a feature discouraged.

@captainbrosset
Copy link
Contributor

I really like this!

Having multiple names/reasons for this mechanism, which in the end could just be very generic and use one field only, seems very useful for consumers. We give them a lot of subtle information about the state of a feature. I'm slightly worried about it making it harder to author/maintain features though. But I guess there's no real way around it anyway.

Some thoughts below:

True tombstones
This is when we don’t think developers should see/care about the feature at a given ID, at all.
[...]
It’s a little bit of a misnomer, but a tombstone could be used to reserve an ID for future use too. [...]
Use cases: a feature that we added erroneously or prematurely; an ID reserved for future use.

I disagree about the fact that developers should not see or care about future features. Raising awareness about newly proposed features and applying pressure to other actors is all part of getting a feature to eventually be part of the web platform. I don't think web-features should hide these.
So maybe reserved:reason:deleted should be handled differently than reserved:reason:future use.

True redirects
This is when we don’t think developers should see/care about the redirect itself, just the data at the other side of that redirect. [...]
For consumer convenience (per #91 (comment)), true redirects could have support information, names, and descriptions, pulled from its redirect target

I don't think this is necessary. If I'm understanding this correctly, this would make it possible for consumers to do something like:

const theNewFeature = webFeatures[theOldId].redirect.to._someLinkToTheFeature;

Which, I don't think is much easier than:

const theNewId = webFeatures[theOldId].redirect.to;
const theNewFeature = webFeatures[theNewId];

[...] we could periodically do breaking changes that force consumers to actually follow the redirects.

Do you think we'd really want to clean up true redirects? I feel like we could live with them forever, just like tombstoned features. Especially if we just give consumers the redirected ID.

Schema bikeshedding

As said before, I like the different fields from a consumer's perspective, although I wonder if it would be nicer to be able to do something like switch (myFeature.state.reason) {...} rather than if (myFeature.disambiguate) {...} else if (myFeature.discouraged) {...} if that makes sense.

When is a feature discouraged (with an alternative) versus redirected?

In many cases, discouraged and redirected are the same thing, but we still want to tell consumers about the difference. A field like reason, or a different top-level field like discouraged vs redirect makes sense for this.

I kinda like how we handled a case like import assertions/attributes—it feels more faithful to reality than a true redirect.

In my head, discouraged features are not true redirects, but informational redirects instead. It's, of course, up to consumers to decide if they just want to treat the discouraged feature is a redirect and just show the target feature, but some consumers like MDN will want to show the old discouraged feature in all its gory details, and provide a link to the new replacement feature.

I think in the case of “early spec changes with new name and semantics” discouragement is probably better for consumers like the Baseline web component, so an old blog post’s banner makes sense given its original context.

Agreed

But maybe minor name changes with no change in semantics shouldn’t get that treatment? I’m having a hard time coming up with examples that would help me decide this.

I like how we do things today: discouraged feature must have a according_to field that points to some spec language. If we continue to rely on this, I think it makes it pretty clear what is discouraged vs. what's a simple redirect.

Customizable select might be a good example:

  • When spec work started, it was called selectmenu. At the time, we might have created a selectmenu.yml file with reserved:reason:future in it.
  • Later, a prototype was made in Chromium. We would have likely removed the future use flag then.
  • Later, the feature got renamed to selectlist, without any major change in the feature's scope. At that time, we might have used a true redirect.
  • Later still, more WG discussions happened, and the proposal because just customizable select without the need for introducing a new HTML element. The feature changed a little, since instead of using a new element, developers would now have to opt-into the new select behavior. At that time, we might have used an informational redirect maybe.

At no point in this early development lifecycle of the feature was a "real" spec published, and there's no spec language anywhere that says that selectmenu or selectlist are obsolete/deprecated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants