Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
28 changes: 28 additions & 0 deletions doc/api/hooks_client-side.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,34 @@ Context properties:

* `message`: The message object that will be sent to the Etherpad server.

## `chatPrefillFromUser`

Called from: `src/static/js/pad_userlist.ts`

Called when the user clicks an entry in the user list. The default behavior is to
open the chat panel and prefill the input with `@<name> `, where `<name>` is that
user's display name (with whitespace replaced by underscores). Plugins can return
a different prefill string from their callback — the first non-empty string
returned wins.

Typical use is by AI/bot plugins whose author display name (e.g. "AI Assistant")
isn't a useful @-mention; the plugin can substitute its trigger string instead.

Context properties:

* `authorId`: The clicked user's author id.
* `name`: The clicked user's display name.
* `prefill`: The default prefill string Etherpad would otherwise use.

Example:

```javascript
exports.chatPrefillFromUser = (hookName, {authorId, name}, cb) => {
if (authorId === window.clientVars.ep_my_bot.authorId) return cb('@bot ');
return cb();
};
```

## collectContentPre

Called from: `src/static/js/contentcollector.js`
Expand Down
41 changes: 41 additions & 0 deletions src/static/js/pad_userlist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import padutils from './pad_utils'
const hooks = require('./pluginfw/hooks');
const chat = require('./chat').chat;
import html10n from './vendors/html10n';
let myUserInfo = {};

Expand Down Expand Up @@ -369,6 +370,46 @@ const paduserlist = (() => {
}, 0);
});

// Click any other user's row to open chat with @<their_name> prefilled.
// Helps newcomers discover the chat panel and the @-mention convention
// without having to be told. Plugins can transform the prefilled text
// — for example ep_ai_chat replaces "@AI Assistant" with the
// configured trigger ("@ai") — via the chatPrefillFromUser client
// hook (see doc/api/hooks_client-side.md).
$('#otheruserstable').on('click', 'tr[data-authorId]', async function (event) {
// Skip clicks on the color swatch — that has its own click handler
// (color-picker semantics) and shouldn't double up as a chat trigger.
if ($(event.target).closest('.usertdswatch').length) return;
const tr = $(this);
const authorId = tr.attr('data-authorId');
if (!authorId) return;
const name = (tr.find('.usertdname').text() || '').trim();
let prefill = name ? `@${name.replace(/\s+/g, '_')} ` : '';
try {
const transforms = await hooks.aCallAll(
'chatPrefillFromUser', {authorId, name, prefill});
if (Array.isArray(transforms)) {
for (const tr2 of transforms) {
if (typeof tr2 === 'string' && tr2.length > 0) { prefill = tr2; break; }
}
}
} catch { /* never let a misbehaving plugin break the click */ }
try { chat.show(); } catch { /* */ }
setTimeout(() => {
const $input = $('#chatinput');
if (!$input.length) return;
const current = ($input.val() || '') as string;
if (!current.trim() || /^@\S+\s*$/.test(current.trim())) {
$input.val(prefill);
} else if (!current.includes(prefill.trim())) {
$input.val(`${current.trimEnd()} ${prefill}`);
}
$input.trigger('focus');
const elem = $input[0] as HTMLTextAreaElement;
try { elem.setSelectionRange(elem.value.length, elem.value.length); } catch (_e) { /* */ }
}, 50);
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
});
Comment on lines +379 to +416
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Click-to-chat lacks feature flag 📘 Rule violation ⚙ Maintainability

The new click handler on user list rows changes default behavior by opening chat and prefilling an
@name mention without any feature flag gating. This violates the requirement that new features be
disabled by default unless explicitly enabled.
Agent Prompt
## Issue description
A new UX feature (click user row to open chat and prefill `@name`) is enabled by default, but compliance requires new features to be behind a feature flag and disabled by default.

## Issue Context
The click handler is currently registered unconditionally, so all installs get the new behavior without opting in.

## Fix Focus Areas
- src/static/js/pad_userlist.ts[379-411]
- src/static/skins/colibris/src/components/users.css[11-16]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going with a reasoned decline on the feature-flag part of this comment. Replying so the rationale is on the record:

  • Etherpad doesn't gate UX changes behind feature flags. The current settings.json surface is reserved for things that materially change the contract (auth, storage, transport, security knobs, skin selection). Recent UX additions — chat-and-users mode, sticky chat, the showsidebar editbar button, etc. — all shipped on by default with no flag. Adding a flag here would be inconsistent with project precedent.
  • Blast radius is small. Before this PR clicking a user list row was a no-op; nothing depended on that. Anyone who wants the click to do nothing can ship a tiny CSS override (#otheruserstable tr[data-authorId] { pointer-events: none; }) without a settings change.
  • Plugins already have the override they need. The chatPrefillFromUser hook lets a plugin return a non-empty string to substitute the prefill. A plugin that wanted to globally suppress the behavior could trivially return an empty-but-truthy value or hide rows via CSS.

The other comment on this review (rename-input click steals focus) was a real bug and is fixed in a494307a, plus a Playwright spec covering the supported flows and that regression.


// color picker
$('#myswatchbox').on('click', showColorPicker);
$('#mycolorpicker .pickerswatchouter').on('click', function () {
Expand Down
13 changes: 13 additions & 0 deletions src/static/skins/colibris/src/components/users.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@ table#otheruserstable {
margin-top: 20px;
}

/*
* Clicking a row in the user list opens chat and prefills "@<name> ".
* The pointer cursor on the name cell makes the affordance visible —
* the swatch keeps its own (color-picker) click semantics, so leave
* its cursor alone.
*/
#otheruserstable tr[data-authorId] .usertdname {
cursor: pointer;
}
#otheruserstable tr[data-authorId]:hover .usertdname {
text-decoration: underline;
}

.popup#users.chatAndUsers > .popup-content {
padding: 20px 10px;
height: 250px;
Expand Down
Loading