Save a story you posted yourself, before it expires
Android half of ios#34. A story disappears from its author's own device too, so there was no way to keep something you made. 🔴 Only ever your own, and the rule is `Vault.isSaveable` rather than a condition on the menu item. Saving somebody else's story keeps a copy of something they were told would disappear — the opposite of what the feature promises them — and a guard living in a composable is one refactor away from being lost. Tested from both directions. ⚠️ Written through MediaStore, so no storage permission is asked for. An app inserting its own entry needs no runtime grant on any version this app supports; asking for WRITE_EXTERNAL_STORAGE in order to save one picture would request read access to everything the person has, to perform a write. ⚠️ The message says the copy has left the disappearing model and names where it went. "Saved" alone would be true and useless — somebody not told that may reasonably assume the expiry still covers it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
bf728727b0
commit
83d7803f9e
@@ -137,6 +137,21 @@ class Vault(private val storage: Storage, private val key: SecretKey) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this story may be written out of the app (ios#34).
|
||||
*
|
||||
* 🔴 **Only ever your own, and the rule lives here rather than in the
|
||||
* button.** Saving somebody else's story keeps a copy of something
|
||||
* they were told would disappear — the opposite of what the feature
|
||||
* promises them — and no wording on a menu item makes that acceptable.
|
||||
* A guard in a composable is one refactor away from being lost.
|
||||
*
|
||||
* ⚠️ A text story (§6.21) has no blob and nothing to write out. False
|
||||
* rather than rendering the words into a picture: a story is what the
|
||||
* author posted, and an image of it is a different object.
|
||||
*/
|
||||
fun isSaveable(mine: Boolean, hasBlob: Boolean): Boolean = mine && hasBlob
|
||||
|
||||
fun threadForUser(userId: Long) = "user:$userId"
|
||||
fun threadForGroup(groupId: String) = "group:$groupId"
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.flamenet.e2e
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* Saving your own story before it expires (ios#34).
|
||||
*
|
||||
* 🔴 **Only ever your own.** Saving somebody else's keeps a copy of something
|
||||
* they were told would disappear, which is the opposite of what the feature
|
||||
* promises them. The rule lives in the engine, not in the button, because a
|
||||
* guard in a composable is one refactor away from being lost.
|
||||
*/
|
||||
class StorySaveTest {
|
||||
|
||||
@Test
|
||||
fun `your own story with a picture can be saved`() {
|
||||
assertTrue(Vault.isSaveable(mine = true, hasBlob = true))
|
||||
}
|
||||
|
||||
/** 🔴 The whole point: never somebody else's, whatever the UI offers. */
|
||||
@Test
|
||||
fun `somebody elses story can never be saved`() {
|
||||
assertFalse(Vault.isSaveable(mine = false, hasBlob = true))
|
||||
}
|
||||
|
||||
/**
|
||||
* ⚠️ A text story (§6.21) has no blob and nothing to write out. False
|
||||
* rather than rendering the words into an image: a story is what the
|
||||
* author posted, and a picture of it is a different object.
|
||||
*/
|
||||
@Test
|
||||
fun `a text story of your own has nothing to save`() {
|
||||
assertFalse(Vault.isSaveable(mine = true, hasBlob = false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `somebody elses text story is also not saveable`() {
|
||||
assertFalse(Vault.isSaveable(mine = false, hasBlob = false))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user