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 2s
CI & Build / Web typecheck and unit tests (push) Successful in 13s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Python tests (push) Successful in 15s
CI & Build / integration (push) Successful in 1m29s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 3m34s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m5s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 3m49s
Desktop (Tauri) / Update manifest (push) Successful in 3s
Android / Kotlin + Rust (APK) (push) Successful in 9m29s
Step 18 of the audit follow-through (#5180), on the operator's decisions. Inkwell is for capture and recall (note 2897), and these three duplicated a surface that does the job already: - Saved views. They lived only on the web; the desktop kept its own set that never synced, and Android had none. Tags in the drawer already give one-click recall. Gone from the server (routes, model, migration 0038 drops the table), the core (store functions, schema v13 drops its table), the desktop commands, the web adapters, the drawer's Views list and the "Save view" link. - The "Has reminder" facet. The Reminders page lists them, sorted by due. - The FilterBar's "Created" range. Timeline is the date lens and keeps the created_after/created_before query it builds from local days, which also retires the UTC/local-day disagreement between the two (B3). An old link that still carries the removed keys opens the plain board; a web test pins that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
182 lines
5.3 KiB
Rust
182 lines
5.3 KiB
Rust
//! Serde shapes for the local store. Output structs mirror the JSON the frontend
|
|
//! already consumes (frontend/src/stores/*.ts) so the same components render whether
|
|
//! the data comes from REST or from these Tauri commands. Field names/optionality
|
|
//! match the TypeScript interfaces exactly.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Serialize)]
|
|
pub struct Note {
|
|
pub id: String,
|
|
/// The note's NAME: its first non-blank body line, else its first checklist item.
|
|
/// Always present, so every note has something to be called. Derived at read time,
|
|
/// never stored.
|
|
pub display_title: String,
|
|
pub body: String,
|
|
pub position: i64,
|
|
pub pinned: bool,
|
|
pub archived: bool,
|
|
pub trashed: bool,
|
|
/// When it was trashed (null unless trashed). Named for the server's field so the
|
|
/// shared frontend counts down the retention window identically either way.
|
|
pub deleted_at: Option<String>,
|
|
pub remind_at: Option<String>,
|
|
pub recurrence: Option<String>,
|
|
pub labels: Vec<NoteLabel>,
|
|
pub items: Vec<ChecklistItem>,
|
|
pub attachments: Vec<Attachment>,
|
|
pub previews: Vec<LinkPreview>,
|
|
pub created_at: Option<String>,
|
|
pub updated_at: Option<String>,
|
|
/// How this account holds the note (#5175): `owner`, or `edit`/`view` for one
|
|
/// someone shared with it. Named and valued as the server's, so the shared
|
|
/// frontend gates the editor identically either way.
|
|
pub permission: String,
|
|
/// Whether the owner has shared it with anyone.
|
|
pub shared: bool,
|
|
/// Who shared it with us; null on our own notes.
|
|
pub shared_by: Option<SharedBy>,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct SharedBy {
|
|
pub id: String,
|
|
pub display_name: String,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct NoteLabel {
|
|
pub id: String,
|
|
pub name: String,
|
|
pub color: String,
|
|
/// True when attached because of a `#tag` in the body (kept in sync with the text).
|
|
pub via_tag: bool,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct ChecklistItem {
|
|
pub id: String,
|
|
pub text: String,
|
|
pub checked: bool,
|
|
pub position: i64,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct Attachment {
|
|
pub id: String,
|
|
pub url: String,
|
|
pub filename: Option<String>,
|
|
pub mime: String,
|
|
pub size: Option<i64>,
|
|
pub sha256: Option<String>,
|
|
/// Why the server refused a file attached on this device, in words to show on it.
|
|
/// Absent for everything else, including a file still waiting to upload.
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub upload_error: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct LinkPreview {
|
|
pub id: String,
|
|
pub url: String,
|
|
pub title: Option<String>,
|
|
pub description: Option<String>,
|
|
pub image_url: Option<String>,
|
|
pub site_name: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct NoteRevision {
|
|
pub id: String,
|
|
pub body: String,
|
|
pub created_at: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct Label {
|
|
pub id: String,
|
|
pub name: String,
|
|
pub color: String,
|
|
// Note count is only meaningful in listings; omitted elsewhere so the frontend
|
|
// preserves the count it already holds (matches the REST single-label responses).
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub count: Option<i64>,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct TitleEntry {
|
|
pub id: String,
|
|
pub title: String,
|
|
}
|
|
|
|
/// A reminder that has come due, as the desktop's reminder worker needs it.
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
|
pub struct DueReminder {
|
|
pub id: String,
|
|
pub title: String,
|
|
/// `remind_at` as stored. It names the occurrence: completing or snoozing a
|
|
/// reminder changes it, and that is how an announcer tells a new occurrence
|
|
/// from one it has already announced.
|
|
pub remind_at: String,
|
|
/// The same instant, in milliseconds since the epoch.
|
|
pub due_ms: i64,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct PublicConfig {
|
|
pub site_name: String,
|
|
pub allow_registration: bool,
|
|
pub version: String,
|
|
pub enable_url_unfurl: bool,
|
|
pub trash_retention_days: u32,
|
|
}
|
|
|
|
/// The synthetic single user the offline core reports, so the app's auth-gated
|
|
/// router resolves without a login. There is no real account offline.
|
|
#[derive(Serialize)]
|
|
pub struct User {
|
|
pub id: String,
|
|
pub email: String,
|
|
pub display_name: String,
|
|
pub is_admin: bool,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct NoteCreateInput {
|
|
#[serde(default)]
|
|
pub body: String,
|
|
#[serde(default)]
|
|
pub items: Option<Vec<String>>,
|
|
}
|
|
|
|
/// The board query (mirrors adapters/repo.ts NoteListQuery). `labelId` is camelCase
|
|
/// on the wire; the facet fields are snake_case, matching NoteFacets.
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ListQuery {
|
|
pub view: String,
|
|
#[serde(default)]
|
|
pub label_id: Option<String>,
|
|
#[serde(default)]
|
|
pub facets: Option<Facets>,
|
|
#[serde(default)]
|
|
pub sort: Option<String>,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct Facets {
|
|
#[serde(default)]
|
|
pub q: Option<String>,
|
|
#[serde(default)]
|
|
pub label: Option<Vec<String>>,
|
|
#[serde(default)]
|
|
pub has_attachment: Option<bool>,
|
|
#[serde(default)]
|
|
pub created_after: Option<String>,
|
|
#[serde(default)]
|
|
pub created_before: Option<String>,
|
|
/// `with_me`: only notes someone else shared with this account.
|
|
#[serde(default)]
|
|
pub shared: Option<String>,
|
|
}
|