diff --git a/src/vfs/_vfscommon.vfs/modules/punk/mix/templates-0.2.0.tm b/src/vfs/_vfscommon.vfs/modules/punk/mix/templates-0.2.0.tm index dfcc2357..8b65b3dc 100644 Binary files a/src/vfs/_vfscommon.vfs/modules/punk/mix/templates-0.2.0.tm and b/src/vfs/_vfscommon.vfs/modules/punk/mix/templates-0.2.0.tm differ diff --git a/src/vfs/_vfscommon.vfs/modules/shellfilter-0.2.4.tm b/src/vfs/_vfscommon.vfs/modules/shellfilter-0.2.5.tm similarity index 98% rename from src/vfs/_vfscommon.vfs/modules/shellfilter-0.2.4.tm rename to src/vfs/_vfscommon.vfs/modules/shellfilter-0.2.5.tm index 0bf17fd8..bd5db3d8 100644 --- a/src/vfs/_vfscommon.vfs/modules/shellfilter-0.2.4.tm +++ b/src/vfs/_vfscommon.vfs/modules/shellfilter-0.2.5.tm @@ -419,7 +419,10 @@ namespace eval shellfilter::chan { } method initialize {transform_handle mode} { #return [list initialize read drain write flush clear finalize] - return [list initialize write flush clear finalize] + #'clear' deliberately NOT declared: when a transform declares it, the core + #delivers a 'clear' op before EVERY write (tclIORTrans.c ReflectOutput), not + #just on seek - see goals/archive/G-145-piped-usage-ansi-remnants.md. + return [list initialize write flush finalize] } method finalize {transform_handle} { #Note that an error in the finalize can stop 'chan pop' from running properly. @@ -430,8 +433,15 @@ namespace eval shellfilter::chan { # must be present but we ignore it because we do not # post any events } + #G-145 defect class: 'clear' must NOT discard o_encbuf (held partial multi-byte + #character). With clear declared, the core calls it before every write; dropping + #the carry leaves the next chunk starting with orphan continuation bytes, making + #whole chunks unconvertible - they accumulate in o_encbuf and successive clears + #discard them, eating contiguous ranges of multibyte-dense output (the 2026-08-03 + #unix repl result-echo corruption). Kept as a state-preserving no-op in case + #'clear' is ever re-declared; this write-only transform has no read-side state + #(the documented scope of 'clear'). method clear {transform_handle} { - set o_encbuf "" return } #method drain {transform_handle} { @@ -471,16 +481,22 @@ namespace eval shellfilter::chan { # return $clear #} method flush {transform_handle} { - set clear $o_buffered$o_encbuf - if {[catch {tcl::encoding::convertfrom $o_enc $clear} stringdata]} { - #if we can't convert the buffer contents to a string - does it make sense to emit the raw bytes? - # - probably not. - #REVIEW? + #this class holds only o_encbuf (raw bytes of a trailing incomplete + #multi-byte character) - there is no o_buffered ansi-carry here (that + #belongs to the ansiwrap/ansistrip style transforms). + #An incomplete char is not decodable on its own: hold it for the next write + #rather than emitting garbage or discarding (G-145: dropping held stream + #state corrupts content split across write chunks). finalize dropping it at + #true end of stream is acceptable. + if {$o_encbuf eq ""} { + return "" + } + if {[catch {tcl::encoding::convertfrom $o_enc $o_encbuf} stringdata]} { return "" } - set o_buffered "" set o_encbuf "" - return $stringdata + puts -nonewline $o_localchan $stringdata + return [tcl::encoding::convertto $o_enc $stringdata] } method write {transform_handle bytes} { #set logdata [tcl::encoding::convertfrom $o_enc $bytes] @@ -570,16 +586,19 @@ namespace eval shellfilter::chan { # return $clear #} method flush {transform_handle} { - set clear $o_buffered$o_encbuf - if {[catch {tcl::encoding::convertfrom $o_enc $clear} stringdata]} { - #if we can't convert the buffer contents to a string - does it make sense to emit the raw bytes? - # - probably not. - #REVIEW? + #only o_encbuf exists in this class (no o_buffered ansi-carry as in ansiwrap). + #An incomplete multi-byte char is not decodable on its own: hold it for the + #next write rather than discarding (G-145: dropping held stream state + #corrupts content split across write chunks). + if {$o_encbuf eq ""} { + return "" + } + if {[catch {tcl::encoding::convertfrom $o_enc $o_encbuf} stringdata]} { return "" } - set o_buffered "" set o_encbuf "" - return $stringdata + ::shellfilter::log::write $o_logsource $stringdata + return [tcl::encoding::convertto $o_enc $stringdata] } method write {ch bytes} { #set logdata [tcl::encoding::convertfrom $o_enc $bytes] @@ -668,16 +687,19 @@ namespace eval shellfilter::chan { # return #} method flush {transform_handle} { - set clear $o_buffered$o_encbuf - if {[catch {tcl::encoding::convertfrom $o_enc $clear} stringdata]} { - #if we have data in the buffer that we haven't been able to convert to a string - #- then we probably have some kind of encoding mismatch. Is it safer to discard it than to emit garbage chars to the log? - #REVIEW. - we are writing the raw bytes to the log here because we can't convert them to a string. - #This may be useful for debugging issues, but it may also result in garbage data in the log. - ::shellfilter::log::write $o_logsource $o_encbuf + #only o_encbuf exists in this class (no o_buffered ansi-carry as in ansiwrap). + #logonly emits nothing downstream. An incomplete multi-byte char is not + #decodable on its own: hold it for the next write rather than discarding + #(G-145: dropping held stream state corrupts content split across write + #chunks); if it decodes anyway (e.g after an encoding change), log it now. + if {$o_encbuf eq ""} { + return "" + } + if {![catch {tcl::encoding::convertfrom $o_enc $o_encbuf} stringdata]} { + ::shellfilter::log::write $o_logsource $stringdata set o_encbuf "" } - return + return "" } method write {transform_handle bytes} { #set logdata [encoding convertfrom $o_enc $bytes] @@ -3838,5 +3860,5 @@ namespace eval shellfilter { package provide shellfilter [namespace eval shellfilter { variable version - set version 0.2.4 + set version 0.2.5 }]