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
|
* **On blur, and only the block being left:** re-splitting on a keystroke would move
|
||||||
* and nothing reads the body again — every edit travels the other way, through
|
* the caret, and converting the moment `- [ ]` is complete would catch an item with
|
||||||
* [joinBlocks]. So a marker typed by hand stayed literal text on screen until the note
|
* no text yet.
|
||||||
* 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.** There is no good moment to convert while
|
* Returns THIS LIST when there was nothing to promote; the caller relies on that to
|
||||||
* someone is typing: re-splitting on a keystroke moves the caret out of the word being
|
* leave the state alone, so a blur that changed nothing doesn't re-key every field.
|
||||||
* 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, not an equal copy, when there was nothing to promote — the caller
|
* Non-canonical markers (`- [X]`, an odd bullet) come back canonical, as they would on
|
||||||
* leans on that to leave the state alone, and a blur that changed nothing must not
|
* reopen.
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
internal fun List<EditorBlock>.promotingTasks(index: Int): List<EditorBlock> {
|
internal fun List<EditorBlock>.promotingTasks(index: Int): List<EditorBlock> {
|
||||||
val block = getOrNull(index)
|
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))
|
.border(1.dp, if (dark) CARD_EDGE_DARK else CARD_EDGE_LIGHT, RoundedCornerShape(CARD_RADIUS))
|
||||||
.padding(12.dp),
|
.padding(12.dp),
|
||||||
) {
|
) {
|
||||||
// TAGS FIRST. They used to sit under everything else, which on a tall note put
|
// TAGS FIRST, on a row of their own above the body: a board is scanned, and
|
||||||
// the one thing that says what a note IS below the fold of a glance. A board is
|
// what a note is about should be the first thing the eye lands on. Not beside
|
||||||
// scanned, not read, and the answer to "which of these is about the thing I am
|
// the body, whose first line is the note's name.
|
||||||
// looking for" should be the first thing the eye lands on rather than the last.
|
|
||||||
//
|
//
|
||||||
// Above the body rather than beside it, because the body's first line is the
|
// Only labels whose text is not still in the note (`via_tag` is false). A tag
|
||||||
// note's NAME (M13 steps 3 and 4) and a chip floated next to it would compete
|
// left in prose is tinted in place instead (see [tintTags]), so this row holds
|
||||||
// with the thing that identifies the note. A row of its own costs one line and
|
// what the body cannot say: a tag lifted off its own line, or one added by hand.
|
||||||
// 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.
|
|
||||||
val chips = note.labels.filterNot { it.viaTag }
|
val chips = note.labels.filterNot { it.viaTag }
|
||||||
if (chips.isNotEmpty()) {
|
if (chips.isNotEmpty()) {
|
||||||
LabelChips(labels = chips)
|
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
|
* The same items as the editor's overflow, in the same words from the same string
|
||||||
* actions are in the editor" — it was *"there are no long hold context menus in the
|
* resources, dispatched through [EditorAction] so `BoardViewModel.onEditorAction`
|
||||||
* app I have no way to delete notes."* Trash was three interactions deep (open, ⋮,
|
* stays the one place they are handled.
|
||||||
* 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
|
* Gated on the NOTE (`note.trashed`), not on the view, because Reminders and search
|
||||||
* resources.** A note has one vocabulary of things that can be done to it, and two
|
* mix piles: a note already in Trash must not offer "Move to trash".
|
||||||
* 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, not on the destination.** `note.trashed` is what the editor
|
* No Labels item: the picker it opens is editor state.
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun NoteMenu(
|
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_LIGHT = Color(0xFFFFFFFF)
|
||||||
private val CARD_SURFACE_DARK = Color(0xFF171717)
|
private val CARD_SURFACE_DARK = Color(0xFF171717)
|
||||||
|
|
||||||
// THE CARD'S EDGE — one grey, every card, both themes. Since M315 it is the only thing
|
// THE CARD'S EDGE — one neutral grey, every card, both themes. The fill barely differs
|
||||||
// that differs from the board by more than a hair, which makes it structure rather than
|
// from the board, so this line is what makes a card a card. Neutral on purpose: a
|
||||||
// decoration: it is what a card IS.
|
// 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
|
// Opaque rather than a translucent black/white, which would composite differently over
|
||||||
// was a `{hue}-900` border and failed twice over: the line measured 1.56-2.09 against
|
// whatever is under it. NoteCard.vue writes the same two hex values, which is how the
|
||||||
// its own fill while the fill managed only 1.03-1.05 against the board, so it was the
|
// surfaces stay the same card. Contrast: #B8B8B8 on white 1.98, #404040 on #171717 1.73.
|
||||||
// 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.
|
|
||||||
private val CARD_EDGE_LIGHT = Color(0xFFB8B8B8)
|
private val CARD_EDGE_LIGHT = Color(0xFFB8B8B8)
|
||||||
private val CARD_EDGE_DARK = Color(0xFF404040)
|
private val CARD_EDGE_DARK = Color(0xFF404040)
|
||||||
private val CHIP_RADIUS = 6.dp
|
private val CHIP_RADIUS = 6.dp
|
||||||
|
|||||||
@@ -44,22 +44,9 @@ data class NoteTint(
|
|||||||
val lightChipForeground: Color,
|
val lightChipForeground: Color,
|
||||||
val darkChipForeground: Color,
|
val darkChipForeground: Color,
|
||||||
/**
|
/**
|
||||||
* THE INK A TAG IS DRAWN IN — inline in the prose AND as a chip's text — see
|
* THE INK A TAG IS DRAWN IN — inline in the prose and as a chip's text — see
|
||||||
* [tagInk].
|
* [tagInk]. One value serves both: `-800` light / `-300` dark clears body-text
|
||||||
*
|
* contrast on the card and on a chip's own fill.
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
val lightTagInk: Color,
|
val lightTagInk: Color,
|
||||||
val darkTagInk: Color,
|
val darkTagInk: Color,
|
||||||
|
|||||||
@@ -187,14 +187,9 @@ pub fn evaluate(info: &ServerInfo) -> Compatibility {
|
|||||||
|
|
||||||
/// Who this client says it is, set once by the host application at startup.
|
/// Who this client says it is, set once by the host application at startup.
|
||||||
///
|
///
|
||||||
/// THE CORE CANNOT KNOW THIS, and the value it used to invent was wrong twice. It
|
/// The core can't know this: it is compiled into both the desktop and the Android
|
||||||
/// was `inkwell-desktop/{CARGO_PKG_VERSION}`, and this crate is compiled into
|
/// app, and its own crate version is a number no build stamps. The app's build is
|
||||||
/// the desktop app AND the Android app — so every phone in the field announced
|
/// what a reader of the header wants (note 3127 §5).
|
||||||
/// itself as a desktop. The version was worse: `CARGO_PKG_VERSION` here is the
|
|
||||||
/// version of the CORE crate, a number no build stamps and no user has ever seen,
|
|
||||||
/// while the thing a reader of that header wants is the app's own build (note 3127
|
|
||||||
/// §5 — with no version tags, the artifact's self-report is the only answer to
|
|
||||||
/// "which build is this?").
|
|
||||||
///
|
///
|
||||||
/// So the host names itself. `OnceLock` because identity is fixed for the life of
|
/// So the host names itself. `OnceLock` because identity is fixed for the life of
|
||||||
/// the process and a second caller should be ignored rather than race the first.
|
/// the process and a second caller should be ignored rather than race the first.
|
||||||
|
|||||||
@@ -110,28 +110,18 @@ export function plusTask(blocks: EditorBlock[]): { blocks: EditorBlock[]; focus:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
* On blur, and only the block being left: re-splitting on a keystroke would move the
|
||||||
* and nothing reads the body again — every edit travels the other way, through
|
* caret, and converting the moment `- [ ]` is complete would catch an item with no
|
||||||
* `joinBlocks`. So a marker typed by hand stayed literal text on screen until the note
|
* text yet.
|
||||||
* 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. There is no good moment to convert while
|
* Returns THE SAME ARRAY when there was nothing to promote; the caller relies on that
|
||||||
* someone is typing: re-splitting on a keystroke moves the caret out of the word being
|
* to leave the ref alone, so a blur that changed nothing doesn't re-key every field.
|
||||||
* 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 THE SAME ARRAY, not an equal copy, when there was nothing to promote — the
|
* Non-canonical markers (`- [X]`, an odd bullet) come back canonical, as they would on
|
||||||
* caller leans on that to leave the ref alone, and a blur that changed nothing must not
|
* reopen.
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
export function promoteTasks(blocks: EditorBlock[], index: number): EditorBlock[] {
|
export function promoteTasks(blocks: EditorBlock[], index: number): EditorBlock[] {
|
||||||
const block = blocks[index];
|
const block = blocks[index];
|
||||||
|
|||||||
@@ -36,29 +36,12 @@ export const NOTE_SWATCH_CLASSES: Record<NoteColor, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// A chip's SHELL: its fill and its hairline edge, keyed by the colour vocabulary. The
|
// A chip's SHELL: its fill and its hairline edge, keyed by the colour vocabulary. The
|
||||||
// INK is not here — see TAG_TEXT_CLASSES, which one table now serves both a chip's text
|
// ink is TAG_TEXT_CLASSES; `labelChipClasses` composes the two.
|
||||||
// and a `#tag` left in the prose. Compose the two with `labelChipClasses`.
|
|
||||||
//
|
//
|
||||||
// THE RING IS NOT DECORATION. A chip's fill measures 1.02-1.26 against the card in
|
// The ring is what gives the pill its shape: a chip's fill measures only 1.02-1.73
|
||||||
// light and 1.02-1.73 in dark — that is to say, very nearly nothing. The pill's shape
|
// against the card. It is the ink at 65%, the lowest alpha that clears WCAG 1.4.11's
|
||||||
// is the edge; the fill only tints it. (Dark red is the extreme at 1.02, which is
|
// 3:1 for all ten hues on the one card surface (light 3.03-4.36, dark 4.52-5.76).
|
||||||
// invisible: without the ring that chip would be loose text.) An edge holds the shape
|
// `default` follows the same rule as every other key.
|
||||||
// against any background, where shifting the fill only moves which card it collides
|
|
||||||
// with.
|
|
||||||
//
|
|
||||||
// AT 65%, NOT 60%, AND SOLVED FOR RATHER THAN GUESSED. 0.60 was chosen against a worst
|
|
||||||
// case that no longer exists — a chip sitting on a card of its own colour, back when a
|
|
||||||
// note took its first tag's fill. Against the one card surface (M315) the ring is the
|
|
||||||
// ink at alpha over a known fill, so the alpha that clears the 3:1 of WCAG 1.4.11 for
|
|
||||||
// all ten hues can simply be solved: 0.60 gives 2.75-3.82 in light and misses for six
|
|
||||||
// of them, 0.65 gives 3.03-4.36 and misses for none. Dark is 4.52-5.76. 0.80 was the
|
|
||||||
// number the old comment named as the fallback and it is more than is needed — it
|
|
||||||
// draws a hard outline where a hairline does the job.
|
|
||||||
//
|
|
||||||
// `default`'s ring was `black/10 dark:white/15` here and the ink at alpha on the phone —
|
|
||||||
// 1.36 against its own fill in light against Compose's 3.21, so the two surfaces were
|
|
||||||
// drawing visibly different pills for the same chip. It is the ink at 65% on both now,
|
|
||||||
// like every other key: one rule for ten hues, not nine and an exception.
|
|
||||||
//
|
//
|
||||||
// Mirrored in NoteTint.kt as `chipBackground` and `chipBorder` / CHIP_EDGE_ALPHA.
|
// Mirrored in NoteTint.kt as `chipBackground` and `chipBorder` / CHIP_EDGE_ALPHA.
|
||||||
export const LABEL_CHIP_SHELL: Record<NoteColor, string> = {
|
export const LABEL_CHIP_SHELL: Record<NoteColor, string> = {
|
||||||
@@ -74,26 +57,13 @@ export const LABEL_CHIP_SHELL: Record<NoteColor, string> = {
|
|||||||
gray: "bg-neutral-200 dark:bg-neutral-700 ring-1 ring-inset ring-neutral-800/65 dark:ring-neutral-200/65",
|
gray: "bg-neutral-200 dark:bg-neutral-700 ring-1 ring-inset ring-neutral-800/65 dark:ring-neutral-200/65",
|
||||||
};
|
};
|
||||||
|
|
||||||
// THE INK A TAG IS DRAWN IN — one table, for a `#tag` left in the prose AND for a
|
// THE INK A TAG IS DRAWN IN — one table, for a `#tag` left in the prose and for a
|
||||||
// chip's text. It was two, and the split was real while it lasted: a chip carried its
|
// chip's text. `-800` in light and `-300` in dark clear body-text contrast in both
|
||||||
// own `-100` fill and could afford `-700`, while inline text sat on whatever the card
|
// places:
|
||||||
// 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, so inline got `-800` and the chip kept `-700`.
|
|
||||||
//
|
|
||||||
// M315 removed the twenty card fills the split was solving for, and this is the payoff:
|
|
||||||
// against ONE card surface both jobs can take the same value. `-800`/`-300` is the one
|
|
||||||
// they take, and the direction is deliberate — the inline token is the common case
|
|
||||||
// (since M311 a tag whose text is in the body is drawn where it was typed and NOT
|
|
||||||
// repeated as a chip), so collapsing onto the inline column leaves what is seen most
|
|
||||||
// exactly as it was, and moves only the chip. The chip is strictly better for it:
|
|
||||||
//
|
//
|
||||||
// inline, on the card as a chip, on its own fill
|
// inline, on the card as a chip, on its own fill
|
||||||
// light `-800` 7.09 - 15.13 6.37 - 12.01 (was 4.52 - 8.23)
|
// light `-800` 7.09 - 15.13 6.37 - 12.01
|
||||||
// dark `-300` 9.45 - 14.23 8.23 - 11.88 (unchanged)
|
// dark `-300` 9.45 - 14.23 8.23 - 11.88
|
||||||
//
|
|
||||||
// Dark needed no decision at all: the two tables were already the same value for all
|
|
||||||
// ten hues, which is on its own most of the argument that one table was always enough.
|
|
||||||
//
|
//
|
||||||
// Mirrored in NoteTint.kt as `lightTagInk` / `darkTagInk`.
|
// Mirrored in NoteTint.kt as `lightTagInk` / `darkTagInk`.
|
||||||
export const TAG_TEXT_CLASSES: Record<NoteColor, string> = {
|
export const TAG_TEXT_CLASSES: Record<NoteColor, string> = {
|
||||||
@@ -249,33 +219,9 @@ export function resolveLabelColor(label: { name: string; color?: string | null }
|
|||||||
// chips of one colour are the same tag. The chip's text is what says which tag it is.
|
// chips of one colour are the same tag. The chip's text is what says which tag it is.
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// THE CARD SURFACE — one neutral, no hue, no note involved (M315).
|
// THE CARD SURFACE — one neutral, the same for every note. Colour lives only on tags
|
||||||
//
|
// (the chip and the inline `#tag`); a card's fill can't carry meaning, because nine
|
||||||
// This used to be a function of the note: a palette class for a tagged one, a fill
|
// keys identify nothing on a board of any size. It is the editor panel's surface too.
|
||||||
// generated from the id for the rest. Both are gone. The operator's verdict after
|
|
||||||
// four passes at it — "my coloring attempt has failed and nothing looks right… we've
|
|
||||||
// tried a lot to make the color work and somehow it never seems to land" — and the
|
|
||||||
// diagnosis underneath it is that a card's fill was being asked to carry meaning it
|
|
||||||
// could not carry. Nine keys is too few to identify anything on a board of any size,
|
|
||||||
// and a generated fill identifies nothing by construction, so a coloured board taught
|
|
||||||
// the eye to read hue as significant and then handed it noise.
|
|
||||||
//
|
|
||||||
// COLOUR NOW LIVES ONLY ON THE TAG — the chip and the inline `#tag`, both of which sit
|
|
||||||
// on this one known background from here on. That is a smaller job done properly
|
|
||||||
// instead of a larger one done four times.
|
|
||||||
//
|
|
||||||
// The codebase had already made this argument about the card's EDGE, one level down:
|
|
||||||
// the hue-coded border came out as "a neutral line carries no information at all,
|
|
||||||
// which is exactly what lets it be structure instead of content". The fill is the same
|
|
||||||
// argument at the next size up.
|
|
||||||
//
|
|
||||||
// THE VALUES. `bg-white dark:bg-neutral-900` — which is exactly what `default` always
|
|
||||||
// was, and exactly what the note EDITOR panel has always been (NoteEditor.vue), so
|
|
||||||
// this is a collapse onto a surface both other surfaces already used rather than a
|
|
||||||
// new colour anybody has to like.
|
|
||||||
//
|
|
||||||
// Measured against the operator's constraint, "not the same color as their background
|
|
||||||
// but close to it":
|
|
||||||
//
|
//
|
||||||
// card vs board light #ffffff on #fafafa 1.04
|
// card vs board light #ffffff on #fafafa 1.04
|
||||||
// dark #171717 on #0a0a0a 1.10
|
// dark #171717 on #0a0a0a 1.10
|
||||||
@@ -283,11 +229,7 @@ export function resolveLabelColor(label: { name: string; color?: string | null }
|
|||||||
// dark #404040 on #171717 1.73
|
// dark #404040 on #171717 1.73
|
||||||
// body vs card light #171717 on #ffffff 17.93 (needs 4.5)
|
// body vs card light #171717 on #ffffff 17.93 (needs 4.5)
|
||||||
// dark #fafafa on #171717 17.17
|
// dark #fafafa on #171717 17.17
|
||||||
// muted vs card light #404040 on #ffffff 10.37
|
|
||||||
// dark #e5e5e5 on #171717 14.23
|
|
||||||
//
|
//
|
||||||
// The fill is deliberately the WEAKEST of those numbers. A card is not separated from
|
// The fill is deliberately the weakest number: the edge and the shadow separate a card
|
||||||
// the board by its fill and never was — the edge and the shadow do that, which is why
|
// from the board, and a fill strong enough to do it alone would make every card a panel.
|
||||||
// 1.04 is enough and why it has to stay near 1: a fill that separated on its own would
|
|
||||||
// be a panel, and a board of panels is the wall this whole line of work started from.
|
|
||||||
export const NOTE_CARD_SURFACE = "bg-white dark:bg-neutral-900";
|
export const NOTE_CARD_SURFACE = "bg-white dark:bg-neutral-900";
|
||||||
|
|||||||
+7
-33
@@ -51,45 +51,19 @@ DEFAULT_LIMIT = 500
|
|||||||
MAX_LIMIT = 1000
|
MAX_LIMIT = 1000
|
||||||
MAX_PUSH = 1000 # per-batch change cap
|
MAX_PUSH = 1000 # per-batch change cap
|
||||||
|
|
||||||
# --- protocol versioning (M10.6) --------------------------------------------
|
# --- protocol versioning ------------------------------------------------------
|
||||||
#
|
#
|
||||||
# The client<->server compatibility contract. These integers version the WIRE
|
# The client<->server compatibility contract. These integers version the WIRE
|
||||||
# PROTOCOL, deliberately separate from the app's release version, so a client and
|
# PROTOCOL, separately from the app's release version, so a client and server on
|
||||||
# server on different releases can still work out whether they can talk. Without
|
# different releases can still work out whether they can talk.
|
||||||
# that separation every protocol change would force app<->server lockstep.
|
|
||||||
#
|
#
|
||||||
# SYNC_PROTOCOL_VERSION what this server speaks.
|
# SYNC_PROTOCOL_VERSION what this server speaks.
|
||||||
# MIN_CLIENT_PROTOCOL_VERSION the oldest client protocol it still accepts.
|
# MIN_CLIENT_PROTOCOL_VERSION the oldest client protocol it still accepts.
|
||||||
#
|
#
|
||||||
# Bump SYNC_PROTOCOL_VERSION for ANY wire change. Raise
|
# Bump SYNC_PROTOCOL_VERSION for ANY wire change. Raise the floor only for a
|
||||||
# MIN_CLIENT_PROTOCOL_VERSION only for a genuinely BREAKING one: it is the switch
|
# BREAKING one: it hard-blocks older clients, so additive changes leave it alone.
|
||||||
# that hard-blocks older clients, so additive changes must leave it alone.
|
# What each version changed, and why the floor moved or didn't, is in docs/sync.md
|
||||||
# v2 (M13): `kind` and `title` both left the wire. Dropping a field a v1 client sends
|
# ("The policy").
|
||||||
# and expects back is breaking, so the FLOOR moves too — a v1 client would keep pushing
|
|
||||||
# both and would read back notes carrying neither.
|
|
||||||
#
|
|
||||||
# One bump for the pair: they landed in the same protocol generation, and nothing ever
|
|
||||||
# ran against a half-applied v2.
|
|
||||||
# v4 (M315): `color` left the note. The FLOOR DELIBERATELY DOES NOT MOVE, and v2 is
|
|
||||||
# the precedent that makes saying so worthwhile — it dropped `kind` and `title` and did
|
|
||||||
# raise the floor, on the rule that dropping a field a client sends and expects back is
|
|
||||||
# breaking. `color` fails the second half of that: a v3 client reading a v4 note gets
|
|
||||||
# `"default"` from its own serde default and draws the colour it derives locally, which
|
|
||||||
# is the board it drew yesterday; a v3 client PUSHING `color` has the key ignored, since
|
|
||||||
# `_assign_note_fields` reads its payload key by key and never validates the shape.
|
|
||||||
# Neither direction errors and neither loses anything visible. `title` was the note's
|
|
||||||
# NAME; this is a field that no longer renders anywhere.
|
|
||||||
# v5 (#5168): attachments became a sync entity — `PUT /attachments/<id>` uploads a file
|
|
||||||
# attached offline, and push takes `attachment` and `preview` deletes. Additive, so the
|
|
||||||
# floor stays: a v4 client never sends either, and gets the same notes it always did.
|
|
||||||
# v6 (#5175): shared notes. A client asking `changes?shares=1` gets the notes shared
|
|
||||||
# with it, each saying how it is held (`permission`, `shared`, `shared_by`), and a
|
|
||||||
# `revoked` list of notes that have left it; push takes body edits to notes shared at
|
|
||||||
# `edit`. Additive and opt-in, so the floor stays: a v5 client never asks, and gets
|
|
||||||
# its own notes exactly as before.
|
|
||||||
# v7 (#5176): a recipient's own pin, archive and place. On a shared note the feed
|
|
||||||
# carries the caller's own, and push takes them, stamped `state_at`, for a note shared
|
|
||||||
# at any level. Additive, so the floor stays: a v6 client pushes only text, as before.
|
|
||||||
SYNC_PROTOCOL_VERSION = 7
|
SYNC_PROTOCOL_VERSION = 7
|
||||||
MIN_CLIENT_PROTOCOL_VERSION = 3
|
MIN_CLIENT_PROTOCOL_VERSION = 3
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user