fix: reveal screen UX and rules hardening (batch v1.0.20)

This commit is contained in:
null 2026-06-20 01:51:02 -05:00
parent 737514d18f
commit 9c1fbf60a0
2 changed files with 20 additions and 8 deletions

View File

@ -187,7 +187,8 @@ private fun AnswerRevealContent(
partnerAnswer = state.partnerAnswer,
question = state.question,
onHistory = onHistory,
onHome = onHome
onHome = onHome,
wasSealed = state.answer.schemaVersion == 3
)
if (state.followUpOptions.isNotEmpty()) {
FollowUpSection(
@ -436,7 +437,7 @@ private fun WaitingForPartnerState(
overflow = TextOverflow.Ellipsis
)
Text(
text = "Your answer key is released. Once your partner opens their reveal, both answers will appear here.",
text = "Your answer key is released. You'll get a notification when your partner completes the reveal — no need to keep checking.",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 4,
@ -480,12 +481,19 @@ private fun LostLocalKeyState(onHome: () -> Unit) {
overflow = TextOverflow.Ellipsis
)
Text(
text = "The sealed answer key was stored on the device you originally answered on. Open the app on that device to complete the reveal.",
text = "The sealed answer key is stored on the device you originally answered on. Open the app on that device to complete the reveal.",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 5,
overflow = TextOverflow.Ellipsis
)
Text(
text = "If you no longer have that device, this answer cannot be recovered.",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error.copy(alpha = 0.75f),
maxLines = 3,
overflow = TextOverflow.Ellipsis
)
OutlinedButton(
onClick = onHome,
modifier = Modifier.fillMaxWidth().heightIn(min = 48.dp),
@ -503,12 +511,13 @@ private fun RevealedState(
partnerAnswer: LocalAnswer?,
question: Question?,
onHistory: () -> Unit,
onHome: () -> Unit
onHome: () -> Unit,
wasSealed: Boolean = false
) {
RevealMessageCard {
Column(verticalArrangement = Arrangement.spacedBy(14.dp)) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
RevealPill("Revealed")
RevealPill(if (wasSealed) "Sealed reveal" else "Revealed")
RevealPill(answer.category.displayCategoryName())
RevealPill(answer.answerType.displayQuestionType())
}

View File

@ -72,15 +72,18 @@ service cloud.firestore {
// Sealed-answer helpers (schemaVersion 3, partner-proof reveal).
function isSealedPayload(value) {
return value is string && value.matches('^sealed:v1:');
// sealed:v1: + URL-safe base64 no-padding body; 80 chars minimum rules out trivially short values
return value is string && value.matches('^sealed:v1:[A-Za-z0-9_-]{80,}$');
}
function isKeybox(value) {
return value is string && value.matches('^keybox:v1:');
// keybox:v1: + URL-safe base64 no-padding; ECIES-P256 wrapping a 32-byte key is ~174 chars
return value is string && value.matches('^keybox:v1:[A-Za-z0-9_-]{120,}$');
}
function isCommitmentHash(value) {
return value is string && value.matches('^sha256:');
// sha256: + URL-safe base64 no-padding of a 32-byte digest = exactly 43 chars
return value is string && value.matches('^sha256:[A-Za-z0-9_-]{43}$');
}
// Returns true when the incoming data satisfies the sealed-answer create shape.