diff --git a/crates/weaver-app/.env-dev b/crates/weaver-app/.env-dev new file mode 100644 index 0000000..5d82bc4 --- /dev/null +++ b/crates/weaver-app/.env-dev @@ -0,0 +1,10 @@ +WEAVER_APP_ENV="dev" +WEAVER_APP_HOST="http://localhost" +WEAVER_APP_DOMAIN="" +WEAVER_PORT=8080 +WEAVER_APP_SCOPES="atproto transition:generic" +WEAVER_CLIENT_NAME="Weaver" + +WEAVER_LOGO_URI="" +WEAVER_TOS_URI="" +WEAVER_PRIVACY_POLICY_URI="" diff --git a/crates/weaver-app/.env-pro b/crates/weaver-app/.env-pro deleted file mode 100644 index 76ce3db..0000000 --- a/crates/weaver-app/.env-pro +++ /dev/null @@ -1,10 +0,0 @@ -WEAVER_APP_ENV="prod" -WEAVER_APP_HOST="https://alpha.weaver.sh" -WEAVER_APP_DOMAIN="https://alpha.weaver.sh" -WEAVER_PORT=8080 -WEAVER_APP_SCOPES="atproto transition:generic" -WEAVER_CLIENT_NAME="Weaver" - -WEAVER_LOGO_URI="https://alpha.weaver.sh/favicon.ico" -WEAVER_TOS_URI="" -WEAVER_PRIVACY_POLICY_URI="" diff --git a/crates/weaver-app/assets/styling/editor.css b/crates/weaver-app/assets/styling/editor.css index 91a27b7..3388629 100644 --- a/crates/weaver-app/assets/styling/editor.css +++ b/crates/weaver-app/assets/styling/editor.css @@ -156,7 +156,6 @@ outline: none; min-height: 700px; line-height: var(--spacing-line-height); - font-size: 16px; background: var(--color-base); border: 1px solid var(--color-overlay); color: var(--color-text); @@ -1197,7 +1196,7 @@ li:has(.md-syntax-block:not(.hidden)) { .remote-cursor-caret { width: 2px; height: var(--cursor-height, 18px); - background: var(--cursor-color, #4ECDC4); + background: var(--cursor-color, #4ecdc4); border-radius: 1px; animation: cursor-blink 1.2s ease-in-out infinite; } @@ -1207,7 +1206,7 @@ li:has(.md-syntax-block:not(.hidden)) { top: -18px; left: 0; padding: 2px 6px; - background: var(--cursor-color, #4ECDC4); + background: var(--cursor-color, #4ecdc4); color: #fff; font-size: 10px; font-weight: 500; @@ -1219,6 +1218,11 @@ li:has(.md-syntax-block:not(.hidden)) { } @keyframes cursor-blink { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } } diff --git a/crates/weaver-app/assets/styling/entry-card.css b/crates/weaver-app/assets/styling/entry-card.css index 5c64884..f0a3669 100644 --- a/crates/weaver-app/assets/styling/entry-card.css +++ b/crates/weaver-app/assets/styling/entry-card.css @@ -97,7 +97,7 @@ .feed-entry-card .entry-card-byline { display: flex; - align-items: center; + align-items: last baseline; gap: 0.5rem; margin-bottom: 0.5rem; } diff --git a/crates/weaver-app/src/auth/mod.rs b/crates/weaver-app/src/auth/mod.rs index 3000678..a6baf99 100644 --- a/crates/weaver-app/src/auth/mod.rs +++ b/crates/weaver-app/src/auth/mod.rs @@ -41,20 +41,24 @@ pub async fn restore_session(_fetcher: Fetcher, _auth_state: Signal) #[cfg(target_arch = "wasm32")] pub async fn restore_session(fetcher: Fetcher, mut auth_state: Signal) -> RestoreResult { + use std::collections::BTreeMap; + use gloo_storage::{LocalStorage, Storage}; use jacquard::oauth::authstore::ClientAuthStore; + use jacquard::smol_str::SmolStr; use jacquard::types::string::Did; // Look for session keys in localStorage (format: oauth_session_{did}_{session_id}) - let Ok(keys) = LocalStorage::get_all::() else { - return RestoreResult::NoSession; - }; - let Some(keys) = keys.as_object() else { - return RestoreResult::NoSession; + let entries = match LocalStorage::get_all::>() { + Ok(e) => e, + Err(e) => { + tracing::warn!("restore_session: localStorage.get_all failed: {:?}", e); + return RestoreResult::NoSession; + } }; let mut found_session: Option<(String, String)> = None; - for key in keys.keys() { + for key in entries.keys() { if key.starts_with("oauth_session_") { let parts: Vec<&str> = key .strip_prefix("oauth_session_") @@ -71,7 +75,9 @@ pub async fn restore_session(fetcher: Fetcher, mut auth_state: Signal let Some((did_str, session_id)) = found_session else { return RestoreResult::NoSession; }; - let Ok(did) = Did::new_owned(did_str) else { + + let Ok(did) = Did::new_owned(did_str.clone()) else { + tracing::warn!("restore_session: invalid DID format: {}", did_str); return RestoreResult::NoSession; }; @@ -82,11 +88,10 @@ pub async fn restore_session(fetcher: Fetcher, mut auth_state: Signal .write() .set_authenticated(restored_did, session_id); fetcher.upgrade_to_authenticated(session).await; - tracing::debug!("session restored"); RestoreResult::Restored } Err(e) => { - tracing::warn!("Session restore failed, clearing dead session: {e}"); + tracing::warn!("restore_session: failed, clearing dead session: {e}"); let _ = AuthStore::new().delete_session(&did, &session_id).await; RestoreResult::SessionExpired } diff --git a/crates/weaver-app/src/env.rs b/crates/weaver-app/src/env.rs index b8c902b..f8a1ad7 100644 --- a/crates/weaver-app/src/env.rs +++ b/crates/weaver-app/src/env.rs @@ -1,11 +1,11 @@ // This file is automatically generated by build.rs #[allow(unused)] -pub const WEAVER_APP_ENV: &'static str = "dev"; +pub const WEAVER_APP_ENV: &'static str = "prod"; #[allow(unused)] -pub const WEAVER_APP_HOST: &'static str = "http://localhost"; +pub const WEAVER_APP_HOST: &'static str = "https://alpha.weaver.sh"; #[allow(unused)] -pub const WEAVER_APP_DOMAIN: &'static str = ""; +pub const WEAVER_APP_DOMAIN: &'static str = "https://alpha.weaver.sh"; #[allow(unused)] pub const WEAVER_PORT: &'static str = "8080"; #[allow(unused)] @@ -13,7 +13,7 @@ pub const WEAVER_APP_SCOPES: &'static str = "atproto transition:generic"; #[allow(unused)] pub const WEAVER_CLIENT_NAME: &'static str = "Weaver"; #[allow(unused)] -pub const WEAVER_LOGO_URI: &'static str = ""; +pub const WEAVER_LOGO_URI: &'static str = "https://alpha.weaver.sh/favicon.ico"; #[allow(unused)] pub const WEAVER_TOS_URI: &'static str = ""; #[allow(unused)] diff --git a/crates/weaver-renderer/src/css.rs b/crates/weaver-renderer/src/css.rs index 2eb5ac4..0dbb09d 100644 --- a/crates/weaver-renderer/src/css.rs +++ b/crates/weaver-renderer/src/css.rs @@ -354,10 +354,10 @@ img {{ }} .embed-avatar {{ - width: 42px; - height: 42px; - max-width: 42px; - max-height: 42px; + width: 36px; + height: 36px; + max-width: 36px; + max-height: 36px; aspect-ratio: 1; margin: 0; object-fit: cover; @@ -366,7 +366,7 @@ img {{ .embed-author-info {{ display: flex; flex-direction: column; - gap: 0.1rem; + gap: 0; min-width: 0; }} @@ -382,6 +382,7 @@ img {{ text-overflow: ellipsis; white-space: nowrap; text-decoration: none; + line-height: 1.2; }} a.embed-author-name:hover {{ @@ -389,13 +390,14 @@ a.embed-author-name:hover {{ }} .embed-author-handle {{ - font-size: 0.9em; + font-size: 0.85em; font-family: var(--font-mono); color: var(--color-subtle); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + line-height: 1.2; }} .embed-author-handle:hover {{ diff --git a/flake.nix b/flake.nix index b949129..0bcf74d 100644 --- a/flake.nix +++ b/flake.nix @@ -252,6 +252,9 @@ LD_LIBRARY_PATH = "$LD_LIBRARY_PATH:$NIX_LD_LIBRARY_PATH"; + # musl linker for static builds + #CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER = "${pkgs.pkgsMusl.stdenv.cc}/bin/cc"; + # Additional dev-shell environment variables can be set directly # MY_CUSTOM_DEVELOPMENT_VAR = "something else"; @@ -269,6 +272,7 @@ wasm-pack twiggy binaryen.out + patchelf llvmPackages_18.clang-unwrapped llvmPackages_18.llvm llvmPackages_18.libclang diff --git a/lexicons/actor/getActorEntries.json b/lexicons/actor/getActorEntries.json new file mode 100644 index 0000000..d7c8ba2 --- /dev/null +++ b/lexicons/actor/getActorEntries.json @@ -0,0 +1,34 @@ +{ + "lexicon": 1, + "id": "sh.weaver.actor.getActorEntries", + "defs": { + "main": { + "type": "query", + "description": "Get all entries for an actor, optionally including collaborations.", + "parameters": { + "type": "params", + "required": ["actor"], + "properties": { + "actor": { "type": "string", "format": "at-identifier" }, + "includeCollaborations": { "type": "boolean", "default": false }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["entries"], + "properties": { + "entries": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#entryView" } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/actor/getActorNotebooks.json b/lexicons/actor/getActorNotebooks.json new file mode 100644 index 0000000..6d1ba6c --- /dev/null +++ b/lexicons/actor/getActorNotebooks.json @@ -0,0 +1,34 @@ +{ + "lexicon": 1, + "id": "sh.weaver.actor.getActorNotebooks", + "defs": { + "main": { + "type": "query", + "description": "Get all notebooks for an actor, optionally including collaborations.", + "parameters": { + "type": "params", + "required": ["actor"], + "properties": { + "actor": { "type": "string", "format": "at-identifier" }, + "includeCollaborations": { "type": "boolean", "default": false }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["notebooks"], + "properties": { + "notebooks": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#notebookView" } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/actor/getActorProfile.json b/lexicons/actor/getActorProfile.json new file mode 100644 index 0000000..ee1719f --- /dev/null +++ b/lexicons/actor/getActorProfile.json @@ -0,0 +1,34 @@ +{ + "lexicon": 1, + "id": "sh.weaver.actor.getActorProfile", + "defs": { + "main": { + "type": "query", + "description": "Get a Weaver profile with notebook/entry statistics.", + "parameters": { + "type": "params", + "required": ["actor"], + "properties": { + "actor": { "type": "string", "format": "at-identifier" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["profile", "notebookCount", "entryCount"], + "properties": { + "profile": { "type": "ref", "ref": "sh.weaver.actor.defs#profileView" }, + "notebookCount": { "type": "integer" }, + "entryCount": { "type": "integer" }, + "collaborationCount": { "type": "integer" }, + "followerCount": { "type": "integer" }, + "followingCount": { "type": "integer" }, + "viewerFollowing": { "type": "string", "format": "at-uri" }, + "viewerFollowedBy": { "type": "string", "format": "at-uri" } + } + } + } + } + } +} diff --git a/lexicons/actor/getSuggestedAuthors.json b/lexicons/actor/getSuggestedAuthors.json new file mode 100644 index 0000000..f697088 --- /dev/null +++ b/lexicons/actor/getSuggestedAuthors.json @@ -0,0 +1,51 @@ +{ + "lexicon": 1, + "id": "sh.weaver.actor.getSuggestedAuthors", + "defs": { + "main": { + "type": "query", + "description": "Get author recommendations for the authenticated user.", + "parameters": { + "type": "params", + "properties": { + "limit": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["authors"], + "properties": { + "authors": { + "type": "array", + "items": { + "type": "object", + "required": ["author", "reason"], + "properties": { + "author": { "type": "ref", "ref": "sh.weaver.actor.defs#profileView" }, + "reason": { + "type": "string", + "knownValues": [ + "similar-to-followed", + "writes-similar-content", + "popular-in-tags-you-read", + "followed-by-people-you-follow" + ] + }, + "mutualFollows": { + "type": "array", + "maxLength": 3, + "items": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "description": "People you follow who also follow this author." + }, + "notebookCount": { "type": "integer" } + } + } + } + } + } + } + } + } +} diff --git a/lexicons/actor/searchActors.json b/lexicons/actor/searchActors.json new file mode 100644 index 0000000..0a37991 --- /dev/null +++ b/lexicons/actor/searchActors.json @@ -0,0 +1,38 @@ +{ + "lexicon": 1, + "id": "sh.weaver.actor.searchActors", + "defs": { + "main": { + "type": "query", + "description": "Find actors matching search criteria.", + "parameters": { + "type": "params", + "required": ["q"], + "properties": { + "q": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Search query - matches handle, display name, description." + }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["actors"], + "properties": { + "actors": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.actor.defs#profileView" } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/actor/searchActorsTypeahead.json b/lexicons/actor/searchActorsTypeahead.json new file mode 100644 index 0000000..246bf16 --- /dev/null +++ b/lexicons/actor/searchActorsTypeahead.json @@ -0,0 +1,36 @@ +{ + "lexicon": 1, + "id": "sh.weaver.actor.searchActorsTypeahead", + "defs": { + "main": { + "type": "query", + "description": "Fast typeahead search for actors (handle/display name prefix matching).", + "parameters": { + "type": "params", + "required": ["q"], + "properties": { + "q": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Search query prefix." + }, + "limit": { "type": "integer", "minimum": 1, "maximum": 25, "default": 10 } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["actors"], + "properties": { + "actors": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" } + } + } + } + } + } + } +} diff --git a/lexicons/collab/defs.json b/lexicons/collab/defs.json index 271c88f..bcb9466 100644 --- a/lexicons/collab/defs.json +++ b/lexicons/collab/defs.json @@ -13,6 +13,155 @@ "chapter": { "type": "token", "description": "Collaboration scoped to a chapter." + }, + "inviteView": { + "type": "object", + "description": "Hydrated view of a collaboration invite with status.", + "required": ["uri", "cid", "inviter", "invitee", "resource", "createdAt", "status"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "inviter": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "invitee": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "resource": { "type": "ref", "ref": "com.atproto.repo.strongRef" }, + "resourceTitle": { "type": "string" }, + "scope": { + "type": "string", + "knownValues": ["notebook", "entry", "chapter"] + }, + "message": { "type": "string" }, + "createdAt": { "type": "string", "format": "datetime" }, + "expiresAt": { "type": "string", "format": "datetime" }, + "status": { + "type": "string", + "knownValues": ["pending", "accepted", "expired", "revoked"] + }, + "acceptUri": { "type": "string", "format": "at-uri" }, + "acceptedAt": { "type": "string", "format": "datetime" } + } + }, + "sessionView": { + "type": "object", + "description": "Active real-time collaboration session.", + "required": ["uri", "user", "resource", "nodeId", "createdAt"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "user": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "resource": { "type": "ref", "ref": "com.atproto.repo.strongRef" }, + "nodeId": { "type": "string" }, + "relayUrl": { "type": "string", "format": "uri" }, + "createdAt": { "type": "string", "format": "datetime" }, + "expiresAt": { "type": "string", "format": "datetime" } + } + }, + "collaborationStateView": { + "type": "object", + "description": "Full state of a collaboration relationship including version reconciliation. Tracks both current and former collaborators.", + "required": ["resource", "status", "participants"], + "properties": { + "resource": { "type": "ref", "ref": "com.atproto.repo.strongRef" }, + "canonicalUri": { + "type": "string", + "format": "at-uri", + "description": "The 'canonical' version URI (usually owner's)" + }, + "status": { + "type": "string", + "knownValues": ["active", "broken", "diverged", "reconciled"], + "description": "active=normal, broken=all invites revoked/expired, diverged=versions differ, reconciled=was diverged but resolved" + }, + "participants": { + "type": "array", + "items": { "type": "ref", "ref": "#participantStateView" }, + "description": "Current active + invited participants" + }, + "formerParticipants": { + "type": "array", + "items": { "type": "ref", "ref": "#formerCollaboratorView" }, + "description": "People who used to collaborate but relationship ended" + }, + "publishedVersions": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#publishedVersionView" } + }, + "hasDivergence": { "type": "boolean" }, + "hasFormerCollaborators": { "type": "boolean" }, + "hasOrphanedVersions": { + "type": "boolean", + "description": "Published versions from former collaborators still exist" + }, + "lastSyncedAt": { "type": "string", "format": "datetime" }, + "createdAt": { "type": "string", "format": "datetime" }, + "firstCollaboratorAddedAt": { "type": "string", "format": "datetime" } + } + }, + "participantStateView": { + "type": "object", + "description": "Individual participant's state in a collaboration. Distinguishes 'was collaborator' vs 'never was'.", + "required": ["user", "role", "status"], + "properties": { + "user": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "role": { + "type": "string", + "knownValues": ["owner", "collaborator", "former_collaborator"] + }, + "status": { + "type": "string", + "knownValues": ["active", "invited", "left", "removed", "expired"], + "description": "active=can edit, invited=pending, left=voluntarily departed, removed=invite revoked, expired=invite timed out" + }, + "wasCollaborator": { + "type": "boolean", + "description": "True if they ever had active collaboration status" + }, + "inviteUri": { "type": "string", "format": "at-uri" }, + "acceptUri": { + "type": "string", + "format": "at-uri", + "description": "If they accepted (even if later broken)" + }, + "publishedVersion": { + "type": "ref", + "ref": "com.atproto.repo.strongRef", + "description": "Their published copy if any" + }, + "firstEditAt": { + "type": "string", + "format": "datetime", + "description": "When they first contributed" + }, + "lastEditAt": { "type": "string", "format": "datetime" }, + "relationshipEndedAt": { + "type": "string", + "format": "datetime", + "description": "When left/removed/expired" + }, + "endReason": { + "type": "string", + "knownValues": ["voluntary_leave", "invite_revoked", "invite_expired", "owner_deleted_resource"], + "description": "Why the relationship ended, if applicable" + } + } + }, + "formerCollaboratorView": { + "type": "object", + "description": "Lightweight view for 'this person used to collaborate but doesn't anymore'.", + "required": ["user", "wasActiveFrom", "wasActiveUntil", "endReason"], + "properties": { + "user": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "wasActiveFrom": { "type": "string", "format": "datetime" }, + "wasActiveUntil": { "type": "string", "format": "datetime" }, + "endReason": { + "type": "string", + "knownValues": ["voluntary_leave", "invite_revoked", "invite_expired", "owner_deleted_resource"] + }, + "contributionCount": { + "type": "integer", + "description": "Number of diffs they created while active" + }, + "hasPublishedVersion": { "type": "boolean" }, + "publishedVersionUri": { "type": "string", "format": "at-uri" } + } } } } diff --git a/lexicons/collab/getCollaborationState.json b/lexicons/collab/getCollaborationState.json new file mode 100644 index 0000000..9c0b62a --- /dev/null +++ b/lexicons/collab/getCollaborationState.json @@ -0,0 +1,24 @@ +{ + "lexicon": 1, + "id": "sh.weaver.collab.getCollaborationState", + "defs": { + "main": { + "type": "query", + "description": "Get full collaboration state for a resource, including all participants and published versions.", + "parameters": { + "type": "params", + "required": ["resource"], + "properties": { + "resource": { "type": "string", "format": "at-uri" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "ref", + "ref": "sh.weaver.collab.defs#collaborationStateView" + } + } + } + } +} diff --git a/lexicons/collab/getInvites.json b/lexicons/collab/getInvites.json new file mode 100644 index 0000000..e837b73 --- /dev/null +++ b/lexicons/collab/getInvites.json @@ -0,0 +1,43 @@ +{ + "lexicon": 1, + "id": "sh.weaver.collab.getInvites", + "defs": { + "main": { + "type": "query", + "description": "Get collaboration invites for an actor (sent, received, or both).", + "parameters": { + "type": "params", + "required": ["actor"], + "properties": { + "actor": { "type": "string", "format": "at-identifier" }, + "direction": { + "type": "string", + "default": "all", + "knownValues": ["sent", "received", "all"] + }, + "status": { + "type": "string", + "default": "all", + "knownValues": ["pending", "accepted", "expired", "all"] + }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["invites"], + "properties": { + "invites": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.collab.defs#inviteView" } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/collab/getResourceParticipants.json b/lexicons/collab/getResourceParticipants.json new file mode 100644 index 0000000..471a196 --- /dev/null +++ b/lexicons/collab/getResourceParticipants.json @@ -0,0 +1,32 @@ +{ + "lexicon": 1, + "id": "sh.weaver.collab.getResourceParticipants", + "defs": { + "main": { + "type": "query", + "description": "Get all participants (owner + collaborators) for a resource.", + "parameters": { + "type": "params", + "required": ["resource"], + "properties": { + "resource": { "type": "string", "format": "at-uri" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["owner", "participants"], + "properties": { + "owner": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "participants": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" } + }, + "viewerCanEdit": { "type": "boolean" } + } + } + } + } + } +} diff --git a/lexicons/collab/getResourceSessions.json b/lexicons/collab/getResourceSessions.json new file mode 100644 index 0000000..d139754 --- /dev/null +++ b/lexicons/collab/getResourceSessions.json @@ -0,0 +1,30 @@ +{ + "lexicon": 1, + "id": "sh.weaver.collab.getResourceSessions", + "defs": { + "main": { + "type": "query", + "description": "Get active real-time collaboration sessions for a resource.", + "parameters": { + "type": "params", + "required": ["resource"], + "properties": { + "resource": { "type": "string", "format": "at-uri" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["sessions"], + "properties": { + "sessions": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.collab.defs#sessionView" } + } + } + } + } + } + } +} diff --git a/lexicons/edit/defs.json b/lexicons/edit/defs.json index 73e80af..ee621cf 100644 --- a/lexicons/edit/defs.json +++ b/lexicons/edit/defs.json @@ -42,6 +42,65 @@ "maxLength": 200 } } + }, + "editHistoryEntry": { + "type": "object", + "description": "Summary of an edit (root or diff) for history queries.", + "required": ["uri", "cid", "author", "createdAt", "type"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "author": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "type": { + "type": "string", + "knownValues": ["root", "diff"] + }, + "rootRef": { "type": "ref", "ref": "com.atproto.repo.strongRef" }, + "prevRef": { "type": "ref", "ref": "com.atproto.repo.strongRef" }, + "snapshotCid": { "type": "string", "format": "cid" }, + "hasInlineDiff": { "type": "boolean" }, + "createdAt": { "type": "string", "format": "datetime" } + } + }, + "editBranchView": { + "type": "object", + "description": "A branch/fork in edit history (for when collaborators diverge).", + "required": ["head", "author", "length", "lastUpdated"], + "properties": { + "head": { "type": "ref", "ref": "com.atproto.repo.strongRef" }, + "root": { "type": "ref", "ref": "com.atproto.repo.strongRef" }, + "author": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "length": { + "type": "integer", + "description": "Number of diffs in this branch" + }, + "lastUpdated": { "type": "string", "format": "datetime" }, + "divergesFrom": { + "type": "ref", + "ref": "com.atproto.repo.strongRef", + "description": "Common ancestor if this is a fork" + }, + "isMerged": { "type": "boolean" } + } + }, + "editTreeView": { + "type": "object", + "description": "Full tree structure showing all branches for a resource.", + "required": ["resource", "branches"], + "properties": { + "resource": { "type": "ref", "ref": "com.atproto.repo.strongRef" }, + "mainBranch": { "type": "ref", "ref": "#editBranchView" }, + "branches": { + "type": "array", + "items": { "type": "ref", "ref": "#editBranchView" } + }, + "hasConflicts": { "type": "boolean" }, + "conflictPoints": { + "type": "array", + "items": { "type": "ref", "ref": "com.atproto.repo.strongRef" }, + "description": "Diffs where branches diverge" + } + } } } } diff --git a/lexicons/edit/getBranch.json b/lexicons/edit/getBranch.json new file mode 100644 index 0000000..ea19e34 --- /dev/null +++ b/lexicons/edit/getBranch.json @@ -0,0 +1,42 @@ +{ + "lexicon": 1, + "id": "sh.weaver.edit.getBranch", + "defs": { + "main": { + "type": "query", + "description": "Get edit history for a specific branch, identified by its head.", + "parameters": { + "type": "params", + "required": ["head"], + "properties": { + "head": { + "type": "string", + "format": "at-uri", + "description": "AT-URI of the branch head (latest diff)" + }, + "afterRkey": { + "type": "string", + "description": "Only diffs after this rkey" + }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["branch", "diffs"], + "properties": { + "branch": { "type": "ref", "ref": "sh.weaver.edit.defs#editBranchView" }, + "diffs": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.edit.defs#editHistoryEntry" } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/edit/getEditHistory.json b/lexicons/edit/getEditHistory.json new file mode 100644 index 0000000..4e0c671 --- /dev/null +++ b/lexicons/edit/getEditHistory.json @@ -0,0 +1,41 @@ +{ + "lexicon": 1, + "id": "sh.weaver.edit.getEditHistory", + "defs": { + "main": { + "type": "query", + "description": "Get edit history (roots and diffs) for a resource.", + "parameters": { + "type": "params", + "required": ["resource"], + "properties": { + "resource": { "type": "string", "format": "at-uri" }, + "afterRkey": { + "type": "string", + "description": "Only diffs after this rkey (incremental sync)" + }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["roots", "diffs"], + "properties": { + "roots": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.edit.defs#editHistoryEntry" } + }, + "diffs": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.edit.defs#editHistoryEntry" } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/edit/getEditTree.json b/lexicons/edit/getEditTree.json new file mode 100644 index 0000000..9fbdb79 --- /dev/null +++ b/lexicons/edit/getEditTree.json @@ -0,0 +1,24 @@ +{ + "lexicon": 1, + "id": "sh.weaver.edit.getEditTree", + "defs": { + "main": { + "type": "query", + "description": "Get the edit tree structure showing all branches and forks.", + "parameters": { + "type": "params", + "required": ["resource"], + "properties": { + "resource": { "type": "string", "format": "at-uri" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "ref", + "ref": "sh.weaver.edit.defs#editTreeView" + } + } + } + } +} diff --git a/lexicons/graph/defs.json b/lexicons/graph/defs.json index 0767503..39690ff 100644 --- a/lexicons/graph/defs.json +++ b/lexicons/graph/defs.json @@ -9,6 +9,116 @@ "readinglist": { "type": "token", "description": "A personal reading list." + }, + "serieslist": { + "type": "token", + "description": "An ordered series of related works (sequels, spin-offs)." + }, + "tagView": { + "type": "object", + "description": "Aggregated view of a tag with usage statistics.", + "required": ["tag", "useCount"], + "properties": { + "tag": { "type": "string" }, + "useCount": { + "type": "integer", + "description": "Total number of resources tagged with this tag." + }, + "notebookCount": { "type": "integer" }, + "entryCount": { "type": "integer" }, + "recentUseCount": { + "type": "integer", + "description": "Uses in the last 30 days." + }, + "trendingScore": { + "type": "number", + "description": "Appview-computed trending score." + } + } + }, + "tagApplicationView": { + "type": "object", + "description": "A single tag application with who applied it.", + "required": ["uri", "tag", "appliedBy", "createdAt"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "tag": { "type": "string" }, + "appliedBy": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "createdAt": { "type": "string", "format": "datetime" } + } + }, + "resourceTagsView": { + "type": "object", + "description": "All tags for a resource, grouped by source.", + "required": ["resource", "authorTags", "communityTags"], + "properties": { + "resource": { "type": "ref", "ref": "com.atproto.repo.strongRef" }, + "authorTags": { + "type": "array", + "description": "Tags from the record itself (author-applied).", + "items": { "type": "string" } + }, + "communityTags": { + "type": "array", + "description": "Aggregated community-applied tags with counts.", + "items": { "type": "ref", "ref": "#communityTagCount" } + }, + "viewerAppliedTags": { + "type": "array", + "description": "Tags the current viewer has applied.", + "items": { "type": "string" } + } + } + }, + "communityTagCount": { + "type": "object", + "description": "A community tag with how many people applied it.", + "required": ["tag", "count"], + "properties": { + "tag": { "type": "string" }, + "count": { "type": "integer" } + } + }, + "listView": { + "type": "object", + "description": "Hydrated view of a list.", + "required": ["uri", "cid", "creator", "name", "purpose", "itemCount", "indexedAt"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "creator": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "name": { "type": "string" }, + "purpose": { "type": "ref", "ref": "#listPurpose" }, + "description": { "type": "string" }, + "avatar": { "type": "string", "format": "uri" }, + "itemCount": { "type": "integer" }, + "indexedAt": { "type": "string", "format": "datetime" }, + "viewerSubscribed": { "type": "string", "format": "at-uri" } + } + }, + "listPurpose": { + "type": "string", + "knownValues": [ + "sh.weaver.graph.defs#curatelist", + "sh.weaver.graph.defs#readinglist", + "sh.weaver.graph.defs#serieslist" + ] + }, + "listItemView": { + "type": "object", + "description": "An item in a list with hydrated subject.", + "required": ["uri", "subject"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "subject": { + "type": "union", + "refs": [ + "sh.weaver.notebook.defs#notebookView", + "sh.weaver.notebook.defs#entryView" + ] + }, + "addedAt": { "type": "string", "format": "datetime" } + } } } } diff --git a/lexicons/graph/getActorBookmarks.json b/lexicons/graph/getActorBookmarks.json new file mode 100644 index 0000000..f9e8cb7 --- /dev/null +++ b/lexicons/graph/getActorBookmarks.json @@ -0,0 +1,47 @@ +{ + "lexicon": 1, + "id": "sh.weaver.graph.getActorBookmarks", + "defs": { + "main": { + "type": "query", + "description": "Get resources an actor has bookmarked.", + "parameters": { + "type": "params", + "required": ["actor"], + "properties": { + "actor": { "type": "string", "format": "at-identifier" }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["bookmarks"], + "properties": { + "bookmarks": { + "type": "array", + "items": { + "type": "object", + "required": ["subject", "createdAt"], + "properties": { + "subject": { + "type": "union", + "refs": [ + "sh.weaver.notebook.defs#notebookView", + "sh.weaver.notebook.defs#entryView" + ] + }, + "note": { "type": "string" }, + "createdAt": { "type": "string", "format": "datetime" } + } + } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/graph/getActorLikes.json b/lexicons/graph/getActorLikes.json new file mode 100644 index 0000000..d8d30fa --- /dev/null +++ b/lexicons/graph/getActorLikes.json @@ -0,0 +1,46 @@ +{ + "lexicon": 1, + "id": "sh.weaver.graph.getActorLikes", + "defs": { + "main": { + "type": "query", + "description": "Get resources an actor has liked.", + "parameters": { + "type": "params", + "required": ["actor"], + "properties": { + "actor": { "type": "string", "format": "at-identifier" }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["likes"], + "properties": { + "likes": { + "type": "array", + "items": { + "type": "object", + "required": ["subject", "createdAt"], + "properties": { + "subject": { + "type": "union", + "refs": [ + "sh.weaver.notebook.defs#notebookView", + "sh.weaver.notebook.defs#entryView" + ] + }, + "createdAt": { "type": "string", "format": "datetime" } + } + } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/graph/getActorLists.json b/lexicons/graph/getActorLists.json new file mode 100644 index 0000000..b769265 --- /dev/null +++ b/lexicons/graph/getActorLists.json @@ -0,0 +1,37 @@ +{ + "lexicon": 1, + "id": "sh.weaver.graph.getActorLists", + "defs": { + "main": { + "type": "query", + "description": "Get lists created by an actor.", + "parameters": { + "type": "params", + "required": ["actor"], + "properties": { + "actor": { "type": "string", "format": "at-identifier" }, + "purpose": { + "type": "string", + "description": "Filter by list purpose." + }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["lists"], + "properties": { + "lists": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.graph.defs#listView" } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/graph/getActorSubscriptions.json b/lexicons/graph/getActorSubscriptions.json new file mode 100644 index 0000000..d8de72b --- /dev/null +++ b/lexicons/graph/getActorSubscriptions.json @@ -0,0 +1,48 @@ +{ + "lexicon": 1, + "id": "sh.weaver.graph.getActorSubscriptions", + "defs": { + "main": { + "type": "query", + "description": "Get notebooks an actor is subscribed to (accepted subscriptions only).", + "parameters": { + "type": "params", + "required": ["actor"], + "properties": { + "actor": { "type": "string", "format": "at-identifier" }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["subscriptions"], + "properties": { + "subscriptions": { + "type": "array", + "items": { + "type": "object", + "required": ["notebook", "subscribedAt"], + "properties": { + "notebook": { "type": "ref", "ref": "sh.weaver.notebook.defs#notebookView" }, + "subscribedAt": { "type": "string", "format": "datetime" }, + "hasUpdates": { + "type": "boolean", + "description": "True if notebook has new content since last visit." + }, + "newEntryCount": { + "type": "integer", + "description": "Number of new entries since last visit." + } + } + } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/graph/getBookmarkedBy.json b/lexicons/graph/getBookmarkedBy.json new file mode 100644 index 0000000..7a01b2e --- /dev/null +++ b/lexicons/graph/getBookmarkedBy.json @@ -0,0 +1,42 @@ +{ + "lexicon": 1, + "id": "sh.weaver.graph.getBookmarkedBy", + "defs": { + "main": { + "type": "query", + "description": "Get users who bookmarked a resource.", + "parameters": { + "type": "params", + "required": ["subject"], + "properties": { + "subject": { "type": "string", "format": "at-uri" }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["uri", "bookmarks"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "bookmarks": { + "type": "array", + "items": { + "type": "object", + "required": ["actor", "createdAt"], + "properties": { + "actor": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "createdAt": { "type": "string", "format": "datetime" } + } + } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/graph/getFollowers.json b/lexicons/graph/getFollowers.json new file mode 100644 index 0000000..0909471 --- /dev/null +++ b/lexicons/graph/getFollowers.json @@ -0,0 +1,34 @@ +{ + "lexicon": 1, + "id": "sh.weaver.graph.getFollowers", + "defs": { + "main": { + "type": "query", + "description": "Get followers of an actor (accepted follows only).", + "parameters": { + "type": "params", + "required": ["actor"], + "properties": { + "actor": { "type": "string", "format": "at-identifier" }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["subject", "followers"], + "properties": { + "subject": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "followers": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/graph/getFollowing.json b/lexicons/graph/getFollowing.json new file mode 100644 index 0000000..81b5324 --- /dev/null +++ b/lexicons/graph/getFollowing.json @@ -0,0 +1,34 @@ +{ + "lexicon": 1, + "id": "sh.weaver.graph.getFollowing", + "defs": { + "main": { + "type": "query", + "description": "Get actors that an actor follows (accepted follows only).", + "parameters": { + "type": "params", + "required": ["actor"], + "properties": { + "actor": { "type": "string", "format": "at-identifier" }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["subject", "following"], + "properties": { + "subject": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "following": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/graph/getLikedBy.json b/lexicons/graph/getLikedBy.json new file mode 100644 index 0000000..b8d8341 --- /dev/null +++ b/lexicons/graph/getLikedBy.json @@ -0,0 +1,42 @@ +{ + "lexicon": 1, + "id": "sh.weaver.graph.getLikedBy", + "defs": { + "main": { + "type": "query", + "description": "Get users who liked a resource.", + "parameters": { + "type": "params", + "required": ["subject"], + "properties": { + "subject": { "type": "string", "format": "at-uri" }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["uri", "likes"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "likes": { + "type": "array", + "items": { + "type": "object", + "required": ["actor", "createdAt"], + "properties": { + "actor": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "createdAt": { "type": "string", "format": "datetime" } + } + } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/graph/getList.json b/lexicons/graph/getList.json new file mode 100644 index 0000000..75839cc --- /dev/null +++ b/lexicons/graph/getList.json @@ -0,0 +1,37 @@ +{ + "lexicon": 1, + "id": "sh.weaver.graph.getList", + "defs": { + "main": { + "type": "query", + "description": "Get a list with its items.", + "parameters": { + "type": "params", + "required": ["list"], + "properties": { + "list": { "type": "string", "format": "at-uri" }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["list", "items"], + "properties": { + "list": { "type": "ref", "ref": "sh.weaver.graph.defs#listView" }, + "items": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.graph.defs#listItemView" } + }, + "cursor": { "type": "string" } + } + } + }, + "errors": [ + { "name": "ListNotFound" } + ] + } + } +} diff --git a/lexicons/graph/getPopularTags.json b/lexicons/graph/getPopularTags.json new file mode 100644 index 0000000..7ab134f --- /dev/null +++ b/lexicons/graph/getPopularTags.json @@ -0,0 +1,31 @@ +{ + "lexicon": 1, + "id": "sh.weaver.graph.getPopularTags", + "defs": { + "main": { + "type": "query", + "description": "Get most-used tags overall.", + "parameters": { + "type": "params", + "properties": { + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["tags"], + "properties": { + "tags": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.graph.defs#tagView" } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/graph/getResourceTags.json b/lexicons/graph/getResourceTags.json new file mode 100644 index 0000000..ef68850 --- /dev/null +++ b/lexicons/graph/getResourceTags.json @@ -0,0 +1,24 @@ +{ + "lexicon": 1, + "id": "sh.weaver.graph.getResourceTags", + "defs": { + "main": { + "type": "query", + "description": "Get all tags for a resource (author + community), grouped by source.", + "parameters": { + "type": "params", + "required": ["resource"], + "properties": { + "resource": { "type": "string", "format": "at-uri" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "ref", + "ref": "sh.weaver.graph.defs#resourceTagsView" + } + } + } + } +} diff --git a/lexicons/graph/getSubscribers.json b/lexicons/graph/getSubscribers.json new file mode 100644 index 0000000..6666a0a --- /dev/null +++ b/lexicons/graph/getSubscribers.json @@ -0,0 +1,34 @@ +{ + "lexicon": 1, + "id": "sh.weaver.graph.getSubscribers", + "defs": { + "main": { + "type": "query", + "description": "Get subscribers to a notebook (accepted subscriptions only).", + "parameters": { + "type": "params", + "required": ["notebook"], + "properties": { + "notebook": { "type": "string", "format": "at-uri" }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["notebook", "subscribers"], + "properties": { + "notebook": { "type": "ref", "ref": "sh.weaver.notebook.defs#notebookView" }, + "subscribers": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/graph/getTagSuggestions.json b/lexicons/graph/getTagSuggestions.json new file mode 100644 index 0000000..cf825ee --- /dev/null +++ b/lexicons/graph/getTagSuggestions.json @@ -0,0 +1,40 @@ +{ + "lexicon": 1, + "id": "sh.weaver.graph.getTagSuggestions", + "defs": { + "main": { + "type": "query", + "description": "Get tag suggestions for autocomplete or based on existing tags.", + "parameters": { + "type": "params", + "properties": { + "query": { + "type": "string", + "maxLength": 128, + "description": "Partial tag for autocomplete." + }, + "existingTags": { + "type": "array", + "maxLength": 20, + "items": { "type": "string" }, + "description": "Tags already on the resource, to suggest related tags." + }, + "limit": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["suggestions"], + "properties": { + "suggestions": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.graph.defs#tagView" } + } + } + } + } + } + } +} diff --git a/lexicons/graph/getTaggedResources.json b/lexicons/graph/getTaggedResources.json new file mode 100644 index 0000000..b90e147 --- /dev/null +++ b/lexicons/graph/getTaggedResources.json @@ -0,0 +1,60 @@ +{ + "lexicon": 1, + "id": "sh.weaver.graph.getTaggedResources", + "defs": { + "main": { + "type": "query", + "description": "Get notebooks and entries tagged with a specific tag.", + "parameters": { + "type": "params", + "required": ["tag"], + "properties": { + "tag": { "type": "string", "minLength": 1, "maxLength": 128 }, + "resourceType": { + "type": "string", + "knownValues": ["notebook", "entry", "all"], + "default": "all" + }, + "includeAuthorTags": { + "type": "boolean", + "default": true, + "description": "Include resources where author applied this tag." + }, + "includeCommunityTags": { + "type": "boolean", + "default": true, + "description": "Include resources where community applied this tag." + }, + "sort": { + "type": "string", + "knownValues": ["recent", "popular"], + "default": "recent" + }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["resources"], + "properties": { + "tag": { "type": "ref", "ref": "sh.weaver.graph.defs#tagView" }, + "resources": { + "type": "array", + "items": { + "type": "union", + "refs": [ + "sh.weaver.notebook.defs#notebookView", + "sh.weaver.notebook.defs#entryView" + ] + } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/graph/getTrendingTags.json b/lexicons/graph/getTrendingTags.json new file mode 100644 index 0000000..a35e372 --- /dev/null +++ b/lexicons/graph/getTrendingTags.json @@ -0,0 +1,29 @@ +{ + "lexicon": 1, + "id": "sh.weaver.graph.getTrendingTags", + "defs": { + "main": { + "type": "query", + "description": "Get trending tags based on recent usage.", + "parameters": { + "type": "params", + "properties": { + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["tags"], + "properties": { + "tags": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.graph.defs#tagView" } + } + } + } + } + } + } +} diff --git a/lexicons/graph/tag.json b/lexicons/graph/tag.json new file mode 100644 index 0000000..a19bcef --- /dev/null +++ b/lexicons/graph/tag.json @@ -0,0 +1,32 @@ +{ + "lexicon": 1, + "id": "sh.weaver.graph.tag", + "defs": { + "main": { + "type": "record", + "description": "Apply a community tag to a notebook or entry. Can be created by readers, authors, or bots for categorization and discovery.", + "key": "tid", + "record": { + "type": "object", + "required": ["subject", "tag", "createdAt"], + "properties": { + "subject": { + "type": "ref", + "ref": "com.atproto.repo.strongRef", + "description": "The notebook or entry being tagged." + }, + "tag": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "The tag value (e.g., 'enemies-to-lovers', 'slow-burn', 'found-family')." + }, + "createdAt": { + "type": "string", + "format": "datetime" + } + } + } + } + } +} diff --git a/lexicons/notebook/book.json b/lexicons/notebook/book.json index 2ba1de8..9a442be 100644 --- a/lexicons/notebook/book.json +++ b/lexicons/notebook/book.json @@ -40,7 +40,9 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp of last modification. Used for canonicality tiebreaking in multi-author scenarios." - } + }, + "contentWarnings": { "type": "ref", "ref": "sh.weaver.notebook.defs#contentWarnings" }, + "rating": { "type": "ref", "ref": "sh.weaver.notebook.defs#contentRating" } } } } diff --git a/lexicons/notebook/chapter.json b/lexicons/notebook/chapter.json index b96eefd..e77bd70 100644 --- a/lexicons/notebook/chapter.json +++ b/lexicons/notebook/chapter.json @@ -35,7 +35,9 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this was originally created." - } + }, + "contentWarnings": { "type": "ref", "ref": "sh.weaver.notebook.defs#contentWarnings" }, + "rating": { "type": "ref", "ref": "sh.weaver.notebook.defs#contentRating" } } } } diff --git a/lexicons/notebook/defs.json b/lexicons/notebook/defs.json index 9f521a1..9e0c0ba 100644 --- a/lexicons/notebook/defs.json +++ b/lexicons/notebook/defs.json @@ -21,7 +21,15 @@ "ref": "#permissionsState" }, "record": { "type": "unknown" }, - "indexedAt": { "type": "string", "format": "datetime" } + "indexedAt": { "type": "string", "format": "datetime" }, + "likeCount": { "type": "integer" }, + "bookmarkCount": { "type": "integer" }, + "subscriberCount": { "type": "integer" }, + "entryCount": { "type": "integer" }, + "viewerLike": { "type": "string", "format": "at-uri" }, + "viewerBookmark": { "type": "string", "format": "at-uri" }, + "viewerSubscription": { "type": "string", "format": "at-uri" }, + "viewerReadingProgress": { "type": "ref", "ref": "#readingProgress" } } }, "entryView": { @@ -47,7 +55,12 @@ "type": "ref", "ref": "#renderedView" }, - "indexedAt": { "type": "string", "format": "datetime" } + "indexedAt": { "type": "string", "format": "datetime" }, + "likeCount": { "type": "integer" }, + "bookmarkCount": { "type": "integer" }, + "viewerLike": { "type": "string", "format": "at-uri" }, + "viewerBookmark": { "type": "string", "format": "at-uri" }, + "viewerReadingProgress": { "type": "ref", "ref": "#readingProgress" } } }, @@ -182,6 +195,171 @@ "description": "For authority: record createdAt. For invitees: accept createdAt" } } + }, + "feedEntryView": { + "type": "object", + "description": "Entry with feed-specific context (discovery reason, notebook context).", + "required": ["entry"], + "properties": { + "entry": { "type": "ref", "ref": "#entryView" }, + "reason": { "type": "ref", "ref": "#feedReason" }, + "notebookContext": { "type": "ref", "ref": "#feedNotebookContext" } + } + }, + "feedNotebookContext": { + "type": "object", + "description": "Minimal notebook context for feed display.", + "required": ["uri", "title"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "title": { "type": "string" }, + "path": { "type": "string" } + } + }, + "feedReason": { + "type": "union", + "description": "Why this entry appeared in the feed.", + "refs": ["#reasonLike", "#reasonBookmark", "#reasonSubscription"] + }, + "reasonLike": { + "type": "object", + "required": ["by", "indexedAt"], + "properties": { + "by": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "indexedAt": { "type": "string", "format": "datetime" } + } + }, + "reasonBookmark": { + "type": "object", + "required": ["by", "indexedAt"], + "properties": { + "by": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "indexedAt": { "type": "string", "format": "datetime" } + } + }, + "reasonSubscription": { + "type": "object", + "required": ["indexedAt"], + "properties": { + "indexedAt": { "type": "string", "format": "datetime" } + } + }, + "publishedVersionView": { + "type": "object", + "description": "A published version of an entry in a collaborator's repo.", + "required": ["uri", "cid", "publisher", "publishedAt"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "publisher": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "publishedAt": { "type": "string", "format": "datetime" }, + "updatedAt": { "type": "string", "format": "datetime" }, + "isCanonical": { + "type": "boolean", + "description": "True if this is the 'primary' version (owner's repo)" + }, + "divergedFrom": { + "type": "ref", + "ref": "com.atproto.repo.strongRef", + "description": "If content differs, the version it diverged from" + } + } + }, + "contentWarning": { + "type": "string", + "description": "Author-applied content warning.", + "knownValues": [ + "violence", + "graphic-violence", + "death", + "major-character-death", + "sexual-content", + "explicit-sexual-content", + "language", + "substance-use", + "self-harm", + "abuse", + "disturbing-imagery" + ] + }, + "contentWarnings": { + "type": "array", + "description": "Author-applied content warnings.", + "maxLength": 10, + "items": { "type": "ref", "ref": "#contentWarning" } + }, + "contentRating": { + "type": "string", + "description": "Author-applied content rating.", + "knownValues": ["general", "teen", "mature", "explicit"] + }, + "chapterView": { + "type": "object", + "description": "Hydrated view of a chapter.", + "required": ["uri", "cid", "notebook", "authors", "record", "indexedAt"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "title": { "type": "ref", "ref": "#title" }, + "tags": { "type": "ref", "ref": "#tags" }, + "notebook": { "type": "ref", "ref": "#notebookView" }, + "authors": { + "type": "array", + "items": { "type": "ref", "ref": "#authorListView" } + }, + "record": { "type": "unknown" }, + "entryCount": { "type": "integer" }, + "indexedAt": { "type": "string", "format": "datetime" } + } + }, + "chapterEntryView": { + "type": "object", + "description": "An entry within a chapter context.", + "required": ["entry", "index"], + "properties": { + "entry": { "type": "ref", "ref": "#entryView" }, + "index": { "type": "integer" }, + "next": { "type": "ref", "ref": "#bookEntryRef" }, + "prev": { "type": "ref", "ref": "#bookEntryRef" } + } + }, + "pageView": { + "type": "object", + "description": "Hydrated view of a page (entries displayed together).", + "required": ["uri", "cid", "notebook", "record", "indexedAt"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "title": { "type": "ref", "ref": "#title" }, + "tags": { "type": "ref", "ref": "#tags" }, + "notebook": { "type": "ref", "ref": "#notebookView" }, + "record": { "type": "unknown" }, + "entryCount": { "type": "integer" }, + "indexedAt": { "type": "string", "format": "datetime" } + } + }, + "readingProgress": { + "type": "object", + "description": "Viewer's reading progress (appview-side state, not a record).", + "properties": { + "status": { + "type": "string", + "knownValues": ["reading", "finished", "abandoned", "want-to-read"] + }, + "currentEntry": { + "type": "string", + "format": "at-uri", + "description": "Last entry the viewer was reading." + }, + "percentComplete": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "lastReadAt": { "type": "string", "format": "datetime" }, + "startedAt": { "type": "string", "format": "datetime" }, + "finishedAt": { "type": "string", "format": "datetime" } + } } } } diff --git a/lexicons/notebook/entry.json b/lexicons/notebook/entry.json index 0f3596f..79e21d1 100644 --- a/lexicons/notebook/entry.json +++ b/lexicons/notebook/entry.json @@ -34,6 +34,8 @@ "format": "datetime", "description": "Client-declared timestamp of last modification. Used for canonicality tiebreaking in multi-author scenarios." }, + "contentWarnings": { "type": "ref", "ref": "sh.weaver.notebook.defs#contentWarnings" }, + "rating": { "type": "ref", "ref": "sh.weaver.notebook.defs#contentRating" }, "embeds": { "type": "object", "description": "The set of images and records, if any, embedded in the notebook entry.", diff --git a/lexicons/notebook/getChapter.json b/lexicons/notebook/getChapter.json new file mode 100644 index 0000000..fa6fd35 --- /dev/null +++ b/lexicons/notebook/getChapter.json @@ -0,0 +1,37 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.getChapter", + "defs": { + "main": { + "type": "query", + "description": "Get a chapter with its entries.", + "parameters": { + "type": "params", + "required": ["chapter"], + "properties": { + "chapter": { "type": "string", "format": "at-uri" }, + "entryLimit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "entryCursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["chapter", "entries"], + "properties": { + "chapter": { "type": "ref", "ref": "sh.weaver.notebook.defs#chapterView" }, + "entries": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#chapterEntryView" } + }, + "entryCursor": { "type": "string" } + } + } + }, + "errors": [ + { "name": "ChapterNotFound" } + ] + } + } +} diff --git a/lexicons/notebook/getContinueReading.json b/lexicons/notebook/getContinueReading.json new file mode 100644 index 0000000..23c9780 --- /dev/null +++ b/lexicons/notebook/getContinueReading.json @@ -0,0 +1,47 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.getContinueReading", + "defs": { + "main": { + "type": "query", + "description": "Get notebooks the user is currently reading, sorted by most recent activity.", + "parameters": { + "type": "params", + "properties": { + "limit": { "type": "integer", "minimum": 1, "maximum": 20, "default": 10 } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["items"], + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "required": ["notebook", "progress", "nextEntry"], + "properties": { + "notebook": { "type": "ref", "ref": "sh.weaver.notebook.defs#notebookView" }, + "progress": { "type": "ref", "ref": "sh.weaver.notebook.defs#readingProgress" }, + "currentEntry": { + "type": "ref", + "ref": "sh.weaver.notebook.defs#entryView", + "description": "Where the user left off." + }, + "nextEntry": { + "type": "ref", + "ref": "sh.weaver.notebook.defs#entryView", + "description": "The next entry to read (if any)." + }, + "entriesRemaining": { "type": "integer" } + } + } + } + } + } + } + } + } +} diff --git a/lexicons/notebook/getEntryDetail.json b/lexicons/notebook/getEntryDetail.json new file mode 100644 index 0000000..6fb59db --- /dev/null +++ b/lexicons/notebook/getEntryDetail.json @@ -0,0 +1,38 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.getEntryDetail", + "defs": { + "main": { + "type": "query", + "description": "Get an entry with its containing notebooks in a single call.", + "parameters": { + "type": "params", + "required": ["entry"], + "properties": { + "entry": { "type": "string", "format": "at-uri" }, + "notebookContext": { "type": "string", "format": "at-uri" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["entry", "record", "notebookCount"], + "properties": { + "entry": { "type": "ref", "ref": "sh.weaver.notebook.defs#entryView" }, + "record": { "type": "unknown" }, + "notebookCount": { "type": "integer" }, + "notebooks": { + "type": "array", + "maxLength": 10, + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#notebookView" } + } + } + } + }, + "errors": [ + { "name": "EntryNotFound" } + ] + } + } +} diff --git a/lexicons/notebook/getEntryFeed.json b/lexicons/notebook/getEntryFeed.json new file mode 100644 index 0000000..e8a694e --- /dev/null +++ b/lexicons/notebook/getEntryFeed.json @@ -0,0 +1,46 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.getEntryFeed", + "defs": { + "main": { + "type": "query", + "description": "Get a feed of entries with optional algorithm and tag filtering.", + "parameters": { + "type": "params", + "properties": { + "algorithm": { + "type": "string", + "default": "chronological", + "knownValues": ["chronological", "popular"] + }, + "feed": { + "type": "string", + "format": "at-uri", + "description": "Optional custom feed generator URI" + }, + "tags": { + "type": "array", + "items": { "type": "string" }, + "maxLength": 10 + }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["feed"], + "properties": { + "feed": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#feedEntryView" } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/notebook/getNotebookChapters.json b/lexicons/notebook/getNotebookChapters.json new file mode 100644 index 0000000..ff9cc9a --- /dev/null +++ b/lexicons/notebook/getNotebookChapters.json @@ -0,0 +1,33 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.getNotebookChapters", + "defs": { + "main": { + "type": "query", + "description": "Get all chapters for a notebook.", + "parameters": { + "type": "params", + "required": ["notebook"], + "properties": { + "notebook": { "type": "string", "format": "at-uri" }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["chapters"], + "properties": { + "chapters": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#chapterView" } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/notebook/getNotebookDetail.json b/lexicons/notebook/getNotebookDetail.json new file mode 100644 index 0000000..220a319 --- /dev/null +++ b/lexicons/notebook/getNotebookDetail.json @@ -0,0 +1,37 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.getNotebookDetail", + "defs": { + "main": { + "type": "query", + "description": "Get a notebook with its entries in a single call.", + "parameters": { + "type": "params", + "required": ["notebook"], + "properties": { + "notebook": { "type": "string", "format": "at-uri" }, + "entryLimit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "entryCursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["notebook", "entries"], + "properties": { + "notebook": { "type": "ref", "ref": "sh.weaver.notebook.defs#notebookView" }, + "entries": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#bookEntryView" } + }, + "entryCursor": { "type": "string" } + } + } + }, + "errors": [ + { "name": "NotebookNotFound" } + ] + } + } +} diff --git a/lexicons/notebook/getNotebookFeed.json b/lexicons/notebook/getNotebookFeed.json new file mode 100644 index 0000000..59df5f7 --- /dev/null +++ b/lexicons/notebook/getNotebookFeed.json @@ -0,0 +1,46 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.getNotebookFeed", + "defs": { + "main": { + "type": "query", + "description": "Get a feed of notebooks with optional algorithm and tag filtering.", + "parameters": { + "type": "params", + "properties": { + "algorithm": { + "type": "string", + "default": "chronological", + "knownValues": ["chronological", "popular"] + }, + "feed": { + "type": "string", + "format": "at-uri", + "description": "Optional custom feed generator URI" + }, + "tags": { + "type": "array", + "items": { "type": "string" }, + "maxLength": 10 + }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["notebooks"], + "properties": { + "notebooks": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#notebookView" } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/notebook/getPage.json b/lexicons/notebook/getPage.json new file mode 100644 index 0000000..e85588d --- /dev/null +++ b/lexicons/notebook/getPage.json @@ -0,0 +1,34 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.getPage", + "defs": { + "main": { + "type": "query", + "description": "Get a page with its entries (all entries, since pages display together).", + "parameters": { + "type": "params", + "required": ["page"], + "properties": { + "page": { "type": "string", "format": "at-uri" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["page", "entries"], + "properties": { + "page": { "type": "ref", "ref": "sh.weaver.notebook.defs#pageView" }, + "entries": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#entryView" } + } + } + } + }, + "errors": [ + { "name": "PageNotFound" } + ] + } + } +} diff --git a/lexicons/notebook/getPublishedVersions.json b/lexicons/notebook/getPublishedVersions.json new file mode 100644 index 0000000..5e81a21 --- /dev/null +++ b/lexicons/notebook/getPublishedVersions.json @@ -0,0 +1,49 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.getPublishedVersions", + "defs": { + "main": { + "type": "query", + "description": "Get all published versions of an entry (same rkey in collaborator repos).", + "parameters": { + "type": "params", + "required": ["entry"], + "properties": { + "entry": { "type": "string", "format": "at-uri" }, + "includeContent": { + "type": "boolean", + "default": false, + "description": "Include full entry record in response" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["versions"], + "properties": { + "canonical": { "type": "ref", "ref": "sh.weaver.notebook.defs#publishedVersionView" }, + "versions": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#publishedVersionView" } + }, + "hasDivergence": { "type": "boolean" }, + "records": { + "type": "array", + "description": "Full entry records if includeContent=true", + "items": { + "type": "object", + "required": ["uri", "record"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "record": { "type": "unknown" } + } + } + } + } + } + } + } + } +} diff --git a/lexicons/notebook/getReadingHistory.json b/lexicons/notebook/getReadingHistory.json new file mode 100644 index 0000000..909efa5 --- /dev/null +++ b/lexicons/notebook/getReadingHistory.json @@ -0,0 +1,50 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.getReadingHistory", + "defs": { + "main": { + "type": "query", + "description": "Get the authenticated user's reading history.", + "parameters": { + "type": "params", + "properties": { + "status": { + "type": "string", + "knownValues": ["reading", "finished", "abandoned", "want-to-read", "all"], + "default": "all", + "description": "Filter by reading status." + }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["history"], + "properties": { + "history": { + "type": "array", + "items": { "type": "ref", "ref": "#readingHistoryItem" } + }, + "cursor": { "type": "string" } + } + } + } + }, + "readingHistoryItem": { + "type": "object", + "required": ["notebook", "progress"], + "properties": { + "notebook": { "type": "ref", "ref": "sh.weaver.notebook.defs#notebookView" }, + "progress": { "type": "ref", "ref": "sh.weaver.notebook.defs#readingProgress" }, + "currentEntry": { + "type": "ref", + "ref": "sh.weaver.notebook.defs#entryView", + "description": "The entry the user was last reading." + } + } + } + } +} diff --git a/lexicons/notebook/getSimilarNotebooks.json b/lexicons/notebook/getSimilarNotebooks.json new file mode 100644 index 0000000..700ae87 --- /dev/null +++ b/lexicons/notebook/getSimilarNotebooks.json @@ -0,0 +1,43 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.getSimilarNotebooks", + "defs": { + "main": { + "type": "query", + "description": "Get notebooks similar to a given notebook (by tags, author, readers who also liked, etc).", + "parameters": { + "type": "params", + "required": ["notebook"], + "properties": { + "notebook": { "type": "string", "format": "at-uri" }, + "limit": { "type": "integer", "minimum": 1, "maximum": 50, "default": 10 } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["notebooks"], + "properties": { + "notebooks": { + "type": "array", + "items": { + "type": "object", + "required": ["notebook"], + "properties": { + "notebook": { "type": "ref", "ref": "sh.weaver.notebook.defs#notebookView" }, + "sharedTags": { + "type": "array", + "items": { "type": "string" } + }, + "sameAuthor": { "type": "boolean" }, + "readersAlsoLiked": { "type": "boolean" } + } + } + } + } + } + } + } + } +} diff --git a/lexicons/notebook/getSuggestedNotebooks.json b/lexicons/notebook/getSuggestedNotebooks.json new file mode 100644 index 0000000..ddf3154 --- /dev/null +++ b/lexicons/notebook/getSuggestedNotebooks.json @@ -0,0 +1,80 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.getSuggestedNotebooks", + "defs": { + "main": { + "type": "query", + "description": "Get notebook recommendations for the authenticated user based on reading history, likes, and follows.", + "parameters": { + "type": "params", + "properties": { + "limit": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["notebooks"], + "properties": { + "notebooks": { + "type": "array", + "items": { "type": "ref", "ref": "#suggestedNotebook" } + } + } + } + } + }, + "suggestedNotebook": { + "type": "object", + "required": ["notebook", "reason"], + "properties": { + "notebook": { "type": "ref", "ref": "sh.weaver.notebook.defs#notebookView" }, + "reason": { "type": "ref", "ref": "#suggestionReason" }, + "score": { + "type": "number", + "description": "Appview-computed relevance score." + } + } + }, + "suggestionReason": { + "type": "object", + "description": "Why this notebook was suggested.", + "required": ["type"], + "properties": { + "type": { + "type": "string", + "knownValues": [ + "similar-tags", + "similar-to-liked", + "similar-to-read", + "followed-author", + "popular-in-tag", + "trending", + "from-list" + ] + }, + "relatedNotebook": { + "type": "ref", + "ref": "sh.weaver.notebook.defs#notebookView", + "description": "If similar-to-X, the notebook it's similar to." + }, + "relatedAuthor": { + "type": "ref", + "ref": "sh.weaver.actor.defs#profileViewBasic", + "description": "If followed-author, the author." + }, + "relatedTags": { + "type": "array", + "items": { "type": "string" }, + "description": "Tags that matched." + }, + "relatedList": { + "type": "ref", + "ref": "sh.weaver.graph.defs#listView", + "description": "If from-list, the list it's from." + } + } + } + } +} diff --git a/lexicons/notebook/resolveEntry.json b/lexicons/notebook/resolveEntry.json new file mode 100644 index 0000000..37bf1be --- /dev/null +++ b/lexicons/notebook/resolveEntry.json @@ -0,0 +1,40 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.resolveEntry", + "defs": { + "main": { + "type": "query", + "description": "Resolve an entry by actor + notebook + entry title/path.", + "parameters": { + "type": "params", + "required": ["actor", "notebook", "entry"], + "properties": { + "actor": { "type": "string", "format": "at-identifier" }, + "notebook": { "type": "string", "maxLength": 300 }, + "entry": { "type": "string", "maxLength": 300 } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["entry", "record", "notebookCount"], + "properties": { + "entry": { "type": "ref", "ref": "sh.weaver.notebook.defs#entryView" }, + "record": { "type": "unknown" }, + "notebookCount": { "type": "integer" }, + "notebooks": { + "type": "array", + "maxLength": 10, + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#notebookView" } + } + } + } + }, + "errors": [ + { "name": "NotebookNotFound" }, + { "name": "EntryNotFound" } + ] + } + } +} diff --git a/lexicons/notebook/resolveNotebook.json b/lexicons/notebook/resolveNotebook.json new file mode 100644 index 0000000..701d144 --- /dev/null +++ b/lexicons/notebook/resolveNotebook.json @@ -0,0 +1,38 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.resolveNotebook", + "defs": { + "main": { + "type": "query", + "description": "Resolve a notebook by actor + title/path, returns full detail.", + "parameters": { + "type": "params", + "required": ["actor", "name"], + "properties": { + "actor": { "type": "string", "format": "at-identifier" }, + "name": { "type": "string", "maxLength": 300 }, + "entryLimit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "entryCursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["notebook", "entries"], + "properties": { + "notebook": { "type": "ref", "ref": "sh.weaver.notebook.defs#notebookView" }, + "entries": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#bookEntryView" } + }, + "entryCursor": { "type": "string" } + } + } + }, + "errors": [ + { "name": "NotebookNotFound" } + ] + } + } +} diff --git a/lexicons/notebook/resolveVersionConflict.json b/lexicons/notebook/resolveVersionConflict.json new file mode 100644 index 0000000..231a49d --- /dev/null +++ b/lexicons/notebook/resolveVersionConflict.json @@ -0,0 +1,40 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.resolveVersionConflict", + "defs": { + "main": { + "type": "query", + "description": "Given multiple URIs that might refer to the same collaborative entry, resolve to canonical version.", + "parameters": { + "type": "params", + "required": ["uris"], + "properties": { + "uris": { + "type": "array", + "minLength": 1, + "maxLength": 20, + "items": { "type": "string", "format": "at-uri" } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["canonical", "related"], + "properties": { + "canonical": { "type": "ref", "ref": "sh.weaver.notebook.defs#publishedVersionView" }, + "related": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#publishedVersionView" } + }, + "collaborationState": { "type": "ref", "ref": "sh.weaver.collab.defs#collaborationStateView" } + } + } + }, + "errors": [ + { "name": "NoRelatedVersions", "description": "The URIs don't appear to be related versions" } + ] + } + } +} diff --git a/lexicons/notebook/searchEntries.json b/lexicons/notebook/searchEntries.json new file mode 100644 index 0000000..1d8337b --- /dev/null +++ b/lexicons/notebook/searchEntries.json @@ -0,0 +1,39 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.searchEntries", + "defs": { + "main": { + "type": "query", + "description": "Full-text search for entries with optional author and tag filtering.", + "parameters": { + "type": "params", + "required": ["q"], + "properties": { + "q": { "type": "string", "minLength": 1, "maxLength": 500 }, + "author": { "type": "string", "format": "at-identifier" }, + "tags": { + "type": "array", + "items": { "type": "string" }, + "maxLength": 10 + }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["entries"], + "properties": { + "entries": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#entryView" } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/notebook/searchNotebooks.json b/lexicons/notebook/searchNotebooks.json new file mode 100644 index 0000000..f06e4c3 --- /dev/null +++ b/lexicons/notebook/searchNotebooks.json @@ -0,0 +1,54 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.searchNotebooks", + "defs": { + "main": { + "type": "query", + "description": "Full-text search for notebooks.", + "parameters": { + "type": "params", + "required": ["q"], + "properties": { + "q": { "type": "string", "minLength": 1, "maxLength": 500 }, + "author": { + "type": "string", + "format": "at-identifier", + "description": "Filter by author." + }, + "tags": { + "type": "array", + "items": { "type": "string" }, + "maxLength": 10, + "description": "Filter by tags (all must match)." + }, + "rating": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#contentRating" }, + "description": "Filter by content rating (any of these)." + }, + "sort": { + "type": "string", + "knownValues": ["relevance", "recent", "popular"], + "default": "relevance" + }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["notebooks"], + "properties": { + "notebooks": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#notebookView" } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/notebook/updateReadingProgress.json b/lexicons/notebook/updateReadingProgress.json new file mode 100644 index 0000000..ea10cc5 --- /dev/null +++ b/lexicons/notebook/updateReadingProgress.json @@ -0,0 +1,44 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.updateReadingProgress", + "defs": { + "main": { + "type": "procedure", + "description": "Update reading progress for a notebook (appview-side state).", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["notebook"], + "properties": { + "notebook": { "type": "string", "format": "at-uri" }, + "currentEntry": { + "type": "string", + "format": "at-uri", + "description": "The entry the user is currently on." + }, + "status": { + "type": "string", + "knownValues": ["reading", "finished", "abandoned", "want-to-read"] + }, + "percentComplete": { + "type": "integer", + "minimum": 0, + "maximum": 100 + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["progress"], + "properties": { + "progress": { "type": "ref", "ref": "sh.weaver.notebook.defs#readingProgress" } + } + } + } + } + } +} diff --git a/lexicons/notification/defs.json b/lexicons/notification/defs.json new file mode 100644 index 0000000..a836f8d --- /dev/null +++ b/lexicons/notification/defs.json @@ -0,0 +1,87 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notification.defs", + "defs": { + "notification": { + "type": "object", + "description": "A notification for a user.", + "required": ["uri", "cid", "author", "reason", "isRead", "indexedAt"], + "properties": { + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "author": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "reason": { "type": "ref", "ref": "#notificationReason" }, + "reasonSubject": { + "type": "string", + "format": "at-uri", + "description": "The subject of the notification (entry, notebook, etc)." + }, + "record": { "type": "unknown" }, + "isRead": { "type": "boolean" }, + "indexedAt": { "type": "string", "format": "datetime" } + } + }, + "notificationReason": { + "type": "string", + "description": "Why this notification was generated.", + "knownValues": [ + "like", + "bookmark", + "follow", + "followAccept", + "subscribe", + "subscribeAccept", + "collaborationInvite", + "collaborationAccept", + "newEntry", + "entryUpdate", + "mention", + "tag", + "comment" + ] + }, + "notificationGroup": { + "type": "object", + "description": "Grouped notifications (e.g., '5 people liked your entry').", + "required": ["reason", "subject", "count", "actors", "mostRecentAt"], + "properties": { + "reason": { "type": "ref", "ref": "#notificationReason" }, + "subject": { + "type": "union", + "refs": [ + "sh.weaver.notebook.defs#notebookView", + "sh.weaver.notebook.defs#entryView" + ] + }, + "count": { "type": "integer" }, + "actors": { + "type": "array", + "maxLength": 5, + "items": { "type": "ref", "ref": "sh.weaver.actor.defs#profileViewBasic" }, + "description": "Most recent actors (up to 5)." + }, + "mostRecentAt": { "type": "string", "format": "datetime" }, + "isRead": { "type": "boolean" } + } + }, + "subscriptionUpdateView": { + "type": "object", + "description": "New content from a notebook subscription.", + "required": ["notebook", "newEntries", "updatedAt"], + "properties": { + "notebook": { "type": "ref", "ref": "sh.weaver.notebook.defs#notebookView" }, + "newEntries": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#entryView" }, + "description": "New entries since last check." + }, + "updatedEntries": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notebook.defs#entryView" }, + "description": "Entries that were updated." + }, + "updatedAt": { "type": "string", "format": "datetime" } + } + } + } +} diff --git a/lexicons/notification/getSubscriptionUpdates.json b/lexicons/notification/getSubscriptionUpdates.json new file mode 100644 index 0000000..8438372 --- /dev/null +++ b/lexicons/notification/getSubscriptionUpdates.json @@ -0,0 +1,36 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notification.getSubscriptionUpdates", + "defs": { + "main": { + "type": "query", + "description": "Get new/updated content from notebooks the user subscribes to.", + "parameters": { + "type": "params", + "properties": { + "since": { + "type": "string", + "format": "datetime", + "description": "Only show updates since this timestamp." + }, + "limit": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["updates"], + "properties": { + "updates": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notification.defs#subscriptionUpdateView" } + }, + "cursor": { "type": "string" } + } + } + } + } + } +} diff --git a/lexicons/notification/getUnreadCount.json b/lexicons/notification/getUnreadCount.json new file mode 100644 index 0000000..30b807f --- /dev/null +++ b/lexicons/notification/getUnreadCount.json @@ -0,0 +1,30 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notification.getUnreadCount", + "defs": { + "main": { + "type": "query", + "description": "Get count of unread notifications for the authenticated user.", + "parameters": { + "type": "params", + "properties": { + "seenAt": { + "type": "string", + "format": "datetime", + "description": "Count notifications after this timestamp." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["count"], + "properties": { + "count": { "type": "integer" } + } + } + } + } + } +} diff --git a/lexicons/notification/listNotifications.json b/lexicons/notification/listNotifications.json new file mode 100644 index 0000000..a310c07 --- /dev/null +++ b/lexicons/notification/listNotifications.json @@ -0,0 +1,42 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notification.listNotifications", + "defs": { + "main": { + "type": "query", + "description": "Get notifications for the authenticated user.", + "parameters": { + "type": "params", + "properties": { + "reasons": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notification.defs#notificationReason" }, + "description": "Filter by notification reasons." + }, + "seenAt": { + "type": "string", + "format": "datetime", + "description": "Only show notifications after this timestamp." + }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["notifications"], + "properties": { + "notifications": { + "type": "array", + "items": { "type": "ref", "ref": "sh.weaver.notification.defs#notification" } + }, + "cursor": { "type": "string" }, + "seenAt": { "type": "string", "format": "datetime" } + } + } + } + } + } +} diff --git a/lexicons/notification/updateSeen.json b/lexicons/notification/updateSeen.json new file mode 100644 index 0000000..089ad80 --- /dev/null +++ b/lexicons/notification/updateSeen.json @@ -0,0 +1,27 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notification.updateSeen", + "defs": { + "main": { + "type": "procedure", + "description": "Mark notifications as seen up to a given timestamp.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["seenAt"], + "properties": { + "seenAt": { "type": "string", "format": "datetime" } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": {} + } + } + } + } +} diff --git a/weaver_notes/.obsidian/workspace.json b/weaver_notes/.obsidian/workspace.json index 9dfaa4c..e1dd5d8 100644 --- a/weaver_notes/.obsidian/workspace.json +++ b/weaver_notes/.obsidian/workspace.json @@ -48,9 +48,21 @@ "icon": "lucide-file", "title": "Why I rewrote pdsls in Rust (tm)" } + }, + { + "id": "4b584137400d2323", + "type": "leaf", + "state": { + "type": "release-notes", + "state": { + "currentVersion": "1.10.6" + }, + "icon": "lucide-book-up", + "title": "Release Notes 1.10.6" + } } ], - "currentTab": 2 + "currentTab": 3 } ], "direction": "vertical" @@ -199,7 +211,7 @@ "bases:Create new base": false } }, - "active": "6029beecc3d03bce", + "active": "4b584137400d2323", "lastOpenFiles": [ "light_mode_excerpt.png", "notebook_entry_preview.png",