@@ -93,23 +93,54 @@ function hasPrettyProperties(node: unknown): boolean {
9393}
9494
9595async function firstTextRange ( args : string [ ] ) : Promise < TextRange > {
96+ // SDM/1: find returns SDNodeResult with SDAddress. For text searches,
97+ // the address is content-level (the containing block). We extract the
98+ // blockId and find the pattern position within the node's text content.
9699 const result = await runCli ( args ) ;
97100 expect ( result . code ) . toBe ( 0 ) ;
98101
99102 const envelope = parseJsonOutput <
100103 SuccessEnvelope < {
101104 result : {
102- items ?: Array < { context ?: { textRanges ?: TextRange [ ] } } > ;
105+ items ?: Array < {
106+ node ?: { kind ?: string ; [ key : string ] : unknown } ;
107+ address ?: { kind ?: string ; nodeId ?: string } ;
108+ } > ;
103109 } ;
104110 } >
105111 > ( result ) ;
106112
107- const range = envelope . data . result . items ?. [ 0 ] ?. context ?. textRanges ?. [ 0 ] ;
108- if ( ! range ) {
109- throw new Error ( 'Expected at least one text range from find result.' ) ;
113+ const item = envelope . data . result . items ?. [ 0 ] ;
114+ const address = item ?. address ;
115+ if ( ! address ?. nodeId ) {
116+ throw new Error ( 'Expected at least one match from find result.' ) ;
110117 }
111118
112- return range ;
119+ // Extract concatenated text from the SDM/1 node's inline content
120+ const node = item ?. node as Record < string , unknown > | undefined ;
121+ const nodeKind = node ?. kind as string | undefined ;
122+ const kindData = nodeKind ? ( node ?. [ nodeKind ] as Record < string , unknown > | undefined ) : undefined ;
123+ const inlines = Array . isArray ( kindData ?. inlines ) ? kindData ! . inlines : [ ] ;
124+ let fullText = '' ;
125+ for ( const inline of inlines ) {
126+ if ( typeof inline === 'object' && inline != null && ( inline as Record < string , unknown > ) . kind === 'run' ) {
127+ const runData = ( inline as Record < string , unknown > ) . run as Record < string , unknown > | undefined ;
128+ if ( typeof runData ?. text === 'string' ) fullText += runData . text as string ;
129+ }
130+ }
131+
132+ // Extract the search pattern from args to find its position within the text
133+ const patternIdx = args . indexOf ( '--pattern' ) ;
134+ const pattern = patternIdx >= 0 ? args [ patternIdx + 1 ] : undefined ;
135+ const matchIndex = pattern ? fullText . indexOf ( pattern ) : - 1 ;
136+ const start = matchIndex >= 0 ? matchIndex : 0 ;
137+ const end = matchIndex >= 0 ? matchIndex + pattern ! . length : Math . max ( fullText . length , 1 ) ;
138+
139+ return {
140+ kind : 'text' ,
141+ blockId : address . nodeId ,
142+ range : { start, end } ,
143+ } ;
113144}
114145
115146function firstInsertedEntityId ( result : RunResult ) : string {
@@ -263,7 +294,7 @@ describe('superdoc CLI', () => {
263294 expect ( result . code ) . toBe ( 0 ) ;
264295 expect ( result . stdout ) . toContain ( 'Parameters:' ) ;
265296 expect ( result . stdout ) . toContain ( '--session' ) ;
266- expect ( result . stdout ) . toContain ( '--include-nodes ' ) ;
297+ expect ( result . stdout ) . toContain ( '--limit ' ) ;
267298 expect ( result . stdout ) . toContain ( 'Constraints:' ) ;
268299 } ) ;
269300
@@ -514,13 +545,13 @@ describe('superdoc CLI', () => {
514545 operationId : string ;
515546 result : {
516547 document : { source : string } ;
517- target : TextRange ;
548+ receipt : { success : boolean } ;
518549 } ;
519550 } >
520551 > ( callResult ) ;
521552 expect ( envelope . data . operationId ) . toBe ( 'doc.insert' ) ;
522553 expect ( envelope . data . result . document . source ) . toBe ( 'path' ) ;
523- expect ( envelope . data . result . target . range . start ) . toBe ( 0 ) ;
554+ expect ( envelope . data . result . receipt . success ) . toBe ( true ) ;
524555
525556 const verifyResult = await runCli ( [ 'find' , out , '--type' , 'text' , '--pattern' , 'CALL_INSERT_TOKEN_1597' ] ) ;
526557 expect ( verifyResult . code ) . toBe ( 0 ) ;
@@ -574,8 +605,7 @@ describe('superdoc CLI', () => {
574605 expect ( envelope . ok ) . toBe ( true ) ;
575606 expect ( envelope . command ) . toBe ( 'find' ) ;
576607 expect ( envelope . data . result . total ) . toBeGreaterThan ( 0 ) ;
577- expect ( envelope . data . result . items [ 0 ] . address . kind ) . toBe ( 'inline' ) ;
578- expect ( envelope . data . result . items [ 0 ] . address . nodeType ) . toBe ( 'run' ) ;
608+ expect ( envelope . data . result . items [ 0 ] . node . kind ) . toBe ( 'run' ) ;
579609 } ) ;
580610
581611 test ( 'find rejects legacy query.include payloads' , async ( ) => {
@@ -595,7 +625,7 @@ describe('superdoc CLI', () => {
595625 expect ( envelope . error . message ) . toContain ( 'query.include' ) ;
596626 } ) ;
597627
598- test ( 'find text queries return context and textRanges without includeNodes ' , async ( ) => {
628+ test ( 'find text queries return content addresses with node projections ' , async ( ) => {
599629 const result = await runCli ( [
600630 'find' ,
601631 SAMPLE_DOC ,
@@ -611,21 +641,22 @@ describe('superdoc CLI', () => {
611641 SuccessEnvelope < {
612642 result : {
613643 items ?: Array < {
614- context ?: {
615- textRanges ?: Array < { kind : 'text' ; blockId : string ; range : { start : number ; end : number } } > ;
616- } ;
644+ node ?: { kind ?: string } ;
645+ address ?: { kind ?: string ; nodeId ?: string } ;
617646 } > ;
618647 } ;
619648 } >
620649 > ( result ) ;
621650
622- const firstContext = envelope . data . result . items ?. [ 0 ] ?. context ;
623- expect ( firstContext ) . toBeDefined ( ) ;
624- expect ( firstContext ?. textRanges ?. length ) . toBeGreaterThan ( 0 ) ;
651+ const firstItem = envelope . data . result . items ?. [ 0 ] ;
652+ expect ( firstItem ) . toBeDefined ( ) ;
653+ expect ( firstItem ?. address ?. kind ) . toBe ( 'content' ) ;
654+ expect ( firstItem ?. address ?. nodeId ) . toBeDefined ( ) ;
655+ expect ( firstItem ?. node ?. kind ) . toBeDefined ( ) ;
625656 } ) ;
626657
627658 test ( 'get-node resolves address returned by find' , async ( ) => {
628- const findResult = await runCli ( [ 'find' , SAMPLE_DOC , '--type' , 'text ' , '--pattern ' , 'Wilde ' , '--limit' , '1' ] ) ;
659+ const findResult = await runCli ( [ 'find' , SAMPLE_DOC , '--type' , 'node ' , '--node-type ' , 'paragraph ' , '--limit' , '1' ] ) ;
629660 expect ( findResult . code ) . toBe ( 0 ) ;
630661
631662 const findEnvelope = parseJsonOutput <
@@ -639,7 +670,17 @@ describe('superdoc CLI', () => {
639670 const address = findEnvelope . data . result . items [ 0 ] ?. address ;
640671 expect ( address ) . toBeDefined ( ) ;
641672
642- const getNodeResult = await runCli ( [ 'get-node' , SAMPLE_DOC , '--address-json' , JSON . stringify ( address ) ] ) ;
673+ // SDM/1 addresses use kind: 'content' for block-level nodes
674+ // getNode still accepts the old NodeAddress format, so we construct one
675+ const nodeId = address ?. nodeId as string ;
676+ expect ( nodeId ) . toBeDefined ( ) ;
677+
678+ const getNodeResult = await runCli ( [
679+ 'get-node' ,
680+ SAMPLE_DOC ,
681+ '--address-json' ,
682+ JSON . stringify ( { kind : 'block' , nodeType : 'paragraph' , nodeId } ) ,
683+ ] ) ;
643684 expect ( getNodeResult . code ) . toBe ( 0 ) ;
644685
645686 const nodeEnvelope = parseJsonOutput < SuccessEnvelope < { node : unknown } > > ( getNodeResult ) ;
@@ -649,7 +690,7 @@ describe('superdoc CLI', () => {
649690 } ) ;
650691
651692 test ( 'get-node pretty includes resolved identity and optional node details' , async ( ) => {
652- const findResult = await runCli ( [ 'find' , SAMPLE_DOC , '--type' , 'text ' , '--pattern ' , 'Wilde ' , '--limit' , '1' ] ) ;
693+ const findResult = await runCli ( [ 'find' , SAMPLE_DOC , '--type' , 'node ' , '--node-type ' , 'paragraph ' , '--limit' , '1' ] ) ;
653694 expect ( findResult . code ) . toBe ( 0 ) ;
654695
655696 const findEnvelope = parseJsonOutput <
@@ -663,51 +704,53 @@ describe('superdoc CLI', () => {
663704 expect ( address ) . toBeDefined ( ) ;
664705 if ( ! address ) return ;
665706
707+ const nodeId = address . nodeId as string ;
708+ const blockAddress = { kind : 'block' , nodeType : 'paragraph' , nodeId } ;
709+
666710 const prettyResult = await runCli ( [
667711 'get-node' ,
668712 SAMPLE_DOC ,
669713 '--address-json' ,
670- JSON . stringify ( address ) ,
714+ JSON . stringify ( blockAddress ) ,
671715 '--output' ,
672716 'pretty' ,
673717 ] ) ;
674718 expect ( prettyResult . code ) . toBe ( 0 ) ;
675719 expect ( prettyResult . stdout ) . toContain ( 'Revision 0:' ) ;
676720
677- const jsonResult = await runCli ( [ 'get-node' , SAMPLE_DOC , '--address-json' , JSON . stringify ( address ) ] ) ;
721+ const jsonResult = await runCli ( [ 'get-node' , SAMPLE_DOC , '--address-json' , JSON . stringify ( blockAddress ) ] ) ;
678722 expect ( jsonResult . code ) . toBe ( 0 ) ;
679723 const jsonEnvelope = parseJsonOutput < SuccessEnvelope < { node : unknown } > > ( jsonResult ) ;
680724 const node = asRecord ( jsonEnvelope . data . node ) ;
681- if ( typeof node ?. text === 'string' && node . text . length > 0 ) {
682- expect ( prettyResult . stdout ) . toContain ( 'Text:' ) ;
683- }
684- if ( hasPrettyProperties ( jsonEnvelope . data . node ) ) {
685- expect ( prettyResult . stdout ) . toContain ( 'Properties:' ) ;
725+ // SDNodeResult: node is under result.node (which contains { kind, ... })
726+ const sdNode = asRecord ( node ?. node ) ?? node ;
727+ if ( sdNode && typeof sdNode . kind === 'string' ) {
728+ expect ( prettyResult . stdout ) . toContain ( 'Revision 0:' ) ;
686729 }
687730 } ) ;
688731
689732 test ( 'get-node-by-id resolves block ID returned by find' , async ( ) => {
690- const findResult = await runCli ( [ 'find' , SAMPLE_DOC , '--type' , 'text ' , '--pattern ' , 'Wilde ' , '--limit' , '1' ] ) ;
733+ const findResult = await runCli ( [ 'find' , SAMPLE_DOC , '--type' , 'node ' , '--node-type ' , 'paragraph ' , '--limit' , '1' ] ) ;
691734 expect ( findResult . code ) . toBe ( 0 ) ;
692735
693736 const findEnvelope = parseJsonOutput <
694737 SuccessEnvelope < {
695738 result : {
696- items : Array < { address : { kind : string ; nodeType : string ; nodeId : string } } > ;
739+ items : Array < { node : { kind : string } ; address : { kind : string ; nodeId : string } } > ;
697740 } ;
698741 } >
699742 > ( findResult ) ;
700743
701- const firstMatch = findEnvelope . data . result . items [ 0 ] . address ;
702- expect ( firstMatch . kind ) . toBe ( 'block ' ) ;
744+ const firstItem = findEnvelope . data . result . items [ 0 ] ;
745+ expect ( firstItem . address . kind ) . toBe ( 'content ' ) ;
703746
704747 const getByIdResult = await runCli ( [
705748 'get-node-by-id' ,
706749 SAMPLE_DOC ,
707750 '--id' ,
708- firstMatch . nodeId ,
751+ firstItem . address . nodeId ,
709752 '--node-type' ,
710- firstMatch . nodeType ,
753+ firstItem . node . kind ,
711754 ] ) ;
712755 expect ( getByIdResult . code ) . toBe ( 0 ) ;
713756
@@ -717,51 +760,44 @@ describe('superdoc CLI', () => {
717760 } ) ;
718761
719762 test ( 'get-node-by-id pretty includes resolved identity and optional node details' , async ( ) => {
720- const findResult = await runCli ( [ 'find' , SAMPLE_DOC , '--type' , 'text ' , '--pattern ' , 'Wilde ' , '--limit' , '1' ] ) ;
763+ const findResult = await runCli ( [ 'find' , SAMPLE_DOC , '--type' , 'node ' , '--node-type ' , 'paragraph ' , '--limit' , '1' ] ) ;
721764 expect ( findResult . code ) . toBe ( 0 ) ;
722765
723766 const findEnvelope = parseJsonOutput <
724767 SuccessEnvelope < {
725768 result : {
726- items : Array < { address : { kind : string ; nodeType : string ; nodeId : string } } > ;
769+ items : Array < { node : { kind : string } ; address : { kind : string ; nodeId : string } } > ;
727770 } ;
728771 } >
729772 > ( findResult ) ;
730773
731- const firstMatch = findEnvelope . data . result . items [ 0 ] . address ;
732- expect ( firstMatch . kind ) . toBe ( 'block ' ) ;
774+ const firstItem = findEnvelope . data . result . items [ 0 ] ;
775+ expect ( firstItem . address . kind ) . toBe ( 'content ' ) ;
733776
734777 const prettyResult = await runCli ( [
735778 'get-node-by-id' ,
736779 SAMPLE_DOC ,
737780 '--id' ,
738- firstMatch . nodeId ,
781+ firstItem . address . nodeId ,
739782 '--node-type' ,
740- firstMatch . nodeType ,
783+ firstItem . node . kind ,
741784 '--output' ,
742785 'pretty' ,
743786 ] ) ;
744787 expect ( prettyResult . code ) . toBe ( 0 ) ;
745788 expect ( prettyResult . stdout ) . toContain ( 'Revision 0:' ) ;
746- expect ( prettyResult . stdout ) . toContain ( firstMatch . nodeId ) ;
747789
748790 const jsonResult = await runCli ( [
749791 'get-node-by-id' ,
750792 SAMPLE_DOC ,
751793 '--id' ,
752- firstMatch . nodeId ,
794+ firstItem . address . nodeId ,
753795 '--node-type' ,
754- firstMatch . nodeType ,
796+ firstItem . node . kind ,
755797 ] ) ;
756798 expect ( jsonResult . code ) . toBe ( 0 ) ;
757799 const jsonEnvelope = parseJsonOutput < SuccessEnvelope < { node : unknown } > > ( jsonResult ) ;
758- const node = asRecord ( jsonEnvelope . data . node ) ;
759- if ( typeof node ?. text === 'string' && node . text . length > 0 ) {
760- expect ( prettyResult . stdout ) . toContain ( 'Text:' ) ;
761- }
762- if ( hasPrettyProperties ( jsonEnvelope . data . node ) ) {
763- expect ( prettyResult . stdout ) . toContain ( 'Properties:' ) ;
764- }
800+ expect ( jsonEnvelope . data . node ) . toBeDefined ( ) ;
765801 } ) ;
766802
767803 test ( 'replace dry-run does not write output file' , async ( ) => {
@@ -873,11 +909,17 @@ describe('superdoc CLI', () => {
873909
874910 const insertEnvelope = parseJsonOutput <
875911 SuccessEnvelope < {
876- target : TextRange ;
912+ receipt : {
913+ success : boolean ;
914+ resolution ?: {
915+ target : { anchor ?: { start : { offset : number } ; end : { offset : number } } } ;
916+ } ;
917+ } ;
877918 } >
878919 > ( insertResult ) ;
879- expect ( insertEnvelope . data . target . range . start ) . toBe ( 0 ) ;
880- expect ( insertEnvelope . data . target . range . end ) . toBe ( 0 ) ;
920+ expect ( insertEnvelope . data . receipt . success ) . toBe ( true ) ;
921+ expect ( insertEnvelope . data . receipt . resolution ?. target . anchor ?. start . offset ) . toBe ( 0 ) ;
922+ expect ( insertEnvelope . data . receipt . resolution ?. target . anchor ?. end . offset ) . toBe ( 0 ) ;
881923
882924 const verifyResult = await runCli ( [
883925 'find' ,
@@ -922,13 +964,19 @@ describe('superdoc CLI', () => {
922964
923965 const insertEnvelope = parseJsonOutput <
924966 SuccessEnvelope < {
925- target : TextRange ;
926- resolvedRange : { from : number ; to : number } ;
967+ receipt : {
968+ success : boolean ;
969+ resolution ?: {
970+ target : { anchor ?: { start : { offset : number } ; end : { offset : number } } } ;
971+ } ;
972+ } ;
927973 } >
928974 > ( insertResult ) ;
929975
930- expect ( insertEnvelope . data . target . range ) . toEqual ( { start : 0 , end : 0 } ) ;
931- expect ( insertEnvelope . data . resolvedRange . from ) . toBe ( insertEnvelope . data . resolvedRange . to ) ;
976+ expect ( insertEnvelope . data . receipt . success ) . toBe ( true ) ;
977+ const anchor = insertEnvelope . data . receipt . resolution ?. target . anchor ;
978+ expect ( anchor ?. start . offset ) . toBe ( 0 ) ;
979+ expect ( anchor ?. end . offset ) . toBe ( 0 ) ;
932980
933981 const verifyResult = await runCli ( [
934982 'find' ,
@@ -1001,12 +1049,18 @@ describe('superdoc CLI', () => {
10011049
10021050 const insertEnvelope = parseJsonOutput <
10031051 SuccessEnvelope < {
1004- target : TextRange ;
1052+ receipt : {
1053+ success : boolean ;
1054+ resolution ?: {
1055+ target : { anchor ?: { start : { offset : number } ; end : { offset : number } } } ;
1056+ } ;
1057+ } ;
10051058 } >
10061059 > ( insertResult ) ;
10071060 // blockId alone → offset defaults to 0 → collapsed range at start
1008- expect ( insertEnvelope . data . target . range . start ) . toBe ( 0 ) ;
1009- expect ( insertEnvelope . data . target . range . end ) . toBe ( 0 ) ;
1061+ expect ( insertEnvelope . data . receipt . success ) . toBe ( true ) ;
1062+ expect ( insertEnvelope . data . receipt . resolution ?. target . anchor ?. start . offset ) . toBe ( 0 ) ;
1063+ expect ( insertEnvelope . data . receipt . resolution ?. target . anchor ?. end . offset ) . toBe ( 0 ) ;
10101064 } ) ;
10111065
10121066 test ( 'insert with --offset but no --block-id returns INVALID_ARGUMENT' , async ( ) => {
@@ -1701,11 +1755,17 @@ describe('superdoc CLI', () => {
17011755
17021756 const insertEnvelope = parseJsonOutput <
17031757 SuccessEnvelope < {
1704- target : TextRange ;
1758+ receipt : {
1759+ success : boolean ;
1760+ resolution ?: {
1761+ target : { anchor ?: { start : { offset : number } ; end : { offset : number } } } ;
1762+ } ;
1763+ } ;
17051764 } >
17061765 > ( insertResult ) ;
1707- expect ( insertEnvelope . data . target . range . start ) . toBe ( 0 ) ;
1708- expect ( insertEnvelope . data . target . range . end ) . toBe ( 0 ) ;
1766+ expect ( insertEnvelope . data . receipt . success ) . toBe ( true ) ;
1767+ expect ( insertEnvelope . data . receipt . resolution ?. target . anchor ?. start . offset ) . toBe ( 0 ) ;
1768+ expect ( insertEnvelope . data . receipt . resolution ?. target . anchor ?. end . offset ) . toBe ( 0 ) ;
17091769
17101770 const verifyResult = await runCli ( [ 'find' , '--type' , 'text' , '--pattern' , 'STATEFUL_DEFAULT_INSERT_1597' ] ) ;
17111771 expect ( verifyResult . code ) . toBe ( 0 ) ;
0 commit comments