Skip to content

Tighten component/state update semantics and Storybook attribute sanitization#66

Merged
JosunLP merged 5 commits intodevelopmentfrom
copilot/sub-pr-64
Mar 14, 2026
Merged

Tighten component/state update semantics and Storybook attribute sanitization#66
JosunLP merged 5 commits intodevelopmentfrom
copilot/sub-pr-64

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 14, 2026

This follow-up addresses review feedback on the v1.6.0 changes in two areas: component rerender semantics and Storybook template sanitization. It prevents beforeUpdate guards from suppressing state-driven renders and narrows Storybook’s inferred sanitizer allowlist so authored templates do not preserve unsafe inline attributes by default.

  • Component update flow

    • Make setState() follow the same rerender path as signal-driven updates by skipping beforeUpdate.
    • Preserve the existing beforeUpdate(newProps, oldProps) contract for prop-driven updates only.
    component<{ label: string }, { count: number }>('x-counter', {
      props: { label: { type: String, default: 'count' } },
      state: { count: 0 },
      beforeUpdate(newProps, oldProps) {
        return newProps.label !== oldProps.label;
      },
      render: ({ props, state }) => html`<div>${props.label}:${state.count}</div>`,
    });
    
    // still rerenders even though props are unchanged
    el.setState('count', 1);
  • Storybook sanitization

    • Restrict inferred allowAttributes to authored attributes on custom elements only.
    • Exclude inline style from the auto-allowlist so Storybook stories do not weaken the default sanitization boundary.
    • Keep safe authored custom-element attributes such as variant, data-*, and aria-* working as expected.
  • Boolean shorthand handling

    • Resolve callback/derived values before evaluating ?attr=${...} shorthand.
    • This makes callback-based Storybook templates behave consistently with normal interpolation.
    storyHtml`<bq-button ?disabled=${() => false}>Save</bq-button>`;
    // => <bq-button>Save</bq-button>
    
    storyHtml`<bq-button ?disabled=${() => true}>Save</bq-button>`;
    // => <bq-button disabled="">Save</bq-button>
  • Regression coverage

    • Add tests for state updates bypassing beforeUpdate.
    • Add tests for excluding style from Storybook’s inferred allowlist.
    • Add tests for boolean shorthand with callback values in both enabled and disabled cases.

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>
Copilot AI changed the title [WIP] Update to version 1.6.0 of @bquery/bquery Tighten component/state update semantics and Storybook attribute sanitization Mar 14, 2026
Copilot AI requested a review from JosunLP March 14, 2026 01:31
@JosunLP JosunLP marked this pull request as ready for review March 14, 2026 11:14
Copilot AI review requested due to automatic review settings March 14, 2026 11:14
@github-actions github-actions bot added component Changes to the component module tests Chenges to the tests labels Mar 14, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refines two core areas in bQuery: component update semantics (so state-driven renders aren’t gated by beforeUpdate) and Storybook template sanitization (so inferred allowlists don’t accidentally preserve unsafe attributes like inline style). It also improves Storybook boolean-attribute shorthand so callback values are evaluated before deciding attribute presence.

Changes:

  • Component: make setState() follow the state/signal rerender path by bypassing beforeUpdate.
  • Storybook: narrow inferred sanitizer allowlists (custom elements only) and exclude inline style from auto-allowed attributes.
  • Tests: add regressions for state-update semantics, Storybook style stripping, and boolean shorthand with callback values.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/component/component.ts Updates setState() to skip beforeUpdate by calling render(..., runBeforeUpdate=false).
src/storybook/index.ts Tightens inferred attribute allowlisting (no style, only for custom elements) and resolves boolean shorthand via callback-aware boolean evaluation.
tests/component.test.ts Adds regression test ensuring beforeUpdate cannot block state-driven renders.
tests/storybook.test.ts Adds coverage for style exclusion and boolean shorthand when values are callbacks.

@JosunLP
Copy link
Copy Markdown
Collaborator

JosunLP commented Mar 14, 2026

@copilot apply changes based on the comments in this thread

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 14, 2026

@copilot apply changes based on the comments in this thread

Applied in f4fb7c1. The Storybook scanner now consumes full quoted and unquoted literal attribute values after =, while still leaving template-gap interpolations intact, and I added regressions for both cases. No UI changes, so no screenshot was needed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refines bQuery’s component update lifecycle and hardens Storybook’s storyHtml sanitization inference so state-driven renders aren’t blocked by beforeUpdate, and Storybook templates don’t implicitly broaden sanitizer allowlists (notably for inline styles).

Changes:

  • Adjust component setState() to re-render without running beforeUpdate (matching signal-driven update semantics).
  • Narrow Storybook’s inferred allowAttributes to custom elements only and exclude unsafe attribute classes (e.g., style, on*).
  • Add regression tests covering the new component update behavior, Storybook allowlist narrowing, and boolean shorthand resolution for callback values.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/component/component.ts Routes setState() through a render path that bypasses beforeUpdate to prevent state-driven updates from being suppressed.
src/storybook/index.ts Tightens inferred sanitizer allowlists (custom elements only; excludes style/on*) and resolves boolean shorthand from callback/derived values.
tests/component.test.ts Adds regression coverage ensuring beforeUpdate doesn’t block state-driven renders.
tests/storybook.test.ts Adds regression coverage for excluding style, avoiding attribute-name leakage from literal values, and callback-based boolean shorthand.

@JosunLP
Copy link
Copy Markdown
Collaborator

JosunLP commented Mar 14, 2026

@copilot apply changes based on the comments in this thread

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 14, 2026

@copilot apply changes based on the comments in this thread

Applied in 21febcf. The Storybook sanitizer scanner now preserves interpolation boundaries while still consuming whitespace-padded literal values after =, and boolean shorthand once again follows truthy semantics after resolving callbacks/arrays. No UI changed here, so there isn’t a screenshot to attach.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refines component update behavior and Storybook template sanitization in bQuery by ensuring state-driven updates can’t be suppressed by beforeUpdate, while tightening Storybook’s inferred sanitizer allowlist to avoid preserving unsafe authored attributes like inline style.

Changes:

  • Adjust component setState() to bypass beforeUpdate (matching signal-driven update semantics) while preserving beforeUpdate(newProps, oldProps) for prop-driven updates.
  • Tighten Storybook storyHtml sanitizer inference: only auto-allow authored attributes on custom elements, excluding style and on*, and harden parsing to avoid allowlisting attributes found inside literal values.
  • Add regression tests covering the updated component/state semantics and Storybook sanitization/boolean-shorthand behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/component/component.ts Makes setState() trigger update renders without running beforeUpdate, aligning state and signal update paths.
src/storybook/index.ts Narrows inferred allowlists to custom-element-authored attributes (excluding style/on*) and resolves boolean shorthand from callback values before deciding attribute presence.
tests/component.test.ts Adds coverage ensuring beforeUpdate does not block state-driven renders when props are unchanged.
tests/storybook.test.ts Adds coverage for excluding inline style from inferred allowlists and for callback-resolved boolean shorthand semantics.

@JosunLP
Copy link
Copy Markdown
Collaborator

JosunLP commented Mar 14, 2026

@copilot apply changes based on the comments in this thread

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 14, 2026

@copilot apply changes based on the comments in this thread

Applied in e23e417. I aligned the skipAttributeValue() JSDoc with the current interpolation-boundary behavior; no UI changed here, so there isn’t a screenshot to attach.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refines bQuery’s component update pipeline and Storybook template sanitization to ensure state/signal-driven renders cannot be suppressed by beforeUpdate, while tightening Storybook’s inferred sanitizer allowlist (notably excluding inline style) and fixing boolean shorthand resolution for callback values.

Changes:

  • Update setState() to bypass beforeUpdate (matching signal-driven rerender semantics) while keeping beforeUpdate(newProps, oldProps) for prop-driven updates.
  • Tighten Storybook’s inferred sanitizer allowlist by only auto-allowlisting authored attributes on custom elements and excluding style/on*.
  • Add regression tests for state update semantics, Storybook allowlist inference edge cases, and boolean shorthand callback resolution.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
src/component/component.ts Routes setState() updates through render(..., runBeforeUpdate=false) so beforeUpdate only gates prop-driven renders.
src/storybook/index.ts Improves template scanning using an interpolation boundary marker; restricts inferred allowlisted attributes to custom elements and filters out style/on*; resolves boolean shorthand from callbacks before presence checks.
tests/component.test.ts Adds regression coverage ensuring beforeUpdate does not block state-driven renders while still applying to prop-driven updates.
tests/storybook.test.ts Adds coverage for excluding inline style, preventing value-text from polluting inferred allowlists, and boolean shorthand callback semantics.

@JosunLP JosunLP merged commit 0447c2c into development Mar 14, 2026
14 checks passed
@JosunLP JosunLP deleted the copilot/sub-pr-64 branch March 14, 2026 12:24
JosunLP added a commit that referenced this pull request Mar 15, 2026
* Add `bool()` support for boolean attributes in component templates (#55)

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Jonas Pfalzgraf <info@josunlp.de>

* Add typed state generics to component definitions (#56)

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Jonas Pfalzgraf <info@josunlp.de>

* Pass attribute change metadata to component `updated()` hooks (#58)

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Jonas Pfalzgraf <info@josunlp.de>

* Add per-component sanitizer overrides for component render output (#57)

* Initial plan

* feat: add per-component sanitizer options

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* docs: clarify component sanitizer baseline

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* docs: warn about sensitive component sanitizer opts

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* test: cover sanitizer allowTags overrides

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* perf: precompute component sanitizer allowlists

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

---------

Signed-off-by: Jonas Pfalzgraf <info@josunlp.de>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>
Co-authored-by: Jonas Pfalzgraf <info@josunlp.de>

* Pass previous props into component `beforeUpdate` (#59)

* Initial plan

* Add previous props to beforeUpdate hook

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* Refine beforeUpdate old props implementation

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* Fix beforeUpdate typing regressions

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

---------

Signed-off-by: Jonas Pfalzgraf <info@josunlp.de>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>
Co-authored-by: Jonas Pfalzgraf <info@josunlp.de>

* Add explicit signal subscriptions to `component()` renders (#60)

* Initial plan

* feat: add component signal subscriptions

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* chore: document signal subscription tracking

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* fix: address signal integration review feedback

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* fix: tighten component signal typing

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* fix: resolve eslint failure in component signal test

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* fix: handle reconnect lifecycle and signal effect errors

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* fix: preserve reconnect signal wiring after hook errors

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* fix: skip beforeUpdate for signal rerenders

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

---------

Signed-off-by: Jonas Pfalzgraf <info@josunlp.de>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>
Co-authored-by: Jonas Pfalzgraf <info@josunlp.de>

* Add trusted sanitized fragments for `safeHtml` composition (#61)

* Initial plan

* feat: add trusted safeHtml fragment support

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* chore: finalize trusted safeHtml support

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* docs: address trusted safeHtml review feedback

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* docs: fix second trusted safeHtml review follow-up

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* refactor: split trusted html helpers from sanitizer

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* refactor: tighten trusted html branding

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* docs: tighten trusted html internals

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

---------

Signed-off-by: Jonas Pfalzgraf <info@josunlp.de>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>
Co-authored-by: Jonas Pfalzgraf <info@josunlp.de>

* Reuse component shadow-root style nodes across re-renders (#62)

* Initial plan

* fix: reuse component style element on rerender

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* chore: finalize component style reuse fix

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* test: tighten style reuse regression assertions

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* Add `@bquery/bquery/storybook` template helpers for Storybook stories (#63)

* Initial plan

* feat: add storybook template helper

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* docs: clarify storybook when fallback behavior

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* fix: keep storybook helpers out of full bundle

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* fix: sanitize storybook helper output

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* test: tighten storybook helper security coverage

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* feat: enhance storyHtml with improved boolean attribute handling and sanitization tests

* fix: update storyHtml example to reflect correct boolean attribute output

* fix: defensive tag delimiter and parser comments per code review

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* chore: release version 1.6.0

- Update CHANGELOG.md for version 1.6.0 release notes including added features, changes, fixes, and security improvements.
- Enhance README.md to document new boolean attribute helpers and Storybook integration.
- Update bun.lock to reflect dependency upgrades for Storybook and happy-dom.
- Modify definition.md to include new Storybook helpers and component state management features.
- Revise components.md to demonstrate new boolean attribute handling and typed state in components.
- Update getting-started.md with Storybook authoring examples and improved module descriptions.
- Enhance security.md to clarify sanitization practices and introduce trusted fragment handling.
- Revise index.md to highlight new Storybook helpers in the feature list.
- Update llms.txt to reflect the new version 1.6.0.
- Bump version in package.json to 1.6.0 and update devDependencies for Storybook.

* Address review feedback for test setup, test type coverage, and component state keys (#65)

* Initial plan

* Address review feedback for test typing and setup

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* Finalize review feedback fixes

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* Export component state key type

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* Default component state key generic

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* Tighten component/state update semantics and Storybook attribute sanitization (#66)

* Initial plan

* fix review follow-up for state updates and storybook sanitization

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* fix storybook attribute value scanning

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* fix storybook interpolation boundary parsing

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* docs align storybook attribute scan jsdoc

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

---------

Signed-off-by: Jonas Pfalzgraf <info@josunlp.de>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
JosunLP added a commit that referenced this pull request Mar 27, 2026
…tization (#66)

* Initial plan

* fix review follow-up for state updates and storybook sanitization

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* fix storybook attribute value scanning

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* fix storybook interpolation boundary parsing

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

* docs align storybook attribute scan jsdoc

Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JosunLP <20913954+JosunLP@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component Changes to the component module tests Chenges to the tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants