Skip to content

Commit e12bdbd

Browse files
committed
Feat(cloud): the portable host service can delete a linked vault
2.33.0 added deleteCloudVault to the bridge contract and implemented it in the desktop service only. The mobile shells build their cloud sync on the portable CloudSyncHostService in shared-domain, so the contract change left them without a way to honor the new Delete Cloud vault button that app-core now shows everywhere. deleteLinkedVault mirrors the desktop method: the remote DELETE goes first, and the link is dropped only after it succeeds, so a failed delete leaves the user able to retry. Local files are never touched. The SyncClient pick gains deleteVault, which CloudSyncApiClient already provides. Claude-Session: https://claude.ai/code/session_01L1yahiA1JRUKBh5mp3xoBP
1 parent cd4b01f commit e12bdbd

2 files changed

Lines changed: 34 additions & 0 deletions

File tree

packages/shared-domain/src/cloud-sync-host-service.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ function setup(vaults: CloudSyncVault[] = []) {
6969
updated_at: '2026-08-10T12:00:00.000Z'
7070
}
7171
})),
72+
deleteVault: vi.fn(async () => undefined),
7273
manifest: vi.fn(async () => ({ data: [], cursor: 0, next_page: null })),
7374
changes: vi.fn(async () => ({ data: [], cursor: 0, has_more: false })),
7475
mutate: vi.fn(async (_vaultId: string, body: CloudSyncMutationRequest) => ({
@@ -206,6 +207,26 @@ describe('CloudSyncHostService', () => {
206207
expect(hostVault.refresh).toHaveBeenCalledTimes(1)
207208
})
208209

210+
it('deletes the remote vault before dropping the link', async () => {
211+
const { client, hostVault, service } = setup()
212+
213+
await service.createAndLink(hostVault, 'My Notes')
214+
await service.deleteLinkedVault(hostVault)
215+
216+
expect(client.deleteVault).toHaveBeenCalledWith('vault-created')
217+
await expect(service.linkedVault(hostVault)).resolves.toBeNull()
218+
})
219+
220+
it('keeps the link when the remote delete fails', async () => {
221+
const { client, hostVault, service } = setup()
222+
223+
await service.createAndLink(hostVault, 'My Notes')
224+
client.deleteVault.mockRejectedValueOnce(new Error('offline'))
225+
226+
await expect(service.deleteLinkedVault(hostVault)).rejects.toThrow('offline')
227+
await expect(service.linkedVault(hostVault)).resolves.toMatchObject({ vault_id: 'vault-created' })
228+
})
229+
209230
it('refuses to sync a link from a different cloud origin', async () => {
210231
const { hostVault, persistence, service } = setup()
211232
await persistence.saveLink(hostVault.key, {

packages/shared-domain/src/cloud-sync-host-service.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type SyncClient = Pick<
2525
| 'account'
2626
| 'listVaults'
2727
| 'createVault'
28+
| 'deleteVault'
2829
| 'manifest'
2930
| 'changes'
3031
| 'mutate'
@@ -120,6 +121,18 @@ export class CloudSyncHostService {
120121
await this.dependencies.persistence.deleteLink(vault.key)
121122
}
122123

124+
/**
125+
* Delete the cloud copy this vault is linked to, then drop the link.
126+
* Mirrors the desktop service: the remote call goes first so a failure
127+
* leaves the link (and the user's ability to retry) intact. Local files are
128+
* never touched.
129+
*/
130+
async deleteLinkedVault(vault: CloudSyncHostVault): Promise<void> {
131+
const { client, link } = await this.linkedConnection(vault)
132+
await client.deleteVault(link.vault_id)
133+
await this.unlink(vault)
134+
}
135+
123136
async listBackups(vault: CloudSyncHostVault): Promise<CloudBackupSnapshot[]> {
124137
const { client, link } = await this.linkedConnection(vault)
125138
return (await client.listBackups(link.vault_id)).data

0 commit comments

Comments
 (0)