@@ -4,8 +4,14 @@ import path from 'node:path'
44import fs from 'fs-extra'
55import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
66import { enqueueCloudDownload } from '../../cloudDownloads'
7+ import { createFoldersStorage } from '../../storages/folders'
8+ import { createSnippetsStorage } from '../../storages/snippets'
79import { runtimeRef } from '../cache'
8- import { setDatalessProbeForTests } from '../shared/cloudFiles'
10+ import { getPaths } from '../paths'
11+ import {
12+ getFileAvailability ,
13+ setDatalessProbeForTests ,
14+ } from '../shared/cloudFiles'
915import { assertNoUnknownDomainFiles } from '../shared/foldersStorage'
1016import { ensureSnippetContentLoaded , writeSnippetToFile } from '../snippets'
1117import { saveState } from '../state'
@@ -25,10 +31,13 @@ vi.mock('electron', () => ({
2531 } ,
2632} ) )
2733
34+ const storageMock = vi . hoisted ( ( ) => ( { vaultPath : '' } ) )
35+
2836vi . mock ( '../../../../../store' , ( ) => ( {
2937 store : {
3038 preferences : {
31- get : ( ) => undefined ,
39+ get : ( key : string ) =>
40+ key === 'storage.vaultPath' ? storageMock . vaultPath : undefined ,
3241 } ,
3342 } ,
3443} ) )
@@ -56,6 +65,16 @@ function createPaths(): Paths {
5665 }
5766}
5867
68+ function createStoragePaths ( ) : Paths {
69+ const vaultPath = fs . mkdtempSync (
70+ path . join ( os . tmpdir ( ) , 'cloud-placeholder-storage-' ) ,
71+ )
72+ tempDirs . push ( vaultPath )
73+ storageMock . vaultPath = vaultPath
74+
75+ return getPaths ( vaultPath )
76+ }
77+
5978function writeSnippetFixture (
6079 paths : Paths ,
6180 relativePath : string ,
@@ -110,6 +129,7 @@ beforeEach(() => {
110129} )
111130
112131afterEach ( ( ) => {
132+ storageMock . vaultPath = ''
113133 setDatalessProbeForTests ( null )
114134 resetRuntimeCache ( )
115135 vi . mocked ( enqueueCloudDownload ) . mockClear ( )
@@ -120,6 +140,176 @@ afterEach(() => {
120140} )
121141
122142describe ( 'cloud placeholder handling in code runtime' , ( ) => {
143+ it ( 'allows immediate content write and rename of an app-written file' , ( ) => {
144+ const paths = createStoragePaths ( )
145+ const targetPath = path . join ( paths . vaultPath , '.masscode/inbox/Renamed.md' )
146+ const statSync = fs . statSync . bind ( fs )
147+ const statSpy = vi . spyOn ( fs , 'statSync' ) . mockImplementation ( ( filePath ) => {
148+ const stats = statSync ( filePath )
149+
150+ if (
151+ typeof filePath === 'string'
152+ && filePath . endsWith ( '.md' )
153+ && stats . size > 0
154+ ) {
155+ return Object . assign ( stats , { blocks : 0 } )
156+ }
157+
158+ return stats
159+ } )
160+
161+ try {
162+ const storage = createSnippetsStorage ( )
163+ const { id } = storage . createSnippet ( { name : 'Resident' } )
164+ expect ( ( ) =>
165+ storage . createSnippetContent ( id , {
166+ label : 'Fragment 1' ,
167+ language : 'plain_text' ,
168+ value : 'body' ,
169+ } ) ,
170+ ) . not . toThrow ( )
171+
172+ resetRuntimeCache ( )
173+ syncRuntimeWithDisk ( paths )
174+ resetRuntimeCache ( )
175+ const lazyCache = syncRuntimeWithDisk ( paths )
176+ const lazySnippet = lazyCache . snippets . find (
177+ snippet => snippet . id === id ,
178+ )
179+ expect ( lazySnippet ?. contents [ 0 ] ?. value ) . toBeNull ( )
180+
181+ expect ( ( ) =>
182+ storage . updateSnippet ( id , { name : 'Renamed' } ) ,
183+ ) . not . toThrow ( )
184+
185+ expect ( fs . readFileSync ( targetPath , 'utf8' ) ) . toContain ( 'body' )
186+ }
187+ finally {
188+ statSpy . mockRestore ( )
189+ }
190+ } )
191+
192+ it ( 'keeps local folder contents available without rewriting placeholders' , ( ) => {
193+ const paths = createStoragePaths ( )
194+ const statSync = fs . statSync . bind ( fs )
195+ const statSpy = vi . spyOn ( fs , 'statSync' ) . mockImplementation ( ( filePath ) => {
196+ const stats = statSync ( filePath )
197+
198+ if (
199+ typeof filePath === 'string'
200+ && filePath . endsWith ( '.md' )
201+ && stats . size > 0
202+ ) {
203+ return Object . assign ( stats , { blocks : 0 } )
204+ }
205+
206+ return stats
207+ } )
208+ const foldersStorage = createFoldersStorage ( )
209+ const storage = createSnippetsStorage ( )
210+ const folder = foldersStorage . createFolder ( { name : 'Folder' } )
211+ let localId = 0
212+ let placeholderId = 0
213+ let placeholderSourcePath = ''
214+
215+ try {
216+ localId = storage . createSnippet ( {
217+ folderId : folder . id ,
218+ name : 'Resident' ,
219+ } ) . id
220+ storage . createSnippetContent ( localId , {
221+ label : 'Fragment 1' ,
222+ language : 'plain_text' ,
223+ value : 'local body' ,
224+ } )
225+
226+ placeholderId = storage . createSnippet ( {
227+ folderId : folder . id ,
228+ name : 'Pending' ,
229+ } ) . id
230+ storage . createSnippetContent ( placeholderId , {
231+ label : 'Fragment 1' ,
232+ language : 'plain_text' ,
233+ value : 'remote body' ,
234+ } )
235+ const placeholder = runtimeRef . cache ! . snippets . find (
236+ item => item . id === placeholderId ,
237+ ) !
238+ placeholderSourcePath = path . join ( paths . vaultPath , placeholder . filePath )
239+
240+ makeSparsePlaceholder ( placeholderSourcePath )
241+ if ( ! hasPlaceholderSignature ( placeholderSourcePath ) ) {
242+ return
243+ }
244+
245+ expect ( foldersStorage . deleteFolder ( folder . id ) . deleted ) . toBe ( true )
246+
247+ const local = runtimeRef . cache ! . snippets . find (
248+ item => item . id === localId ,
249+ ) !
250+ const pending = runtimeRef . cache ! . snippets . find (
251+ item => item . id === placeholderId ,
252+ ) !
253+ const localTargetPath = path . join ( paths . vaultPath , local . filePath )
254+ const placeholderTargetPath = path . join (
255+ paths . vaultPath ,
256+ pending . filePath ,
257+ )
258+
259+ expect ( getFileAvailability ( localTargetPath ) . isCloudPlaceholder ) . toBe (
260+ false ,
261+ )
262+ expect ( hasPlaceholderSignature ( placeholderTargetPath ) ) . toBe ( true )
263+ expect ( fs . statSync ( placeholderTargetPath ) . size ) . toBe ( 4096 )
264+ }
265+ finally {
266+ statSpy . mockRestore ( )
267+ }
268+ } )
269+
270+ it ( 'keeps a committed write successful when its follow-up stat fails' , ( ) => {
271+ const paths = createPaths ( )
272+ const snippetPath = path . join ( paths . vaultPath , '.masscode/inbox/saved.md' )
273+ const statSync = fs . statSync . bind ( fs )
274+ const statSpy = vi . spyOn ( fs , 'statSync' ) . mockImplementation ( ( filePath ) => {
275+ if ( filePath === snippetPath && fs . existsSync ( snippetPath ) ) {
276+ throw new Error ( 'post-write stat failed' )
277+ }
278+
279+ return statSync ( filePath )
280+ } )
281+
282+ try {
283+ expect ( ( ) =>
284+ writeSnippetToFile ( paths , {
285+ contents : [
286+ {
287+ id : 1 ,
288+ label : 'Fragment 1' ,
289+ language : 'plain_text' ,
290+ value : 'saved body' ,
291+ } ,
292+ ] ,
293+ createdAt : 1700000000000 ,
294+ description : null ,
295+ filePath : '.masscode/inbox/saved.md' ,
296+ folderId : null ,
297+ id : 2 ,
298+ isDeleted : 0 ,
299+ isFavorites : 0 ,
300+ name : 'Saved' ,
301+ tags : [ ] ,
302+ updatedAt : 1700000000000 ,
303+ } ) ,
304+ ) . not . toThrow ( )
305+ }
306+ finally {
307+ statSpy . mockRestore ( )
308+ }
309+
310+ expect ( fs . readFileSync ( snippetPath , 'utf8' ) ) . toContain ( 'saved body' )
311+ } )
312+
123313 it ( 'keeps known placeholder snippets in the list without reading them' , ( ) => {
124314 const paths = createPaths ( )
125315 const localPath = writeSnippetFixture (
0 commit comments