Skip to content

Commit 20aa3d7

Browse files
committed
Merge branch 'main' into nick/sd-2054-history-namespace-undoredo-document-api-commands
2 parents 0d5ab88 + 456f60e commit 20aa3d7

229 files changed

Lines changed: 27234 additions & 3052 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci-examples.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,31 @@ jobs:
1212
strategy:
1313
fail-fast: false
1414
matrix:
15-
example: [react, vue, vanilla, cdn, angular]
15+
example: [react, vue, vanilla, cdn, angular, nuxt, laravel]
1616
steps:
1717
- uses: actions/checkout@v6
1818

19+
- name: Setup PHP
20+
if: matrix.example == 'laravel'
21+
uses: shivammathur/setup-php@v2
22+
with:
23+
php-version: '8.2'
24+
1925
- name: Setup Node.js
2026
uses: actions/setup-node@v6
2127
with:
2228
node-version: '20'
2329

30+
- name: Install Composer dependencies
31+
if: matrix.example == 'laravel'
32+
working-directory: examples/getting-started/laravel
33+
run: composer install --no-interaction --prefer-dist
34+
35+
- name: Prepare Laravel environment
36+
if: matrix.example == 'laravel'
37+
working-directory: examples/getting-started/laravel
38+
run: cp .env.example .env && php artisan key:generate
39+
2440
- name: Install example dependencies
2541
if: matrix.example != 'cdn'
2642
working-directory: examples/getting-started/${{ matrix.example }}
Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: PR Risk Label
1+
name: PR Labels
22

33
on:
44
pull_request:
@@ -85,3 +85,30 @@ jobs:
8585
labels: [LABELS[level].name],
8686
});
8787
}
88+
89+
- name: Add community label
90+
if: >-
91+
github.event.action == 'opened' &&
92+
github.event.pull_request.author_association != 'MEMBER' &&
93+
github.event.pull_request.author_association != 'OWNER' &&
94+
github.event.pull_request.author_association != 'COLLABORATOR' &&
95+
github.event.pull_request.user.type != 'Bot'
96+
uses: actions/github-script@v7
97+
with:
98+
script: |
99+
const owner = context.repo.owner;
100+
const repo = context.repo.repo;
101+
const label = { name: 'community', color: '7057ff' };
102+
103+
try {
104+
await github.rest.issues.getLabel({ owner, repo, name: label.name });
105+
} catch {
106+
await github.rest.issues.createLabel({ owner, repo, ...label });
107+
}
108+
109+
await github.rest.issues.addLabels({
110+
owner,
111+
repo,
112+
issue_number: context.issue.number,
113+
labels: [label.name],
114+
});

.github/workflows/risk-assess.yml

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ name: AI Risk Assessment
22

33
# Runs AFTER risk-label.yml applies the file-path label.
44
# Only triggers for critical/sensitive PRs — low-risk PRs skip AI entirely.
5+
# Fork PRs skip AI (no access to ANTHROPIC_API_KEY) — file-path label still applies.
56
#
67
# - Updates the risk label if AI disagrees with file-path classification
78
# - Logs full details to workflow output (visible in Actions tab)
@@ -45,27 +46,36 @@ jobs:
4546
env:
4647
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4748

48-
# Skip AI for low-risk PRs
49-
- name: Skip AI for low-risk
50-
if: steps.filepath.outputs.level == 'low'
51-
run: echo "Low risk — skipping AI assessment"
52-
53-
# Install Agent SDK (only for non-low PRs)
49+
# Skip AI when low-risk or API key unavailable (e.g. fork PRs)
50+
- name: Check AI eligibility
51+
id: ai
52+
run: |
53+
if [ "${{ steps.filepath.outputs.level }}" = "low" ]; then
54+
echo "skip=true" >> $GITHUB_OUTPUT
55+
echo "Skipping AI — low risk"
56+
elif [ -z "${{ secrets.ANTHROPIC_API_KEY }}" ]; then
57+
echo "skip=true" >> $GITHUB_OUTPUT
58+
echo "Skipping AI — no API key (fork PR)"
59+
else
60+
echo "skip=false" >> $GITHUB_OUTPUT
61+
fi
62+
63+
# Install Agent SDK (only when AI will run)
5464
- name: Setup Node and Agent SDK
55-
if: steps.filepath.outputs.level != 'low'
65+
if: steps.ai.outputs.skip != 'true'
5666
uses: actions/setup-node@v4
5767
with:
5868
node-version: 20
5969

6070
- name: Install Agent SDK
61-
if: steps.filepath.outputs.level != 'low'
71+
if: steps.ai.outputs.skip != 'true'
6272
run: npm install --prefix .github/scripts @anthropic-ai/claude-agent-sdk
6373
env:
6474
NODE_ENV: production
6575

6676
# Layer 2+3: AI assessment
6777
- name: Run tiered AI assessment
68-
if: steps.filepath.outputs.level != 'low'
78+
if: steps.ai.outputs.skip != 'true'
6979
id: assess
7080
run: node .github/scripts/risk-assess.mjs ${{ env.PR_NUMBER }}
7181
env:
@@ -77,7 +87,7 @@ jobs:
7787

7888
# Update risk label if AI disagrees with file-path classification
7989
- name: Update risk label from AI
80-
if: steps.filepath.outputs.level != 'low'
90+
if: steps.ai.outputs.skip != 'true'
8191
uses: actions/github-script@v7
8292
with:
8393
script: |

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ Special thanks to these community members who have contributed code to SuperDoc:
144144
<a href="https://github.com/asumaran"><img src="https://github.com/asumaran.png" width="50" height="50" alt="asumaran" title="Alfredo Sumaran" /></a>
145145
<a href="https://github.com/J-Michalek"><img src="https://github.com/J-Michalek.png" width="50" height="50" alt="J-Michalek" title="Jakub Michálek" /></a>
146146
<a href="https://github.com/gm1357"><img src="https://github.com/gm1357.png" width="50" height="50" alt="gm1357" title="Gabriel Machado" /></a>
147+
<a href="https://github.com/roncallyt"><img src="https://github.com/roncallyt.png" width="50" height="50" alt="roncallyt" title="Thomerson Roncally" /></a>
148+
<a href="https://github.com/gpardhivvarma"><img src="https://github.com/gpardhivvarma.png" width="50" height="50" alt="gpardhivvarma" title="G Pardhiv Varma" /></a>
147149

148150
Want to see your avatar here? Check the [Contributing Guide](CONTRIBUTING.md) to get started.
149151

apps/cli/scripts/export-sdk-contract.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { resolve, dirname } from 'node:path';
1616
import { createHash } from 'node:crypto';
1717
import { tmpdir } from 'node:os';
1818

19-
import { COMMAND_CATALOG } from '@superdoc/document-api';
19+
import { COMMAND_CATALOG, INLINE_PROPERTY_REGISTRY } from '@superdoc/document-api';
2020

2121
import { CLI_OPERATION_METADATA } from '../src/cli/operation-params';
2222
import {
@@ -60,9 +60,12 @@ const INTENT_NAMES = {
6060
'doc.delete': 'delete_content',
6161
'doc.blocks.delete': 'delete_block',
6262
'doc.format.apply': 'format_apply',
63-
'doc.format.fontSize': 'format_font_size',
64-
'doc.format.fontFamily': 'format_font_family',
65-
'doc.format.color': 'format_color',
63+
...Object.fromEntries(
64+
INLINE_PROPERTY_REGISTRY.map((entry) => [
65+
`doc.format.${entry.key}`,
66+
`format_${entry.key.replace(/[A-Z]/g, (char) => `_${char.toLowerCase()}`)}`,
67+
]),
68+
),
6669
'doc.format.align': 'format_align',
6770
'doc.styles.apply': 'styles_apply',
6871
'doc.create.paragraph': 'create_paragraph',
@@ -86,6 +89,7 @@ const INTENT_NAMES = {
8689
'doc.sections.setLinkToPrevious': 'set_section_link_to_previous',
8790
'doc.sections.setPageBorders': 'set_section_page_borders',
8891
'doc.sections.clearPageBorders': 'clear_section_page_borders',
92+
'doc.create.tableOfContents': 'create_table_of_contents',
8993
'doc.lists.list': 'list_lists',
9094
'doc.lists.get': 'get_list',
9195
'doc.lists.insert': 'insert_list',
@@ -102,6 +106,11 @@ const INTENT_NAMES = {
102106
'doc.trackChanges.list': 'list_tracked_changes',
103107
'doc.trackChanges.get': 'get_tracked_change',
104108
'doc.trackChanges.decide': 'decide_tracked_change',
109+
'doc.toc.list': 'list_table_of_contents',
110+
'doc.toc.get': 'get_table_of_contents',
111+
'doc.toc.configure': 'configure_table_of_contents',
112+
'doc.toc.update': 'update_table_of_contents',
113+
'doc.toc.remove': 'remove_table_of_contents',
105114
'doc.query.match': 'query_match',
106115
'doc.mutations.preview': 'preview_mutations',
107116
'doc.mutations.apply': 'apply_mutations',

apps/cli/src/__tests__/conformance/harness.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { copyFile, mkdtemp, mkdir, rm } from 'node:fs/promises';
22
import { tmpdir } from 'node:os';
33
import path from 'node:path';
44
import { run } from '../../index';
5-
import { resolveListDocFixture, resolveSourceDocFixture } from '../fixtures';
5+
import { resolveListDocFixture, resolveSourceDocFixture, resolveTocDocFixture } from '../fixtures';
66

77
type RunResult = {
88
code: number;
@@ -48,6 +48,12 @@ export type ListItemAddress = {
4848
nodeId: string;
4949
};
5050

51+
export type TocAddress = {
52+
kind: 'block';
53+
nodeType: 'tableOfContents';
54+
nodeId: string;
55+
};
56+
5157
function parseEnvelope(raw: RunResult): CommandEnvelope {
5258
const source = raw.stdout.trim() || raw.stderr.trim();
5359
if (!source) {
@@ -119,6 +125,36 @@ export class ConformanceHarness {
119125
return filePath;
120126
}
121127

128+
async copyTocFixtureDoc(label: string, stateDir: string): Promise<string> {
129+
const filePath = path.join(this.docsDir, `${this.nextId()}-${label}.docx`);
130+
131+
try {
132+
await copyFile(await resolveTocDocFixture(), filePath);
133+
const probe = await this.runCli(['toc', 'list', filePath, '--limit', '1'], stateDir);
134+
if (probe.result.code === 0) {
135+
return filePath;
136+
}
137+
} catch {
138+
// Fall back to creating a TOC fixture from the generic source doc.
139+
}
140+
141+
const sourceDoc = await this.copyFixtureDoc(`${label}-seed`);
142+
const seededPath = path.join(this.docsDir, `${this.nextId()}-${label}-seeded.docx`);
143+
const { result, envelope } = await this.runCli(
144+
['create', 'table-of-contents', sourceDoc, '--out', seededPath],
145+
stateDir,
146+
);
147+
148+
if (result.code !== 0 || envelope.ok !== true) {
149+
const details = envelope.ok
150+
? 'unexpected non-success envelope'
151+
: `${envelope.error.code}: ${envelope.error.message}`;
152+
throw new Error(`Unable to seed TOC fixture for ${label}: ${details}`);
153+
}
154+
155+
return seededPath;
156+
}
157+
122158
createOutputPath(label: string): string {
123159
return path.join(this.docsDir, `${this.nextId()}-${label}.docx`);
124160
}
@@ -236,6 +272,27 @@ export class ConformanceHarness {
236272
return address;
237273
}
238274

275+
async firstTocAddress(docPath: string, stateDir: string): Promise<TocAddress> {
276+
const { result, envelope } = await this.runCli(['toc', 'list', docPath, '--limit', '1'], stateDir);
277+
if (result.code !== 0) {
278+
throw new Error(`Unable to resolve first table of contents for ${docPath}`);
279+
}
280+
281+
assertSuccessEnvelope(envelope);
282+
const data = envelope.data as {
283+
result?: {
284+
items?: Array<{
285+
address?: TocAddress;
286+
}>;
287+
};
288+
};
289+
const address = data.result?.items?.[0]?.address;
290+
if (!address) {
291+
throw new Error(`No table of contents address found in ${docPath}`);
292+
}
293+
return address;
294+
}
295+
239296
async addCommentFixture(
240297
stateDir: string,
241298
label: string,

0 commit comments

Comments
 (0)