Search and tags look through the Archive, and archived cards say so

A search or a tag filter on the main board, and a tag's own view, now
list every note not in Trash (a new `all` board view on the server and in
the core), so finished work can still be looked up. Archive and Trash
keep a search inside themselves.

An archived note met anywhere but the Archive carries an "Archived" chip
at the top of its card, on the web and on Android.

Task #6315.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 16:23:08 -04:00
co-authored by Claude Opus 5.5
parent 5780220ff1
commit b98a0c315f
10 changed files with 107 additions and 18 deletions
@@ -258,6 +258,7 @@ fun BoardScreen(
onNoteAction = onCardAction,
onConfirmDelete = { confirmingDelete = it },
reorderable = state.reorderable,
marksArchived = state.marksArchived,
onReorder = onReorder,
)
}
@@ -540,6 +541,8 @@ private fun NoteBoard(
onConfirmDelete: (Note) -> Unit,
/** Whether cards can be dragged into a new order: the plain main board only. */
reorderable: Boolean,
/** Whether an archived card carries the Archived chip (BoardState.marksArchived). */
marksArchived: Boolean,
onReorder: (List<String>) -> Unit,
) {
// The web's breakpoints (NoteGrid.vue: three columns from `lg`, 1024px, four from
@@ -635,6 +638,7 @@ private fun NoteBoard(
onAction = { onNoteAction(note, it) },
onConfirmDelete = { onConfirmDelete(note) },
dragging = carried,
marksArchived = marksArchived,
)
}
DisposableEffect(note.id) { onDispose { bounds.forget(note.id) } }
@@ -91,6 +91,14 @@ data class BoardState(
* filtered subset, whose order would be renumbered against notes it can't see.
*/
val reorderable: Boolean get() = filterable && filters.count == 0 && !searching
/**
* Whether an archived card says so: anywhere the Archive is not the whole view. A
* search or a tag looks through the Archive as well as the board, so finished
* work can still be looked up, and the chip is what keeps it from reading as
* current.
*/
val marksArchived: Boolean get() = destination != Destination.Archive
}
/**
@@ -183,15 +191,18 @@ class BoardViewModel(
): List<Note> {
// Only the main board has filters; every board view takes the search text.
val searchOnly = BoardFilters().facets(text)
// A search or a tag looks through the Archive too, as on the web; Archive and
// Trash keep a search inside themselves.
val notesView = if (text.isNotBlank() || filters.labelIds.isNotEmpty()) VIEW_ALL else VIEW_NOTES
return when (destination) {
Destination.Notes -> core.listNotes(query(VIEW_NOTES, facets = filters.facets(text)))
Destination.Notes -> core.listNotes(query(notesView, facets = filters.facets(text)))
Destination.Archive -> core.listNotes(query(VIEW_ARCHIVE, facets = searchOnly))
Destination.Trash -> core.listNotes(query(VIEW_TRASH, facets = searchOnly))
// Not a board view: the core models reminders as its own query, since
// "has a reminder" cuts across archived and active alike. `search` moves
// a search off this view, so it never has text to apply.
Destination.Reminders -> core.reminderNotes()
is Destination.WithLabel -> core.listNotes(query(VIEW_NOTES, labelId = destination.id, facets = searchOnly))
is Destination.WithLabel -> core.listNotes(query(VIEW_ALL, labelId = destination.id, facets = searchOnly))
}
}
@@ -647,6 +658,9 @@ class BoardViewModel(
private const val VIEW_ARCHIVE = "archived"
private const val VIEW_TRASH = "trash"
/** The board and the Archive together — everything not in Trash. */
private const val VIEW_ALL = "all"
fun factory(
core: Inkwell,
readFile: (Uri) -> PickedFile,
@@ -55,6 +55,8 @@ fun NoteCard(
onConfirmDelete: () -> Unit,
/** The board is carrying this card (BoardDrag.kt). */
dragging: Boolean = false,
/** Whether an archived note says so — everywhere but the Archive itself. */
marksArchived: Boolean = true,
) {
val dark = isSystemInDarkTheme()
val haptics = LocalHapticFeedback.current
@@ -107,7 +109,11 @@ fun NoteCard(
.border(1.dp, if (dark) CARD_EDGE_DARK else CARD_EDGE_LIGHT, RoundedCornerShape(CARD_RADIUS))
.padding(12.dp),
) {
CardContents(note = note, onToggleItem = onToggleItem)
CardContents(
note = note,
onToggleItem = onToggleItem,
archivedChip = marksArchived && note.archived && !note.trashed,
)
}
NoteMenu(
@@ -120,12 +126,23 @@ fun NoteCard(
}
}
/** What a card shows, top to bottom: tags, body, links, attachments, reminder, sharing. */
/**
* What a card shows, top to bottom: archived or not, tags, body, links, attachments,
* reminder, sharing.
*/
@Composable
private fun CardContents(
note: Note,
onToggleItem: (Int, Boolean) -> Unit,
archivedChip: Boolean,
) {
// Above everything, tags included: whether a note is put away is the first thing
// to know about a search result or a tag's note, before what it says.
if (archivedChip) {
TintChip(stringResource(R.string.card_archived), "default")
Spacer(Modifier.height(8.dp))
}
// TAGS FIRST, on a row of their own above the body: a board is scanned, and
// what a note is about should be the first thing the eye lands on. Not beside
// the body, whose first line is the note's name.
@@ -27,6 +27,7 @@
<!-- Board -->
<string name="board_empty_note">Empty note</string>
<string name="card_archived">Archived</string>
<!-- The long-press menu. Its ITEMS are the editor_* strings, deliberately: a
note has one vocabulary of things you can do to it, and a board that said