all: trim the history essays out of the longest comments
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 9s
CI & Build / Python tests (push) Successful in 12s
CI & Build / integration (push) Successful in 2m0s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 3m11s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m51s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 3m39s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 9m4s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 9s
CI & Build / Python tests (push) Successful in 12s
CI & Build / integration (push) Successful in 2m0s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 3m11s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m51s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 3m39s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 9m4s
From the audit (#5179). Ten comment blocks narrated how the code got here: milestone numbers, earlier values, the operator's verdict on an old design. Each now says what the code does and why, and the history stays in git, Scribe and docs/sync.md. The protocol-version comment in sync.py points at docs/sync.md's policy section, which already lists every bump. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -143,28 +143,18 @@ fun List<EditorBlock>.plusTask(): Pair<List<EditorBlock>, Long> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-read ONE prose block for `- [ ] ` lines somebody typed by hand.
|
||||
* Re-read ONE prose block for `- [ ] ` lines somebody typed by hand, so a marker
|
||||
* typed in the editor becomes an item without closing and reopening the note.
|
||||
*
|
||||
* [splitBlocks] runs once, when the editor opens. After that the blocks are the state
|
||||
* and nothing reads the body again — every edit travels the other way, through
|
||||
* [joinBlocks]. So a marker typed by hand stayed literal text on screen until the note
|
||||
* was closed and reopened, even though it was already a real item in storage and the
|
||||
* card was already drawing a checkbox for it. The editor was the only place that
|
||||
* disagreed.
|
||||
* **On blur, and only the block being left:** re-splitting on a keystroke would move
|
||||
* the caret, and converting the moment `- [ ]` is complete would catch an item with
|
||||
* no text yet.
|
||||
*
|
||||
* **On blur, and only the block being left.** There is no good moment to convert while
|
||||
* someone is typing: re-splitting on a keystroke moves the caret out of the word being
|
||||
* written, and converting the instant `- [ ]` is complete does it before the item has
|
||||
* any text. Blur is the one moment the person has demonstrably finished with the block,
|
||||
* so a re-split costs no caret and cannot catch a half-typed line.
|
||||
* Returns THIS LIST when there was nothing to promote; the caller relies on that to
|
||||
* leave the state alone, so a blur that changed nothing doesn't re-key every field.
|
||||
*
|
||||
* Returns THIS LIST, not an equal copy, when there was nothing to promote — the caller
|
||||
* leans on that to leave the state alone, and a blur that changed nothing must not
|
||||
* re-key every field below it.
|
||||
*
|
||||
* Non-canonical markers (`- [X]`, an odd bullet) come back canonical, exactly as they
|
||||
* would have on reopen. That is the only case where this changes the body rather than
|
||||
* only the way it is drawn.
|
||||
* Non-canonical markers (`- [X]`, an odd bullet) come back canonical, as they would on
|
||||
* reopen.
|
||||
*/
|
||||
internal fun List<EditorBlock>.promotingTasks(index: Int): List<EditorBlock> {
|
||||
val block = getOrNull(index)
|
||||
|
||||
@@ -101,22 +101,13 @@ fun NoteCard(
|
||||
.border(1.dp, if (dark) CARD_EDGE_DARK else CARD_EDGE_LIGHT, RoundedCornerShape(CARD_RADIUS))
|
||||
.padding(12.dp),
|
||||
) {
|
||||
// TAGS FIRST. They used to sit under everything else, which on a tall note put
|
||||
// the one thing that says what a note IS below the fold of a glance. A board is
|
||||
// scanned, not read, and the answer to "which of these is about the thing I am
|
||||
// looking for" should be the first thing the eye lands on rather than the last.
|
||||
// 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.
|
||||
//
|
||||
// Above the body rather than beside it, because the body's first line is the
|
||||
// note's NAME (M13 steps 3 and 4) and a chip floated next to it would compete
|
||||
// with the thing that identifies the note. A row of its own costs one line and
|
||||
// only on notes that have tags at all.
|
||||
//
|
||||
// ONLY the labels whose text is not still in the note. `via_tag` means exactly
|
||||
// "backed by body text" since M311, so a chip for one printed the same tag
|
||||
// twice — once where it was typed, once up here — and the card was carrying
|
||||
// furniture for information it was already showing. A tag left in prose is
|
||||
// tinted in place instead; see [tintTags]. What reaches this row is what the
|
||||
// body cannot say: a tag lifted off its own line, and a label added by hand.
|
||||
// Only labels whose text is not still in the note (`via_tag` is false). A tag
|
||||
// left in prose is tinted in place instead (see [tintTags]), so this row holds
|
||||
// what the body cannot say: a tag lifted off its own line, or one added by hand.
|
||||
val chips = note.labels.filterNot { it.viaTag }
|
||||
if (chips.isNotEmpty()) {
|
||||
LabelChips(labels = chips)
|
||||
@@ -192,34 +183,16 @@ fun NoteCard(
|
||||
}
|
||||
|
||||
/**
|
||||
* What you can do to a note without opening it.
|
||||
* What you can do to a note without opening it, on a long press.
|
||||
*
|
||||
* The board used to have none of this, and the operator's read of that was not "the
|
||||
* actions are in the editor" — it was *"there are no long hold context menus in the
|
||||
* app I have no way to delete notes."* Trash was three interactions deep (open, ⋮,
|
||||
* Move to trash), and on a phone that is far enough from the gesture people reach
|
||||
* for that it may as well not exist.
|
||||
* The same items as the editor's overflow, in the same words from the same string
|
||||
* resources, dispatched through [EditorAction] so `BoardViewModel.onEditorAction`
|
||||
* stays the one place they are handled.
|
||||
*
|
||||
* **The same items as the editor's overflow, in the same words, from the same string
|
||||
* resources.** A note has one vocabulary of things that can be done to it, and two
|
||||
* surfaces that named them differently would be describing two different apps. It
|
||||
* dispatches [EditorAction] for the same reason — `BoardViewModel.onEditorAction` is
|
||||
* already the exhaustive dispatcher for every one of them, so the board reuses the
|
||||
* seam rather than growing a parallel one that could drift.
|
||||
* Gated on the NOTE (`note.trashed`), not on the view, because Reminders and search
|
||||
* mix piles: a note already in Trash must not offer "Move to trash".
|
||||
*
|
||||
* **Gated on the NOTE, not on the destination.** `note.trashed` is what the editor
|
||||
* gates its own read-only mode on, and it is the only reading that survives the views
|
||||
* that mix piles: Reminders cuts across archived and active alike, and a search hits
|
||||
* whatever matches. A menu that offered "Move to trash" on a note already in the
|
||||
* trash would be offering to do something twice.
|
||||
*
|
||||
* **Colour is absent**, though #2946 suggested it. It was left out because `note.color`
|
||||
* was already scheduled for removal; M315 removed it. There is no colour to set on a
|
||||
* note any more — a card is one neutral surface and the only coloured thing on a board
|
||||
* is a tag — so the row this menu never grew is a row that could not exist.
|
||||
*
|
||||
* Labels are absent too, for a duller reason: the picker they open is editor state,
|
||||
* and hoisting it to the board is a bigger change than the friction actually reported.
|
||||
* No Labels item: the picker it opens is editor state.
|
||||
*/
|
||||
@Composable
|
||||
private fun NoteMenu(
|
||||
@@ -555,31 +528,13 @@ fun noteCardSurface(dark: Boolean): Color = if (dark) CARD_SURFACE_DARK else CAR
|
||||
private val CARD_SURFACE_LIGHT = Color(0xFFFFFFFF)
|
||||
private val CARD_SURFACE_DARK = Color(0xFF171717)
|
||||
|
||||
// THE CARD'S EDGE — one grey, every card, both themes. Since M315 it is the only thing
|
||||
// that differs from the board by more than a hair, which makes it structure rather than
|
||||
// decoration: it is what a card IS.
|
||||
// THE CARD'S EDGE — one neutral grey, every card, both themes. The fill barely differs
|
||||
// from the board, so this line is what makes a card a card. Neutral on purpose: a
|
||||
// coloured edge would carry information and turn a board into a grid of outlines.
|
||||
//
|
||||
// It was already neutral before the fill was. The version that came from the palette
|
||||
// was a `{hue}-900` border and failed twice over: the line measured 1.56-2.09 against
|
||||
// its own fill while the fill managed only 1.03-1.05 against the board, so it was the
|
||||
// loudest thing on the card — and it carried the same information the fill did, so a
|
||||
// field of cards read as a grid of outlines however different the colours inside were.
|
||||
// A neutral line carries no information at all, which is exactly what lets it be
|
||||
// structure instead of content. The fill is that same argument one size up.
|
||||
//
|
||||
// MEASURED AGAINST ONE FILL NOW, and deliberately left where it was. #B8B8B8 on white
|
||||
// is 1.98 and #404040 on #171717 is 1.73 — both inside the ranges these values already
|
||||
// shipped at across twenty fills (light 1.57-1.98, dark 1.58-1.73), but at the top of
|
||||
// them rather than the ~1.6-1.7 the pair was originally matched on. Softening the light
|
||||
// edge to re-match would weaken the only boundary a white card on a #FAFAFA board has,
|
||||
// and the complaint that started M315 was about fill, never about edge weight. If an
|
||||
// operator pass disagrees it is one constant, in two files.
|
||||
//
|
||||
// NOT a translucent black/white edge, which is the tidier way to write this and was
|
||||
// measured and rejected: a border composites over what is under it, so `White` at 20%
|
||||
// came out #56396D on a purple card and #A3C9C1 on a teal one. With one fill that
|
||||
// argument no longer bites — but an opaque grey is what NoteCard.vue must also write,
|
||||
// and two surfaces stating the same hex is how they stay the same card.
|
||||
// Opaque rather than a translucent black/white, which would composite differently over
|
||||
// whatever is under it. NoteCard.vue writes the same two hex values, which is how the
|
||||
// surfaces stay the same card. Contrast: #B8B8B8 on white 1.98, #404040 on #171717 1.73.
|
||||
private val CARD_EDGE_LIGHT = Color(0xFFB8B8B8)
|
||||
private val CARD_EDGE_DARK = Color(0xFF404040)
|
||||
private val CHIP_RADIUS = 6.dp
|
||||
|
||||
@@ -44,22 +44,9 @@ data class NoteTint(
|
||||
val lightChipForeground: Color,
|
||||
val darkChipForeground: Color,
|
||||
/**
|
||||
* THE INK A TAG IS DRAWN IN — inline in the prose AND as a chip's text — see
|
||||
* [tagInk].
|
||||
*
|
||||
* These were two columns until M315, and the split was real while it lasted: a chip
|
||||
* brought its own `-100` fill and could afford `-700`, while inline text sat on
|
||||
* whatever the card was, which included a gray-tagged card at `neutral-200` where
|
||||
* `-700` measured 3.98 (green), 4.11 (orange) and 4.34 (teal), all under the 4.5
|
||||
* body text needs. One step deeper cleared every fill at once.
|
||||
*
|
||||
* The twenty card fills that split was solving for are gone, so both jobs take this
|
||||
* one value. The direction is deliberate: since M311 a tag whose text is in the body
|
||||
* is drawn where it was typed and NOT repeated as a chip, so the inline token is the
|
||||
* common case and collapsing onto ITS column leaves what is seen most exactly as it
|
||||
* was. The chip is strictly better for the move — on its own fill it goes from
|
||||
* 4.52-8.23 to 6.37-12.01 in light. Dark needed no decision: the two columns already
|
||||
* held the same value for all ten hues.
|
||||
* THE INK A TAG IS DRAWN IN — inline in the prose and as a chip's text — see
|
||||
* [tagInk]. One value serves both: `-800` light / `-300` dark clears body-text
|
||||
* contrast on the card and on a chip's own fill.
|
||||
*/
|
||||
val lightTagInk: Color,
|
||||
val darkTagInk: Color,
|
||||
|
||||
Reference in New Issue
Block a user