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
135 changes: 135 additions & 0 deletions .github/workflows/feature-screenshots.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
name: Feature Screenshots

on:
push:
branches:
- main
pull_request:
workflow_dispatch:

# This workflow is a focused mirror of the `system_tests` job from
# `discourse/.github/.github/workflows/discourse-plugin.yml` (the reusable
# workflow used by `discourse-plugin.yml`), trimmed to run only the visual
# feature-screenshot spec and to ALWAYS upload `tmp/capybara/feature_screenshots/*.png`
# as a build artifact (not just on failure, the way the reusable workflow does it).

jobs:
screenshots:
name: feature_screenshots
runs-on: ubuntu-latest
container: discourse/discourse_test:slim-browsers
timeout-minutes: 30

env:
RUBY_GLOBAL_METHOD_CACHE_SIZE: 131072
RAILS_ENV: test
PGUSER: discourse
PGPASSWORD: discourse
PLUGIN_NAME: ${{ github.event.repository.name }}
CHEAP_SOURCE_MAPS: "1"
MINIO_RUNNER_LOG_LEVEL: DEBUG
MINIO_RUNNER_INSTALL_DIR: /home/discourse/.minio_runner
USES_PARALLEL_DATABASES: "false"
PARALLEL_TEST_PROCESSORS: 1
LOAD_PLUGINS: 1
CAPYBARA_DEFAULT_MAX_WAIT_TIME: 10

steps:
- name: Set working directory owner
run: chown root:root .

- name: Checkout Discourse
uses: actions/checkout@v6
with:
repository: discourse/discourse
fetch-depth: 1
ref: latest

- name: Install plugin
uses: actions/checkout@v6
with:
path: plugins/${{ env.PLUGIN_NAME }}
fetch-depth: 1

- name: Setup Git
run: |
git config --global user.email "ci@ci.invalid"
git config --global user.name "Discourse CI"

- name: Start redis
run: redis-server /etc/redis/redis.conf &

- name: Start Postgres
run: |
chown -R postgres /var/run/postgresql
sudo -E -u postgres script/start_test_db.rb
sudo -u postgres psql -c "CREATE ROLE $PGUSER LOGIN SUPERUSER PASSWORD '$PGPASSWORD';"

- name: Container envs
id: container-envs
run: |
echo "ruby_version=$RUBY_VERSION" >> $GITHUB_OUTPUT
echo "debian_release=$DEBIAN_RELEASE" >> $GITHUB_OUTPUT

- name: Bundler cache
uses: actions/cache@v5
with:
path: vendor/bundle
key: ${{ runner.os }}-${{ steps.container-envs.outputs.ruby_version }}-${{ steps.container-envs.outputs.debian_release }}-gem-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-${{ steps.container-envs.outputs.ruby_version }}-${{ steps.container-envs.outputs.debian_release }}-gem-

- name: Setup gems
run: |
bundle config --local path vendor/bundle
bundle config --local deployment true
bundle config --local without development
bundle install --jobs 4
bundle clean

- name: Install JS Dependencies
run: if [ -f yarn.lock ]; then yarn install --frozen-lockfile; else pnpm install --frozen-lockfile; fi

- name: Install playwright
run: |
if pnpm playwright -V; then
pnpm playwright install --with-deps --no-shell chromium
fi

- name: Create and migrate database
run: |
bin/rake db:create
bin/rake db:migrate

- name: Ember Build
env:
EMBER_ENV: development
run: |
if [ -f script/assemble_ember_build.rb ]; then
export DISCOURSE_DOWNLOAD_PRE_BUILT_ASSETS=$(bin/rails runner 'puts(Discourse.has_needed_version?(Discourse::VERSION::STRING, "2026.3.0-latest") ? 1 : 0)')
script/assemble_ember_build.rb
else
bin/ember-cli --build
fi

- name: Add hosts to /etc/hosts (Chrome can reach minio)
run: |
echo "127.0.0.1 minio.local" | sudo tee -a /etc/hosts
echo "127.0.0.1 discoursetest.minio.local" | sudo tee -a /etc/hosts

- name: Run feature-screenshot spec
run: |
bundle exec rspec \
--format documentation \
plugins/${{ env.PLUGIN_NAME }}/spec/system/feature_screenshots_spec.rb
continue-on-error: true

- name: Upload feature screenshots (always)
if: always()
uses: actions/upload-artifact@v6
with:
name: feature-screenshots
path: |
tmp/capybara/feature_screenshots/*.png
tmp/capybara/*.png
if-no-files-found: warn
53 changes: 30 additions & 23 deletions app/controllers/discourse_mod_categories/messages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -271,21 +271,37 @@ def notes_feed_seen
.update_all(read: true)

# Push the recalculated bell counts to every open tab so they refresh
# in lockstep with the shield tab being opened.
# in lockstep with the shield tab being opened. This also drops the
# in-dropdown shield-tab pip, which now derives from unread Notification
# rows (the same source the bell uses), so it stays in lockstep with
# the bell badge without a dedicated MessageBus channel.
current_user.publish_notifications_state if marked > 0

# Reset any other browser tabs/devices the staff member has open so
# their header pip + title prefix clears in lockstep, not on the next
# page load.
MessageBus.publish(
"/mod-note-unread-count/#{current_user.id}",
{ reset: true },
user_ids: [current_user.id],
)

render json: success_json
end

# Resolves a badge id to the current set of usernames who hold it.
# Used by the PM composer "Add badge group" button to splice badge
# holders into the standard `target_recipients` field — the PM is then
# sent through the normal PostCreator path with no further plugin code.
# Self is excluded (no point messaging yourself); the list is deduped.
def badge_members
guardian.ensure_can_send_private_messages!
badge = Badge.find_by(id: params[:badge_id])
raise Discourse::NotFound unless badge

usernames =
User
.joins(:user_badges)
.where(user_badges: { badge_id: badge.id })
.where(active: true)
.where.not(id: current_user.id)
.distinct
.pluck(:username)

render json: { usernames: usernames, badge: { id: badge.id, name: badge.display_name } }
end

# Adds a user to a topic's cumulative whisper conversation. From then on
# that user sees every whisper in the topic (both Guardian#can_see_post?
# and the topic-stream SQL filter grant visibility to participants).
Expand Down Expand Up @@ -387,22 +403,13 @@ def notify_staff_of_note(topic)
)

publish_note_alert(staff_user, topic, note, note_url)
publish_unread_count_bump(staff_user)
# The standard /notifications poll picks up the new unread row so
# both the bell dot and the in-dropdown shield-tab pip refresh
# together. No separate /mod-note-unread-count channel is needed.
staff_user.publish_notifications_state
end
end

# Publishes a small "+1" payload on a dedicated MessageBus channel the
# header pip / title-prefix subscriber listens on. A separate channel
# (independent of `/notification-alert/`) keeps the client-side reactivity
# focused on the moderator-notes counter rather than the bell badge.
def publish_unread_count_bump(staff_user)
MessageBus.publish(
"/mod-note-unread-count/#{staff_user.id}",
{ delta: 1 },
user_ids: [staff_user.id],
)
end

# Fires the small live notification pop-up for one staff member. The
# payload mirrors `PostAlerter.create_notification_alert`, but carries an
# explicit `translated_title` so the pop-up text clearly names a
Expand Down
178 changes: 0 additions & 178 deletions assets/javascripts/discourse/components/mod-note-header-pip.gjs

This file was deleted.

Loading