Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ff5eb01
Fix tests that were not being run due to missing browser context (#3331)
BryanValverdeU May 8, 2026
9aa472c
Revert "Fix tests that were not being run due to missing browser cont…
BryanValverdeU May 8, 2026
d969fcf
Fix maxWidth and maxHeight not stripped when pasting DIV/P elements (…
BryanValverdeU May 9, 2026
ed74d7f
Bump fast-uri from 3.1.0 to 3.1.2 (#3339)
dependabot[bot] May 11, 2026
ef08da4
Fix tests that were not being run due to missing browser context (#3340)
BryanValverdeU May 12, 2026
e167c7b
reafctor markdown (#3335)
juliaroldi May 12, 2026
8f08301
Fix 428673: While replying to messages in OWA , replacing old images …
JiuqingSong May 13, 2026
df1f2c2
Fix #3341: Preserve selection marker when reconciling range across tw…
JiuqingSong May 19, 2026
f943139
Fix #3100: Hoist whitespace outside markdown emphasis markers (#3343)
JiuqingSong May 21, 2026
369996b
Skip justify-self: flex-end on RTL table inside RTL container (#3345)
JiuqingSong May 21, 2026
71affcd
Add per-PR preview deployment workflow (#3348)
JiuqingSong May 21, 2026
a95c717
Bump tmp from 0.2.4 to 0.2.7 (#3352)
dependabot[bot] May 28, 2026
fe51ee6
Create a AI Skill for version bump (#3353)
JiuqingSong May 28, 2026
2a95926
Filter out invisible unicode characters from text segments (#3344)
JiuqingSong May 28, 2026
7453bb9
Fix borderFormatHandler to strip 'initial' color value (#3350)
BryanValverdeU May 28, 2026
6c7ad6e
Fix build (#3354)
BryanValverdeU May 28, 2026
781d4ff
Shadow DOM Support (#3347)
BryanValverdeU May 29, 2026
1b17aad
Use unsanitized HTML in demo clipboard read (#3355)
JiuqingSong May 29, 2026
48760de
Make copy text content respect modified clonedRoot from beforeCutCopy…
JiuqingSong May 29, 2026
19d15ee
Merge branch 'master' into u/nguyenvi/bump-9.53.0
vinguyen12 May 29, 2026
7b6d399
Version bump to 9.53.0
vinguyen12 May 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 226 additions & 0 deletions .claude/skills/version-bump/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
---
name: version-bump
description: Performs a version bump for roosterjs. Merges changes from master into release, determines the correct SemVer version bump based on public interface changes, and creates a draft PR. Use when asked to do a version bump, release prep, or bump versions.
---

# Version Bump Skill

This skill performs a version bump for roosterjs. It merges changes from `master` into `release`, determines the correct SemVer version bump based on public interface changes, and creates a draft PR.

## Steps

### Step 1: Check for uncommitted changes

Run `git status --porcelain`. If there is any output (uncommitted changes exist), **stop immediately** and ask the user to deal with their uncommitted changes first before proceeding.

### Step 2: Switch to master and pull latest

```bash
git checkout master
git pull origin master
```

If either command fails, stop and report the error.

### Step 3: Switch to release and pull latest

```bash
git checkout release
git pull origin release
```

If either command fails, stop and report the error.

### Step 4: Find the last version bump commit on release

Search the git log on the `release` branch for the most recent version bump commit. Version bump commits typically have "Version bump" or "version bump" in their commit message, or modify only `versions.json`.

```bash
git log release --oneline --grep="ersion bump" -1
```

If not found via message, look for the last commit that modified `versions.json`:

```bash
git log release --oneline -1 -- versions.json
```

Record the commit hash and date of this commit.

### Step 5: Find all PRs merged into master after the last version bump

Using the date/hash from Step 4, find all merge commits (PRs) merged into `master` after that point:

```bash
git log master --oneline --merges --after="<date_of_last_bump>"
```

Alternatively, find commits on master that are not on release:

```bash
git log release..master --oneline --merges
```

If **no PRs are found**, stop and tell the user: "Version bump is not required since there is no PR merged since the last version bump."

### Step 6: Create PR descriptions

For each PR found in Step 5, create a one-line description with the PR link. Format:

```
- #<PR_NUMBER> <PR_TITLE> (https://github.com/microsoft/roosterjs/pull/<PR_NUMBER>)
```

Use `gh pr view <number> --json title,number,url` to get details if needed. Save these descriptions for use in Step 15.

### Step 7: Create a new branch based on release

Create a new branch from `release`. Use the naming convention `u/<username>/bump-<N>` where N is incremented, or as specified by the user:

```bash
git checkout -b <branch_name> release
```

### Step 8: Merge master using "accept theirs" strategy

Merge master into the new branch, preferring master's changes for any conflicts:

```bash
git merge master -X theirs
```

### Step 9: Verify no unresolved conflicts

Check for any remaining conflict markers:

```bash
git diff --check
grep -r "<<<<<<" --include="*.ts" --include="*.js" --include="*.json" .
```

If conflicts remain, stop and report them to the user.

### Step 10: Compare current branch with master

Run a diff between the current branch and master:

```bash
git diff master -- . ':!versions.json'
```

Ignore differences that are **only** whitespace/formatting (newlines, indentation). You can verify with:

```bash
git diff master --stat -- . ':!versions.json'
```

If there are substantive code differences (not just formatting), show the differences to the user and ask: "There are code differences between this branch and master (other than versions.json). Do you want to continue?"

If the user says no, stop the flow.

### Step 11: Update versions.json with SemVer bump

The `versions.json` file has 4 version groups:

| Group | Packages |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `main` | roosterjs, roosterjs-content-model-types, roosterjs-content-model-dom, roosterjs-content-model-core, roosterjs-content-model-api, roosterjs-content-model-plugins, roosterjs-color-utils, roosterjs-content-model-markdown |
| `legacyAdapter` | roosterjs-editor-adapter |
| `react` | roosterjs-react |
| `overrides` | Per-package overrides (usually empty) |

The grouping is defined in `/tools/buildTools/common.js` in the `buildConfig` object.

**SemVer rules:**

- **Patch bump** (0.0.x → 0.0.x+1): Only bug fixes, no API changes
- **Minor bump** (0.x.0 → 0.x+1.0): New features/APIs added, but backward-compatible
- **Major bump** (x.0.0 → x+1.0.0): Breaking changes to public interfaces

To determine the bump level:

1. Compare the exported types/interfaces between `release` and the merged code
2. Look at the `lib/index.ts` barrel files in each package for added/removed/changed exports
3. Check if any existing public interface signatures changed (breaking = major)
4. Check if new exports were added (non-breaking addition = minor)
5. If only implementation changes with no public API changes = patch

**If a major version bump appears needed**, show the interface differences to the user and ask: "A major version bump seems needed due to these interface changes. Do you want to bump the major version, or just bump minor instead?"

Update `versions.json` with the new version numbers for each affected group.

### Step 12: Build and test

Run the full build and test suite:

```bash
yarn build
yarn test:fast
```

If **any errors** occur, show the full error output and **stop the flow**. Do not proceed with a broken build.

### Step 13: Commit the change

```bash
git add versions.json
git commit -m "Version bump to <new_versions>"
```

Include all changed version numbers in the commit message.

### Step 14: Push the branch

```bash
git push origin <branch_name>
```

### Step 15: Create a draft PR

Create a draft PR targeting the `release` branch using the `gh` CLI:

**Title:** `Version bump <group>: <old_version> → <new_version>` (list all groups that changed)

**Description:** Include:

1. A table showing old and new versions for each group:

```markdown
| Group | Old Version | New Version |
| ------------- | ----------- | ----------- |
| main | x.y.z | x.y.z+1 |
| react | x.y.z | x.y.z+1 |
| legacyAdapter | x.y.z | x.y.z+1 |
```

2. The PR descriptions from Step 6:

```markdown
## Changes included

- #123 Add feature X (https://github.com/microsoft/roosterjs/pull/123)
- #124 Fix bug Y (https://github.com/microsoft/roosterjs/pull/124)
```

Command:

```bash
gh pr create --draft --base release --title "<title>" --body "<description>"
```

### Step 16: Show the PR link

Display the PR URL to the user. Example output:

```
✅ Version bump PR created successfully!
PR: https://github.com/microsoft/roosterjs/pull/<number>
```

## Error Handling

- If any git operation fails, show the error and stop
- If build/test fails, show errors and stop
- If there are unexpected code differences, ask the user before continuing
- If major version bump is detected, confirm with user before applying
- Always leave the repo in a clean state if stopping early
1 change: 1 addition & 0 deletions .github/workflows/build-and-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages
FOLDER: dist/deploy
CLEAN_EXCLUDE: '["pr-preview/**"]'
48 changes: 48 additions & 0 deletions .github/workflows/pr-preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Deploy PR Preview
on:
pull_request:
types:
- opened
- reopened
- synchronize
- closed

concurrency: preview-${{ github.ref }}

jobs:
deploy-preview:
# Skip PRs from forks: GITHUB_TOKEN is read-only for fork PRs,
# so it cannot push to gh-pages. Fork support requires a separate
# workflow_run-based design.
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set Node Version
if: github.event.action != 'closed'
uses: actions/setup-node@v4
with:
node-version: 'v18.16.0'

- name: Install dependencies
if: github.event.action != 'closed'
run: yarn

- name: Build
if: github.event.action != 'closed'
run: yarn build

- name: Deploy preview
# Pinned to commit SHA (v1.8.1) for supply-chain safety, since
# this third-party action runs with write access to gh-pages and PRs.
uses: rossjrw/pr-preview-action@ffa7509e91a3ec8dfc2e5536c4d5c1acdf7a6de9 # v1.8.1
with:
source-dir: ./dist/deploy
preview-branch: gh-pages
umbrella-dir: pr-preview
action: auto
8 changes: 6 additions & 2 deletions demo/scripts/controlsV2/demoButtons/pasteButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { extractClipboardItems } from 'roosterjs-content-model-dom';
import { paste } from 'roosterjs-content-model-core';
import type { RibbonButton } from 'roosterjs-react';

interface ClipboardWithUnsanitized {
read(options?: { unsanitized?: string[] }): Promise<ClipboardItems>;
}

/**
* @internal
* "Paste" button on the format ribbon
Expand All @@ -12,10 +16,10 @@ export const pasteButton: RibbonButton<'buttonNamePaste'> = {
iconName: 'Paste',
onClick: async editor => {
const doc = editor.getDocument();
const clipboard = doc.defaultView.navigator.clipboard;
const clipboard = doc.defaultView.navigator.clipboard as ClipboardWithUnsanitized;
if (clipboard && clipboard.read) {
try {
const clipboardItems = await clipboard.read();
const clipboardItems = await clipboard.read({ unsanitized: ['text/html'] });
const dataTransferItems = await Promise.all(
createDataTransferItems(clipboardItems)
);
Expand Down
35 changes: 32 additions & 3 deletions demo/scripts/controlsV2/mainPane/MainPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -353,11 +353,40 @@ export class MainPane extends React.Component<{}, MainPaneState> {
);
}

private shadowDomEditorDiv: HTMLDivElement | undefined;
private resetEditor() {
const useShadowDom = this.editorOptionPlugin
.getBuildInPluginState()
.experimentalFeatures.has('ShadowDom');

this.setState({
editorCreator: (div: HTMLDivElement, options: EditorOptions) => {
return new Editor(div, options);
},
editorCreator: useShadowDom
? (div: HTMLDivElement, options: EditorOptions) => {
while (div.firstChild) {
div.removeChild(div.firstChild);
}
const newDivHost = document.createElement('div');
div.appendChild(newDivHost);
const shadowRoot = newDivHost.attachShadow({ mode: 'open' });
const innerDiv = document.createElement('div');
innerDiv.style.width = '100%';
innerDiv.style.height = '100%';
innerDiv.style.outline = 'none';
shadowRoot.appendChild(innerDiv);
this.shadowDomEditorDiv = newDivHost;
const editor = new Editor(innerDiv, options);

div.setAttribute('style', newDivHost.getAttribute('style') || '');
newDivHost.style.width = '100%';
newDivHost.style.height = '100%';

return editor;
}
: (div: HTMLDivElement, options: EditorOptions) => {
this.shadowDomEditorDiv?.remove();
this.shadowDomEditorDiv = undefined;
return new Editor(div, options);
},
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ interface PastePaneState {

let lastClipboardData: ClipboardData | undefined = undefined;

interface ClipboardWithUnsanitized {
read(options?: { unsanitized?: string[] }): Promise<ClipboardItems>;
}

export default class PastePane extends React.Component<ApiPaneProps, PastePaneState>
implements ApiPlaygroundComponent {
private clipboardDataRef = React.createRef<HTMLTextAreaElement>();
Expand Down Expand Up @@ -72,10 +76,10 @@ export default class PastePane extends React.Component<ApiPaneProps, PastePaneSt

private onExtractClipboardProgrammatically = async () => {
const doc = this.clipboardDataRef.current.ownerDocument;
const clipboard = doc.defaultView.navigator.clipboard;
const clipboard = doc.defaultView.navigator.clipboard as ClipboardWithUnsanitized;
if (clipboard && clipboard.read) {
try {
const clipboardItems = await clipboard.read();
const clipboardItems = await clipboard.read({ unsanitized: ['text/html'] });
const dataTransferItems = await Promise.all(
createDataTransferItems(clipboardItems)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export class ExperimentalFeatures extends React.Component<DefaultFormatProps, {}
<>
{this.renderFeature('KeepSelectionMarkerWhenEnteringTextNode')}
{this.renderFeature('TransformTableBorderColors')}
{this.renderFeature('ShadowDom')}
</>
);
}
Expand Down
Loading
Loading