Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 34 additions & 1 deletion packages/layout-engine/layout-resolved/src/versionSignature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,40 @@ const getSdtMetadataLockMode = (metadata: SdtMetadata | null | undefined): strin
return metadata.type === 'structuredContent' ? (metadata.lockMode ?? '') : '';
};

const getSdtMetadataAlias = (metadata: SdtMetadata | null | undefined): string => {
if (!metadata) return '';
if ('alias' in metadata && metadata.alias != null) {
return String(metadata.alias);
}
return '';
};

const getSdtMetadataTag = (metadata: SdtMetadata | null | undefined): string => {
if (!metadata) return '';
if ('tag' in metadata && metadata.tag != null) {
return String(metadata.tag);
}
return '';
};

const getSdtMetadataAppearance = (metadata: SdtMetadata | null | undefined): string => {
if (!metadata) return '';
if (metadata.type === 'structuredContent' && 'appearance' in metadata && metadata.appearance != null) {
return String(metadata.appearance);
}
return '';
};

const getSdtMetadataVersion = (metadata: SdtMetadata | null | undefined): string => {
if (!metadata) return '';
return [metadata.type, getSdtMetadataLockMode(metadata), getSdtMetadataId(metadata)].join(':');
return [
metadata.type,
getSdtMetadataLockMode(metadata),
getSdtMetadataId(metadata),
getSdtMetadataAlias(metadata),
getSdtMetadataTag(metadata),
getSdtMetadataAppearance(metadata),
].join(':');
};

const getTrackedChangeLayers = (run: TextRun): TrackedChangeMeta[] => {
Expand Down Expand Up @@ -359,6 +390,8 @@ export const deriveBlockVersion = (block: FlowBlock): string => {
textRun.comments?.length ?? 0,
// SD-3098: DomPainter reads run.bidi to apply dir + RLM injection; signature must include it.
textRun.bidi ? JSON.stringify(textRun.bidi) : '',
// Include inline SDT metadata (alias, tag, etc.) so changes trigger repaint.
getSdtMetadataVersion(textRun.sdt),
].join(',');
})
.join('|');
Expand Down
36 changes: 9 additions & 27 deletions packages/layout-engine/pm-adapter/src/sdt/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,10 @@
* document sections, TOC entries, structured content blocks, etc.
*/

import type {
FlowBlock,
TableBlock,
ListBlock,
SdtMetadata,
FieldAnnotationMetadata,
StructuredContentMetadata,
DocumentSectionMetadata,
DocPartMetadata,
} from '@superdoc/contracts';
import type { FlowBlock, TableBlock, ListBlock, SdtMetadata } from '@superdoc/contracts';
import type { PMNode } from '../types.js';
import { resolveSdtMetadata } from '@superdoc/style-engine';

type SdtMetadataForOverride<TOverride extends string | undefined> = TOverride extends 'fieldAnnotation'
? FieldAnnotationMetadata
: TOverride extends 'structuredContent' | 'structuredContentBlock'
? StructuredContentMetadata
: TOverride extends 'documentSection'
? DocumentSectionMetadata
: TOverride extends 'docPartObject'
? DocPartMetadata
: SdtMetadata;

/**
* Type guard to check if a node has instruction attribute.
*/
Expand Down Expand Up @@ -76,16 +57,17 @@ export function getDocPartObjectId(node: PMNode): string | undefined {
* @param overrideType - Optional type override (e.g., 'documentSection', 'docPartObject')
* @returns Resolved SDT metadata, or undefined if none
*/
export function resolveNodeSdtMetadata<TOverride extends string | undefined = undefined>(
node: PMNode,
overrideType?: TOverride,
): SdtMetadataForOverride<TOverride> | undefined {
export function resolveNodeSdtMetadata(node: PMNode, overrideType?: string): SdtMetadata | undefined {
const attrs = node.attrs;
if (!attrs) return undefined;
const nodeType = overrideType ?? node.type;
if (!nodeType) return undefined;
const cacheKey =
typeof attrs.hash === 'string'
// Don't pass cacheKey for structuredContent - let buildSdtCacheKey use full attrs
// This ensures mutable properties are included in the cache key
const isStructuredContent = nodeType === 'structuredContent' || nodeType === 'structuredContentBlock';
const cacheKey = isStructuredContent
? undefined
: typeof attrs.hash === 'string'
? attrs.hash
: typeof attrs.id === 'string'
? attrs.id
Expand All @@ -96,7 +78,7 @@ export function resolveNodeSdtMetadata<TOverride extends string | undefined = un
nodeType,
attrs,
cacheKey,
}) as SdtMetadataForOverride<TOverride> | undefined;
});
}

/**
Expand Down
9 changes: 9 additions & 0 deletions packages/layout-engine/style-engine/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,15 @@ function buildSdtCacheKey(

const id = toOptionalString(attrs.id);
if (id) {
// For structuredContent, include mutable properties in the cache key
// so that changes to alias/tag/lockMode/appearance cause cache misses.
if (nodeType === 'structuredContent' || nodeType === 'structuredContentBlock') {
const alias = toOptionalString(attrs.alias) ?? '';
const tag = toOptionalString(attrs.tag) ?? '';
const lockMode = toOptionalString(attrs.lockMode) ?? '';
const appearance = toOptionalString(attrs.appearance) ?? '';
return `${nodeType}:${id}:${alias}:${tag}:${lockMode}:${appearance}`;
}
return `${nodeType}:${id}`;
}

Expand Down
Loading