Commit 87368de
authored
feat(document-api): customXml.parts.* — list/get/create/patch/remove (SD-3105) (#3245)
* wip(document-api): customXml.parts.* contract layer (SD-3105)
Locks in the public API surface for Custom XML Data Storage Parts:
- Types + validators (customXml/customXml.types.ts, customXml.ts)
- 5 operation definitions in operation-definitions.ts
- Registry entries in operation-registry.ts
- Dispatch entries in invoke.ts
- DocumentApi.customXml + adapter slot in index.ts
- Re-exports in package barrel
Adapter implementation, plan-engine wrapper, OOXML package writer,
and tests are pending. Two known typecheck failures left:
reference-doc-map.ts (needs customXml group entry) and schemas.ts
(needs JSON schemas for 5 ops).
* wip(document-api): customXml.parts read adapter + contract gaps (SD-3105)
Completes the read path through the Document API and closes the
remaining contract-layer wiring gaps.
Contract:
- reference-doc-map.ts: customXml group title/description/page entry
- schemas.ts: 5 op JSON schemas + customXmlPartTargetSchema helper
- 30 validator tests passing (target xor id/partName, content
well-formedness smell-test, schemaRefs string[] check, patch
requires at-least-one)
Read adapter:
- super-converter/custom-xml-parts.js: discovery, parsing, serialization
helpers (listCustomXmlStoragePartNames, parsePropsPart, readCustomXmlPart)
- plan-engine/custom-xml-wrappers.ts: list/get adapter routing through
buildDiscoveryItem/Result, filters by rootNamespace and schemaRef,
partName-targeting fallback for foreign parts without Properties Parts
- assemble-adapters.ts: customXml plugged in alongside bookmarks
- 10 integration tests passing (list empty, list with filter, get by
id, get by partName fallback, get unknown id returns null)
Write adapter:
- create/patch/remove return CAPABILITY_UNAVAILABLE pending Phase B
(OOXML package file coordination)
* wip(document-api): customXml.parts write adapter + conformance vectors (SD-3105)
create / patch / remove now implement the full OOXML package
coordination instead of returning CAPABILITY_UNAVAILABLE.
Write adapter (super-converter/custom-xml-parts.js):
- createCustomXmlPart: generates fresh GUID itemID, allocates next
free index, writes Storage Part + Properties Part + item rels +
document-level relationship (5-file coordination)
- patchCustomXmlPart: resolves by id or partName, replaces content
and/or schemaRefs, preserves itemID. Creates a Properties Part
on the fly when patching schemaRefs into a foreign part that
doesn't have one yet.
- removeCustomXmlPart: deletes storage, props, item rels, and the
document-level relationship pointing at the part.
Adapter wrappers (plan-engine/custom-xml-wrappers.ts):
- All three writers call rejectTrackedMode (matches the contract
declaration of supportsTrackedMode: false).
- Errors map cleanly to INVALID_INPUT / TARGET_NOT_FOUND.
- supportsDryRun set to false for v1; dry-run support can come later.
Conformance:
- contract-conformance.test.ts: throw/apply vectors registered for
all three customXml.parts mutating ops.
- contract.test.ts: customXml added to the validGroups list.
Coverage:
- 16 integration tests passing (read + write + round-trip).
- 1195/1195 conformance tests passing.
- 3392/3392 across the full document-api-adapters suite.
- 1428/1428 across the document-api package suite.
Round-trip test exports a created part to DOCX, reimports through
the canonical loader, and verifies the itemID GUID, rootNamespace,
schemaRefs, and content all survive. The 5-file package
coordination is empirically OOXML-faithful.
* wip(document-api): address review — safety, lifecycle, rels pairing, tombstones (SD-3105)
All six findings from the SD-3105 review:
#1 (High) partName scoping
- resolveTargetPartName and readCustomXmlPart now require the path to
match customXml/itemN.xml. Targets like word/document.xml are rejected
cleanly instead of letting through.
#2 (High) bypassed mutation lifecycle
- create/patch/remove now route through executeOutOfBandMutation, the
same shared primitive citation sources use. Each call gets:
* expectedRevision check
* dryRun preview path
* dirty marking + GUID promotion
* revision increment on actual change
- supportsDryRun: true on all three ops with real dry-run validation
(well-formedness for create/patch, target resolution for patch/remove).
#3 (High) deletion didn't persist for imported DOCX parts
- removeCustomXmlPart now stamps the removed paths into a
converter.removedCustomXmlPaths set. Editor.ts export loop emits
updatedDocs[path] = null for each entry, so original-zip entries are
tombstoned instead of being copied through.
#4 (Medium) props parts paired by filename
- findPropsPartFor now reads customXml/_rels/itemN.xml.rels and follows
the Type=customXmlProps relationship. Falls back to the index-name
heuristic only when no rels file exists. Foreign docs with non-
matching names now resolve correctly.
#5 (Medium) contract metadata lied about failures
- possibleFailureCodes updated to actual codes: ['INVALID_INPUT'],
['TARGET_NOT_FOUND', 'INVALID_INPUT'], ['TARGET_NOT_FOUND'].
#6 (Medium) JSON schemas didn't match runtime
- get output now { oneOf: [{ type: 'object' }, { type: 'null' }] }.
- patch input encodes 'at least one of content or schemaRefs' via
anyOf, with additionalProperties: false.
- content fields gain minLength: 1.
Coverage update:
- Two new integration tests assert #1 (partName rejection on
word/document.xml etc) and #4 (foreign-name props resolved via rels).
- failureCase and dryRun vectors added for all three customXml.parts
mutating ops in the conformance suite.
Verified:
- @superdoc/super-editor: 3398/3398 across 123 files
- @superdoc/document-api: 1428/1428 across 51 files
* wip(document-api): preserve revision errors + fix tombstone collision (SD-3105)
Two correctness fixes from the second review pass:
#1 Broad catch was swallowing REVISION_MISMATCH
customXml.parts.create and patch wrapped the entire
executeOutOfBandMutation call in a try/catch that converted
everything to INVALID_INPUT. Lifecycle errors from
checkRevision (REVISION_MISMATCH) and any future PlanError
propagation were being eaten.
Replaced the outer try/catch with a scoped safeValidate helper
that only catches content-parsing errors from createCustomXmlPart
/ patchCustomXmlPart, returning them as structured INVALID_INPUT
outcomes. The executeOutOfBandMutation call itself now lets
revision and other lifecycle errors bubble.
Also reordered patch: target resolution runs FIRST, so a missing
target reports TARGET_NOT_FOUND instead of (potentially)
INVALID_INPUT if patch happened to throw.
#2 Tombstone could null a newly-created part on the same index
remove → create on a recycled index (customXml/item1.xml) had
this sequence: remove writes 'customXml/item1.xml' to the
tombstone set; create reuses index 1 because convertedXml has
no item1.xml; export serializes the new part, then the tombstone
loop runs and overwrites updatedDocs['customXml/item1.xml'] with
null, deleting the freshly-created part from the exported zip.
Fix: createCustomXmlPart now removes its written paths
(partName, propsPartName, itemRelsPath) from
converter.removedCustomXmlPaths whenever a converter is passed.
The new integration test exercises the exact remove → create →
export → reimport sequence and asserts the new part survives
with its fresh id and content.
Coverage:
- 19/19 integration tests passing (incl. the new tombstone test).
- 3401/3401 super-editor document-api-adapters tests.
- 1428/1428 document-api package tests.
* wip(document-api): OPC rels resolution, bibliography invalidation, content-types pruning (SD-3105)
Three review findings — each verified by a failing test first, then fixed:
#1 findPropsPartFor used an ad-hoc regex that only handled bare names
and `/` prefixes. Valid OPC Target forms like `./itemPropsN.xml`
and `../customXml/itemPropsN.xml` (per RFC 3986 §5.2.4 and ECMA-376
§9.1.4) silently fell through to the index-name fallback or missed
the props entirely. Route through resolveOpcTargetPath with
baseDir='customXml' (the source part's directory). Two new tests
assert resolution for `./` and `../customXml/` Target forms.
#2 removeCustomXmlPart on the bibliography part left
converter.bibliographyPart populated. On the next export,
syncBibliographyPartToPackage(convertedXml, bibliographyPart) wrote
the cached sources back into convertedXml — resurrecting the
supposedly-removed part in the in-memory state (the tombstone still
nulled the exported zip entry, but the editor's own view of the
document silently re-grew the part). patchCustomXmlPart on the
bibliography part had the worse variant: cached sources overwrote
the customer's new content. Both now call
invalidateConverterCachesForPath, which clears
converter.bibliographyPart when its partPath matches the part we
touched. New test exercises remove + exportDocx and asserts the
convertedXml entry stays gone.
#3 DocxZipper.updateContentTypes only pruned stale Override entries
for comment parts. After customXml.parts.remove, the original DOCX's
`<Override PartName="/customXml/itemPropsN.xml" .../>` survived
in the exported [Content_Types].xml, pointing at a non-existent
part. The operation's cleanup contract claimed otherwise. Extended
the stale-override pruning to also cover customXml/itemPropsN.xml
paths absent from the final zip (i.e. tombstoned via
updatedDocs[path] = null).
Also clears the now-stale top-of-file docblock on the integration test
that claimed write-side was `CAPABILITY_UNAVAILABLE`-stubbed; the
file actually contains a full write-side suite including round-trip
and bibliography-cache tests.
Verified:
- 22/22 customXml integration tests
- 3404/3404 super-editor document-api-adapters
- 1428/1428 document-api package
* wip(document-api): foreign-named props Override pruning + schema minLength (SD-3105)
Three findings from another review round; verified two with failing
tests, then fixed:
#1 (real bug, verified) — Content_Types Override pruning regex was too
tight: `/^\/customXml\/itemProps\d+\.xml$/i` only matched
numeric-named props parts. But `findPropsPartFor` correctly resolves
foreign-named props parts (e.g. `customXml/itemPropsFOREIGN.xml`)
via the OPC rels file, so removing one would tombstone the file but
leave a stale Override pointing at it. Fix: identify props Overrides
by ContentType (`application/vnd.openxmlformats-officedocument.customXmlProperties+xml`)
instead of filename. New DocxZipper test confirms the foreign-named
Override is pruned on tombstone.
#2 (contract gap, verified) — `customXmlPartTargetSchema` allowed
empty `id` and `partName` strings even though the runtime
validator requires non-empty. Added `minLength: 1` to both
target-shape branches. Also added `minLength: 1` to `content`
and `schemaRefs.items` on create/patch.
Pulling minLength into the contract surfaced a secondary issue: the
conformance test's custom JSON Schema validator didn't support
`minLength` / `maxLength`. Added support (lines 84-91 had been
rejecting unsupported keywords entirely, which made my oneOf
branches both fail). The validator now matches the keywords its
schemas actually use.
#3 (scope question, not changed) — Discovery of foreign-named Storage
Parts (filenames other than `customXml/itemN.xml`). Considered:
walking word/_rels/document.xml.rels for `customXml`-type rels
would cover this. But `isCustomXmlStoragePartName` and
`findPropsPartFor` both key off the numeric-index convention, so
broadening discovery without also broadening those would leave list
and get/patch/remove disagreeing about what's a valid part.
Documented as an explicit v1 scope limitation via AIDEV-NOTE on
`listCustomXmlStoragePartNames`. No real-world producer (Word,
Google Docs, LibreOffice, pandoc) deviates from the convention, so
v1 ships Word-style only.
Verified:
- 3429/3429 super-editor document-api-adapters + DocxZipper
- 1428/1428 document-api package
- New DocxZipper test exercises the foreign-named props Override
pruning end-to-end
* wip(document-api): preserve schemaRefs omitted vs empty distinction (SD-3105)
ECMA-376 §22.5.2.3 distinguishes three schemaRefs states:
- <schemaRefs> omitted → app may infer schemas
- <schemaRefs/> present empty → explicit "no schemas should be used"
- <schemaRefs> with children → these schemas validate the part
The previous write side conflated the first two: it always emitted
<schemaRefs/> regardless of whether the caller passed undefined or [].
This made downstream consumers see "no schemas" when the customer
intent was actually "app picks".
Fix:
- buildItemPropsRoot now omits the <schemaRefs> element when schemaRefs
is undefined, and emits it (empty or with children) when an array is
passed explicitly.
- createCustomXmlPart no longer coerces undefined to [] before calling
buildItemPropsRoot.
- patchCustomXmlPart already passed through verbatim — no change needed.
Read side keeps returning schemaRefs: [] for both the omitted and the
present-empty cases. The distinction is lost in the public summary
type (schemaRefs: string[]), and recovering it would require a type
contract change — deferred for v1.
Two new integration tests assert:
1. create({ content }) without schemaRefs produces a Properties Part
with NO <ds:schemaRefs> element.
2. create({ content, schemaRefs: [] }) produces a Properties Part
WITH an empty <ds:schemaRefs/> element.
Verified:
- 3431/3431 super-editor document-api-adapters
- 1428/1428 document-api package
* wip(document-api): tighten v1 scope statements (SD-3105)
Two reviewer follow-ups on commit b47d3c9:
1. AIDEV note framing softened. "Every real producer" was a stronger
claim than I could verify. Reframed to "the Word-compatible
producers we target use this convention" — accurate and doesn't
pretend to have audited every OOXML producer.
2. v1 scope now surfaced in the public type contract, not just internal
notes. Generated docs and consumer JSDoc tooltips now show the
Word-style filename constraint:
- CustomXmlPartTarget.partName: scope note added
- CustomXmlPartSummary.partName: scope note added
Foreign-named Properties Parts still work (paired via rels); only
Storage Part filenames are constrained.
Not in this commit:
- The reviewer flagged the conformance schema validator's early-return
after oneOf/anyOf — keywords like required and additionalProperties
sitting at the same level as anyOf are not checked. Confirmed via
source inspection. Affects the patch input schema specifically. Not
a production API bug (runtime validators in customXml.ts cover these
constraints); just a slight weakening of conformance signal. Worth a
separate ticket on the test harness, not this PR.
* wip(document-api): update CustomXmlPartsCreateInput JSDoc to match new schemaRefs semantics (SD-3105)
The JSDoc on CustomXmlPartsCreateInput.schemaRefs and
CustomXmlPartsPatchInput.schemaRefs was stale after commit ced0fe3
swapped the writer to preserve the ECMA-376 §22.5.2.3 omitted-vs-empty
distinction.
Old text claimed 'when omitted or empty, [an] empty <ds:schemaRefs/>
[is] still emitted'. That was true before ced0fe3; now omitted
produces no element. Updated both JSDoc blocks to explain the three
distinct spec states (omitted, empty, populated) so generated docs and
IDE tooltips match runtime behavior.
* wip(document-api): surface v1 partName scope in operation descriptions (SD-3105)
operation-definitions.ts descriptions feed reference docs (Mintlify),
LLM tool catalog descriptions, and CLI help text. The public type
JSDoc on CustomXmlPartTarget already states the v1 partName scope,
but consumers reading generated docs or tool descriptions wouldn't
see it. Added the constraint to the three operation descriptions that
accept a partName target: get, patch, remove. Not on list (returns
whatever's discovered) or create (always emits Word-style filenames).
* fix(document-api): narrow custom XML write outcomes
* wip(document-api): surface patch-minted itemID, type removedCustomXmlPaths, test name fix (SD-3105)
Three findings from another review round:
#1 patch on a foreign Storage Part minted a fresh itemID but didn't
surface it
Scenario: customer targets by partName because the imported part
had no Properties Part; patches schemaRefs; patchCustomXmlPart
creates a new Properties Part with a fresh GUID; wrapper returned
{ success, target: input.target } leaving the caller unable to
address the part by id without re-listing.
Fix:
- patchCustomXmlPart now returns { partName, id? } where id is the
resolved (existing or freshly minted) itemID.
- CustomXmlPartsMutationSuccess gains an optional id?: CustomXmlPartId
field with JSDoc explaining the patch-foreign-part case.
- Wrapper passes id through to the success result.
- Schema customXmlPartMutation gains an optional id field
(minLength: 1).
- New integration test: import a Storage Part with no props,
patch schemaRefs, assert the result includes a new GUID and
get({ id }) finds the part.
#2 removedCustomXmlPaths accessed via as unknown as casts
Two readers (Editor.ts, custom-xml-wrappers.ts) and one writer
(custom-xml-parts.js) all coupled via casts. A rename would break
tombstone emission silently.
Fix: added removedCustomXmlPaths?: Set<string> to
SuperConverter.d.ts with JSDoc. Dropped the cast in Editor.ts. The
local type alias in custom-xml-wrappers.ts is still convenient as
structural typing (it duck-types the converter without importing
the full class), so leaving it.
#3 Test name at customXml.test.ts:206 said 'rejects' but body asserted
.not.toThrow(). Renamed to 'accepts patch with empty schemaRefs
alongside valid content'.
Out-of-scope items the reviewer flagged but already on branch:
- DocxZipper Content_Types Override pruning for tombstoned customXml
props (fixed in b47d3c9)
- Schema minLength on target id/partName (fixed in b47d3c9)
- ./-prefix in item-rels resolver (fixed in 7cb928e via
resolveOpcTargetPath)
Word-fixture observation re: ds:schemaRefs auto-fill from root
namespace is real but our v1 stance is deliberate (omit/[]/populated
distinct per ECMA-376 §22.5.2.3, see ced0fe3).
Verified:
- 3432/3432 super-editor document-api-adapters + DocxZipper
- 1428/1428 document-api package
* wip(document-api): revert SuperConverter.d.ts typing, keep local cast (SD-3105)
Reviewer caught a regression: adding removedCustomXmlPaths?: Set<string>
as an explicit field on SuperConverter.d.ts in ee06aa0 triggered
TypeScript weak-type errors at three call sites that pass
SuperConverter into local structural types not including the new field:
- Editor.ts:1103 → ConverterWithDocumentSettings
- HeaderFooterSessionManager.ts:703, 2322 → ConverterLike
- PresentationEditor.ts:6039 → ConverterWithDocumentSettings
Verified by running types:check with and without the d.ts change —
errors only appear with the typed field present, because the
[key: string]: any index signature alone is enough to satisfy weak
types, but an explicit named field forces TypeScript to require at
least one property overlap with the target shape.
Per reviewer's smaller-fix suggestion: revert the d.ts change, restore
the cast in Editor.ts with an AIDEV-NOTE pointing at this regression
so future maintainers don't try the same simplification.
Properly cleaning up the converter declaration is a separate piece of
work (would need to enumerate the actual fields ConverterWithDocumentSettings /
ConverterLike consume from real producers). Not in scope here.
Verified:
- 3432/3432 super-editor document-api-adapters + DocxZipper
- SuperConverter weak-type errors no longer in types:check output1 parent 51627ac commit 87368de
19 files changed
Lines changed: 2673 additions & 1 deletion
File tree
- packages
- document-api/src
- contract
- customXml
- invoke
- super-editor/src/editors/v1
- core
- super-converter
- document-api-adapters
- __conformance__
- plan-engine
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
391 | 391 | | |
392 | 392 | | |
393 | 393 | | |
| 394 | + | |
394 | 395 | | |
395 | 396 | | |
396 | 397 | | |
| |||
Lines changed: 82 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
69 | | - | |
| 69 | + | |
| 70 | + | |
70 | 71 | | |
71 | 72 | | |
72 | 73 | | |
| |||
6259 | 6260 | | |
6260 | 6261 | | |
6261 | 6262 | | |
| 6263 | + | |
| 6264 | + | |
| 6265 | + | |
| 6266 | + | |
| 6267 | + | |
| 6268 | + | |
| 6269 | + | |
| 6270 | + | |
| 6271 | + | |
| 6272 | + | |
| 6273 | + | |
| 6274 | + | |
| 6275 | + | |
| 6276 | + | |
| 6277 | + | |
| 6278 | + | |
| 6279 | + | |
| 6280 | + | |
| 6281 | + | |
| 6282 | + | |
| 6283 | + | |
| 6284 | + | |
| 6285 | + | |
| 6286 | + | |
| 6287 | + | |
| 6288 | + | |
| 6289 | + | |
| 6290 | + | |
| 6291 | + | |
| 6292 | + | |
| 6293 | + | |
| 6294 | + | |
| 6295 | + | |
| 6296 | + | |
| 6297 | + | |
| 6298 | + | |
| 6299 | + | |
| 6300 | + | |
| 6301 | + | |
| 6302 | + | |
| 6303 | + | |
| 6304 | + | |
| 6305 | + | |
| 6306 | + | |
| 6307 | + | |
| 6308 | + | |
| 6309 | + | |
| 6310 | + | |
| 6311 | + | |
| 6312 | + | |
| 6313 | + | |
| 6314 | + | |
| 6315 | + | |
| 6316 | + | |
| 6317 | + | |
| 6318 | + | |
| 6319 | + | |
| 6320 | + | |
| 6321 | + | |
| 6322 | + | |
| 6323 | + | |
| 6324 | + | |
| 6325 | + | |
| 6326 | + | |
| 6327 | + | |
| 6328 | + | |
| 6329 | + | |
| 6330 | + | |
| 6331 | + | |
| 6332 | + | |
| 6333 | + | |
| 6334 | + | |
| 6335 | + | |
| 6336 | + | |
| 6337 | + | |
| 6338 | + | |
| 6339 | + | |
| 6340 | + | |
| 6341 | + | |
| 6342 | + | |
6262 | 6343 | | |
6263 | 6344 | | |
6264 | 6345 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
262 | 262 | | |
263 | 263 | | |
264 | 264 | | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
265 | 277 | | |
266 | 278 | | |
267 | 279 | | |
| |||
1544 | 1556 | | |
1545 | 1557 | | |
1546 | 1558 | | |
| 1559 | + | |
| 1560 | + | |
| 1561 | + | |
| 1562 | + | |
| 1563 | + | |
| 1564 | + | |
| 1565 | + | |
| 1566 | + | |
| 1567 | + | |
| 1568 | + | |
| 1569 | + | |
| 1570 | + | |
| 1571 | + | |
| 1572 | + | |
| 1573 | + | |
| 1574 | + | |
| 1575 | + | |
| 1576 | + | |
| 1577 | + | |
| 1578 | + | |
| 1579 | + | |
| 1580 | + | |
| 1581 | + | |
| 1582 | + | |
| 1583 | + | |
| 1584 | + | |
| 1585 | + | |
1547 | 1586 | | |
1548 | 1587 | | |
1549 | 1588 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
191 | 191 | | |
192 | 192 | | |
193 | 193 | | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
194 | 200 | | |
195 | 201 | | |
196 | 202 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2583 | 2583 | | |
2584 | 2584 | | |
2585 | 2585 | | |
| 2586 | + | |
| 2587 | + | |
| 2588 | + | |
| 2589 | + | |
| 2590 | + | |
| 2591 | + | |
| 2592 | + | |
| 2593 | + | |
| 2594 | + | |
| 2595 | + | |
| 2596 | + | |
| 2597 | + | |
| 2598 | + | |
| 2599 | + | |
| 2600 | + | |
| 2601 | + | |
| 2602 | + | |
| 2603 | + | |
| 2604 | + | |
| 2605 | + | |
| 2606 | + | |
| 2607 | + | |
| 2608 | + | |
| 2609 | + | |
| 2610 | + | |
| 2611 | + | |
| 2612 | + | |
| 2613 | + | |
| 2614 | + | |
| 2615 | + | |
2586 | 2616 | | |
2587 | 2617 | | |
2588 | 2618 | | |
| |||
7628 | 7658 | | |
7629 | 7659 | | |
7630 | 7660 | | |
| 7661 | + | |
| 7662 | + | |
| 7663 | + | |
| 7664 | + | |
| 7665 | + | |
| 7666 | + | |
| 7667 | + | |
| 7668 | + | |
| 7669 | + | |
| 7670 | + | |
| 7671 | + | |
| 7672 | + | |
| 7673 | + | |
| 7674 | + | |
| 7675 | + | |
| 7676 | + | |
| 7677 | + | |
| 7678 | + | |
| 7679 | + | |
| 7680 | + | |
| 7681 | + | |
| 7682 | + | |
| 7683 | + | |
| 7684 | + | |
| 7685 | + | |
| 7686 | + | |
| 7687 | + | |
| 7688 | + | |
| 7689 | + | |
| 7690 | + | |
| 7691 | + | |
| 7692 | + | |
| 7693 | + | |
| 7694 | + | |
| 7695 | + | |
| 7696 | + | |
| 7697 | + | |
| 7698 | + | |
| 7699 | + | |
| 7700 | + | |
| 7701 | + | |
| 7702 | + | |
| 7703 | + | |
| 7704 | + | |
7631 | 7705 | | |
7632 | 7706 | | |
7633 | 7707 | | |
| |||
0 commit comments