diff --git a/CHANGELOG.md b/CHANGELOG.md index 5da8285c..abc9e2ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- The `rw comment` CLI now stamps comments it creates with the AI identity (`{ id: "local:ai", name: "AI" }`, a sparkles avatar in the viewer) by default, instead of the human `{ id: "local:human", name: "You" }`. The CLI's primary user is an LLM agent, so unattributed agent comments are now visually distinct from a human reviewer's own comments in the browser. Set `RW_COMMENT_AUTHOR_ID`/`RW_COMMENT_AUTHOR_NAME` (or `--author-id`/`--author-name`) to override. Browser-authored comments via `rw serve` are unchanged (still `local:human`). - The "Add comment" button that appears when you select text in a doc is now icon-only (a speech-bubble icon) instead of icon + "Add comment" text, for a more compact popover. The button keeps its "Add comment" accessible name, so screen readers and keyboard users are unaffected. ### Fixed diff --git a/crates/rw-comments/src/model.rs b/crates/rw-comments/src/model.rs index fd5d4e89..616bf54d 100644 --- a/crates/rw-comments/src/model.rs +++ b/crates/rw-comments/src/model.rs @@ -25,6 +25,20 @@ impl Author { avatar_url: None, } } + + /// The identity stamped on comments created through the `rw comment` CLI + /// when the caller supplies no author. The CLI's primary user is an LLM + /// agent, so its default is AI — rendered with a sparkles avatar in the + /// viewer — rather than [`Author::local_human`], which remains the default + /// for browser-authored comments. + #[must_use] + pub fn local_ai() -> Self { + Self { + id: "local:ai".to_owned(), + name: "AI".to_owned(), + avatar_url: None, + } + } } /// A selector that identifies a text range within a document. diff --git a/crates/rw/src/commands/comment/add.rs b/crates/rw/src/commands/comment/add.rs index c0b9eb0d..5479b8fc 100644 --- a/crates/rw/src/commands/comment/add.rs +++ b/crates/rw/src/commands/comment/add.rs @@ -55,7 +55,7 @@ pub(crate) async fn run(ctx: &Context, args: AddArgs) -> Result<(), CliError> { let input = NewComment { document_id, parent_id: None, - author, + author: Some(author), body, selectors, quote: None, diff --git a/crates/rw/src/commands/comment/identity.rs b/crates/rw/src/commands/comment/identity.rs index cbac10d6..543c04b2 100644 --- a/crates/rw/src/commands/comment/identity.rs +++ b/crates/rw/src/commands/comment/identity.rs @@ -2,27 +2,25 @@ use rw_comments::Author; use crate::error::CliError; -/// Resolve an optional author claim. +/// Resolve the author claim for a CLI-created comment. /// /// Returns: -/// - `Ok(Some(Author))` when both id and name are provided. -/// - `Ok(None)` when neither is provided (callers fall back to -/// [`Author::local_human`]). -/// - `Err(CliError::Validation)` when exactly one of the two is provided. -pub(crate) fn resolve_author( - id: Option<&str>, - name: Option<&str>, -) -> Result, CliError> { +/// - the explicit [`Author`] when both id and name are provided; +/// - the default [`Author::local_ai`] identity when neither is provided — the +/// `rw comment` CLI's primary user is an LLM agent, so an unattributed +/// comment is stamped as AI rather than as a human; +/// - [`CliError::Validation`] when exactly one of the two is provided. +pub(crate) fn resolve_author(id: Option<&str>, name: Option<&str>) -> Result { match (id, name) { - (Some(id), Some(name)) => Ok(Some(Author { + (Some(id), Some(name)) => Ok(Author { id: id.to_owned(), name: name.to_owned(), avatar_url: None, - })), - (None, None) => Ok(None), + }), + (None, None) => Ok(Author::local_ai()), _ => Err(CliError::Validation( "--author-id and --author-name must be set together (or both left unset \ - to use the default `local:human` identity)" + to use the default `local:ai` identity)" .to_owned(), )), } @@ -39,17 +37,17 @@ mod tests { let author = resolve_author(Some("local:claude-code"), Some("Claude Code")).unwrap(); assert_eq!( author, - Some(Author { + Author { id: "local:claude-code".to_owned(), name: "Claude Code".to_owned(), avatar_url: None, - }) + } ); } #[test] - fn neither_set_returns_none() { - assert_eq!(resolve_author(None, None).unwrap(), None); + fn neither_set_returns_local_ai() { + assert_eq!(resolve_author(None, None).unwrap(), Author::local_ai()); } #[test] diff --git a/crates/rw/src/commands/comment/reply.rs b/crates/rw/src/commands/comment/reply.rs index bcdbe45b..cd278d62 100644 --- a/crates/rw/src/commands/comment/reply.rs +++ b/crates/rw/src/commands/comment/reply.rs @@ -40,7 +40,7 @@ pub(crate) async fn run(ctx: &Context, args: ReplyArgs) -> Result<(), CliError> .create(CreateComment { document_id: parent.document_id.clone(), parent_id: Some(parent.id), - author, + author: Some(author), body, selectors: Vec::new(), }) diff --git a/docs/comment-cli.md b/docs/comment-cli.md index 7dc8dd12..501505e9 100644 --- a/docs/comment-cli.md +++ b/docs/comment-cli.md @@ -63,7 +63,7 @@ New comments carry an `author` stamp. Resolution order: 1. `--author-id` and `--author-name` flags on the subcommand. 2. `$RW_COMMENT_AUTHOR_ID` and `$RW_COMMENT_AUTHOR_NAME` env vars. -3. If both are unset, the CLI writes the comment with the default identity `{ id: "local:human", name: "You" }`. (This is the same default `rw serve` uses when the browser writes a comment — viewer and CLI stay aligned.) +3. If both are unset, the CLI writes the comment with the default identity `{ id: "local:ai", name: "AI" }` — the `rw comment` CLI's primary user is an LLM agent, so an unattributed comment is stamped as AI (a sparkles avatar in the browser) rather than as a human. (Browser-authored comments via `rw serve` still default to `{ id: "local:human", name: "You" }`.) Set both or neither — a partial identity (e.g. id without name) is rejected before the request leaves the CLI.