maarcadetweet: initial commit

AT Protocol PDS + AppView + Tauri Desktop Client, 160-char post limit.

- PDS (Rust + axum + sqlx)
  - Auth: createAccount, createSession, refreshSession
  - Records: createRecord, deleteRecord (race-safe via SELECT FOR UPDATE)
  - Feed: feed.like.create, feed.repost.create
  - Sync: getRepo, getBlocks, getLatestCommit, getRecord (with MST proof), listRepos
  - Identity: resolveHandle
  - MST: spec-conformant (at-mst crate, 27 tests)
  - Repo: signed commits, TID counter (monotonic, 4096 wrap safe)

- AppView (Rust + axum + sqlx)
  - Jetstream consumer (WebSocket, exponential backoff, 38k+ events indexed)
  - REST API: timeline/home (graph-aware), profile, search, post (with thread hydration)
  - Handle-sync worker (did:plc + did:web)
  - JSONB embed storage + thread columns (migration 0003)
  - Like/repost counter cache (migration 0004)

- Tauri 2 + Svelte 5 Desktop Client
  - System tray (Show/Compose/Quit menu)
  - OS notifications (tauri-plugin-notification)
  - Auto-update (tauri-plugin-updater, placeholder endpoint)
  - Window-state (tauri-plugin-window-state)
  - 160-char compose with live counter
  - Image/Link embed rendering
  - LocalStorage-persisted like state
  - Timeline with poll (prepend new posts)
  - Custom TitleBar (transparent, no decorations)
  - Orange/IBM Plex Mono maarcade design

Tests: 231 Rust + 9 vitest = 240 passed.
This commit is contained in:
tomdebone
2026-07-05 20:01:31 +02:00
commit c586fd39c9
134 changed files with 35279 additions and 0 deletions
+133
View File
@@ -0,0 +1,133 @@
use anyhow::Result;
use k256::ecdsa::{signature::Signer, Signature as K256Signature, SigningKey};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use crate::cid::cid_for_cbor;
#[allow(unused_imports)]
use crate::did_key::verifying_key_to_multibase;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum PlcOperation {
#[serde(rename = "plc_tombstone")]
Tombstone { prev: Option<String> },
#[serde(rename = "plc_operation")]
Op {
prev: Option<String>,
sigs: Vec<String>,
#[serde(flatten)]
op: PlcOpInner,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlcOpInner {
#[serde(rename = "type")]
pub op_type: String,
pub services: serde_json::Value,
pub identifier: String,
pub rotation_keys: Vec<String>,
pub verification_methods: serde_json::Value,
pub also_known_as: Vec<String>,
}
impl PlcOperation {
pub fn create(
handle: &str,
signing_key: &SigningKey,
rotation_key_pub_mb: &str,
pds_endpoint: &str,
) -> Result<Self> {
let inner = create_unsigned_op(handle, rotation_key_pub_mb, pds_endpoint);
let sig = sign_op(signing_key, &inner)?;
Ok(Self::Op {
prev: None,
sigs: vec![sig],
op: inner,
})
}
}
pub fn create_unsigned_op(handle: &str, rotation_key_pub_mb: &str, pds_endpoint: &str) -> PlcOpInner {
PlcOpInner {
op_type: "plc_operation".into(),
identifier: handle.to_string(),
rotation_keys: vec![rotation_key_pub_mb.to_string()],
verification_methods: json!({
"atproto": format!("did:key:{}", rotation_key_pub_mb),
}),
also_known_as: vec![format!("at://{}", handle)],
services: json!({
"atproto_pds": {
"type": "AtprotoPersonalDataServer",
"endpoint": pds_endpoint,
}
}),
}
}
pub fn sign_op(signing_key: &SigningKey, op: &PlcOpInner) -> Result<String> {
let canonical = json!({
"type": op.op_type,
"identifier": op.identifier,
"rotationKeys": op.rotation_keys,
"verificationMethods": op.verification_methods,
"alsoKnownAs": op.also_known_as,
"services": op.services,
});
let mut buf = Vec::new();
ciborium::into_writer(&canonical, &mut buf)?;
let sig: K256Signature = signing_key.sign(&buf);
Ok(hex::encode(sig.to_bytes()))
}
pub trait PlcOpSigner {
fn sign(&self, op: &PlcOpInner) -> Result<String>;
}
pub struct K256PlcOpSigner<'a>(pub &'a SigningKey);
impl<'a> PlcOpSigner for K256PlcOpSigner<'a> {
fn sign(&self, op: &PlcOpInner) -> Result<String> {
sign_op(self.0, op)
}
}
pub fn op_cid(op: &Value) -> Result<String> {
let mut buf = Vec::new();
ciborium::into_writer(op, &mut buf)?;
Ok(cid_for_cbor(&buf)?.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
use k256::SecretKey;
#[test]
fn create_op_signs_and_contains_handle() {
let sk = SecretKey::from_slice(&[3u8; 32]).unwrap();
let rot_mb = crate::did_key::pubkey_to_multibase(&sk.public_key()).unwrap();
let signing = SigningKey::from(sk);
let op = PlcOperation::create(
"alice.maarcadetweet.local",
&signing,
&rot_mb,
"https://pds.example",
)
.unwrap();
let serialized = serde_json::to_value(&op).unwrap();
let inner = serialized
.get("op")
.or_else(|| serialized.get("services").and_then(|_| Some(&serialized)))
.unwrap();
let identifier = inner
.get("identifier")
.unwrap()
.as_str()
.unwrap();
assert_eq!(identifier, "alice.maarcadetweet.local");
assert!(serialized.get("sigs").is_some());
}
}