Skip to content

[css-cascade][css-syntax] New !revertable flag to mark a declaration as "can be reverted when IACVT" #10443

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

Open
LeaVerou opened this issue Jun 13, 2024 · 25 comments

Comments

@LeaVerou
Copy link
Member

This came out of #5319 but I figured I should make a new post for this for visibility, since it’s a distinct proposal.
#5319 discusses a keyword like revert-declaration, however I don’t think a keyword will work, since anything that is part of the value cannot be known in advance (it can be part of a variable for example).

The problem is that UAs throw the other declarations away when they see a new one in the same scope. It's impossible to know whether the new one will ever use that keyword so they can keep the old ones around too. It works with revert and revert-layer because the other declarations are kept around anyway since they’re in a separate origin/layer.

However, I think a new !-flag may actually work. TBB, but some potential names could be !revertable, !revocable, !transient, !layered. Since !-flags need to be present at parse time and are always at the end, it doesn’t have the issues that a keyword would.
When a declaration with this flag is encountered, the UA keeps old declarations around instead of replacing them with this.

/* Will not reset border-radius to 0 if --pill-radius is not set, it will just cascade normally */
border-radius: var(--pill-radius) !revertable; 

There can be more than one:

* {
	/* Will not override animation if --effect is not set to one of these values */
	animation: if(style(--effect: grow), 1s grow) !revertable;
	animation: if(style(--effect: pulse), 4s pulse infinite) !revertable;
}

One open question is how would this work in the CSS OM. But we could just expose the last declaration, and authors can use Typed OM to get all of them (which allows append).

@andruud
Copy link
Member

andruud commented Jun 14, 2024

There's overlap here with the ideas in #1594.

The problem is that UAs throw the other declarations away when they see a new one in the same scope.

Ideally, we'd just not do that, and make it always possible to revert to the previous "winner" of the cascade (using a keyword). We'd need to investigate the performance implications of this, though.

@LeaVerou
Copy link
Member Author

LeaVerou commented Jun 14, 2024

That’s the idea for the opt-in: that authors would not be doing this for every single declaration, so the performance implications should be minimal.

@Loirooriol
Copy link
Contributor

#foo {
  height: 1px;
  block-size: 2px;
  height: var(--invalid) !revertable;
}

Assuming an horizontal writing mode, should this revert to 2px or 1px? I guess 2px like revert-layer would do in different layers, but it seems extra annoying for CSSOM.

I wonder if it would make more sense to add an entirely new rule like

@revertable #foo {
  height: 1px;
  block-size: 2px;
  height: var(--invalid);
}

@LeaVerou
Copy link
Member Author

LeaVerou commented Jun 15, 2024

#foo {
  height: 1px;
  block-size: 2px;
  height: var(--invalid) !revertable;
}

Assuming an horizontal writing mode, should this revert to 2px or 1px? I guess 2px like revert-layer would do in different layers, but it seems extra annoying for CSSOM.

This seems orthogonal to the issue we’re discussing? It would revert to whatever this would revert to:

#foo {
	height: 1px;
	block-size: 2px;
	height: invalid;
}

Not sure how UAs handle the aliasing, but that doesn't seem particularly difficult to define. They'd probably keep both around?

I wonder if it would make more sense to add an entirely new rule like

@revertable #foo {
  height: 1px;
  block-size: 2px;
  height: yolo;
}

I would be strongly opposed to doing this as a rule. Rules are far more heavyweight, scoping has to be defined from scratch, and authors cannot port knowledge about it from other parts of CSS. @-rules should be a last resort, not the first thing we reach for because defining syntax with good ergonomics is hard.

@LeaVerou
Copy link
Member Author

Agenda+ since I had some discussions with implementers at the CSS WG meeting last week that indicated this is very implementable.

@Loirooriol
Copy link
Contributor

This seems orthogonal to the issue we’re discussing?

It isn't orthogonal. If we are only reverting to the same property, we could just shift down all declarations for the same property to the last one, then store the values together as a list, say that getPropertyValue provides the last one, and add getPropertyValues to get all of them. Similar for the setter and the priority. But we can at least keep the same indexing as now.

However, if we can revert to other properties, we can't keep assuming that a given property can only appear at most at one index. So it seems the API and the data structures used by browsers will need considerably bigger changes.

I would be strongly opposed to doing this as a rule.

Well, I would be strongly opposed to not exposing the necessary information via CSSOM.

And it doesn't seem great to pollute CSSStyleDeclaration with several things that aren't needed by normal style rules.

@LeaVerou
Copy link
Member Author

Most aliases are known in advance. The only complication with the issue you're pointing out is that the aliasing can be dynamic. These cases are sufficiently few they can just be stored in a data structure (if they aren’t already).

Nothing is polluting CSSStyleDeclaration, please read the first post more carefully?
But as a general principle, the ergonomics of the language itself are more important than CSS OM ergonomics. That doesn't mean the CSS OM doesn't matter, but the weightings of tradeoffs are different.

@Monknow
Copy link

Monknow commented Jun 25, 2024

I wonder if it would make more sense to add an entirely new rule like

@revertable #foo {
 height: 1px;
 block-size: 2px;
 height: var(--invalid);
}

Even if an at-rule and a !-flag are both present after parse time, I think an at-rule would prompt users to put all their styles inside the block, instead of narrowing down which properties they want to enforce the revertable behavior.

Besides, as specified on the original issue at #5319, the fallback property may be on another CSS rule (or even on third-party styles), like the following:

<p class="lorem">Lorem ipsum dolor</p>
p {
 background-color: red; // declaration gets thrown away
} 

.lorem { 
 background-color: var(--not-a-color);  // declaration is IACVT 
}

If the rule with the IACVT is more specific, the declaration on the lesser specific rule gets thrown away. If I understand your approach correctly, both declarations would need to be in the at-rule, which is harder if they are in different rules.

I think putting a !-flag (or even the initial proposal for a keyword) after the custom property that may be IACVT is cleaner and gives the user the necessary control.

@Loirooriol
Copy link
Contributor

If I understand your approach correctly, both declarations would need to be in the at-rule

@Monknow No. When the second winner of the cascade is in another rule, then it's no different than !revertable. It's not thrown away at parse time, it's just a matter of discussion whether browsers can afford to keep it during the cascade or not.

The problem with normal rules is that duplicate declarations for the same properties get overridden at parse time. So we need a different data structure. Then a different kind of rule may be cleaner.

Nothing is polluting CSSStyleDeclaration, please read the first post more carefully?

@LeaVerou Please read my comment more carefully: "I would be strongly opposed to not exposing the necessary information via CSSOM", which clashes with your proposal in the first post of not exposing it via CSSOM.

@LeaVerou
Copy link
Member Author

Nothing is polluting CSSStyleDeclaration, please read the first post more carefully?

@LeaVerou Please read my comment more carefully: "I would be strongly opposed to not exposing the necessary information via CSSOM", which clashes with your proposal in the first post of not exposing it via CSSOM.

I did. Exposing it via the CSS OM doesn't mean exposing it via every single CSS OM API. If .style does the most commonly needed thing, and .styleMap gives you all the information, that sounds like it would meet your requirements?


I would be strongly opposed to doing this as a rule.

That said, there may be value in having an @-rule as well, as sugar for applying !revertable to a bunch of declarations. But as it was pointed out, it should probably not be done from the start, so that authors don’t use it to wrap all their declarations without thinking. The entire reason !revertable might work is because the idea is an !flag is not going to be used everywhere.

@astearns astearns moved this to TPAC/FTF agenda items in CSSWG Agenda TPAC 2024 Sep 13, 2024
@astearns astearns moved this from TPAC/FTF agenda items to Regular agenda items in CSSWG Agenda TPAC 2024 Sep 13, 2024
@miragecraft
Copy link

How would the !revertable work with custom properties, if at all?

I imagine registered custom property would force variable resolution during type checking, so below should work right?

.example {
  --registered: revert to this value;

  --IACVT: max(invalid);
  --registered: var(--IACVT) !revertable;
}

@property --registered {
  syntax: "<number>";
  inherits: false;
  initial-value:0;
}

What about unregistered custom property?

Would the presence of the !revertable force the variable to resolve in order to check for IACVT?

.example {
  --unregistered: revert to this value;

  --IACVT: max(invalid);
  --unregistered: var(--IACVT) !revertable;
}

@kizu
Copy link
Member

kizu commented Oct 10, 2024

Good question! For registered custom properties, things should be relatively straightforward and work as for any other custom properties. In your example it will be 0, as no --registered is defined with a valid value, and if we will have something like:

.example {
  --registered: 1;
  --IACVT: max(invalid);
  --registered: var(--IACVT) !revertable;
}

then it will revert to 1.

For unregistered custom properties, the most logical thing to do is to treat them as they're treated now: always valid until used, in the same way it currently “taints” any declaration making fallbacks impossible (and for which we'll have first-valid() — #5055).

I don't know if it is currently possible to determine if an unregistered custom property is invalid before using it? If so, I can see it useful to be able to revert to previous values when doing !revertable. Otherwise it will be useless, and making it work this way should not break anything, as !revertable is itself new, so we can define how it works for this case (again, if this is technically possible/feasible).

@miragecraft
Copy link

miragecraft commented Oct 10, 2024

In your example it will be 0, as no --registered is defined with a valid value

Good catch on my code snippet, I forgot that the reverted value needs to conform to the syntax specified.

I don't know if it is currently possible to determine if an unregistered custom property is invalid before using it? If so, I can see it useful to be able to revert to previous values when doing !revertable.

That's what I'm hoping it can do, I believe currently registered property with "any token" syntax behave the same as unregistered property, correct? So simply doing so doesn't solve the problem.

My simplified use case is follows - I want to create a design system where you can specify width as fractions.

.example {
  width: calc(100% * var(--width));
  --width: 2/3;
  --width: max(invalid) !revertable;
}

As you can see here there isn't any syntax I can use other than any token, since "2/3" doesn't conform to any type.

Of course I can use calc(2/3) instead, but it's no longer elegantly expressed.

If we can force IACVT checking with !revertable then this problem would be solved.

Edit: I thought about using an intermediate variable with calc() and use !revertable on that instead, but while the syntax checking would work the revert would not target the correct variable.

@LeaVerou
Copy link
Member Author

LeaVerou commented Oct 22, 2024

The thing is, --width: max(invalid) is not IACVT — it just makes other declarations IACVT. !revertable can't do anything if the property is not IACVT.

@miragecraft
Copy link

The thing is, --width: max(invalid) is not IACVT — it just makes other declarations IACVT. !revertable can't do anything if the property is not IACVT.

I think this use case is more of a limitation of @property than !revertable, having !revertable work for registered custom property (that's not using syntax:"*") is good enough.

@Loirooriol
Copy link
Contributor

Yes, we can't decide whether a --width declaration is valid or not depending on where var(--width) is used, because it could be used in different places which expect different grammars.
You would need to define it with syntax: "<number> | <number> / <number>", which I don't think is valid.

@astearns astearns moved this to FTF agenda items in CSSWG January 2025 meeting Jan 22, 2025
@LeaVerou LeaVerou moved this from FTF agenda items to Friday morning in CSSWG January 2025 meeting Jan 24, 2025
@dbaron
Copy link
Member

dbaron commented Jan 31, 2025

Both https://en.wiktionary.org/wiki/revertible and Google books ngrams search suggest that "revertible" is the more common spelling than "revertable".

(Though I'm a little hesitant about the name because I think people will have trouble spelling it either way.)

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-cascade][css-syntax] New `!revertable` flag to mark a declaration as "can be reverted when IACVT", and agreed to the following:

  • RESOLVED: Add revert-rule as a css-wide keyword
The full IRC log of that discussion <ntim> lea: instead of having declarations override other declarations when they are IACVT
<oriol> q+
<ntim> lea: this would allow declarations to revert
<emilio> q+
<ntim> lea: make it like they didn't exist, which is very useful for if()
<ntim> lea: this is the last piece for making it work properly
<ntim> lea: we have style queries/if() functions, this last piece gives us conditionals that can be rolled back
<ntim> lea: the more we add IACVT things to the language, the more useful it is
<astearns> ack fantasai
<Zakim> fantasai, you wanted to react to lea to explain this in a different way
<dbaron> Wiktionary says revertible is a more common spelling than revertable. (I'm also a little hesitant about the name because I think people will have trouble spelling it.)
<dbaron> Otherwise I like the idea.
<lea> s/ this is the last piece for making it work properly/ this is the last piece for having conditionals in CSS, but it's also useful for anything that does IACVT/
<lea> q+
<miriam> q+
<ntim> fantasai: if you have a bunch of invalid things, we need to make it revertible, instead of falling back to the previous declaration
<ntim> lea: this behavior should have been the default in the past
<ntim> lea: it's a pain for authors
<ntim> lea: an opt-in makes it very intentional
<astearns> ack oriol
<fantasai> s/if you have a bunch of invalid things/if you have things that can become invalid at computed value time, rather than at cascade time, then when it becomes invalid you have to fall back to a default value rather than to the previous declaration in the cascade./
<fantasai> s/we need to make it revertible/Lea's proposal is to flag such declarations to request that the previous declaration in the cascade be cached, so that you can fall back to it if the !revertible declaration becomes invalid/
<ntim> oriol: I don't like a !revertible flag for this because the way you are block, the indexing of the block is that you have an ordering, and each property has value.
<ntim> oriol: if we were only reverting within the same property, we could shift down declarations for the same property
<lea> I don't understand the issue, perhaps an example could help?
<fantasai> s/instead of falling back to the previous declaration/just as if it had become invalid during the cascade/
<ntim> oriol: we could shift everything down to the last declaration for the last property
<ntim> oriol: since we have cases where different properties share properties, like logical properties.
<bramus> scribe+
<bramus> oriol: strongly thingk that here we should have the same reverting behavior that reverts within props that share the computed value
<bramus> … and then this implies to keep track of the value declarations and they could be interleaved
<bramus> … and we would need to keep trackt of that in order to revert propertly
<bramus> … deos not work with how CSSOM exposes these
<bramus> … and how its implemented
<bramus> … isntead of a flag, this hsould be a new at-rule
<bramus> … @Revertible selector
<bramus> … that would not assume you have a single value for each prop to allow ??? with differnent indexigin
<bramus> … in the issue lea argued against this asahying it would make it easier for authors to opt in and bad for perf
<bramus> … dont agree that making things harder is ??
<bramus> … its backwards
<bramus> … an atrule could be better
<astearns> ack emilio
<bramus> emilio: big +1 to the counterpoints oriol made
<bramus> … this would really complicate how css is exposed in CSSOM and other details
<bramus> … but i have an idea that might work
<bramus> … seems to me that what you want is fallback within same decl block and that is problematic
<bramus> … and fallback within separate decl blocks
<bramus> … with revertable you revert as in some new kind of revert unlike the revert keyword
<bramus> … feels like 2 features
<bramus> … 1 could be a csswide keyword that says ignore
<bramus> … a nice way to represent this would be a top level value function
<bramus> … you would do border-radius: var(--width);
<bramus> … you’d try a list of values instead with a function
<TabAtkins> qq+
<bramus> … and none of them parse then the decl gets ignored
<bramus> lea: sounds like first-valid
<lea> q+ to say this does not address use cases at all
<bramus> TabAtkins: with the addition of that wipe keyword
<dbaron> s/wipe/CSS-wide/
<bramus> emilio: that would achieve what you want
<bramus> TabAtkins: i thkn it does
<bramus> emilio: no need to repeat property name
<lea> q?
<bramus> … and more consistent with everything else
<TabAtkins> ack
<TabAtkins> q-
<dbaron> So I think emilio is proposing try(value1, value2, ignore)
<ntim> emilio: I think it addresses it in a less verbose way
<emilio> right
<oriol> q+
<bramus> lea: oh, that would bake it into first-valid
<bramus> TabAtkins: i gues sthe keyword is not usseful ouside of first-valid
<bramus> lea: it is
<bramus> … more useful than the function
<bramus> … you can have expressions that output the keyword
<bramus> … sounds better
<bramus> … its like revert-declaration
<bramus> … can we do that?>
<bramus> emilio: i think so
<oriol> This is https://github.com//issues/5319
<bramus> … at least more easily than what you proposed
<bramus> lea: its even better
<bramus> astearns: lets go to the queue
<bramus> TabAtkins: emilio, is that only easier at parse time?
<bramus> emilio: no, should work fine at computed value time
<bramus> TabAtkins: how would you treat it as if it has never been said?
<bramus> emilio: when apply decls … so revergint is ???
<bramus> TabAtkins: i see, you have a full cascade list
<lea> s/lea: its even better/lea: if we can actually do that, that is even better!/
<dbaron> s/revergint is ???/reverting is just keeping going/
<bramus> emilio: and you already have that for revert-layer
<bramus> TabAtkins: thought that ws simpler
<bramus> lea: will that work if you have a diffrent decl for the same prop in the same rule
<bramus> emilio: no
<astearns> ack lea
<Zakim> lea, you wanted to say this does not address use cases at all
<bramus> lea: so like revert-rule then
<bramus> … love this
<bramus> … amazing
<bramus> … lets piggy back on revert-layer for the name
<astearns> ack miriam
<dbaron> lea: ... and call it something like revert-rule
<bramus> miriam: emilio’s proposal solves all my issues with this, i like it
<bramus> emilio: naming is chosen by ?? – open to any naming
<astearns> ack dbaron
<emilio> s/??/jet-lagged emilio/
<bramus> dbaron: issue that i was gonna raise that i felt i need a diff between in-rule and across-rule behavior
<bramus> … but if name is revert-rule that is addressed
<TabAtkins> so, replacing the issue example
<TabAtkins>
<TabAtkins> animation: if(style(--effect: grow), 1s grow) !revertable;
<TabAtkins> animation: if(style(--effect: pulse), 4s pulse infinite) !revertable;
<TabAtkins>
<TabAtkins> with
<TabAtkins>
<TabAtkins> animation: if(style(--effect: grow): 1s grow; style(--effect: pulse): 4s pulse infinite; else: revert-rule);
<bramus> … the revert-rule name cleverly addresses that confusion
<astearns> ack oriol
<bramus> oriol: i expressed earlier concerns about messing with the data structures of how rules are stored in impls
<bramus> … adding a keyword was proposed in another issue before
<bramus> … support it
<bramus> … its already implemented that in fallback values hwere you use revert-layer
<bramus> … could auto0-wrap without forcing all this
<bramus> … it convenient
<dbaron> And the proposal seems to be to use https://www.w3.org/TR/css-values-5/#first-valid
<bramus> astearns: do we have a proposed resolution?
<bramus> emilio: dont know status of first-valid
<bramus> emilio: resolution wuold be to add a new revert-rule css-wide keyword that reverses to the previous declaration in any rule (not within cascade layers)
<bramus> dbaron: in a different rule
<bramus> emilio: right
<astearns> ack fantasai
<bramus> fantasai: so a rule is selectr { decls }
<bramus> … so woul dyou revert entire block, or only the 1 prop?
<bramus> TabAtkins: 1 prop
<bramus> fantasai: so only that prop bu tout of tha tentire block of decls
<bramus> emilio: yes
<bramus> … conceptullay there is no order decl for tha tprop in that block
<bramus> fantasai: yers
<bramus> s/yers/yes
<kizu> q+
<bramus> fantasai: makes sens
<ntim> I think TabAtkins' example illustrates it somewhat well:
<ntim> animation: if(style(--effect: grow): 1s grow; style(--effect: pulse): 4s pulse infinite; else: revert-rule);
<bramus> … but thye cant do it in same block
<bramus> emilio: but can use first-valid for that
<oriol> q+
<bramus> TabAtkins: yes, already have first-valid that covers this
<bramus> fantasai: so you”d always have to wrap the decl value in first-valid
<bramus> TabAtkins: or use if()
<bramus> lea: can we make first valid work like that by default?
<bramus> TabAtkins: would prefert not. usually to be explicti when doing this stuff
<lea> q+ to ask, can we have a function that works like first-valid() but has this fallback automatically
<bramus> fantasai: this si gonna be author-to-author - you are not gonna creata this list. overhead when using first-valid to attach the keyword at the end is not ergonomic
<bramus> … other than that it seams reaosnable
<lea> +1 to fantasai. I missed what the reason to always wrap in a function is?
<bramus> emilio: would choose a different name for the fn, maybe try
<bramus> fantasai: its the fact that you need to use the fn, not the keyword
<bramus> emilio: syntactic overhead is already there
<bramus> fantasai: what if you did !try
<bramus> emilio: then you have the same cssom issues
<bramus> fantasai: would behave as if you wrap it in first-valid
<astearns> qq+
<fantasai> foo: first-valid(stuff, revert-rule); -> foo: stuff !try;
<bramus> emilio: think i misunderstood – so elika is saying that !try implitcly wraps it i first-valid(…, revert-thing) by itself
<bramus> … its less obvious when you have
<bramus> … if i use !try , ’d expect reverting to prev decl
<bramus> fantasai: i’m saying its a lot to write with first-valid + keyword
<lea> Another downside with this approach is that it's less composable. Often values are composed from other variables, now every point of usage needs to know about this
<bramus> … does not require much syntactic structure to this
<bramus> … authors woul duse this a lot i think
<bramus> astearns: can add the short name in the future
<astearns> ack astearns
<Zakim> astearns, you wanted to react to fantasai
<astearns> ack kizu
<astearns> s/short name/short syntax/
<bramus> kizu: should work in first-valid, else would need to work as fallback for custom props
<bramus> … only chrome handles css-wide keywrods in custo m props properly i think
<kbabbitt> s/else would need/also would need/
<bramus> … !try as syntax sugar would be nice to have as well
<astearns> ack oriol
<bramus> oriol: i recall that tab strongly saying that first-valid should only check parse validity, not the computed values
<bramus> … do we change first-valid then?
<bramus> TabAtkins: dont recall that, let me check
<bramus> … if thats the case then we use a diff fn or change first-valid
<astearns> ack lea
<Zakim> lea, you wanted to ask, can we have a function that works like first-valid() but has this fallback automatically
<bramus> lea: why did we conclude that we need to wrap in fn?
<bramus> TabAtkins: the css-wide keyword is a no-op
<bramus> emilio: would override decl in the same block
<bramus> TabAtkins: if you do color: revert-rule is same as writing it not at all
<bramus> lea: in prcatice youd not do that
<bramus> … you could var(--sth, revert-rule)
<bramus> TabAtkins: that is fine
<bramus> emilio: that would work
<bramus> TabAtkins: if you use it literally you need the fn
<bramus> emilio: the variable fallback case addresses some of the syntactic boiler plate thing
<bramus> TabAtkins: i see an issue about first-valid to decide on its name
<bramus> TabAtkins: can go for resolution about revert-rule keyword
<bramus> astearns: comments or qs?
<bramus> … objections?
<bramus> RESOLVED: Add revert-rule as a css-wide keyword

@benface
Copy link

benface commented Feb 25, 2025

I apologize if this was answered in the minutes, I had some trouble understanding all of it... but why revert-rule and not revert-declaration? Does it actually revert all the declarations in the rule as soon as one property is set to that value? If so, that sounds really surprising.

@benface
Copy link

benface commented Feb 25, 2025

Ah, I just thought about it and can see why revert-rule makes sense. It reverts the property's value to whatever the previous rule that matches sets it to, kinda like how revert-layer reverts it to what the @layer below the current one sets it to.

Still, I wonder if a simpler keyword like cancel would make more sense, because it's basically used to cancel the current declaration, right?

@Loirooriol
Copy link
Contributor

No, it doesn't just cancel the current declaration. It reverts to another rule, so it can also cancel other declarations in the current rule.

@benface
Copy link

benface commented Feb 25, 2025

Ah, so the name is not actually counterintuitive, but the feature is, IMO. 😅 What's the reason for giving any single declaration the ability to affect all the declarations of the entire rule it's in? Seems like a first in CSS. Does it include declarations in parent rules, in the case of nesting?

@benface
Copy link

benface commented Feb 25, 2025

So if I'm understanding it right, in this example:

button {
  background-color: red;
  --maybe-revert: revert-rule;
}

button:hover {
  --maybe-revert: dont-actually-revert;
}

The button's background color would be red on hover only?

@Loirooriol
Copy link
Contributor

Loirooriol commented Feb 25, 2025

See my comments and meeting minutes where I explained why just cancelling the current declaration shouldn't be done with style rules (it would need to be a new kind of rule).

Also, I didn't say revert-rule cancels ALL declarations in the current rule. It cancels declarations for the same property, or for properties that share a computed value.

@benface
Copy link

benface commented Feb 25, 2025

Ahhhh, my bad. Thank you @Loirooriol, I understand now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: TPAC/FTF agenda items
Status: Friday morning
Development

No branches or pull requests

10 participants