notes: color leaves the model, the wire and all three surfaces
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 4s
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 14s
CI & Build / integration (push) Successful in 19s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Tauri desktop (Linux) (push) Failing after 2m28s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m52s
Desktop (Tauri) / Update manifest (push) Skipped
Android / Kotlin + Rust (APK) (push) Failing after 4m1s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 4s
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 14s
CI & Build / integration (push) Successful in 19s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Tauri desktop (Linux) (push) Failing after 2m28s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m52s
Desktop (Tauri) / Update manifest (push) Skipped
Android / Kotlin + Rust (APK) (push) Failing after 4m1s
Step 3 of M315, and the destructive half. Steps 1 and 2 stopped every read of this field: a card is one neutral surface per theme, and the only coloured thing on a board is a tag. What was left was a column written by a picker and read by nothing. Rule 22 — the old path comes out completely. No flag, no fallback, no "override if set". Server: the column, the `?color=` facet, the create/update/serialise paths, the sync assignment, the front-matter line, and Keep's colour map. Alembic 0029 drops it and sweeps `"color"` out of stored saved-filter params — a view that silently filtered on a field the app no longer has would return nothing and never say why. That sweep is Python, not `params::jsonb - 'color'`, because Postgres has no try-cast and one malformed blob would abort a migration that is running over somebody's saved views. `NOTE_COLORS` moves from `models/note.py` to `colors.py`. A palette defined on the model that lost one is an invitation to put the column back; labels still name a colour, so the vocabulary belongs where the normalizer already is. Core: the field, the facet, the `NoteCreateInput`, and every read and write in store/push/pull. Local schema v9 drops the column and does the same saved-filter sweep, guarded on `json_valid` so a corrupt blob loses a key rather than becoming NULL. The uniffi layer drops `NoteEdit::Color` and `NoteDraft.color` with it. Web: `ColorPicker.vue`, the per-card swatch popover and its stylesheet rule, the FilterBar colour row, the facet in the query round-trip, and the colour half of the editor's baseline-and-save. Android: the `ColorSheet`, the `Picker.COLOR` case, the toolbar's swatch dot, `EditorAction.SetColor`. ## The protocol: v4, and the floor deliberately stays at 3 Checked against `compat.rs` and the push handler rather than trusting the `#[serde(default)]` annotation, because the v2 precedent points the other way: v2 dropped `kind` and `title` and DID raise both floors, on the rule that dropping a field a client sends and expects back is breaking. `color` fails the second half of that test. A v3 client reading a v4 note gets `"default"` from its own serde default and draws the colour it derives locally — 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 shows anything wrong. `title` was the note's NAME; this is a field that no longer renders. So `SYNC_PROTOCOL_VERSION` and `CLIENT_PROTOCOL_VERSION` go to 4, and both floors stay at 3. `docs/sync.md` carries the reasoning and the per-version history, and its push example is brought back in line — it still listed `title`, `kind` and `items`, all gone before this. Import stays tolerant: a pre-M315 export or a Keep takeout carrying `color:` imports fine, the key simply read past. Old exports must still import. #3041 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -617,7 +617,6 @@ mod tests {
|
||||
fn draft(body: &str) -> NoteDraft {
|
||||
NoteDraft {
|
||||
body: body.to_string(),
|
||||
color: "default".to_string(),
|
||||
items: None,
|
||||
}
|
||||
}
|
||||
@@ -671,8 +670,7 @@ mod tests {
|
||||
let created = app
|
||||
.create_note(NoteDraft {
|
||||
body: String::new(),
|
||||
color: "default".to_string(),
|
||||
items: Some(vec!["milk".to_string(), "eggs".to_string()]),
|
||||
items: Some(vec!["milk".to_string(), "eggs".to_string()]),
|
||||
})
|
||||
.expect("create should succeed");
|
||||
assert_eq!(created.display_title, "milk");
|
||||
@@ -707,8 +705,7 @@ mod tests {
|
||||
let note = app
|
||||
.create_note(NoteDraft {
|
||||
body: "Packing".to_string(),
|
||||
color: "default".to_string(),
|
||||
items: Some(vec!["socks".to_string()]),
|
||||
items: Some(vec!["socks".to_string()]),
|
||||
})
|
||||
.expect("create");
|
||||
assert_eq!(note.items.len(), 1);
|
||||
|
||||
@@ -33,7 +33,6 @@ pub struct Note {
|
||||
/// Always present. Derived by the core, never stored.
|
||||
pub display_title: String,
|
||||
pub body: String,
|
||||
pub color: String,
|
||||
pub position: i64,
|
||||
pub pinned: bool,
|
||||
pub archived: bool,
|
||||
@@ -187,7 +186,6 @@ impl From<core_models::Note> for Note {
|
||||
id,
|
||||
display_title,
|
||||
body,
|
||||
color,
|
||||
position,
|
||||
pinned,
|
||||
archived,
|
||||
@@ -206,7 +204,6 @@ impl From<core_models::Note> for Note {
|
||||
id,
|
||||
display_title,
|
||||
body,
|
||||
color,
|
||||
position,
|
||||
pinned,
|
||||
archived,
|
||||
@@ -344,7 +341,6 @@ pub struct NoteQuery {
|
||||
#[derive(Debug, Clone, uniffi::Record)]
|
||||
pub struct NoteFacets {
|
||||
pub q: Option<String>,
|
||||
pub color: Option<String>,
|
||||
pub label: Option<Vec<String>>,
|
||||
pub has_reminder: Option<bool>,
|
||||
pub has_attachment: Option<bool>,
|
||||
@@ -373,7 +369,6 @@ impl From<NoteFacets> for core_models::Facets {
|
||||
fn from(value: NoteFacets) -> Self {
|
||||
let NoteFacets {
|
||||
q,
|
||||
color,
|
||||
label,
|
||||
has_reminder,
|
||||
has_attachment,
|
||||
@@ -382,7 +377,6 @@ impl From<NoteFacets> for core_models::Facets {
|
||||
} = value;
|
||||
core_models::Facets {
|
||||
q,
|
||||
color,
|
||||
label,
|
||||
has_reminder,
|
||||
has_attachment,
|
||||
@@ -396,8 +390,6 @@ impl From<NoteFacets> for core_models::Facets {
|
||||
#[derive(Debug, Clone, uniffi::Record)]
|
||||
pub struct NoteDraft {
|
||||
pub body: String,
|
||||
/// "default" unless the user picked a colour.
|
||||
pub color: String,
|
||||
/// Checklist lines. A note can carry both a body and items (M13 step 2), so this
|
||||
/// is not an alternative to `body` — it is an addition to it.
|
||||
pub items: Option<Vec<String>>,
|
||||
@@ -405,8 +397,8 @@ pub struct NoteDraft {
|
||||
|
||||
impl From<NoteDraft> for core_models::NoteCreateInput {
|
||||
fn from(value: NoteDraft) -> Self {
|
||||
let NoteDraft { body, color, items } = value;
|
||||
core_models::NoteCreateInput { body, color, items }
|
||||
let NoteDraft { body, items } = value;
|
||||
core_models::NoteCreateInput { body, items }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +413,6 @@ impl From<NoteDraft> for core_models::NoteCreateInput {
|
||||
#[derive(Debug, Clone, uniffi::Enum)]
|
||||
pub enum NoteEdit {
|
||||
Body { value: String },
|
||||
Color { value: String },
|
||||
Pinned { value: bool },
|
||||
Archived { value: bool },
|
||||
RemindAt { value: String },
|
||||
@@ -441,7 +432,6 @@ impl NoteEdit {
|
||||
use serde_json::Value;
|
||||
match self {
|
||||
NoteEdit::Body { value } => ("body", Value::String(value)),
|
||||
NoteEdit::Color { value } => ("color", Value::String(value)),
|
||||
NoteEdit::Pinned { value } => ("pinned", Value::Bool(value)),
|
||||
NoteEdit::Archived { value } => ("archived", Value::Bool(value)),
|
||||
NoteEdit::RemindAt { value } => ("remind_at", Value::String(value)),
|
||||
|
||||
Reference in New Issue
Block a user