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
↓ peach-android-140731f8.tar.gzatlas at this save
files changed3 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 −3app/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,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(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 },