140731f8bafb

entry detail and the entry edit form take the same note round trip as notes mode: the notes row displays parsed HTML and copies plain text, the form loads markup and writes the tags back, so a login's formatted note is no longer flattened by an edit on the phone

time
2026-07-26 19:01 UTC · 2 months ago
machine
koh2
agent
claude-opus-5
session
android notes html
parent
052628df2835
3 files app/src/main/java/com/peachpasswords/ui/vault/EditableFieldRow.kt+12 −4app/src/main/java/com/peachpasswords/ui/vault/EntryDetailScreen.kt+7 −2app/src/main/java/com/peachpasswords/ui/vault/EntryEditViewModel.kt+12 −3
app/src/main/java/com/peachpasswords/ui/vault/EditableFieldRow.kt+12 −4
@@ -55,6 +55,7 @@5555 import androidx.compose.ui.geometry.Offset5656 import androidx.compose.ui.graphics.SolidColor5757 import androidx.compose.ui.platform.LocalContext58+import androidx.compose.ui.text.AnnotatedString5859 import androidx.compose.ui.text.input.PasswordVisualTransformation5960 import androidx.compose.ui.text.input.VisualTransformation6061 import androidx.compose.ui.unit.dp@@ -78,6 +79,10 @@7879  *                (EntryDetailViewModel.updateField) persists it.7980  * @param suggestions identity-suggest values for USERNAME/EMAIL; empty disables the dropdown.8081  * @param mono    mono body style for the value (false = serif body, used by notes).82+ * @param displayText rendered form of [value] for view mode; null shows [value] itself.83+ *                Notes pass their parsed HTML here (see NoteHtml.kt).84+ * @param draftText what edit mode starts from; null edits [value] itself. Notes85+ *                edit the markup form and [onSave] converts it back.8186  */8287 @Composable8388 fun EditableFieldRow(@@ -91,20 +96,23 @@9196     mono: Boolean = true,9297     singleLine: Boolean = true,9398     placeholder: String = "",99+    displayText: AnnotatedString? = null,100+    draftText: String? = null,94101 ) {102+    val editable = draftText ?: value95103     var editing by remember(value) { mutableStateOf(false) }96-    var draft by remember(value) { mutableStateOf(value) }104+    var draft by remember(value) { mutableStateOf(editable) }97105 98106     FieldShell(label) {99107         if (!editing) {100108             val isEmpty = value.isBlank()101109             Text(102-                if (isEmpty) placeholder else value,110+                if (isEmpty) AnnotatedString(placeholder) else displayText ?: AnnotatedString(value),103111                 color = if (isEmpty) peachColors.T2 else peachColors.T0,104112                 style = if (mono) PeachType.bodyMono else PeachType.body,105113                 modifier = Modifier106114                     .weight(1f)107-                    .clickable { draft = value; editing = true },115+                    .clickable { draft = editable; editing = true },108116             )109117             if (!isEmpty) CopyButton(copied, onCopy)110118         } else {@@ -112,7 +120,7 @@112120                 draft = draft,113121                 onDraftChange = { draft = it },114122                 onSave = { onSave(draft.trim()); editing = false },115-                onCancel = { draft = value; editing = false },123+                onCancel = { draft = editable; editing = false },116124                 suggestions = suggestions,117125                 onPickSuggestion = { draft = it },118126                 singleLine = singleLine,

view file

app/src/main/java/com/peachpasswords/ui/vault/EntryDetailScreen.kt+7 −2
@@ -360,12 +360,17 @@360360                     }361361                 }362362                 e.notes?.takeIf { it.isNotBlank() }?.let { v ->363+                    // A note authored in the browser is HTML: shown rendered,364+                    // edited as markup, saved back as the same tags.365+                    val notesAreHtml = isNoteHtml(v)363366                     EditableFieldRow(364367                         label = "notes", field = "notes", value = v,365368                         copied = copiedField == "notes",366-                        onCopy = { vm.copy("notes", v) },367-                        onSave = { vm.updateField("notes", it) },369+                        onCopy = { vm.copy("notes", notePlainText(v)) },370+                        onSave = { vm.updateField("notes", noteStorageText(it, notesAreHtml)) },368371                         mono = false, singleLine = false,372+                        displayText = noteRendered(v),373+                        draftText = noteEditorText(v),369374                     )370375                 }371376                 DetailTagRow(

view file

app/src/main/java/com/peachpasswords/ui/vault/EntryEditViewModel.kt+12 −3
@@ -62,6 +62,15 @@6262     val isNew: Boolean = id == null6363     private val existing: VaultEntry? = id?.let { vault.getEntry(it) }6464 65+    /**66+     * A note authored in the browser is HTML; the form edits its markup form and67+     * writes the tags back, so editing a login's notes here cannot flatten them.68+     */69+    private val notesAreHtml: Boolean = isNoteHtml(existing?.notes.orEmpty())70+71+    private fun notesForStorage(edited: String): String? =72+        noteStorageText(edited, notesAreHtml).ifBlank { null }73+6574     // Prefill values arrive on the EntryEdit route when the user follows the6675     // autofill picker's "+ Create new entry" path. Only apply to fresh entries6776     // — editing an existing entry must not be silently overwritten.@@ -80,7 +89,7 @@8089                 url = it.url.orEmpty(),8190                 additionalUrls = it.additionalUrls.orEmpty(),8291                 totp = it.totp.orEmpty(),83-                notes = it.notes.orEmpty(),92+                notes = noteEditorText(it.notes.orEmpty()),8493                 cardNickname = it.cardNickname.orEmpty(),8594                 cardNumber = CardFieldUtils.formatPan(it.cardNumber.orEmpty()),8695                 cardholderName = it.cardholderName.orEmpty(),@@ -227,7 +236,7 @@227236                                 .map { it.trim() }.filter { it.isNotBlank() }228237                                 .ifEmpty { null },229238                             totp = f.totp.ifBlank { null },230-                            notes = f.notes.ifBlank { null },239+                            notes = notesForStorage(f.notes),231240                             cardNickname = f.cardNickname.ifBlank { null },232241                             cardNumber = f.cardNumber.filter { it.isDigit() }.ifBlank { null },233242                             cardholderName = f.cardholderName.ifBlank { null },@@ -251,7 +260,7 @@251260                                 .map { it.trim() }.filter { it.isNotBlank() }252261                                 .ifEmpty { null },253262                             totp = f.totp.ifBlank { null },254-                            notes = f.notes.ifBlank { null },263+                            notes = notesForStorage(f.notes),255264                             cardNickname = f.cardNickname.ifBlank { null },256265                             cardNumber = f.cardNumber.filter { it.isDigit() }.ifBlank { null },257266                             cardholderName = f.cardholderName.ifBlank { null },

view file