Skip to content
Draft
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 app/assets/stylesheets/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ $danger: #EB5959;
$success: #2ECC71;
$info: #58A09A;
$brand: #4B68FF;
$brand-comp: #F05137;


$data: (
Expand Down
41 changes: 41 additions & 0 deletions app/assets/stylesheets/complaints.scss
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,44 @@
.is-lead + h1.complaint-title {
margin-top: -1rem;
}

.is-brand-comp {
color: $brand-comp;
}

.with-subtitle {
margin-bottom: 0;
}

.subtitle {
margin-top: 0;
font-weight: normal;
font-size: 1.2rem;
}

.modules {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
gap: 0.5rem;
}

.module-widget {
border: 1px solid $muted-graphic;
border-radius: 0.2rem;
padding: 0.5rem;
color: $key !important;

> h4 {
text-decoration: underline;
}

> p {
text-decoration: none;
}

&:hover {
background: $primary;
color: white !important;
text-decoration: none !important;
}
}
17 changes: 17 additions & 0 deletions app/controllers/complaints_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class ComplaintsController < ApplicationController
before_action :access_check, only: [:show, :comment]
before_action :write_access_check, only: [:self_assign, :update_status, :change_content_type]
before_action :verify_staff, only: [:reports, :reporting]
before_action :training_access, only: [:training]

def index
render layout: 'without_sidebar'
Expand Down Expand Up @@ -202,6 +203,16 @@ def reporting
render layout: 'without_sidebar'
end

def training
pages = Dir.glob(Rails.root.join('app', 'views', 'complaints', 'training', '*.html.erb'))
.map { |page| File.basename(page, '.html.erb') }
if pages.include?(params[:page])
render "complaints/training/#{params[:page]}", layout: 'osa_training'
else
not_found!
end
end

private

def access_check
Expand Down Expand Up @@ -235,4 +246,10 @@ def set_complaint

@complaint
end

def training_access
unless user_signed_in? && (current_user.staff? || current_user.at_least_moderator?)
not_found!
end
end
end
11 changes: 9 additions & 2 deletions app/views/complaints/report.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
<p>
Thank you for taking the time to make a report. If you've seen harmful, abusive, or illegal content on our
communities, you can report this to us here. You can also use this page if you've received a message saying we've
classified your content as harmful, abusive, or illegal and you wish to contest it.
classified your content as harmful, abusive, or illegal and you wish to contest it, or if you have a complaint
about our processes or our compliance with our duties.
</p>

<div class="notice is-warning has-color-yellow-900 flex-row-always ai-c has-font-size-caption h-m-b-4">
Expand Down Expand Up @@ -45,7 +46,8 @@
<%= label_tag :reported_url, 'Where is this content?' %>
<div class="form-caption">
Enter a URL or link to where we can find this content on our network. You can use the Copy Link button under
posts to get a direct link to a post.
posts to get a direct link to a post. Enter N/A if you are not complaining about specific content; add more
details below.
</div>
<%= text_field_tag :reported_url, nil, class: 'form-element', required: true %>
</div>
Expand Down Expand Up @@ -95,6 +97,11 @@
Tell us any additional information you have about this report. Is there any additional content we removed that
you would like to include? Provide detailed reasoning explaining why you disagree with our classification.
</div>
<div class="form-caption hidden" data-report-type="process">
Tell us the details of your complaint. Do you believe we have failed to comply with our statutory duties, or
that we have failed to follow our policies and procedures? In what way? Provide any relevant examples or
evidence.
</div>
<%= text_area_tag :content, nil, class: 'form-element', required: true, rows: 10 %>
</div>

Expand Down
274 changes: 274 additions & 0 deletions app/views/complaints/training/definitions.html.erb

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions app/views/complaints/training/home.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<h1 class="is-brand-comp with-subtitle">Online Safety Act</h1>
<h2 class="subtitle">Moderator Training</h2>

<p>
As part of our responsibilities under the Online Safety Act, we're obligated to provide training to all staff and
volunteers undertaking moderation duties.
</p>
<p>
Take your training here. Completing all the modules will record that you have completed the training, but you can
revisit these pages at any time if you need guidance when making moderation decisions.
</p>

<div class="modules">
<%= link_to osa_training_path('overview'), class: 'module-widget' do %>
<h4>Overview</h4>
<p class="has-font-size-small">
An overview of the Online Safety Act, our duties, and your responsibilities as a volunteer moderator.
</p>
<% end %>
<%= link_to osa_training_path('illegal-content'), class: 'module-widget' do %>
<h4>Priority & Non-Priority Illegal Content</h4>
<p class="has-font-size-small">
An explanation of the difference between the 17 types of priority illegal content, and other applicable types of
non-priority illegal content.
</p>
<% end %>
<%= link_to osa_training_path('definitions'), class: 'module-widget' do %>
<h4>Definitions</h4>
<p class="has-font-size-small">
Definitions of all the types of illegal content which apply to us.
</p>
<% end %>
<%= link_to osa_training_path('handling'), class: 'module-widget' do %>
<h4>Handling Illegal Content</h4>
<p class="has-font-size-small">
Your responsibilities and the steps you need to take in response to identifying potentially illegal content.
</p>
<% end %>
<%= link_to osa_training_path('higher-risk'), class: 'module-widget' do %>
<h4>Higher-Risk Content</h4>
<p class="has-font-size-small">
Some types of content are more likely to occur in our communities than others. More detail on those here.
</p>
<% end %>
<%= link_to osa_training_path('conclusion'), class: 'module-widget' do %>
<h4>Conclusion</h4>
<p class="has-font-size-small">
Thank you for taking the time to complete this training. Mark it as complete here and come back here if you need
to refer back to it.
</p>
<% end %>
</div>
19 changes: 19 additions & 0 deletions app/views/complaints/training/illegal-content.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<p class="has-font-size-caption has-color-tertiary-600">Last updated 12 March 2026</p>

<h1 class="is-brand-comp with-subtitle">Online Safety Act</h1>
<h2 class="subtitle">Priority & Non-Priority Illegal Content</h2>

<p>
The Act sets out 17 types of priority illegal content, and a number of types of non-priority illegal content. We have
carried out a risk assessment for all types of priority illegal content, and applicable types of non-priority illegal
content, which details the likelihood and impact of each type of content on our platform specifically.
</p>



<div class="has-text-align-right">
<%= link_to osa_training_path('definitions') do %>
<strong>Next</strong><br/>
Definitions &raquo;
<% end %>
</div>
64 changes: 64 additions & 0 deletions app/views/complaints/training/overview.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<p class="has-font-size-caption has-color-tertiary-600">Last updated 12 March 2026</p>

<h1 class="is-brand-comp with-subtitle">Online Safety Act</h1>
<h2 class="subtitle">Overview</h2>

<p>
The Online Safety Act 2023 (<a href="https://www.legislation.gov.uk/ukpga/2023/50">available here</a>) is a law
established in the UK in 2023 with the aim of improving online safety, particularly with regard to children, but with
wide-ranging effects for all online services. All services with UK users are required to comply with the Act. There
are ongoing cases which will define whether this is enforceable on non-UK entities in practice, but because Codidact
is a UK-based entity, we are clearly within scope and required to comply.
</p>
<p>
The Act is enforced by the UK's communications regulator, Ofcom, which also sets out the Register of Risks and Codes
of Practice on which our approach is based.
</p>

<h3>Types of service</h3>
<p>
The Act defines two types of service: search services and user-to-user services. User-to-user services are those where
users may interact with one another; this is where we fall. There are different requirements imposed on each kind of
service, which for user-to-user services primarily focus on preventing and removing harmful content, and protecting
users from related harms.
</p>

<h3>Our responsibilities</h3>
<p>
Responsibility for compliance with the requirements of the Act obviously falls on us (meaning the Codidact Foundation
as the organisation running the platform). The Foundation designates one of the Board of Directors as a named
individual with ultimate responsibility for compliance with the Act, which is currently
<a href="https://meta.codidact.com/users/8045">ArtOfCode</a>.
</p>
<p>
One of our responsibilities is to ensure that our volunteer moderators (that's you) have an awareness of the Act and
are provided with appropriate training in order to equip them to handle any harmful content which may appear on the
platform.
</p>

<h3>Your responsibilities</h3>
<p>
As a volunteer moderator, your job is to guide, curate, and set the tone for your community. Part of that job is
protecting the community from any unwanted content. The majority of the time, that might take the form of off-topic
posts, arguments between users, or handling flags for your attention. Unfortunately, it may also take the form of
harmful or illegal content covered by the Act, and one of your responsibilities is to ensure this is dealt with and
escalated appropriately.
</p>
<p>
To be clear: we're <strong>not</strong> expecting you to handle harmful or illegal content alone. Our ask of you is
simple: if you identify something that you think would be covered by the Act, please:
</p>
<ul>
<li>Delete it to remove it from public view</li>
<li>
Escalate it to the Community Team straight away: either ping us in Discord, or flag it yourself and escalate your
own flag for us to review.
</li>
</ul>

<div class="has-text-align-right">
<%= link_to osa_training_path('illegal-content') do %>
<strong>Next</strong><br/>
Priority & Non-Priority Illegal Content &raquo;
<% end %>
</div>
60 changes: 60 additions & 0 deletions app/views/layouts/osa_training.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<%= render 'layouts/head' %>
</head>
<body class="<%= Rails.env.development? ? 'development' : '' %>"
data-user-id="<%= user_signed_in? ? current_user.id : 'none' %>"
data-mathjax="<%= SiteSetting['MathJaxEnabled'] %>">
<%= render 'layouts/header' %>

<main class="container">
<div class="grid">
<div class="grid--cell is-8-lg is-12">
<div class="has-padding-4">
<%= render 'shared/notices' %>

<% if @first_visit_notice %>
<%= render 'notices/first_visit' %>
<% end %>

<%= yield %>
</div>
</div>

<div class="grid--cell is-4-lg is-12 js-sidebar" role="complementary">
<nav class="widget">
<div class="widget--body">
<h3>Modules</h3>
<p>Navigate through the modules here.</p>
<div class="menu">
<%= link_to 'Home', osa_training_path('home'),
class: "menu--item #{'is-active' if params[:page] == 'home'}" %>
<%= link_to 'Overview', osa_training_path('overview'),
class: "menu--item #{'is-active' if params[:page] == 'overview'}" %>
<%= link_to 'Priority & Non-Prority Illegal Content', osa_training_path('illegal-content'),
class: "menu--item #{'is-active' if params[:page] == 'illegal-content'}" %>
<%= link_to 'Definitions', osa_training_path('definitions'),
class: "menu--item #{'is-active' if params[:page] == 'definitions'}" %>
<%= link_to 'Handling Illegal Content', osa_training_path('handling'),
class: "menu--item #{'is-active' if params[:page] == 'handling'}" %>
<%= link_to 'Higher-Risk Content', osa_training_path('higher-risk'),
class: "menu--item #{'is-active' if params[:page] == 'higher-risk'}" %>
<%= link_to 'Conclusion', osa_training_path('conclusion'),
class: "menu--item #{'is-active' if params[:page] == 'conclusion'}" %>
</div>
</div>
</nav>
</div>
</div>
</main>

<%= render 'layouts/footer' %>

<%= render 'layouts/matomo' %>

<% if Rails.env.production? %>
<script src="https://7zb04r9ckbwg.statuspage.io/embed/script.js"></script>
<% end %>
</body>
</html>
18 changes: 16 additions & 2 deletions config/config/safety_center.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,24 @@ outcomes:
copyright: *illegal_upheld
appeal:
content: our community team agreed with your appeal and have reversed the action taken in your case.
process:
content: our community team have reviewed your complaint and found information to substantiate it.

actionable:
name: Actionable
description: The content is actionable but the reported classification is not correct. NOT applicable to appeals.
description: The content is actionable but the reported classification is not correct. NOT applicable to appeals or
process complaints.
user_facing:
illegal: &illegal_actionable
content: our community team agreed that the content you reported was actionable and have taken appropriate
action, but have changed your classification of the content for reporting purposes.
abusive: *illegal_actionable
copyright: *illegal_actionable
appeal: ~
appeal:
content: ~
process:
content: ~

disputed:
name: Disputed
description: The reporter's classification is disputed; the content does not appear to be actionable; in the case of
Expand All @@ -69,6 +77,8 @@ outcomes:
appeal:
content: our community team have reviewed your appeal and have decided that the action taken in your case was
appropriate.
process:
content: our community team have reviewed your complaint but found no information to substantiate it.

report_types:
illegal:
Expand All @@ -87,6 +97,10 @@ report_types:
enabled: true
name: Classification Appeal
description: an appeal regarding how we've handled your content
process:
enabled: true
name: Complaint about our process
description: a complaint about our processes or compliance with our duties

# This list is sourced from Ofcom's list of the 17 types of priority illegal content, which is in turn sourced from
# Schedules 5-7 of the Online Safety Act.
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@
post 'report/:token/content_type', to: 'complaints#change_content_type', as: :update_complaint_content_type
get 'reports', to: 'complaints#reports', as: :complaints
get 'reporting', to: 'complaints#reporting', as: :complaints_reporting
get 'training/:page', to: 'complaints#training', as: :osa_training
end

get '403', to: 'errors#forbidden'
Expand Down
2 changes: 1 addition & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.2].define(version: 2025_12_26_185531) do
ActiveRecord::Schema[7.2].define(version: 2026_02_08_223211) do
create_table "abilities", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "community_id"
t.string "name"
Expand Down
Loading