diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index e857065372afc93421ca2d9029358e07d57c2cde..5c13c868c9895be2fa78d05dd5461ba9237ee4c3 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -1,4 +1,6 @@
 class SessionsController < ApplicationController
+  before_action :authorize_shiftcoordinator, except: [:index, :show]
+
   def index
     @conference = Conference.find_by(slug: params[:conference_slug])
     @sessions = @conference.sessions.includes(:stage).order(:starts_at)
@@ -22,4 +24,37 @@ class SessionsController < ApplicationController
     @session = Session.includes(:stage).find_by(conference: @conference, ref_id: params[:ref_id])
     @users = User.all
   end
+
+  def update_notes
+    conference = Conference.find_by!(slug: params[:conference_slug])
+    session = Session.find_by!(conference:, ref_id: params[:ref_id])
+    session.notes = params[:note]
+
+    if session.save
+      Rails.logger.debug("Updated notes on session #{session.ref_id} to: #{session.notes}")
+      Turbo::StreamsChannel.broadcast_replace_to(
+        session.conference,
+        target: helpers.dom_id(session),
+        partial: "sessions/session",
+        locals: { session: }
+      )
+      flash.now[:success] = 'Notes saved successfully.'
+      respond_to do |format|
+        format.turbo_stream { render turbo_stream: turbo_stream.replace(helpers.dom_id(session), partial: "sessions/session", locals: { session: }) }
+        format.html { redirect_to conference_session_path(session.conference, session), success: 'Notes saved successfully.' }
+      end
+    else
+      flash.now[:alert] = 'Failed to save notes.'
+      respond_to do |format|
+        format.turbo_stream { render turbo_stream: turbo_stream.replace(helpers.dom_id(session), partial: "sessions/session", locals: { session: }), status: :unprocessable_entity }
+        format.html { render :show, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  private
+
+  def notes_params
+    params.require(:session).permit(:notes)
+  end
 end
diff --git a/app/javascript/controllers/session_controller.js b/app/javascript/controllers/session_controller.js
index ffc5dfdde1cc32f720e39f404467f8a66e20a379..3694c698ddd8374feeec279a56234136509faeaa 100644
--- a/app/javascript/controllers/session_controller.js
+++ b/app/javascript/controllers/session_controller.js
@@ -53,8 +53,8 @@ export default class extends Controller {
   submitWithPrompt(event) {
     event.preventDefault();
 
-    const reason = prompt(event.target.dataset.prompt);
-    if (reason !== null && reason.trim() !== "") {
+    const reason = prompt(event.target.dataset.prompt, event.target.dataset?.prefill);
+    if (reason !== null && event.target.dataset?.submitempty || reason.trim() !== "") {
       const input = document.createElement("input");
       input.type = "hidden";
       input.name = "note";
diff --git a/app/views/sessions/_session.html.erb b/app/views/sessions/_session.html.erb
index 5d0c016151f36e45376834769a6ba43b46e3cd63..90be7e61c531c29973fdaf0934ecbf0d2a2f5d13 100644
--- a/app/views/sessions/_session.html.erb
+++ b/app/views/sessions/_session.html.erb
@@ -21,6 +21,11 @@
         <%= session.stage.name %>
       </span>
       <span class="absolute top-0 right-0 text-3xl only-loggedin hidden">
+        <%= form_with url: update_notes_conference_session_path(session.conference, session), method: :patch, class: "inline", data: { turbo_frame: dom_id(session) } do |form| %>
+          <%= link_to update_notes_conference_session_path(session.conference, session), class: "pr-1 only-shiftcoordinator hidden", title: "Edit Note", aria_label: "Edit Note", data: { action: "session#submitWithPrompt", prompt: "Please enter the note", prefill: session.notes, submitempty: true, turbo_prefetch:"false" } do %>
+              📝
+          <% end %>
+        <% end %>
         <%= link_to conference_session_candidates_path(session.conference, session), class: "pr-1 only-candidate hidden", title: "Withdraw", aria_label: "Withdraw", data: { turbo_method: :delete, turbo_frame: dom_id(session) }, method: :delete do %>
           🙅
         <% end %>
@@ -50,6 +55,13 @@
       <% end %>
     <% end %>
 
+    <% unless session.notes.blank? %>
+    <div class="flex items-center bg-blue-100 bg-opacity-50 border border-blue-500 text-blue-700 p-2 my-2 rounded-md" role="alert">
+      <span class="text-xl mr-2" aria-label="Notes" title="Notes">ℹ️</span>
+      <span><%= session.notes %></span>
+    </div>
+    <% end %>
+
     <small>assigned (<%= session.assignments.length %>)</small>
     <ul class="inline-flex flex-wrap gap-1 my-1">
       <% session.assignments.each do |assignment| %>
diff --git a/config/routes.rb b/config/routes.rb
index cd2292ffbe757144f65571d54343cee92a56683c..cce0b9be65876b1da221bde24f2e846afa76e7d0 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -17,6 +17,9 @@ Rails.application.routes.draw do
     get 'stats', on: :member
     get ':date', action: :show, on: :member, as: :date, date: /\d{4}-\d{2}-\d{2}/
     resources :sessions, param: :ref_id do
+      member do
+        patch :update_notes
+      end
       resources :assignments, only: [:create, :destroy]
       resources :candidates, only: [:create, :destroy]
       delete 'candidates', to: 'candidates#destroy_self'
diff --git a/db/migrate/20241223004818_add_notes_to_sessions.rb b/db/migrate/20241223004818_add_notes_to_sessions.rb
new file mode 100644
index 0000000000000000000000000000000000000000..720888b8f3cfe23ad78bfa7e769c74f6df4600e0
--- /dev/null
+++ b/db/migrate/20241223004818_add_notes_to_sessions.rb
@@ -0,0 +1,5 @@
+class AddNotesToSessions < ActiveRecord::Migration[7.1]
+  def change
+    add_column :sessions, :notes, :string
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index d3627a455be68900b67ab2e2fa0e07ebf0cefea6..2347d52f1ab6a2bb47c65399ee8d2e5a1cca46f8 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema[7.1].define(version: 2024_12_22_215716) do
+ActiveRecord::Schema[7.1].define(version: 2024_12_23_004818) do
   create_table "assignments", force: :cascade do |t|
     t.integer "user_id", null: false
     t.integer "session_id", null: false
@@ -136,6 +136,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_12_22_215716) do
     t.datetime "created_at", null: false
     t.datetime "updated_at", null: false
     t.string "ref_id"
+    t.string "notes"
     t.index ["conference_id"], name: "index_sessions_on_conference_id"
     t.index ["ref_id", "conference_id"], name: "index_sessions_on_ref_id_and_conference_id", unique: true
     t.index ["stage_id"], name: "index_sessions_on_stage_id"