Skip to content

Commit 542e27f

Browse files
committed
fix: the Vaults sheet marks the open vault by its folder, and shows each vault's own name
Found while driving the same change on the iPhone simulator. The Vaults sheet lists folders, and its rename and delete act on the folder, but it marked the open vault by comparing an entry's folder name with the name the shell snapshot shows. Once a vault carries a display name (ZenNotes #692, core 2.56.x) those are different strings, so a renamed vault lost its check mark, its row became tappable, and the manage view offered Delete and Rename for the vault that was open. The sheet now asks the bridge for the open vault's folder name (`currentVaultFolderName`, the MobileVault it holds) and compares that, the external-root clause unchanged; the snapshot's name is only what the open vault's row shows. The other rows show the name each vault goes by, read from its own vault.json (one small read per local or cloud folder, no download wait for an evicted cloud file, which keeps its folder name), and the manage view names the folder beside a display name that differs, so a rename or delete is understood to act on the folder. `MobileVaultEntry` gains `displayName`; `name` stays the folder, which every management call keys on.
1 parent 2f2d8f6 commit 542e27f

3 files changed

Lines changed: 64 additions & 14 deletions

File tree

‎src/bridge/mobile-bridge.ts‎

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import type {
3939
VaultTextSearchMatch
4040
} from '@zennotes/shared-domain/ipc'
4141
import { createDatabaseOps } from '@zennotes/shared-domain/database-ops'
42-
import { resolveVaultName } from '@zennotes/shared-domain/vault-display-name'
42+
import { normalizeVaultDisplayName, resolveVaultName } from '@zennotes/shared-domain/vault-display-name'
4343
import type {
4444
CustomCodeLanguage,
4545
CustomCodeLanguageInstallInput,
@@ -62,6 +62,7 @@ import { MobileVault } from './vault-fs'
6262
import {
6363
listVaultDirs,
6464
readVaultDisplayName,
65+
readVaultDisplayNameAtUrl,
6566
VAULTS_DIR,
6667
vaultsRoot,
6768
initVaultsRoot
@@ -182,7 +183,12 @@ export function currentExternalVaultRoot(): string | null {
182183

183184
export interface MobileVaultEntry {
184185
root: string
186+
/** The folder's name on disk: what rename and delete act on, and what the
187+
* open vault's `folderName` is compared with to mark it current. */
185188
name: string
189+
/** The name the vault goes by when its vault.json carries one (ZenNotes
190+
* #692), normalized; the sheet shows it in place of the folder name. */
191+
displayName?: string
186192
tier: 'local' | 'icloud' | 'external'
187193
}
188194

@@ -226,17 +232,25 @@ async function looksLikeVaultDir(url: string): Promise<boolean> {
226232
export async function listSwitchableVaults(): Promise<MobileVaultEntry[]> {
227233
const out: MobileVaultEntry[] = []
228234
for (const d of await listVaultDirs()) {
229-
out.push({ root: `${VAULT_ROOT_PREFIX}${d.name}`, name: d.name, tier: 'local' })
235+
const displayName = normalizeVaultDisplayName(await readVaultDisplayName(d.name))
236+
out.push({
237+
root: `${VAULT_ROOT_PREFIX}${d.name}`,
238+
name: d.name,
239+
tier: 'local',
240+
...(displayName ? { displayName } : {})
241+
})
230242
}
231243
const status = await icloudStatus().catch(() => null)
232244
if (status?.available && status.rootUrl) {
233245
for (const name of filterCloudVaultNames(status.vaults ?? [])) {
234246
const url = `${status.rootUrl}/${encodeURIComponent(name)}`
235247
if (!(await looksLikeVaultDir(url))) continue
248+
const displayName = normalizeVaultDisplayName(await readVaultDisplayNameAtUrl(url))
236249
out.push({
237250
root: `${ICLOUD_VAULT_ROOT_PREFIX}${encodeURIComponent(name)}`,
238251
name,
239-
tier: 'icloud'
252+
tier: 'icloud',
253+
...(displayName ? { displayName } : {})
240254
})
241255
}
242256
}
@@ -458,6 +472,18 @@ function currentVaultInfo(): VaultInfo | null {
458472
* open, and a vault whose vault.json cannot be read keeps its folder name,
459473
* the way it always did.
460474
*/
475+
/**
476+
* The open local vault's folder name, null in a remote workspace or before
477+
* boot. The Vaults sheet lists folders and must mark the one that is open
478+
* whatever the vault is called (rename and delete act on the folder), and
479+
* the core's shell snapshot carries the vault's shown name, not its folder,
480+
* so the sheet asks the bridge, which holds the MobileVault by folder name.
481+
*/
482+
export function currentVaultFolderName(): string | null {
483+
if (remoteVaultInfo()) return null
484+
return vault?.name ?? null
485+
}
486+
461487
async function describeCurrentVault(): Promise<VaultInfo | null> {
462488
const info = currentVaultInfo()
463489
if (!info || remoteVaultInfo() || !vault) return info

‎src/bridge/native-fs.ts‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -428,12 +428,25 @@ export async function listVaultDirs(): Promise<{ name: string; mtime: number }[]
428428
* folder name, the way desktop's describeVault does.
429429
*/
430430
export async function readVaultDisplayName(vaultName: string): Promise<string | null> {
431+
return readDisplayNameFrom({
432+
path: `${VAULTS_DIR}/${vaultName}/.zennotes/vault.json`,
433+
directory: vaultsRoot()
434+
})
435+
}
436+
437+
/**
438+
* The same for a vault addressed by a file URL, which is how the cloud tier
439+
* lists its vaults (see looksLikeVaultDir). One read attempt, no download
440+
* wait: an evicted vault.json answers null and the vault keeps its folder
441+
* name in the switcher, rather than every listing waiting on the cloud.
442+
*/
443+
export async function readVaultDisplayNameAtUrl(url: string): Promise<string | null> {
444+
return readDisplayNameFrom({ path: `${url}/.zennotes/vault.json` })
445+
}
446+
447+
async function readDisplayNameFrom(loc: { path: string; directory?: Directory }): Promise<string | null> {
431448
try {
432-
const res = await Filesystem.readFile({
433-
path: `${VAULTS_DIR}/${vaultName}/.zennotes/vault.json`,
434-
directory: vaultsRoot(),
435-
encoding: Encoding.UTF8
436-
})
449+
const res = await Filesystem.readFile({ ...loc, encoding: Encoding.UTF8 })
437450
const parsed: unknown = JSON.parse(typeof res.data === 'string' ? res.data : '')
438451
const name = (parsed as { displayName?: unknown } | null)?.displayName
439452
return typeof name === 'string' ? name : null

‎src/ui-mobile/MobileDrawer.tsx‎

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { getStoragePref } from '../bridge/icloud'
3232
import {
3333
ICLOUD_VAULT_ROOT_PREFIX,
3434
VAULT_ROOT_PREFIX,
35+
currentVaultFolderName,
3536
listSwitchableVaults,
3637
renameVault,
3738
deleteVault,
@@ -280,7 +281,14 @@ const TIER_SECTIONS = [
280281
] as const
281282

282283
export function VaultsSheet({ onClose }: { onClose: () => void }): React.JSX.Element {
283-
const currentName = useShellSnapshot().vault?.name ?? null
284+
// The open vault is matched by its folder name, which is what an entry's
285+
// `name` is: rename and delete act on the folder, so the guards below must
286+
// hold for the folder that is open whatever the vault is called. The
287+
// bridge names the folder; the snapshot's vault name is the display name
288+
// (ZenNotes #692), which is what the rows show: the snapshot's for the
289+
// open vault (the shell resolves it) and each entry's for the others.
290+
const currentDisplayName = useShellSnapshot().vault?.name ?? null
291+
const currentFolder = currentVaultFolderName()
284292
const { mode: workspaceMode, remoteProfileId, remoteProfiles } = useWorkspaceSnapshot()
285293
const [entries, setEntries] = useState<MobileVaultEntry[] | null>(null)
286294
const [view, setView] = useState<ManagerView>({ kind: 'list' })
@@ -305,9 +313,11 @@ export function VaultsSheet({ onClose }: { onClose: () => void }): React.JSX.Ele
305313
const currentTier = workspaceMode === 'remote' ? 'remote' : getStoragePref()
306314
const isCurrent = (e: MobileVaultEntry): boolean =>
307315
currentTier === e.tier &&
308-
e.name === currentName &&
309-
// Picked folders may share a display name — the root token decides.
316+
e.name === currentFolder &&
317+
// Picked folders may share a folder name: the root token decides.
310318
(e.tier !== 'external' || e.root === currentExternalVaultRoot())
319+
const shownName = (e: MobileVaultEntry): string =>
320+
(isCurrent(e) ? currentDisplayName : null) ?? e.displayName ?? e.name
311321

312322
/** Switch flows close the sheet and drawer on success. */
313323
const act = (key: string, fn: () => Promise<unknown>): void => {
@@ -433,13 +443,13 @@ export function VaultsSheet({ onClose }: { onClose: () => void }): React.JSX.Ele
433443
)
434444
}
435445
>
436-
<span className="zn-truncate">{entry.name}</span>
446+
<span className="zn-truncate">{shownName(entry)}</span>
437447
{current && <span className="zn-mobile-sheet-row-check">✓</span>}
438448
</button>
439449
<button
440450
type="button"
441451
className="zn-mobile-sheet-row-more"
442-
aria-label={`Manage ${entry.name}`}
452+
aria-label={`Manage ${shownName(entry)}`}
443453
onClick={() => {
444454
setError('')
445455
setView({ kind: 'vault', entry })
@@ -523,7 +533,8 @@ export function VaultsSheet({ onClose }: { onClose: () => void }): React.JSX.Ele
523533
<>
524534
{backRow}
525535
<p className="zn-mobile-sheet-note">
526-
“{view.entry.name}” —{' '}
536+
“{shownName(view.entry)}”
537+
{shownName(view.entry) !== view.entry.name ? ` (folder ${view.entry.name})` : ''},{' '}
527538
{view.entry.tier === 'icloud'
528539
? 'iCloud Drive'
529540
: view.entry.tier === 'external'

0 commit comments

Comments
 (0)