Skip to content
Open
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
Binary file added app/assets/images/outpost-favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions app/assets/stylesheets/pages/_notifications.scss
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,20 @@
display: block;
}

// Full-colour brand chip (e.g. the Outpost flame) instead of a muted line icon.
&__icon--image {
opacity: 1;
}

&__icon-img {
width: 28px;
height: 28px;
// A square, transparent brand mark (the Outpost flame favicon) — show the
// whole thing so nothing gets clipped.
object-fit: contain;
display: block;
}

&__main {
flex: 1 1 auto;
display: flex;
Expand Down
8 changes: 6 additions & 2 deletions app/components/notifications/item_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
(card_data wires the Stimulus controller + click action; links are ignored by
the controller). Reading is handled by opening the inbox. %>
<%= tag.li class: li_classes, data: card_data do %>
<span class="notifications-item__icon" aria-hidden="true">
<%= inline_svg_tag type_icon_path, class: "notifications-item__icon-svg" %>
<span class="<%= class_names("notifications-item__icon", "notifications-item__icon--image": type_icon_image) %>" aria-hidden="true">
<% if type_icon_image %>
<%= image_tag type_icon_image, alt: "", class: "notifications-item__icon-img" %>
<% else %>
<%= inline_svg_tag type_icon_path, class: "notifications-item__icon-svg" %>
<% end %>
</span>

<span class="notifications-item__main">
Expand Down
11 changes: 11 additions & 0 deletions app/components/notifications/item_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ class ItemComponent < ViewComponent::Base
"Notifications::ShopOrders::StatusChanged" => "bag"
}.freeze

# Types that show a cropped image chip instead of a line-art SVG icon — used
# to carry a bit of brand colour into the row (e.g. the Outpost flame).
TYPE_ICON_IMAGES = {
"Notifications::HardwareMovedToOutpost" => "outpost-favicon.png"
}.freeze

attr_reader :notification

def initialize(notification:)
Expand Down Expand Up @@ -59,6 +65,11 @@ def type_icon_path
"icons/notifications/#{name}.svg"
end

# An image asset to use as the icon chip instead of an SVG, or nil.
def type_icon_image
TYPE_ICON_IMAGES[notification.type]
end

def avatar_actor
notification.actor
end
Expand Down
7 changes: 7 additions & 0 deletions app/controllers/projects/lookout_sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ def create
started_at: Time.current
)

# Hardware projects live on Outpost now — nudge the builder there with an
# inbox notification rather than the old on-recorder popup, without blocking
# the recording.
if @project.hardware? && Flipper.enabled?(:hardware_to_outpost, current_user)
Notifications::HardwareMovedToOutpost.notify(recipient: current_user, record: @project)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Timelapse nudge lost without inbox

Medium Severity

The recorder Outpost modal was removed for users with only hardware_to_outpost, while the replacement notification is skipped unless week_2_release is enabled for the recipient inside Notification.notify. Those users no longer get any timelapse Outpost nudge.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7d92c89. Configure here.

end

render json: session_json(@session), status: :created
end

Expand Down
9 changes: 0 additions & 9 deletions app/javascript/controllers/lookout_capture_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,6 @@ export default class extends Controller {
this.setText(statusEl, "Your timelapse is ready!");
}
if (showVideo) await this.revealVideo();
this.showHardwareOutpostNotice();
return;
}
if (data.status === "failed") {
Expand Down Expand Up @@ -480,14 +479,6 @@ export default class extends Controller {
}
}

// Hardware moved to Outpost — once the timelapse is done, let the builder know.
// The notice is only rendered on the page for hardware projects with the flag
// on, so this is a no-op everywhere else.
showHardwareOutpostNotice() {
const dialog = document.getElementById("hardware-outpost-modal");
if (dialog && !dialog.open) dialog.showModal();
}

close(event) {
if (event) event.preventDefault();
this.cleanup();
Expand Down
35 changes: 35 additions & 0 deletions app/jobs/one_time/backfill_hardware_outpost_notifications_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

# One-off sweep to drop the "hardware moved to Outpost" notice into the inbox of
# everyone who already owns a hardware project — not just those who go on to
# record a timelapse (LookoutSessionsController#create fires the same notice for
# that path). New hardware projects can't be created on Stardance anymore
# (creation is redirected to Outpost), so this audience is a fixed set; run once
# when the feature is live.
#
# HardwareMovedToOutpost is aggregatable, so a user who already has an unread
# copy (e.g. from recording) just gets it bumped, not duplicated. notify() also
# self-gates on the notification system's own rollout flag (week_2_release), so
# users without it are skipped.
#
# DRY RUN BY DEFAULT: logs how many users it would notify and writes nothing.
# Pass dry_run: false to actually create the notifications.
class OneTime::BackfillHardwareOutpostNotificationsJob < ApplicationJob
queue_as :literally_whenever

def perform(dry_run: true)
user_ids = Project::Membership
.where(project_id: Project.where.not(hardware_stage: nil).select(:id))
.distinct
.pluck(:user_id)

Rails.logger.info("[BackfillHardwareOutpost] #{user_ids.size} hardware-project users (dry_run: #{dry_run})")
return if dry_run

notified = 0
User.where(id: user_ids).find_each do |user|
notified += 1 if Notifications::HardwareMovedToOutpost.notify(recipient: user)
end
Rails.logger.info("[BackfillHardwareOutpost] notified #{notified} of #{user_ids.size} users")
end
end
19 changes: 19 additions & 0 deletions app/models/notifications/hardware_moved_to_outpost.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module Notifications
# Heads-up that hardware projects live on Hack Club Outpost now, surfaced when
# a hardware project records a Lookout timelapse (replaces the old on-recorder
# popup). Inbox-only (low priority — no Slack/email), and aggregatable so a
# builder who records repeatedly keeps a single unread notice rather than a
# pile of them.
class HardwareMovedToOutpost < ::Notification
self.default_priority = :low
self.aggregatable = true
self.category_key = :hardware_moved_to_outpost
self.category_label = "Hardware moved to Outpost"
self.category_description = "Hardware projects have moved to Hack Club Outpost"
self.category_group = "General"

def preview_text
"Building hardware? It all happens over on Hack Club Outpost now."
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing aggregate group key

Medium Severity

HardwareMovedToOutpost sets aggregatable but leaves the default build_group_key (nil). Other aggregatable notification types define a non-nil key so the partial unique index can dedupe unread rows; with a nil group_key, concurrent notify calls can each insert a separate unread notification instead of one aggregated notice.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7d92c89. Configure here.

end
end
1 change: 1 addition & 0 deletions app/models/notifications/registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module Registry
Notifications::Payouts::ShipEventIssued
Notifications::Payouts::VoteDeficitBlocked
Notifications::ShopOrders::StatusChanged
Notifications::HardwareMovedToOutpost
].freeze

module_function
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<span class="notifications-item__verb">Hardware projects have moved to</span>
<%= link_to "Hack Club Outpost", "https://outpost.hackclub.com",
class: "notifications-item__actor",
target: "_blank", rel: "noopener" %>
6 changes: 0 additions & 6 deletions app/views/projects/lookout_sessions/record.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,3 @@
hidden: true %>
</section>
</main>

<%# Hardware moved to Outpost — once this timelapse finishes, the capture
controller pops this notice (rendered only for hardware projects). %>
<% if @project.hardware? && Flipper.enabled?(:hardware_to_outpost, current_user) %>
<%= render "projects/hardware_outpost_modal" %>
<% end %>