Skip to content

Commit 5d061f9

Browse files
chore: merge stable into main (#3026)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 87e53bc commit 5d061f9

10 files changed

Lines changed: 183 additions & 244 deletions

File tree

.github/workflows/pr-renderer-build.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: "Labs: PR Renderer Build"
1+
name: 'Labs: PR Renderer Build'
22

33
on:
44
pull_request:
@@ -337,8 +337,6 @@ jobs:
337337
echo "renderer_build_id=${RENDERER_BUILD_ID}" >> "${GITHUB_OUTPUT}"
338338
echo "renderer_build_status=${RENDERER_BUILD_STATUS}" >> "${GITHUB_OUTPUT}"
339339
340-
<<<<<<< Updated upstream
341-
=======
342340
- name: Wait for Labs renderer artifact
343341
id: wait_renderer_artifact
344342
env:
@@ -644,7 +642,6 @@ jobs:
644642
echo "| New run created | \`${{ steps.dispatch.outputs.created }}\` |"
645643
} >> "${GITHUB_STEP_SUMMARY}"
646644
647-
>>>>>>> Stashed changes
648645
cleanup:
649646
name: Cleanup
650647
if: >-

.github/workflows/release-qualification-dispatch.yml

Lines changed: 0 additions & 212 deletions
This file was deleted.

cicd.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,21 +81,25 @@ main (next) → stable (latest) → X.x (maintenance)
8181
- If the merge conflicts, commits the conflicted merge to the branch so a human can resolve it there
8282
- Merging that PR triggers the automatic stable release workflow
8383

84-
#### 4. Release Qualification Dispatch (`release-qualification-dispatch.yml`)
84+
#### 4. Labs PR Build and Stable Promotion Checks (`pr-renderer-build.yml`)
8585

86-
**Trigger**: Pull requests targeting `stable` (`opened`, `reopened`, `synchronize`, `ready_for_review`)
86+
**Trigger**: Pull requests targeting `main` or `stable` (`opened`, `reopened`, `synchronize`, `ready_for_review`, `closed`)
8787

8888
**Actions**:
8989

90-
- Sends the PR head SHA and branch metadata to the Labs release-orchestrator service
91-
- Polls Labs for the terminal release-qualification state
92-
- Uses the GitHub Actions job itself as the required public status check
93-
- Re-triggers automatically when new commits are pushed to the PR branch
90+
- Builds a `superdoc.tgz` tarball for the PR head SHA
91+
- Uploads the package artifact to Labs
92+
- Registers the PR renderer build in Labs
93+
- For PRs targeting `stable`, runs `Labs: Stable promotion checks` after the PR renderer build is registered
94+
- Polls Labs for the terminal stable-promotion state
95+
- Cleans up registered Labs artifacts when a same-repository PR is merged
9496

9597
Only same-repository PRs dispatch to Labs. Forked PRs are intentionally skipped so private Labs credentials are never exposed to untrusted branches.
9698

9799
**Required configuration**:
98100

101+
- variable: `LABS_API_URL`
102+
- secret: `LABS_PR_BUILD_TOKEN` (falls back to `LABS_RELEASE_QUALIFICATION_TOKEN`)
99103
- variable: `LABS_RELEASE_QUALIFICATION_URL`
100104
- secret: `LABS_RELEASE_QUALIFICATION_TOKEN`
101105

@@ -229,9 +233,9 @@ These skip semantic-release entirely — useful for re-publishing a failed platf
229233

230234
1. Run "Promote to Stable" workflow
231235
2. Review the generated PR from the candidate branch into `stable`
232-
3. Labs receives the PR head SHA, records the qualification run, and the workflow job polls Labs for the terminal result
236+
3. Labs receives the PR package artifact, registers a PR renderer build, and then runs stable promotion checks for that exact PR head SHA
233237
4. If needed, resolve merge conflicts on the candidate branch and push fixes
234-
5. Re-run or wait for qualification on the new PR head SHA
238+
5. Re-run or wait for the stable promotion checks on the new PR head SHA
235239
6. Merge the PR into `stable`
236240
7. Automatically publishes `1.1.0` as @latest
237241
8. Syncs back to main with version bump

packages/esign/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@superdoc-dev/esign",
3-
"version": "2.6.0",
3+
"version": "2.6.1",
44
"description": "React eSignature component for SuperDoc",
55
"type": "module",
66
"main": "./dist/index.js",

packages/react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@superdoc-dev/react",
3-
"version": "1.2.0",
3+
"version": "1.2.1",
44
"description": "Official React wrapper for the SuperDoc document editor",
55
"type": "module",
66
"main": "./dist/index.cjs",

packages/super-editor/src/editors/v1/document-api-adapters/helpers/selection-info-resolver.test.ts

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { describe, expect, it, vi } from 'vitest';
2+
import { NodeSelection } from 'prosemirror-state';
23
import type { Node as ProseMirrorNode } from 'prosemirror-model';
34
import type { Editor } from '../../core/Editor.js';
45
import { resolveCurrentSelectionInfo } from './selection-info-resolver.js';
@@ -143,14 +144,31 @@ function doc(blocks: ProseMirrorNode[]): ProseMirrorNode {
143144
return createNode('doc', blocks, { isBlock: false, inlineContent: false });
144145
}
145146

147+
function makeRealNodeSelection(
148+
from: number,
149+
to: number,
150+
node: { type: { name: string }; isBlock: boolean; isLeaf: boolean; isInline: boolean; nodeSize: number },
151+
): NodeSelection {
152+
const sel = Object.create(NodeSelection.prototype);
153+
Object.defineProperty(sel, 'from', { value: from, configurable: true });
154+
Object.defineProperty(sel, 'to', { value: to, configurable: true });
155+
Object.defineProperty(sel, 'empty', { value: false, configurable: true });
156+
Object.defineProperty(sel, 'node', { value: node, configurable: true });
157+
return sel as NodeSelection;
158+
}
159+
146160
/** Minimal editor stub whose doc + selection are controllable per test. */
147-
function makeEditor(docNode: ProseMirrorNode, selection: { from: number; to: number; empty?: boolean }): Editor {
161+
function makeEditor(
162+
docNode: ProseMirrorNode,
163+
selection: { from: number; to: number; empty?: boolean; node?: unknown },
164+
): Editor {
148165
const empty = selection.empty ?? selection.from === selection.to;
166+
const pmSelection = 'node' in selection ? selection : { from: selection.from, to: selection.to, empty };
149167
const listeners = new Map<string, Array<() => void>>();
150168
return {
151169
state: {
152170
doc: docNode,
153-
selection: { from: selection.from, to: selection.to, empty },
171+
selection: pmSelection,
154172
storedMarks: null,
155173
},
156174
on(event: string, listener: () => void) {
@@ -215,6 +233,41 @@ describe('resolveCurrentSelectionInfo', () => {
215233
]);
216234
});
217235

236+
it('returns null target for a NodeSelection over an addressable text block', () => {
237+
// SelectionInfo.target is only for text selections. A NodeSelection
238+
// over a text-bearing block still represents the node, not a user text
239+
// range that can safely feed comments.create.
240+
const paragraph = textBlock('p1', 'Hello');
241+
const docNode = doc([paragraph]);
242+
const selection = makeRealNodeSelection(1, 1 + paragraph.nodeSize, paragraph as any);
243+
const editor = makeEditor(docNode, selection);
244+
245+
const info = resolveCurrentSelectionInfo(editor, {});
246+
247+
expect(info.empty).toBe(false);
248+
expect(info.target).toBeNull();
249+
});
250+
251+
it('returns null target for a NodeSelection over a text-bearing structured content block', () => {
252+
// Presentation clicks can select a block SDT as a NodeSelection. Even
253+
// though the wrapper contains textblocks, the selection itself is not
254+
// a text selection and should not be projected into a TextTarget.
255+
const innerParagraph = textBlock('p-inside-sdt', 'Field text');
256+
const blockSdt = createNode('structuredContentBlock', [innerParagraph], {
257+
isBlock: true,
258+
inlineContent: false,
259+
attrs: { sdBlockId: 'sdt-1' },
260+
});
261+
const docNode = doc([blockSdt]);
262+
const selection = makeRealNodeSelection(1, 1 + blockSdt.nodeSize, blockSdt as any);
263+
const editor = makeEditor(docNode, selection);
264+
265+
const info = resolveCurrentSelectionInfo(editor, {});
266+
267+
expect(info.empty).toBe(false);
268+
expect(info.target).toBeNull();
269+
});
270+
218271
it('returns null target when no selected block has an addressable blockId', () => {
219272
// Block without sdBlockId / id / blockId — resolver skips it.
220273
const textNode = createNode('text', [], { text: 'Hello' });

0 commit comments

Comments
 (0)