@@ -955,6 +958,29 @@ export default {
cardRevealed: { number: false, cvv: false, pin: false },
/** Reveal state for the identity's BSN row (masked by default). */
bsnRevealed: false,
+ /**
+ * Bumped by every successful `load()`, and bound to
+ * `` so the field remounts whenever the
+ * loaded secret changes.
+ *
+ * PasswordField decrypts lazily and then CACHES the plaintext
+ * for its own lifetime (`plain` is only resolved while it is
+ * still `null`), and it keeps `revealed` across that lifetime
+ * too. That was harmless while the detail was a full page which
+ * remounted per secret. This sidebar deliberately does NOT
+ * remount — the `secretId` watcher above swaps the secret in
+ * place — so without a key the cache outlives the secret it
+ * belongs to, in two ways that both matter for a vault:
+ *
+ * • Edit the open secret: `load()` refreshes `this.secret`,
+ * the field keeps showing the OLD plaintext.
+ * • Click another row while revealed: the panel shows secret
+ * B's name with secret A's plaintext, and Copy copies A's.
+ *
+ * Remounting resets `plain` and `revealed` together, so a
+ * changed secret is re-masked until the user asks for it again.
+ */
+ secretLoadToken: 0,
}
},
@@ -1340,6 +1366,9 @@ export default {
this.error = ''
try {
this.secret = await useSecretStore().fetchSecret(this.secretId)
+ // Retire the previous secret's decrypted plaintext with the
+ // secret it came from. See `secretLoadToken` in data().
+ this.secretLoadToken += 1
// Write-grade badge (folder-permission-grades §4.3) — a
// copy the user may team-write shows the sync warning.
try {
diff --git a/tests/e2e/workflows/folder-sharing.spec.ts b/tests/e2e/workflows/folder-sharing.spec.ts
index 939efeb5..c1e8611f 100644
--- a/tests/e2e/workflows/folder-sharing.spec.ts
+++ b/tests/e2e/workflows/folder-sharing.spec.ts
@@ -39,13 +39,13 @@
* were credited to zero scenarios. They are anchored per-test below, against the
* `secrets-write-ui` scenarios they actually drive.
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
import {
clickOverflowAction,
gotoLockSettled,
openVault,
unlockVault,
-} from './_workflow-helpers'
+} from './_workflow-helpers.ts'
const REQ_TOKEN = `(() => {
const head = document.querySelector('head[data-requesttoken]');
@@ -279,14 +279,25 @@ test.describe('Workflow: folders + sharing — folders/spec.md', () => {
await page
.getByRole('button', { name: /Secret actions/i })
.evaluate((el: HTMLElement) => el.click())
- // `data-testid` falls through to NcActionButton's ROOT, which is the
- //