Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions crates/rw-comments/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion crates/rw/src/commands/comment/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
32 changes: 15 additions & 17 deletions crates/rw/src/commands/comment/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<Author>, 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<Author, CliError> {
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(),
)),
}
Expand All @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion crates/rw/src/commands/comment/reply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
})
Expand Down
2 changes: 1 addition & 1 deletion docs/comment-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Loading