fix: Fix regressions in Field. - #9011

Merged
gonfunko merged 1 commit into
RaspberryPiFoundation:rc/v12.0.0from
gonfunko:field-regression
May 13, 2025
Merged

fix: Fix regressions in Field.#9011
gonfunko merged 1 commit into
RaspberryPiFoundation:rc/v12.0.0from
gonfunko:field-regression

Conversation

@gonfunko

Copy link
Copy Markdown
Contributor

The basics

The details

Resolves

Fixes#9005

Proposed Changes

This PR fixes several regressions in Field introduced by an earlier change:

  • It restores the Field.NBSP constant, which was in fact in use
  • It makes empty input fields have a reasonable minimum width
  • It prevents sequential spaces from collapsing when rendering a field

@gonfunko
gonfunko requested a review from cpcallenMay 7, 2025 20:44
@gonfunko
gonfunko requested a review from a team as a code ownerMay 7, 2025 20:44
@github-actionsgithub-actionsBot added the PR: fix Fixes a bug label May 7, 2025
Comment threadcore/field.ts
Comment on lines +117 to +137
/** This field's dimensions. */
private size: Size = new Size(0, 0);

/**
* Gets the size of this field. Because getSize() and updateSize() have side
* effects, this acts as a shim for subclasses which wish to adjust field
* bounds when setting/getting the size without triggering unwanted rendering
* or other side effects. Note that subclasses must override *both* get and
* set if either is overridden; the implementation may just call directly
* through to super, but it must exist per the JS spec.
*/
protected get size_(): Size {
return this.size;
}

/**
* Sets the size of this field.
*/
protected set size_(newValue: Size) {
this.size = newValue;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Styleguide says "At least one accessor for a property must be non-trivial: do not define "pass-through" accessors only for the purpose of hiding a property."

I gather you are doing this in order to be able to write super.size_ = newValue in FieldInput's get size_ accessor, but I think a better approach might be to have updateSize_ enforce a minimum width (possibly in Field but preferably in FieldInput). (Alas, updateSize_ isn't written in a way that makes it easy to override for this particular purpose.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you noted, updateSize_() is not well-suited to being overridden, and short of introducing a setSizeWithoutSideEffects() method I couldn't see a way to refactor it to be more amendable. Likewise, overriding getSize() (or calling it from updateSize_() instead of accessing the size_ property directly) is problematic due to its side effects of rerendering the field. Hence this seemed like the least-bad option, as it preserves backwards compatibility and allows for subclasses to adjust the size as they need. If you have an idea for how updateSize_() might be refactored I'd certainly be open to that though!

@cpcallencpcallenMay 9, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to think about this for a bit, but I think I see a way to do this without needing to have accessors on Field.

The obvious approach is to have the get and set accessors on the subclasses that want to enforce a minimum width only, and leave Field alone. As you doubtless noticed, the reason this doesn't work is that the Field constructor sets .size_, and that results in (e.g.) FieldInput instances having a .size_ property that is found before the get size_/set size_ accessors on FieldInput.prototype.

But if the Field constructor either didn't set that property if there were accessors, like so:

classFieldInput{constructor(){this.size_??=newSize(0,0);}}

or if the FieldInput constructor removed it, like so:

classFieldInput{constructor(){super();deletethis.size_;}}

then I think it's possible to have the behaviour you have here without the mostly-useless accessors on Field.prototype. Both approaches are a bit kludgy, and the delete approach might have some performance consequences (it's generally preferable not to change the shape of an object, though here at least it's being done at construction time). I'm genuinely not sure either is clearly preferable to your proposal, but I offer them for your consideration.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, I think that even if you keep the get/set accessors on Field it might be preferable to not call them from the corresponding accessors on FieldInput, either by making size protected (so that that FieldInput's accessors can use it directly) or by having FieldInput keep it's own separate Size property. A pair of accessors ought to be interchangeable with a data property, in my view, but you have revealed that that isn't always the case. I'd like to at least not tie ourselves too tightly to using accessors everywhere.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the mostly-useless accessors (which most fields can just ignore) over the kludgy approaches mentioned above.

Comment threadcore/field_image.ts
@cpcallen

Copy link
Copy Markdown
Collaborator

(Restoration of .NBSP and space-to-NBSP anti-collapse code all LGTM, though.)

Comment threadcore/field.ts
protected size_: Size;

/** This field's dimensions. */
private size: Size = new Size(0, 0);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style guide suggests something like sizeInternal:

Suggested change
private size: Size=newSize(0,0);
privatesizeInternal: Size=newSize(0,0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused about this, why? it's already private, why would we also add the word internal

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quoting the Styleguide section on get and set accesors says:

If an accessor is used to hide a class property, the hidden property may be prefixed or suffixed with any whole word, like internal or wrapped.

They do not provide a rational, but I suspect the reason is to avoid clashing with the name of the accessors. In this case the accessors are named size_ (which, with the trailing underscore, is contrary to current naming policy) but if it were size then the internal property would need to be named something else.

@maribethbmaribethb self-assigned this May 13, 2025
Comment threadcore/field.ts
Comment on lines +117 to +137
/** This field's dimensions. */
private size: Size = new Size(0, 0);

/**
* Gets the size of this field. Because getSize() and updateSize() have side
* effects, this acts as a shim for subclasses which wish to adjust field
* bounds when setting/getting the size without triggering unwanted rendering
* or other side effects. Note that subclasses must override *both* get and
* set if either is overridden; the implementation may just call directly
* through to super, but it must exist per the JS spec.
*/
protected get size_(): Size {
return this.size;
}

/**
* Sets the size of this field.
*/
protected set size_(newValue: Size) {
this.size = newValue;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the mostly-useless accessors (which most fields can just ignore) over the kludgy approaches mentioned above.

Comment threadcore/field.ts
protected size_: Size;

/** This field's dimensions. */
private size: Size = new Size(0, 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused about this, why? it's already private, why would we also add the word internal

@gonfunko
gonfunko merged commit 14e1ef6 into RaspberryPiFoundation:rc/v12.0.0May 13, 2025
@gonfunko
gonfunko deleted the field-regression branch May 13, 2025 21:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: fixFixes a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@gonfunko@cpcallen@maribethb
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix: Fix regressions in Field. - #9011

Merged
gonfunko merged 1 commit into
RaspberryPiFoundation:rc/v12.0.0from
gonfunko:field-regression
May 13, 2025
Merged

fix: Fix regressions in Field.#9011
gonfunko merged 1 commit into
RaspberryPiFoundation:rc/v12.0.0from
gonfunko:field-regression

Conversation

@gonfunko

Copy link
Copy Markdown
Contributor

The basics

The details

Resolves

Fixes#9005

Proposed Changes

This PR fixes several regressions in Field introduced by an earlier change:

  • It restores the Field.NBSP constant, which was in fact in use
  • It makes empty input fields have a reasonable minimum width
  • It prevents sequential spaces from collapsing when rendering a field

@gonfunko
gonfunko requested a review from cpcallenMay 7, 2025 20:44
@gonfunko
gonfunko requested a review from a team as a code ownerMay 7, 2025 20:44
@github-actionsgithub-actionsBot added the PR: fix Fixes a bug label May 7, 2025
Comment threadcore/field.ts
Comment on lines +117 to +137
/** This field's dimensions. */
private size: Size = new Size(0, 0);

/**
* Gets the size of this field. Because getSize() and updateSize() have side
* effects, this acts as a shim for subclasses which wish to adjust field
* bounds when setting/getting the size without triggering unwanted rendering
* or other side effects. Note that subclasses must override *both* get and
* set if either is overridden; the implementation may just call directly
* through to super, but it must exist per the JS spec.
*/
protected get size_(): Size {
return this.size;
}

/**
* Sets the size of this field.
*/
protected set size_(newValue: Size) {
this.size = newValue;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Styleguide says "At least one accessor for a property must be non-trivial: do not define "pass-through" accessors only for the purpose of hiding a property."

I gather you are doing this in order to be able to write super.size_ = newValue in FieldInput's get size_ accessor, but I think a better approach might be to have updateSize_ enforce a minimum width (possibly in Field but preferably in FieldInput). (Alas, updateSize_ isn't written in a way that makes it easy to override for this particular purpose.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you noted, updateSize_() is not well-suited to being overridden, and short of introducing a setSizeWithoutSideEffects() method I couldn't see a way to refactor it to be more amendable. Likewise, overriding getSize() (or calling it from updateSize_() instead of accessing the size_ property directly) is problematic due to its side effects of rerendering the field. Hence this seemed like the least-bad option, as it preserves backwards compatibility and allows for subclasses to adjust the size as they need. If you have an idea for how updateSize_() might be refactored I'd certainly be open to that though!

@cpcallencpcallenMay 9, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to think about this for a bit, but I think I see a way to do this without needing to have accessors on Field.

The obvious approach is to have the get and set accessors on the subclasses that want to enforce a minimum width only, and leave Field alone. As you doubtless noticed, the reason this doesn't work is that the Field constructor sets .size_, and that results in (e.g.) FieldInput instances having a .size_ property that is found before the get size_/set size_ accessors on FieldInput.prototype.

But if the Field constructor either didn't set that property if there were accessors, like so:

classFieldInput{constructor(){this.size_??=newSize(0,0);}}

or if the FieldInput constructor removed it, like so:

classFieldInput{constructor(){super();deletethis.size_;}}

then I think it's possible to have the behaviour you have here without the mostly-useless accessors on Field.prototype. Both approaches are a bit kludgy, and the delete approach might have some performance consequences (it's generally preferable not to change the shape of an object, though here at least it's being done at construction time). I'm genuinely not sure either is clearly preferable to your proposal, but I offer them for your consideration.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, I think that even if you keep the get/set accessors on Field it might be preferable to not call them from the corresponding accessors on FieldInput, either by making size protected (so that that FieldInput's accessors can use it directly) or by having FieldInput keep it's own separate Size property. A pair of accessors ought to be interchangeable with a data property, in my view, but you have revealed that that isn't always the case. I'd like to at least not tie ourselves too tightly to using accessors everywhere.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the mostly-useless accessors (which most fields can just ignore) over the kludgy approaches mentioned above.

Comment threadcore/field_image.ts
@cpcallen

Copy link
Copy Markdown
Collaborator

(Restoration of .NBSP and space-to-NBSP anti-collapse code all LGTM, though.)

Comment threadcore/field.ts
protected size_: Size;

/** This field's dimensions. */
private size: Size = new Size(0, 0);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style guide suggests something like sizeInternal:

Suggested change
private size: Size=newSize(0,0);
privatesizeInternal: Size=newSize(0,0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused about this, why? it's already private, why would we also add the word internal

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quoting the Styleguide section on get and set accesors says:

If an accessor is used to hide a class property, the hidden property may be prefixed or suffixed with any whole word, like internal or wrapped.

They do not provide a rational, but I suspect the reason is to avoid clashing with the name of the accessors. In this case the accessors are named size_ (which, with the trailing underscore, is contrary to current naming policy) but if it were size then the internal property would need to be named something else.

@maribethbmaribethb self-assigned this May 13, 2025
Comment threadcore/field.ts
Comment on lines +117 to +137
/** This field's dimensions. */
private size: Size = new Size(0, 0);

/**
* Gets the size of this field. Because getSize() and updateSize() have side
* effects, this acts as a shim for subclasses which wish to adjust field
* bounds when setting/getting the size without triggering unwanted rendering
* or other side effects. Note that subclasses must override *both* get and
* set if either is overridden; the implementation may just call directly
* through to super, but it must exist per the JS spec.
*/
protected get size_(): Size {
return this.size;
}

/**
* Sets the size of this field.
*/
protected set size_(newValue: Size) {
this.size = newValue;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the mostly-useless accessors (which most fields can just ignore) over the kludgy approaches mentioned above.

Comment threadcore/field.ts
protected size_: Size;

/** This field's dimensions. */
private size: Size = new Size(0, 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused about this, why? it's already private, why would we also add the word internal

@gonfunko
gonfunko merged commit 14e1ef6 into RaspberryPiFoundation:rc/v12.0.0May 13, 2025
@gonfunko
gonfunko deleted the field-regression branch May 13, 2025 21:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: fixFixes a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@gonfunko@cpcallen@maribethb
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: Fix regressions in Field. - #9011

Merged
gonfunko merged 1 commit into
RaspberryPiFoundation:rc/v12.0.0from
gonfunko:field-regression
May 13, 2025
Merged

fix: Fix regressions in Field.#9011
gonfunko merged 1 commit into
RaspberryPiFoundation:rc/v12.0.0from
gonfunko:field-regression

Conversation

@gonfunko

Copy link
Copy Markdown
Contributor

The basics

The details

Resolves

Fixes#9005

Proposed Changes

This PR fixes several regressions in Field introduced by an earlier change:

  • It restores the Field.NBSP constant, which was in fact in use
  • It makes empty input fields have a reasonable minimum width
  • It prevents sequential spaces from collapsing when rendering a field

@gonfunko
gonfunko requested a review from cpcallenMay 7, 2025 20:44
@gonfunko
gonfunko requested a review from a team as a code ownerMay 7, 2025 20:44
@github-actionsgithub-actionsBot added the PR: fix Fixes a bug label May 7, 2025
Comment threadcore/field.ts
Comment on lines +117 to +137
/** This field's dimensions. */
private size: Size = new Size(0, 0);

/**
* Gets the size of this field. Because getSize() and updateSize() have side
* effects, this acts as a shim for subclasses which wish to adjust field
* bounds when setting/getting the size without triggering unwanted rendering
* or other side effects. Note that subclasses must override *both* get and
* set if either is overridden; the implementation may just call directly
* through to super, but it must exist per the JS spec.
*/
protected get size_(): Size {
return this.size;
}

/**
* Sets the size of this field.
*/
protected set size_(newValue: Size) {
this.size = newValue;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Styleguide says "At least one accessor for a property must be non-trivial: do not define "pass-through" accessors only for the purpose of hiding a property."

I gather you are doing this in order to be able to write super.size_ = newValue in FieldInput's get size_ accessor, but I think a better approach might be to have updateSize_ enforce a minimum width (possibly in Field but preferably in FieldInput). (Alas, updateSize_ isn't written in a way that makes it easy to override for this particular purpose.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you noted, updateSize_() is not well-suited to being overridden, and short of introducing a setSizeWithoutSideEffects() method I couldn't see a way to refactor it to be more amendable. Likewise, overriding getSize() (or calling it from updateSize_() instead of accessing the size_ property directly) is problematic due to its side effects of rerendering the field. Hence this seemed like the least-bad option, as it preserves backwards compatibility and allows for subclasses to adjust the size as they need. If you have an idea for how updateSize_() might be refactored I'd certainly be open to that though!

@cpcallencpcallenMay 9, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to think about this for a bit, but I think I see a way to do this without needing to have accessors on Field.

The obvious approach is to have the get and set accessors on the subclasses that want to enforce a minimum width only, and leave Field alone. As you doubtless noticed, the reason this doesn't work is that the Field constructor sets .size_, and that results in (e.g.) FieldInput instances having a .size_ property that is found before the get size_/set size_ accessors on FieldInput.prototype.

But if the Field constructor either didn't set that property if there were accessors, like so:

classFieldInput{constructor(){this.size_??=newSize(0,0);}}

or if the FieldInput constructor removed it, like so:

classFieldInput{constructor(){super();deletethis.size_;}}

then I think it's possible to have the behaviour you have here without the mostly-useless accessors on Field.prototype. Both approaches are a bit kludgy, and the delete approach might have some performance consequences (it's generally preferable not to change the shape of an object, though here at least it's being done at construction time). I'm genuinely not sure either is clearly preferable to your proposal, but I offer them for your consideration.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, I think that even if you keep the get/set accessors on Field it might be preferable to not call them from the corresponding accessors on FieldInput, either by making size protected (so that that FieldInput's accessors can use it directly) or by having FieldInput keep it's own separate Size property. A pair of accessors ought to be interchangeable with a data property, in my view, but you have revealed that that isn't always the case. I'd like to at least not tie ourselves too tightly to using accessors everywhere.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the mostly-useless accessors (which most fields can just ignore) over the kludgy approaches mentioned above.

Comment threadcore/field_image.ts
@cpcallen

Copy link
Copy Markdown
Collaborator

(Restoration of .NBSP and space-to-NBSP anti-collapse code all LGTM, though.)

Comment threadcore/field.ts
protected size_: Size;

/** This field's dimensions. */
private size: Size = new Size(0, 0);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style guide suggests something like sizeInternal:

Suggested change
private size: Size=newSize(0,0);
privatesizeInternal: Size=newSize(0,0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused about this, why? it's already private, why would we also add the word internal

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quoting the Styleguide section on get and set accesors says:

If an accessor is used to hide a class property, the hidden property may be prefixed or suffixed with any whole word, like internal or wrapped.

They do not provide a rational, but I suspect the reason is to avoid clashing with the name of the accessors. In this case the accessors are named size_ (which, with the trailing underscore, is contrary to current naming policy) but if it were size then the internal property would need to be named something else.

@maribethbmaribethb self-assigned this May 13, 2025
Comment threadcore/field.ts
Comment on lines +117 to +137
/** This field's dimensions. */
private size: Size = new Size(0, 0);

/**
* Gets the size of this field. Because getSize() and updateSize() have side
* effects, this acts as a shim for subclasses which wish to adjust field
* bounds when setting/getting the size without triggering unwanted rendering
* or other side effects. Note that subclasses must override *both* get and
* set if either is overridden; the implementation may just call directly
* through to super, but it must exist per the JS spec.
*/
protected get size_(): Size {
return this.size;
}

/**
* Sets the size of this field.
*/
protected set size_(newValue: Size) {
this.size = newValue;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the mostly-useless accessors (which most fields can just ignore) over the kludgy approaches mentioned above.

Comment threadcore/field.ts
protected size_: Size;

/** This field's dimensions. */
private size: Size = new Size(0, 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused about this, why? it's already private, why would we also add the word internal

@gonfunko
gonfunko merged commit 14e1ef6 into RaspberryPiFoundation:rc/v12.0.0May 13, 2025
@gonfunko
gonfunko deleted the field-regression branch May 13, 2025 21:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: fixFixes a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@gonfunko@cpcallen@maribethb
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: Fix regressions in Field. - #9011

Merged
gonfunko merged 1 commit into
RaspberryPiFoundation:rc/v12.0.0from
gonfunko:field-regression
May 13, 2025
Merged

fix: Fix regressions in Field.#9011
gonfunko merged 1 commit into
RaspberryPiFoundation:rc/v12.0.0from
gonfunko:field-regression

Conversation

@gonfunko

Copy link
Copy Markdown
Contributor

The basics

The details

Resolves

Fixes#9005

Proposed Changes

This PR fixes several regressions in Field introduced by an earlier change:

  • It restores the Field.NBSP constant, which was in fact in use
  • It makes empty input fields have a reasonable minimum width
  • It prevents sequential spaces from collapsing when rendering a field

@gonfunko
gonfunko requested a review from cpcallenMay 7, 2025 20:44
@gonfunko
gonfunko requested a review from a team as a code ownerMay 7, 2025 20:44
@github-actionsgithub-actionsBot added the PR: fix Fixes a bug label May 7, 2025
Comment threadcore/field.ts
Comment on lines +117 to +137
/** This field's dimensions. */
private size: Size = new Size(0, 0);

/**
* Gets the size of this field. Because getSize() and updateSize() have side
* effects, this acts as a shim for subclasses which wish to adjust field
* bounds when setting/getting the size without triggering unwanted rendering
* or other side effects. Note that subclasses must override *both* get and
* set if either is overridden; the implementation may just call directly
* through to super, but it must exist per the JS spec.
*/
protected get size_(): Size {
return this.size;
}

/**
* Sets the size of this field.
*/
protected set size_(newValue: Size) {
this.size = newValue;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Styleguide says "At least one accessor for a property must be non-trivial: do not define "pass-through" accessors only for the purpose of hiding a property."

I gather you are doing this in order to be able to write super.size_ = newValue in FieldInput's get size_ accessor, but I think a better approach might be to have updateSize_ enforce a minimum width (possibly in Field but preferably in FieldInput). (Alas, updateSize_ isn't written in a way that makes it easy to override for this particular purpose.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you noted, updateSize_() is not well-suited to being overridden, and short of introducing a setSizeWithoutSideEffects() method I couldn't see a way to refactor it to be more amendable. Likewise, overriding getSize() (or calling it from updateSize_() instead of accessing the size_ property directly) is problematic due to its side effects of rerendering the field. Hence this seemed like the least-bad option, as it preserves backwards compatibility and allows for subclasses to adjust the size as they need. If you have an idea for how updateSize_() might be refactored I'd certainly be open to that though!

@cpcallencpcallenMay 9, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to think about this for a bit, but I think I see a way to do this without needing to have accessors on Field.

The obvious approach is to have the get and set accessors on the subclasses that want to enforce a minimum width only, and leave Field alone. As you doubtless noticed, the reason this doesn't work is that the Field constructor sets .size_, and that results in (e.g.) FieldInput instances having a .size_ property that is found before the get size_/set size_ accessors on FieldInput.prototype.

But if the Field constructor either didn't set that property if there were accessors, like so:

classFieldInput{constructor(){this.size_??=newSize(0,0);}}

or if the FieldInput constructor removed it, like so:

classFieldInput{constructor(){super();deletethis.size_;}}

then I think it's possible to have the behaviour you have here without the mostly-useless accessors on Field.prototype. Both approaches are a bit kludgy, and the delete approach might have some performance consequences (it's generally preferable not to change the shape of an object, though here at least it's being done at construction time). I'm genuinely not sure either is clearly preferable to your proposal, but I offer them for your consideration.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, I think that even if you keep the get/set accessors on Field it might be preferable to not call them from the corresponding accessors on FieldInput, either by making size protected (so that that FieldInput's accessors can use it directly) or by having FieldInput keep it's own separate Size property. A pair of accessors ought to be interchangeable with a data property, in my view, but you have revealed that that isn't always the case. I'd like to at least not tie ourselves too tightly to using accessors everywhere.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the mostly-useless accessors (which most fields can just ignore) over the kludgy approaches mentioned above.

Comment threadcore/field_image.ts
@cpcallen

Copy link
Copy Markdown
Collaborator

(Restoration of .NBSP and space-to-NBSP anti-collapse code all LGTM, though.)

Comment threadcore/field.ts
protected size_: Size;

/** This field's dimensions. */
private size: Size = new Size(0, 0);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style guide suggests something like sizeInternal:

Suggested change
private size: Size=newSize(0,0);
privatesizeInternal: Size=newSize(0,0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused about this, why? it's already private, why would we also add the word internal

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quoting the Styleguide section on get and set accesors says:

If an accessor is used to hide a class property, the hidden property may be prefixed or suffixed with any whole word, like internal or wrapped.

They do not provide a rational, but I suspect the reason is to avoid clashing with the name of the accessors. In this case the accessors are named size_ (which, with the trailing underscore, is contrary to current naming policy) but if it were size then the internal property would need to be named something else.

@maribethbmaribethb self-assigned this May 13, 2025
Comment threadcore/field.ts
Comment on lines +117 to +137
/** This field's dimensions. */
private size: Size = new Size(0, 0);

/**
* Gets the size of this field. Because getSize() and updateSize() have side
* effects, this acts as a shim for subclasses which wish to adjust field
* bounds when setting/getting the size without triggering unwanted rendering
* or other side effects. Note that subclasses must override *both* get and
* set if either is overridden; the implementation may just call directly
* through to super, but it must exist per the JS spec.
*/
protected get size_(): Size {
return this.size;
}

/**
* Sets the size of this field.
*/
protected set size_(newValue: Size) {
this.size = newValue;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the mostly-useless accessors (which most fields can just ignore) over the kludgy approaches mentioned above.

Comment threadcore/field.ts
protected size_: Size;

/** This field's dimensions. */
private size: Size = new Size(0, 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused about this, why? it's already private, why would we also add the word internal

@gonfunko
gonfunko merged commit 14e1ef6 into RaspberryPiFoundation:rc/v12.0.0May 13, 2025
@gonfunko
gonfunko deleted the field-regression branch May 13, 2025 21:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: fixFixes a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@gonfunko@cpcallen@maribethb
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix: Fix regressions in Field. - #9011

Merged
gonfunko merged 1 commit into
RaspberryPiFoundation:rc/v12.0.0from
gonfunko:field-regression
May 13, 2025
Merged

fix: Fix regressions in Field.#9011
gonfunko merged 1 commit into
RaspberryPiFoundation:rc/v12.0.0from
gonfunko:field-regression

Conversation

@gonfunko

Copy link
Copy Markdown
Contributor

The basics

The details

Resolves

Fixes#9005

Proposed Changes

This PR fixes several regressions in Field introduced by an earlier change:

  • It restores the Field.NBSP constant, which was in fact in use
  • It makes empty input fields have a reasonable minimum width
  • It prevents sequential spaces from collapsing when rendering a field

@gonfunko
gonfunko requested a review from cpcallenMay 7, 2025 20:44
@gonfunko
gonfunko requested a review from a team as a code ownerMay 7, 2025 20:44
@github-actionsgithub-actionsBot added the PR: fix Fixes a bug label May 7, 2025
Comment threadcore/field.ts
Comment on lines +117 to +137
/** This field's dimensions. */
private size: Size = new Size(0, 0);

/**
* Gets the size of this field. Because getSize() and updateSize() have side
* effects, this acts as a shim for subclasses which wish to adjust field
* bounds when setting/getting the size without triggering unwanted rendering
* or other side effects. Note that subclasses must override *both* get and
* set if either is overridden; the implementation may just call directly
* through to super, but it must exist per the JS spec.
*/
protected get size_(): Size {
return this.size;
}

/**
* Sets the size of this field.
*/
protected set size_(newValue: Size) {
this.size = newValue;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Styleguide says "At least one accessor for a property must be non-trivial: do not define "pass-through" accessors only for the purpose of hiding a property."

I gather you are doing this in order to be able to write super.size_ = newValue in FieldInput's get size_ accessor, but I think a better approach might be to have updateSize_ enforce a minimum width (possibly in Field but preferably in FieldInput). (Alas, updateSize_ isn't written in a way that makes it easy to override for this particular purpose.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you noted, updateSize_() is not well-suited to being overridden, and short of introducing a setSizeWithoutSideEffects() method I couldn't see a way to refactor it to be more amendable. Likewise, overriding getSize() (or calling it from updateSize_() instead of accessing the size_ property directly) is problematic due to its side effects of rerendering the field. Hence this seemed like the least-bad option, as it preserves backwards compatibility and allows for subclasses to adjust the size as they need. If you have an idea for how updateSize_() might be refactored I'd certainly be open to that though!

@cpcallencpcallenMay 9, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to think about this for a bit, but I think I see a way to do this without needing to have accessors on Field.

The obvious approach is to have the get and set accessors on the subclasses that want to enforce a minimum width only, and leave Field alone. As you doubtless noticed, the reason this doesn't work is that the Field constructor sets .size_, and that results in (e.g.) FieldInput instances having a .size_ property that is found before the get size_/set size_ accessors on FieldInput.prototype.

But if the Field constructor either didn't set that property if there were accessors, like so:

classFieldInput{constructor(){this.size_??=newSize(0,0);}}

or if the FieldInput constructor removed it, like so:

classFieldInput{constructor(){super();deletethis.size_;}}

then I think it's possible to have the behaviour you have here without the mostly-useless accessors on Field.prototype. Both approaches are a bit kludgy, and the delete approach might have some performance consequences (it's generally preferable not to change the shape of an object, though here at least it's being done at construction time). I'm genuinely not sure either is clearly preferable to your proposal, but I offer them for your consideration.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, I think that even if you keep the get/set accessors on Field it might be preferable to not call them from the corresponding accessors on FieldInput, either by making size protected (so that that FieldInput's accessors can use it directly) or by having FieldInput keep it's own separate Size property. A pair of accessors ought to be interchangeable with a data property, in my view, but you have revealed that that isn't always the case. I'd like to at least not tie ourselves too tightly to using accessors everywhere.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the mostly-useless accessors (which most fields can just ignore) over the kludgy approaches mentioned above.

Comment threadcore/field_image.ts
@cpcallen

Copy link
Copy Markdown
Collaborator

(Restoration of .NBSP and space-to-NBSP anti-collapse code all LGTM, though.)

Comment threadcore/field.ts
protected size_: Size;

/** This field's dimensions. */
private size: Size = new Size(0, 0);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style guide suggests something like sizeInternal:

Suggested change
private size: Size=newSize(0,0);
privatesizeInternal: Size=newSize(0,0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused about this, why? it's already private, why would we also add the word internal

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quoting the Styleguide section on get and set accesors says:

If an accessor is used to hide a class property, the hidden property may be prefixed or suffixed with any whole word, like internal or wrapped.

They do not provide a rational, but I suspect the reason is to avoid clashing with the name of the accessors. In this case the accessors are named size_ (which, with the trailing underscore, is contrary to current naming policy) but if it were size then the internal property would need to be named something else.

@maribethbmaribethb self-assigned this May 13, 2025
Comment threadcore/field.ts
Comment on lines +117 to +137
/** This field's dimensions. */
private size: Size = new Size(0, 0);

/**
* Gets the size of this field. Because getSize() and updateSize() have side
* effects, this acts as a shim for subclasses which wish to adjust field
* bounds when setting/getting the size without triggering unwanted rendering
* or other side effects. Note that subclasses must override *both* get and
* set if either is overridden; the implementation may just call directly
* through to super, but it must exist per the JS spec.
*/
protected get size_(): Size {
return this.size;
}

/**
* Sets the size of this field.
*/
protected set size_(newValue: Size) {
this.size = newValue;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the mostly-useless accessors (which most fields can just ignore) over the kludgy approaches mentioned above.

Comment threadcore/field.ts
protected size_: Size;

/** This field's dimensions. */
private size: Size = new Size(0, 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused about this, why? it's already private, why would we also add the word internal

@gonfunko
gonfunko merged commit 14e1ef6 into RaspberryPiFoundation:rc/v12.0.0May 13, 2025
@gonfunko
gonfunko deleted the field-regression branch May 13, 2025 21:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: fixFixes a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@gonfunko@cpcallen@maribethb
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: Fix regressions in Field. - #9011

Merged
gonfunko merged 1 commit into
RaspberryPiFoundation:rc/v12.0.0from
gonfunko:field-regression
May 13, 2025
Merged

fix: Fix regressions in Field.#9011
gonfunko merged 1 commit into
RaspberryPiFoundation:rc/v12.0.0from
gonfunko:field-regression

Conversation

@gonfunko

Copy link
Copy Markdown
Contributor

The basics

The details

Resolves

Fixes#9005

Proposed Changes

This PR fixes several regressions in Field introduced by an earlier change:

  • It restores the Field.NBSP constant, which was in fact in use
  • It makes empty input fields have a reasonable minimum width
  • It prevents sequential spaces from collapsing when rendering a field

@gonfunko
gonfunko requested a review from cpcallenMay 7, 2025 20:44
@gonfunko
gonfunko requested a review from a team as a code ownerMay 7, 2025 20:44
@github-actionsgithub-actionsBot added the PR: fix Fixes a bug label May 7, 2025
Comment threadcore/field.ts
Comment on lines +117 to +137
/** This field's dimensions. */
private size: Size = new Size(0, 0);

/**
* Gets the size of this field. Because getSize() and updateSize() have side
* effects, this acts as a shim for subclasses which wish to adjust field
* bounds when setting/getting the size without triggering unwanted rendering
* or other side effects. Note that subclasses must override *both* get and
* set if either is overridden; the implementation may just call directly
* through to super, but it must exist per the JS spec.
*/
protected get size_(): Size {
return this.size;
}

/**
* Sets the size of this field.
*/
protected set size_(newValue: Size) {
this.size = newValue;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Styleguide says "At least one accessor for a property must be non-trivial: do not define "pass-through" accessors only for the purpose of hiding a property."

I gather you are doing this in order to be able to write super.size_ = newValue in FieldInput's get size_ accessor, but I think a better approach might be to have updateSize_ enforce a minimum width (possibly in Field but preferably in FieldInput). (Alas, updateSize_ isn't written in a way that makes it easy to override for this particular purpose.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you noted, updateSize_() is not well-suited to being overridden, and short of introducing a setSizeWithoutSideEffects() method I couldn't see a way to refactor it to be more amendable. Likewise, overriding getSize() (or calling it from updateSize_() instead of accessing the size_ property directly) is problematic due to its side effects of rerendering the field. Hence this seemed like the least-bad option, as it preserves backwards compatibility and allows for subclasses to adjust the size as they need. If you have an idea for how updateSize_() might be refactored I'd certainly be open to that though!

@cpcallencpcallenMay 9, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to think about this for a bit, but I think I see a way to do this without needing to have accessors on Field.

The obvious approach is to have the get and set accessors on the subclasses that want to enforce a minimum width only, and leave Field alone. As you doubtless noticed, the reason this doesn't work is that the Field constructor sets .size_, and that results in (e.g.) FieldInput instances having a .size_ property that is found before the get size_/set size_ accessors on FieldInput.prototype.

But if the Field constructor either didn't set that property if there were accessors, like so:

classFieldInput{constructor(){this.size_??=newSize(0,0);}}

or if the FieldInput constructor removed it, like so:

classFieldInput{constructor(){super();deletethis.size_;}}

then I think it's possible to have the behaviour you have here without the mostly-useless accessors on Field.prototype. Both approaches are a bit kludgy, and the delete approach might have some performance consequences (it's generally preferable not to change the shape of an object, though here at least it's being done at construction time). I'm genuinely not sure either is clearly preferable to your proposal, but I offer them for your consideration.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, I think that even if you keep the get/set accessors on Field it might be preferable to not call them from the corresponding accessors on FieldInput, either by making size protected (so that that FieldInput's accessors can use it directly) or by having FieldInput keep it's own separate Size property. A pair of accessors ought to be interchangeable with a data property, in my view, but you have revealed that that isn't always the case. I'd like to at least not tie ourselves too tightly to using accessors everywhere.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the mostly-useless accessors (which most fields can just ignore) over the kludgy approaches mentioned above.

Comment threadcore/field_image.ts
@cpcallen

Copy link
Copy Markdown
Collaborator

(Restoration of .NBSP and space-to-NBSP anti-collapse code all LGTM, though.)

Comment threadcore/field.ts
protected size_: Size;

/** This field's dimensions. */
private size: Size = new Size(0, 0);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style guide suggests something like sizeInternal:

Suggested change
private size: Size=newSize(0,0);
privatesizeInternal: Size=newSize(0,0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused about this, why? it's already private, why would we also add the word internal

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quoting the Styleguide section on get and set accesors says:

If an accessor is used to hide a class property, the hidden property may be prefixed or suffixed with any whole word, like internal or wrapped.

They do not provide a rational, but I suspect the reason is to avoid clashing with the name of the accessors. In this case the accessors are named size_ (which, with the trailing underscore, is contrary to current naming policy) but if it were size then the internal property would need to be named something else.

@maribethbmaribethb self-assigned this May 13, 2025
Comment threadcore/field.ts
Comment on lines +117 to +137
/** This field's dimensions. */
private size: Size = new Size(0, 0);

/**
* Gets the size of this field. Because getSize() and updateSize() have side
* effects, this acts as a shim for subclasses which wish to adjust field
* bounds when setting/getting the size without triggering unwanted rendering
* or other side effects. Note that subclasses must override *both* get and
* set if either is overridden; the implementation may just call directly
* through to super, but it must exist per the JS spec.
*/
protected get size_(): Size {
return this.size;
}

/**
* Sets the size of this field.
*/
protected set size_(newValue: Size) {
this.size = newValue;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the mostly-useless accessors (which most fields can just ignore) over the kludgy approaches mentioned above.

Comment threadcore/field.ts
protected size_: Size;

/** This field's dimensions. */
private size: Size = new Size(0, 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused about this, why? it's already private, why would we also add the word internal

@gonfunko
gonfunko merged commit 14e1ef6 into RaspberryPiFoundation:rc/v12.0.0May 13, 2025
@gonfunko
gonfunko deleted the field-regression branch May 13, 2025 21:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: fixFixes a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@gonfunko@cpcallen@maribethb
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: Fix regressions in Field. - #9011

Merged
gonfunko merged 1 commit into
RaspberryPiFoundation:rc/v12.0.0from
gonfunko:field-regression
May 13, 2025
Merged

fix: Fix regressions in Field.#9011
gonfunko merged 1 commit into
RaspberryPiFoundation:rc/v12.0.0from
gonfunko:field-regression

Conversation

@gonfunko

Copy link
Copy Markdown
Contributor

The basics

The details

Resolves

Fixes#9005

Proposed Changes

This PR fixes several regressions in Field introduced by an earlier change:

  • It restores the Field.NBSP constant, which was in fact in use
  • It makes empty input fields have a reasonable minimum width
  • It prevents sequential spaces from collapsing when rendering a field

@gonfunko
gonfunko requested a review from cpcallenMay 7, 2025 20:44
@gonfunko
gonfunko requested a review from a team as a code ownerMay 7, 2025 20:44
@github-actionsgithub-actionsBot added the PR: fix Fixes a bug label May 7, 2025
Comment threadcore/field.ts
Comment on lines +117 to +137
/** This field's dimensions. */
private size: Size = new Size(0, 0);

/**
* Gets the size of this field. Because getSize() and updateSize() have side
* effects, this acts as a shim for subclasses which wish to adjust field
* bounds when setting/getting the size without triggering unwanted rendering
* or other side effects. Note that subclasses must override *both* get and
* set if either is overridden; the implementation may just call directly
* through to super, but it must exist per the JS spec.
*/
protected get size_(): Size {
return this.size;
}

/**
* Sets the size of this field.
*/
protected set size_(newValue: Size) {
this.size = newValue;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Styleguide says "At least one accessor for a property must be non-trivial: do not define "pass-through" accessors only for the purpose of hiding a property."

I gather you are doing this in order to be able to write super.size_ = newValue in FieldInput's get size_ accessor, but I think a better approach might be to have updateSize_ enforce a minimum width (possibly in Field but preferably in FieldInput). (Alas, updateSize_ isn't written in a way that makes it easy to override for this particular purpose.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you noted, updateSize_() is not well-suited to being overridden, and short of introducing a setSizeWithoutSideEffects() method I couldn't see a way to refactor it to be more amendable. Likewise, overriding getSize() (or calling it from updateSize_() instead of accessing the size_ property directly) is problematic due to its side effects of rerendering the field. Hence this seemed like the least-bad option, as it preserves backwards compatibility and allows for subclasses to adjust the size as they need. If you have an idea for how updateSize_() might be refactored I'd certainly be open to that though!

@cpcallencpcallenMay 9, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to think about this for a bit, but I think I see a way to do this without needing to have accessors on Field.

The obvious approach is to have the get and set accessors on the subclasses that want to enforce a minimum width only, and leave Field alone. As you doubtless noticed, the reason this doesn't work is that the Field constructor sets .size_, and that results in (e.g.) FieldInput instances having a .size_ property that is found before the get size_/set size_ accessors on FieldInput.prototype.

But if the Field constructor either didn't set that property if there were accessors, like so:

classFieldInput{constructor(){this.size_??=newSize(0,0);}}

or if the FieldInput constructor removed it, like so:

classFieldInput{constructor(){super();deletethis.size_;}}

then I think it's possible to have the behaviour you have here without the mostly-useless accessors on Field.prototype. Both approaches are a bit kludgy, and the delete approach might have some performance consequences (it's generally preferable not to change the shape of an object, though here at least it's being done at construction time). I'm genuinely not sure either is clearly preferable to your proposal, but I offer them for your consideration.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, I think that even if you keep the get/set accessors on Field it might be preferable to not call them from the corresponding accessors on FieldInput, either by making size protected (so that that FieldInput's accessors can use it directly) or by having FieldInput keep it's own separate Size property. A pair of accessors ought to be interchangeable with a data property, in my view, but you have revealed that that isn't always the case. I'd like to at least not tie ourselves too tightly to using accessors everywhere.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the mostly-useless accessors (which most fields can just ignore) over the kludgy approaches mentioned above.

Comment threadcore/field_image.ts
@cpcallen

Copy link
Copy Markdown
Collaborator

(Restoration of .NBSP and space-to-NBSP anti-collapse code all LGTM, though.)

Comment threadcore/field.ts
protected size_: Size;

/** This field's dimensions. */
private size: Size = new Size(0, 0);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style guide suggests something like sizeInternal:

Suggested change
private size: Size=newSize(0,0);
privatesizeInternal: Size=newSize(0,0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused about this, why? it's already private, why would we also add the word internal

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quoting the Styleguide section on get and set accesors says:

If an accessor is used to hide a class property, the hidden property may be prefixed or suffixed with any whole word, like internal or wrapped.

They do not provide a rational, but I suspect the reason is to avoid clashing with the name of the accessors. In this case the accessors are named size_ (which, with the trailing underscore, is contrary to current naming policy) but if it were size then the internal property would need to be named something else.

@maribethbmaribethb self-assigned this May 13, 2025
Comment threadcore/field.ts
Comment on lines +117 to +137
/** This field's dimensions. */
private size: Size = new Size(0, 0);

/**
* Gets the size of this field. Because getSize() and updateSize() have side
* effects, this acts as a shim for subclasses which wish to adjust field
* bounds when setting/getting the size without triggering unwanted rendering
* or other side effects. Note that subclasses must override *both* get and
* set if either is overridden; the implementation may just call directly
* through to super, but it must exist per the JS spec.
*/
protected get size_(): Size {
return this.size;
}

/**
* Sets the size of this field.
*/
protected set size_(newValue: Size) {
this.size = newValue;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the mostly-useless accessors (which most fields can just ignore) over the kludgy approaches mentioned above.

Comment threadcore/field.ts
protected size_: Size;

/** This field's dimensions. */
private size: Size = new Size(0, 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused about this, why? it's already private, why would we also add the word internal

@gonfunko
gonfunko merged commit 14e1ef6 into RaspberryPiFoundation:rc/v12.0.0May 13, 2025
@gonfunko
gonfunko deleted the field-regression branch May 13, 2025 21:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: fixFixes a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@gonfunko@cpcallen@maribethb
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix: Fix regressions in Field. - #9011

Merged
gonfunko merged 1 commit into
RaspberryPiFoundation:rc/v12.0.0from
gonfunko:field-regression
May 13, 2025
Merged

fix: Fix regressions in Field.#9011
gonfunko merged 1 commit into
RaspberryPiFoundation:rc/v12.0.0from
gonfunko:field-regression

Conversation

@gonfunko

Copy link
Copy Markdown
Contributor

The basics

The details

Resolves

Fixes#9005

Proposed Changes

This PR fixes several regressions in Field introduced by an earlier change:

  • It restores the Field.NBSP constant, which was in fact in use
  • It makes empty input fields have a reasonable minimum width
  • It prevents sequential spaces from collapsing when rendering a field

@gonfunko
gonfunko requested a review from cpcallenMay 7, 2025 20:44
@gonfunko
gonfunko requested a review from a team as a code ownerMay 7, 2025 20:44
@github-actionsgithub-actionsBot added the PR: fix Fixes a bug label May 7, 2025
Comment threadcore/field.ts
Comment on lines +117 to +137
/** This field's dimensions. */
private size: Size = new Size(0, 0);

/**
* Gets the size of this field. Because getSize() and updateSize() have side
* effects, this acts as a shim for subclasses which wish to adjust field
* bounds when setting/getting the size without triggering unwanted rendering
* or other side effects. Note that subclasses must override *both* get and
* set if either is overridden; the implementation may just call directly
* through to super, but it must exist per the JS spec.
*/
protected get size_(): Size {
return this.size;
}

/**
* Sets the size of this field.
*/
protected set size_(newValue: Size) {
this.size = newValue;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Styleguide says "At least one accessor for a property must be non-trivial: do not define "pass-through" accessors only for the purpose of hiding a property."

I gather you are doing this in order to be able to write super.size_ = newValue in FieldInput's get size_ accessor, but I think a better approach might be to have updateSize_ enforce a minimum width (possibly in Field but preferably in FieldInput). (Alas, updateSize_ isn't written in a way that makes it easy to override for this particular purpose.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you noted, updateSize_() is not well-suited to being overridden, and short of introducing a setSizeWithoutSideEffects() method I couldn't see a way to refactor it to be more amendable. Likewise, overriding getSize() (or calling it from updateSize_() instead of accessing the size_ property directly) is problematic due to its side effects of rerendering the field. Hence this seemed like the least-bad option, as it preserves backwards compatibility and allows for subclasses to adjust the size as they need. If you have an idea for how updateSize_() might be refactored I'd certainly be open to that though!

@cpcallencpcallenMay 9, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to think about this for a bit, but I think I see a way to do this without needing to have accessors on Field.

The obvious approach is to have the get and set accessors on the subclasses that want to enforce a minimum width only, and leave Field alone. As you doubtless noticed, the reason this doesn't work is that the Field constructor sets .size_, and that results in (e.g.) FieldInput instances having a .size_ property that is found before the get size_/set size_ accessors on FieldInput.prototype.

But if the Field constructor either didn't set that property if there were accessors, like so:

classFieldInput{constructor(){this.size_??=newSize(0,0);}}

or if the FieldInput constructor removed it, like so:

classFieldInput{constructor(){super();deletethis.size_;}}

then I think it's possible to have the behaviour you have here without the mostly-useless accessors on Field.prototype. Both approaches are a bit kludgy, and the delete approach might have some performance consequences (it's generally preferable not to change the shape of an object, though here at least it's being done at construction time). I'm genuinely not sure either is clearly preferable to your proposal, but I offer them for your consideration.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, I think that even if you keep the get/set accessors on Field it might be preferable to not call them from the corresponding accessors on FieldInput, either by making size protected (so that that FieldInput's accessors can use it directly) or by having FieldInput keep it's own separate Size property. A pair of accessors ought to be interchangeable with a data property, in my view, but you have revealed that that isn't always the case. I'd like to at least not tie ourselves too tightly to using accessors everywhere.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the mostly-useless accessors (which most fields can just ignore) over the kludgy approaches mentioned above.

Comment threadcore/field_image.ts
@cpcallen

Copy link
Copy Markdown
Collaborator

(Restoration of .NBSP and space-to-NBSP anti-collapse code all LGTM, though.)

Comment threadcore/field.ts
protected size_: Size;

/** This field's dimensions. */
private size: Size = new Size(0, 0);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style guide suggests something like sizeInternal:

Suggested change
private size: Size=newSize(0,0);
privatesizeInternal: Size=newSize(0,0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused about this, why? it's already private, why would we also add the word internal

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quoting the Styleguide section on get and set accesors says:

If an accessor is used to hide a class property, the hidden property may be prefixed or suffixed with any whole word, like internal or wrapped.

They do not provide a rational, but I suspect the reason is to avoid clashing with the name of the accessors. In this case the accessors are named size_ (which, with the trailing underscore, is contrary to current naming policy) but if it were size then the internal property would need to be named something else.

@maribethbmaribethb self-assigned this May 13, 2025
Comment threadcore/field.ts
Comment on lines +117 to +137
/** This field's dimensions. */
private size: Size = new Size(0, 0);

/**
* Gets the size of this field. Because getSize() and updateSize() have side
* effects, this acts as a shim for subclasses which wish to adjust field
* bounds when setting/getting the size without triggering unwanted rendering
* or other side effects. Note that subclasses must override *both* get and
* set if either is overridden; the implementation may just call directly
* through to super, but it must exist per the JS spec.
*/
protected get size_(): Size {
return this.size;
}

/**
* Sets the size of this field.
*/
protected set size_(newValue: Size) {
this.size = newValue;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the mostly-useless accessors (which most fields can just ignore) over the kludgy approaches mentioned above.

Comment threadcore/field.ts
protected size_: Size;

/** This field's dimensions. */
private size: Size = new Size(0, 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused about this, why? it's already private, why would we also add the word internal

@gonfunko
gonfunko merged commit 14e1ef6 into RaspberryPiFoundation:rc/v12.0.0May 13, 2025
@gonfunko
gonfunko deleted the field-regression branch May 13, 2025 21:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: fixFixes a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@gonfunko@cpcallen@maribethb