Date: Tue, 18 Aug 2026 23:04:37 +0200
Subject: [PATCH 2/4] Render assistant chat replies as formatted markdown
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Wires the renderer into the message partial behind a streaming gate, ships the
.msg-prose component, and tells the agent which subset to emit.
Parsing happens once, on the final render. Three broadcast paths reach the
partial, and each passes streaming: explicitly rather than inferring state from
a billing column: mid-stream and the create append stay plain, the final replace
formats. The append needs the gate even though the row is empty when it commits,
because broadcast_append_later_to renders inside
Turbo::Streams::ActionBroadcastJob 100-300ms later from a GlobalID reload, by
which point the streaming job has written chunks. It is also the render the user
sees first: every replace fired before it targets a dom_id not yet in the DOM,
and Turbo drops those silently. Ungated, it would format half-written markdown
on every reply.
One path the gate cannot reach is a page load landing mid-reply, which formats a
half-written message. It self-repairs on the next chunk and the window is a few
seconds, so it is accepted rather than fixed; the deterministic fix wants a
terminal-state column on messages, which cancel/retry would need anyway.
.msg-prose must stay after .msg-body in the stylesheet — both are single-class
selectors on the same element, so source order breaks the white-space tie.
.msg-body also gains overflow-wrap: anywhere, so long paths and URLs stop
overflowing the bubble on every path, formatted or not. Fenced blocks get
white-space: pre and scroll horizontally instead of breaking a line mid-token.
The prompt gains one bullet naming the supported subset and asking for bare
URLs. It opens with "write short paragraphs", which sits in tension with the two
rules that keep a tool call prose-free, so turn-taking was re-checked against
the live model: the tool call still arrives with empty content, and the
post-tool response is still empty.
Tests cover all three broadcast paths — the two that must stay plain and the one
that must format — plus the helper's three-way branch, the page-load path, and
the case where a tool-call message also carries prose, which the partial can
represent regardless of what the model does.
---
app/assets/tailwind/application.css | 53 ++++++++++++++++++-
app/helpers/messages_helper.rb | 19 +++++++
app/jobs/chat_respond_job.rb | 5 +-
app/models/message.rb | 7 ++-
.../generator_agent/instructions.txt.erb | 1 +
app/views/messages/_message.html.erb | 3 +-
docs/02-architecture/04-design-system.md | 2 +-
.../projects_controller_show_test.rb | 35 ++++++++++++
test/helpers/messages_helper_test.rb | 43 +++++++++++++++
test/jobs/chat_respond_job_test.rb | 24 +++++++++
test/models/message_test.rb | 32 +++++++++++
11 files changed, 219 insertions(+), 5 deletions(-)
diff --git a/app/assets/tailwind/application.css b/app/assets/tailwind/application.css
index 51c7e4e..0375bee 100644
--- a/app/assets/tailwind/application.css
+++ b/app/assets/tailwind/application.css
@@ -861,7 +861,7 @@ body {
letter-spacing: var(--tracking-caps);
margin-bottom: 4px;
}
-.msg-body { color: var(--fg); white-space: pre-wrap; line-height: 1.55; }
+.msg-body { color: var(--fg); white-space: pre-wrap; line-height: 1.55; overflow-wrap: anywhere; }
.msg-pill {
font-family: var(--hi-font-mono);
font-size: 20px;
@@ -880,6 +880,57 @@ body {
animation: hi-blink 1.6s ease-in-out infinite;
}
+/* Assistant replies rendered from markdown (lib/markdown.rb). Replaces .msg-body's
+ pre-wrap — markdown supplies / structure, and pre-wrap on top of that
+ would double every gap. Must stay after .msg-body: equal specificity, so source
+ order decides. */
+.msg-prose { white-space: normal; }
+.msg-prose > :first-child { margin-block-start: 0; }
+.msg-prose > :last-child { margin-block-end: 0; }
+.msg-prose p { margin-block: 0 10px; }
+.msg-prose ul,
+.msg-prose ol {
+ margin-block: 0 10px;
+ padding-inline-start: 1.4em;
+}
+.msg-prose ul { list-style: disc; }
+.msg-prose ol { list-style: decimal; }
+.msg-prose li + li { margin-block-start: 6px; }
+/* The chat model separates numbered items with blank lines, which makes the list
+ loose and wraps each item's content in
. Collapse those. */
+.msg-prose li > p { margin-block: 0; }
+.msg-prose li > p + p { margin-block-start: 6px; }
+.msg-prose strong { font-weight: 600; }
+/* Inline code. A fenced block is
, handled below, so this can never
+ receive a newline and needs no white-space of its own. */
+.msg-prose code {
+ font-family: var(--hi-font-mono);
+ font-size: 0.92em;
+ background: var(--bg-sunken);
+ padding: 1px 6px;
+ border-radius: var(--radius-sm);
+}
+/* Fenced block, at any depth — top level or inside an . The panel (padding +
+ background) lives on rather than on the inner so it stays put
+ while the content scrolls horizontally instead of sliding out from under it. */
+.msg-prose pre {
+ margin-block: 0 10px;
+ padding: 8px 10px;
+ background: var(--bg-sunken);
+ border-radius: var(--radius-sm);
+ /* `pre`, not `pre-wrap`: a code line must not be broken mid-token. It also
+ neutralises the inherited overflow-wrap: anywhere, which would otherwise
+ split a long URL or path inside the block. With wrapping off the line can
+ overflow, which is what overflow-x scrolls. */
+ white-space: pre;
+ overflow-x: auto;
+}
+.msg-prose pre > code {
+ background: none;
+ padding: 0;
+ border-radius: 0;
+}
+
/* ============================================================
COMPOSER
============================================================ */
diff --git a/app/helpers/messages_helper.rb b/app/helpers/messages_helper.rb
index 19b552e..5dc16b9 100644
--- a/app/helpers/messages_helper.rb
+++ b/app/helpers/messages_helper.rb
@@ -15,4 +15,23 @@ def tool_call_pill_text(message)
"running: #{message.tool_calls.map(&:name).uniq.join(", ")}"
end
end
+
+ # Assistant replies render as markdown once the stream has finished; user text and
+ # mid-stream text stay plain. Returns an html_safe fragment in the formatted case
+ # and a plain String otherwise, so the view's <%= %> escapes it.
+ def message_body_html(message, streaming: false)
+ return message.content.to_s unless format_message_body?(message, streaming)
+
+ Markdown.render(message.content)
+ end
+
+ def message_body_class(message, streaming: false)
+ format_message_body?(message, streaming) ? "msg-body msg-prose" : "msg-body"
+ end
+
+ private
+
+ def format_message_body?(message, streaming)
+ !streaming && message.role == "assistant"
+ end
end
diff --git a/app/jobs/chat_respond_job.rb b/app/jobs/chat_respond_job.rb
index 9b442c2..1a700d1 100644
--- a/app/jobs/chat_respond_job.rb
+++ b/app/jobs/chat_respond_job.rb
@@ -81,7 +81,10 @@ def broadcast_replace(project, message)
project,
target: ActionView::RecordIdentifier.dom_id(message),
partial: "messages/message",
- locals: { message: message }
+ # Mid-stream: markdown is still incomplete (an unclosed ``` fence would
+ # swallow the rest of the reply), so render plain text and let the final
+ # broadcast from Message#broadcast_replace_message format it.
+ locals: { message: message, streaming: true }
)
end
end
diff --git a/app/models/message.rb b/app/models/message.rb
index e6ae0a0..66a91d2 100644
--- a/app/models/message.rb
+++ b/app/models/message.rb
@@ -25,7 +25,12 @@ def broadcast_append_message
broadcast_append_later_to chat.project,
target: "messages",
partial: "messages/message",
- locals: { message: self }
+ # `_later` renders inside Turbo::Streams::ActionBroadcastJob, ~100-300ms
+ # after this commit, from a GlobalID reload — by which point ChatRespondJob
+ # has already written chunks. An append is only ever a user message or
+ # RubyLLM's empty assistant placeholder, never a finished reply, so it must
+ # never format.
+ locals: { message: self, streaming: true }
end
def broadcast_replace_message
diff --git a/app/prompts/generator_agent/instructions.txt.erb b/app/prompts/generator_agent/instructions.txt.erb
index d773548..53c6f4e 100644
--- a/app/prompts/generator_agent/instructions.txt.erb
+++ b/app/prompts/generator_agent/instructions.txt.erb
@@ -17,6 +17,7 @@ GENERAL RULES:
- Do NOT generate an implementation plan yourself. Do NOT list models, controllers, or files. That's the backend's job.
- After `create_application` or `modify_application` returns, leave your text response empty. Do not narrate that a build started or finished — those events surface in the UI on their own; you don't need to announce them.
- Call each tool AT MOST ONCE per user turn. After a tool returns, write your final reply (or no reply at all, per the rule above) and stop — do not call the same tool again until the user replies. Calling the same tool twice in one turn corrupts the conversation history.
+- Formatting: write short paragraphs. Use **bold** for emphasis, `backticks` for code, file and command names, and `-` or `1.` lists where a list genuinely helps. Do not use headings, tables, images, or raw HTML. Write URLs plainly rather than as markdown links. If you need to show markup, put it in backticks.
Current project state:
<%= current_state %>
diff --git a/app/views/messages/_message.html.erb b/app/views/messages/_message.html.erb
index a8941bd..fc63f16 100644
--- a/app/views/messages/_message.html.erb
+++ b/app/views/messages/_message.html.erb
@@ -1,3 +1,4 @@
+<% streaming = local_assigns.fetch(:streaming, false) %>
<% if message.visible_in_chat? %>
@@ -7,7 +8,7 @@
<%= tool_call_pill_text(message) %>
<% end %>
<% if message.content.to_s.strip.present? %>
-
<%= message.content %>
+
<%= message_body_html(message, streaming: streaming) %>
<% end %>
<% end %>
diff --git a/docs/02-architecture/04-design-system.md b/docs/02-architecture/04-design-system.md
index dc81666..e3e312b 100644
--- a/docs/02-architecture/04-design-system.md
+++ b/docs/02-architecture/04-design-system.md
@@ -84,7 +84,7 @@ that consume it.
| `.tag` (`--pending` `--gen` `--ok` `--err` `--running` `--starting` `--stopped` `--failed` `--new` `--generating` `--ready`) + `.tag-dot` | same | revisions/_revision, projects/index, projects/show, projects/_state_tag, previews/_* — the build-state variants (`--new` `--generating` `--ready` `--failed`) via the `project_state_tag` helper (app/helpers/projects_helper.rb) |
| `.project-card` (+ stripe + status modifier) | same | projects/index |
| `.revisions`, `.revision-row` (+ `--ok` `--gen` `--pend` `--err`) | same | revisions/_list, _revision |
-| `.msg-bubble`, `.msg-role`, `.msg-pill` | same | messages/_message |
+| `.msg-bubble`, `.msg-role`, `.msg-pill`, `.msg-prose` | same | messages/_message — `.msg-prose` is added alongside `.msg-body` on a **finished assistant** reply only (`message_body_class`), and styles the markdown subset `lib/markdown.rb` emits: `p br strong em code pre ul ol li`. It overrides `.msg-body`'s `white-space: pre-wrap` by source order (equal specificity), so it must stay **after** `.msg-body` in the file. Mid-stream and user text keep plain `.msg-body`. |
| `.composer`, `.suggestion-chip` | same | messages/_form, suggestions/_frame, projects/new |
| `.preview-pane` (+ header / body / empty / error) + `.preview-frame` | same | previews/_pane, _running, _starting, _stopped, _failed |
| `.h-display`, `.h-section`, `.lede`, `.eyebrow`, `.numeral`, `.kanji`, `.mono` | same | home/index (display + lede + kanji), layouts (nav brand kanji), home/dashboard (the one remaining `h-section` "Welcome back" + eyebrow + numeral). `.eyebrow` doubles as the page **breadcrumb** on projects/index ("projects"), projects/new + projects/show (`
` — parent link · current crumb), and devise/registrations/edit ("account"); a linked crumb (`.eyebrow a`) inherits the muted look, hovers accent. These pages dropped their `h1.h-section` heading in favour of the breadcrumb. `.eyebrow` also labels in-form sub-sections (devise account, projects/new). |
diff --git a/test/controllers/projects_controller_show_test.rb b/test/controllers/projects_controller_show_test.rb
index 2860927..fa9a591 100644
--- a/test/controllers/projects_controller_show_test.rb
+++ b/test/controllers/projects_controller_show_test.rb
@@ -184,6 +184,41 @@ class ProjectsControllerShowTest < ActionDispatch::IntegrationTest
end
end
+ # A stored assistant reply is a finished reply on the page-load path, so the
+ # partial formats it. The user message from setup stays plain in the same feed.
+ test "assistant reply renders as formatted markdown; user message stays plain" do
+ @chat.messages.create!(role: :assistant, content: "1. **bold** step")
+
+ get project_url(@project)
+ assert_response :success
+ assert_select "div#messages" do
+ assert_select ".msg-asst .msg-body.msg-prose ol li strong", text: "bold"
+ assert_select ".msg-user .msg-body:not(.msg-prose)", 1
+ end
+ assert_select ".msg-prose", text: /\*\*/, count: 0
+ end
+
+ # The prompt tells the agent that a tool call is its entire response, but the
+ # partial renders the pill and the body independently, so prose alongside a
+ # tool call is representable — and since this change that prose is formatted.
+ # CLAUDE.md logs the refused-tool-call flash as a known deferred UX issue, so
+ # pin what the coexisting case actually renders rather than assuming it cannot
+ # happen.
+ test "a tool-call message that also carries prose renders the pill and a formatted body" do
+ message = @chat.messages.create!(role: :assistant, content: "Starting on the **habit tracker** now.")
+ message.tool_calls.create!(
+ tool_call_id: "tc_show", name: "create_application",
+ arguments: { "intent" => "habit tracker" }
+ )
+
+ get project_url(@project)
+ assert_response :success
+ assert_select "##{ActionView::RecordIdentifier.dom_id(message)}" do
+ assert_select ".msg-pill", text: /Build started: habit tracker/
+ assert_select ".msg-body.msg-prose strong", text: "habit tracker"
+ end
+ end
+
test "duplicated inline flash strip is gone; layout-level strip still renders (regression guard)" do
post project_messages_url(@project), params: { message: { content: "" } }
follow_redirect!
diff --git a/test/helpers/messages_helper_test.rb b/test/helpers/messages_helper_test.rb
index a4d6139..e7f123e 100644
--- a/test/helpers/messages_helper_test.rb
+++ b/test/helpers/messages_helper_test.rb
@@ -48,4 +48,47 @@ class MessagesHelperTest < ActionView::TestCase
assert_equal "running: some_other_tool", tool_call_pill_text(@message)
end
+
+ test "message_body_html formats a finished assistant message" do
+ @message.update!(content: "1. **hi**")
+
+ html = message_body_html(@message)
+
+ assert_includes html, "hi "
+ assert_includes html, ""
+ refute_includes html, "**"
+ assert_predicate html, :html_safe?
+ end
+
+ test "message_body_html leaves a mid-stream assistant message plain" do
+ @message.update!(content: "1. **hi**")
+
+ html = message_body_html(@message, streaming: true)
+
+ assert_equal "1. **hi**", html
+ refute_predicate html, :html_safe?
+ end
+
+ test "message_body_html leaves a user message plain" do
+ user_message = @chat.messages.create!(role: :user, content: "1. **hi**")
+
+ html = message_body_html(user_message)
+
+ assert_equal "1. **hi**", html
+ refute_predicate html, :html_safe?
+ end
+
+ test "message_body_class adds msg-prose only for a finished assistant message" do
+ assert_equal "msg-body msg-prose", message_body_class(@message)
+ end
+
+ test "message_body_class stays msg-body for a user message" do
+ user_message = @chat.messages.create!(role: :user, content: "hi")
+
+ assert_equal "msg-body", message_body_class(user_message)
+ end
+
+ test "message_body_class stays msg-body mid-stream" do
+ assert_equal "msg-body", message_body_class(@message, streaming: true)
+ end
end
diff --git a/test/jobs/chat_respond_job_test.rb b/test/jobs/chat_respond_job_test.rb
index 899a5ae..2bce1fc 100644
--- a/test/jobs/chat_respond_job_test.rb
+++ b/test/jobs/chat_respond_job_test.rb
@@ -60,6 +60,18 @@ class ChatRespondJobTest < ActiveJob::TestCase
assert_equal "", latest_assistant.content
end
+ test "mid-stream broadcast renders plain text, not markdown" do
+ stub_complete(chunks: [ "**bold**" ]) do
+ perform_enqueued_jobs { ChatRespondJob.perform_now(@user_message.id) }
+ end
+
+ payloads = decoded_broadcasts
+ assert payloads.any? { |p| p.include?("**bold**") },
+ "expected the literal markdown source in a mid-stream broadcast"
+ assert payloads.none? { |p| p.include?("bold ") },
+ "mid-stream broadcasts must not format markdown"
+ end
+
test "applies the project's chat model selection to the chat on each turn" do
@project.update!(chat_model: "anthropic/claude-sonnet-4.6")
stub_complete(chunks: [ "ok" ]) do
@@ -124,6 +136,10 @@ class ChatRespondJobTest < ActiveJob::TestCase
assert_includes rendered, "describe a Rails web application"
assert_includes rendered, "Current project state:"
assert_includes rendered, "No generation is currently running"
+ # The label only — the bullet's wording gets tuned by hand, and pinning the
+ # prose just creates friction. lib/markdown.rb is what actually enforces the
+ # subset; this only guards that the rule is still shipped.
+ assert_includes rendered, "Formatting:"
end
test "injects a RUNNING state line when the project has a non-terminal instruction" do
@@ -235,6 +251,14 @@ class ChatRespondJobTest < ActiveJob::TestCase
private
+ # broadcasts(stream) returns ActiveSupport-JSON-encoded strings, and
+ # escape_html_entities_in_json is on, so every "<" arrives as "\u003c".
+ # Asserting on raw "" against the encoded payload can never fail —
+ # decode first, or the negative assertion above is dead weight.
+ def decoded_broadcasts
+ broadcasts(@stream_name).map { |p| ActiveSupport::JSON.decode(p) }
+ end
+
def last_chat_notice_broadcast
broadcasts(@stream_name).reverse.find { |b| b.to_s.match?(/target=\\?"chat_notice\\?"/) }
end
diff --git a/test/models/message_test.rb b/test/models/message_test.rb
index 60d860d..6ea14a7 100644
--- a/test/models/message_test.rb
+++ b/test/models/message_test.rb
@@ -55,6 +55,38 @@ class MessageTest < ActiveSupport::TestCase
end
end
+ # Reproduces the production sequence: create (enqueues but does not run the
+ # broadcast job), then chunks land via update_columns, then the job renders the
+ # partial from a GlobalID reload — so the appended row already carries
+ # half-written markdown by render time.
+ test "append broadcast renders plain text even when chunks landed before the job ran" do
+ message = @chat.messages.create!(role: :assistant, content: "")
+ message.update_columns(content: "**bold**")
+
+ perform_enqueued_jobs
+ payload = JSON.parse(broadcasts(@stream_name).last)
+
+ assert_includes payload, "**bold**"
+ refute_includes payload, "bold "
+ end
+
+ # The third broadcast site, and the only one that SHOULD format: the final save
+ # after streaming ends. Its two siblings (append here, mid-stream in
+ # chat_respond_job_test) assert the opposite, so without this a stray
+ # `streaming: true` on broadcast_replace_message would leave every finished
+ # reply plain with a fully green suite.
+ test "replace broadcast formats a finished assistant reply as markdown" do
+ message = @chat.messages.create!(role: :assistant, content: "")
+ perform_enqueued_jobs # drain the create/append broadcast first
+
+ perform_enqueued_jobs { message.update!(content: "**bold**") }
+ payload = JSON.parse(broadcasts(@stream_name).last)
+
+ assert_includes payload, 'action="replace"'
+ assert_includes payload, "bold "
+ refute_includes payload, "**bold**"
+ end
+
test "creating a message touches the chat's project (bumps active timestamp)" do
travel_to 1.hour.from_now do
assert_changes -> { @project.reload.updated_at } do
From b8cd0e4cf67fbd5bffd814b01ac4a0ee8dd12433 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pawe=C5=82=20Strza=C5=82kowski?=
Date: Tue, 18 Aug 2026 23:17:01 +0200
Subject: [PATCH 3/4] Bump twelve gems to clear a fresh batch of advisories
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
scan_ruby went red on bin/bundler-audit with 104 advisories across activestorage,
concurrent-ruby, crass, faraday, json, loofah, mail, msgpack, nokogiri,
rails-html-sanitizer, sqlite3 and websocket-driver. None of it is caused by the
markdown work on this branch: commonmarker is flagged zero times, and main fails
identically with the same 104. bundler-audit clones ruby-advisory-db fresh on a
clean runner, so CI picked up today's snapshot while local caches were still on
the 2026-07-29 one and reported clean.
All twelve are patch-level, and the transitive Rails bumps ride along to reach
activestorage >= 8.1.3.1. Every platform row in the lock is preserved, which
BUNDLE_DEPLOYMENT=1 needs.
Two of these — rails-html-sanitizer 1.7.0 → 1.7.1 and loofah 2.25.1 → 2.25.2 —
are the sanitizer that lib/markdown.rb layers on top of, so the allowlist was
re-verified against the full hostile-input set rather than trusting a green
suite. Output is byte-identical: script, img/onerror, svg/onload, iframe/srcdoc,
form/input, base, style tags and attributes, tab-obfuscated javascript: hrefs,
markdown images and links all still reduce to nothing or to bare text.
---
Gemfile.lock | 226 +++++++++++++++++++++++++--------------------------
1 file changed, 113 insertions(+), 113 deletions(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 36c252d..26cc050 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -3,29 +3,29 @@ GEM
specs:
action_text-trix (2.1.18)
railties
- actioncable (8.1.3)
- actionpack (= 8.1.3)
- activesupport (= 8.1.3)
+ actioncable (8.1.3.1)
+ actionpack (= 8.1.3.1)
+ activesupport (= 8.1.3.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
- actionmailbox (8.1.3)
- actionpack (= 8.1.3)
- activejob (= 8.1.3)
- activerecord (= 8.1.3)
- activestorage (= 8.1.3)
- activesupport (= 8.1.3)
+ actionmailbox (8.1.3.1)
+ actionpack (= 8.1.3.1)
+ activejob (= 8.1.3.1)
+ activerecord (= 8.1.3.1)
+ activestorage (= 8.1.3.1)
+ activesupport (= 8.1.3.1)
mail (>= 2.8.0)
- actionmailer (8.1.3)
- actionpack (= 8.1.3)
- actionview (= 8.1.3)
- activejob (= 8.1.3)
- activesupport (= 8.1.3)
+ actionmailer (8.1.3.1)
+ actionpack (= 8.1.3.1)
+ actionview (= 8.1.3.1)
+ activejob (= 8.1.3.1)
+ activesupport (= 8.1.3.1)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
- actionpack (8.1.3)
- actionview (= 8.1.3)
- activesupport (= 8.1.3)
+ actionpack (8.1.3.1)
+ actionview (= 8.1.3.1)
+ activesupport (= 8.1.3.1)
nokogiri (>= 1.8.5)
rack (>= 2.2.4)
rack-session (>= 1.0.1)
@@ -33,36 +33,36 @@ GEM
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
useragent (~> 0.16)
- actiontext (8.1.3)
+ actiontext (8.1.3.1)
action_text-trix (~> 2.1.15)
- actionpack (= 8.1.3)
- activerecord (= 8.1.3)
- activestorage (= 8.1.3)
- activesupport (= 8.1.3)
+ actionpack (= 8.1.3.1)
+ activerecord (= 8.1.3.1)
+ activestorage (= 8.1.3.1)
+ activesupport (= 8.1.3.1)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
- actionview (8.1.3)
- activesupport (= 8.1.3)
+ actionview (8.1.3.1)
+ activesupport (= 8.1.3.1)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
- activejob (8.1.3)
- activesupport (= 8.1.3)
+ activejob (8.1.3.1)
+ activesupport (= 8.1.3.1)
globalid (>= 0.3.6)
- activemodel (8.1.3)
- activesupport (= 8.1.3)
- activerecord (8.1.3)
- activemodel (= 8.1.3)
- activesupport (= 8.1.3)
+ activemodel (8.1.3.1)
+ activesupport (= 8.1.3.1)
+ activerecord (8.1.3.1)
+ activemodel (= 8.1.3.1)
+ activesupport (= 8.1.3.1)
timeout (>= 0.4.0)
- activestorage (8.1.3)
- actionpack (= 8.1.3)
- activejob (= 8.1.3)
- activerecord (= 8.1.3)
- activesupport (= 8.1.3)
+ activestorage (8.1.3.1)
+ actionpack (= 8.1.3.1)
+ activejob (= 8.1.3.1)
+ activerecord (= 8.1.3.1)
+ activesupport (= 8.1.3.1)
marcel (~> 1.0)
- activesupport (8.1.3)
+ activesupport (8.1.3.1)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
@@ -114,13 +114,13 @@ GEM
commonmarker (2.9.0-arm64-darwin)
commonmarker (2.9.0-x86_64-linux)
commonmarker (2.9.0-x86_64-linux-musl)
- concurrent-ruby (1.3.6)
+ concurrent-ruby (1.3.8)
connection_pool (3.0.2)
console (1.34.3)
fiber-annotation
fiber-local (~> 1.1)
json
- crass (1.0.6)
+ crass (1.0.7)
date (3.5.1)
debug (1.11.1)
irb (~> 1.10)
@@ -139,7 +139,7 @@ GEM
et-orbi (1.4.0)
tzinfo
event_stream_parser (1.0.0)
- faraday (2.14.2)
+ faraday (2.14.3)
faraday-net_http (>= 2.0, < 3.5)
json
logger
@@ -183,7 +183,7 @@ GEM
prism (>= 1.3.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
- json (2.19.5)
+ json (2.21.2)
jwt (3.2.0)
base64
kamal (2.11.0)
@@ -200,10 +200,10 @@ GEM
language_server-protocol (3.17.0.5)
lint_roller (1.1.0)
logger (1.7.0)
- loofah (2.25.1)
+ loofah (2.25.2)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
- mail (2.9.0)
+ mail (2.9.1)
logger
mini_mime (>= 0.1.1)
net-imap
@@ -218,7 +218,7 @@ GEM
minitest (6.0.6)
drb (~> 2.0)
prism (~> 1.5)
- msgpack (1.8.0)
+ msgpack (1.8.4)
multi_xml (0.9.1)
bigdecimal (>= 3.1, < 5)
multipart-post (2.4.1)
@@ -239,19 +239,19 @@ GEM
net-protocol
net-ssh (7.3.2)
nio4r (2.7.5)
- nokogiri (1.19.3-aarch64-linux-gnu)
+ nokogiri (1.19.4-aarch64-linux-gnu)
racc (~> 1.4)
- nokogiri (1.19.3-aarch64-linux-musl)
+ nokogiri (1.19.4-aarch64-linux-musl)
racc (~> 1.4)
- nokogiri (1.19.3-arm-linux-gnu)
+ nokogiri (1.19.4-arm-linux-gnu)
racc (~> 1.4)
- nokogiri (1.19.3-arm-linux-musl)
+ nokogiri (1.19.4-arm-linux-musl)
racc (~> 1.4)
- nokogiri (1.19.3-arm64-darwin)
+ nokogiri (1.19.4-arm64-darwin)
racc (~> 1.4)
- nokogiri (1.19.3-x86_64-linux-gnu)
+ nokogiri (1.19.4-x86_64-linux-gnu)
racc (~> 1.4)
- nokogiri (1.19.3-x86_64-linux-musl)
+ nokogiri (1.19.4-x86_64-linux-musl)
racc (~> 1.4)
oauth2 (2.0.22)
auth-sanitizer (~> 0.2, >= 0.2.1)
@@ -313,30 +313,30 @@ GEM
rack (>= 1.3)
rackup (2.3.1)
rack (>= 3)
- rails (8.1.3)
- actioncable (= 8.1.3)
- actionmailbox (= 8.1.3)
- actionmailer (= 8.1.3)
- actionpack (= 8.1.3)
- actiontext (= 8.1.3)
- actionview (= 8.1.3)
- activejob (= 8.1.3)
- activemodel (= 8.1.3)
- activerecord (= 8.1.3)
- activestorage (= 8.1.3)
- activesupport (= 8.1.3)
+ rails (8.1.3.1)
+ actioncable (= 8.1.3.1)
+ actionmailbox (= 8.1.3.1)
+ actionmailer (= 8.1.3.1)
+ actionpack (= 8.1.3.1)
+ actiontext (= 8.1.3.1)
+ actionview (= 8.1.3.1)
+ activejob (= 8.1.3.1)
+ activemodel (= 8.1.3.1)
+ activerecord (= 8.1.3.1)
+ activestorage (= 8.1.3.1)
+ activesupport (= 8.1.3.1)
bundler (>= 1.15.0)
- railties (= 8.1.3)
+ railties (= 8.1.3.1)
rails-dom-testing (2.3.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
- rails-html-sanitizer (1.7.0)
- loofah (~> 2.25)
+ rails-html-sanitizer (1.7.1)
+ loofah (~> 2.25, >= 2.25.2)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
- railties (8.1.3)
- actionpack (= 8.1.3)
- activesupport (= 8.1.3)
+ railties (8.1.3.1)
+ actionpack (= 8.1.3.1)
+ activesupport (= 8.1.3.1)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
@@ -436,13 +436,13 @@ GEM
fugit (~> 1.11)
railties (>= 7.1)
thor (>= 1.3.1)
- sqlite3 (2.9.4-aarch64-linux-gnu)
- sqlite3 (2.9.4-aarch64-linux-musl)
- sqlite3 (2.9.4-arm-linux-gnu)
- sqlite3 (2.9.4-arm-linux-musl)
- sqlite3 (2.9.4-arm64-darwin)
- sqlite3 (2.9.4-x86_64-linux-gnu)
- sqlite3 (2.9.4-x86_64-linux-musl)
+ sqlite3 (2.9.6-aarch64-linux-gnu)
+ sqlite3 (2.9.6-aarch64-linux-musl)
+ sqlite3 (2.9.6-arm-linux-gnu)
+ sqlite3 (2.9.6-arm-linux-musl)
+ sqlite3 (2.9.6-arm64-darwin)
+ sqlite3 (2.9.6-x86_64-linux-gnu)
+ sqlite3 (2.9.6-x86_64-linux-musl)
sshkit (1.25.0)
base64
logger
@@ -491,7 +491,7 @@ GEM
bindex (>= 0.4.0)
railties (>= 8.0.0)
websocket (1.2.11)
- websocket-driver (0.8.0)
+ websocket-driver (0.8.2)
base64
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
@@ -544,17 +544,17 @@ DEPENDENCIES
CHECKSUMS
action_text-trix (2.1.18) sha256=3fdb83f8bff4145d098be283cdd47ac41caf5110bfa6df4695ed7127d7fb3642
- actioncable (8.1.3) sha256=e5bc7f75e44e6a22de29c4f43176927c3a9ce4824464b74ed18d8226e75a80f0
- actionmailbox (8.1.3) sha256=df7da474eaa0e70df4ed5a6fef66eb3b3b0f2dbf7f14518deee8d77f1b4aae59
- actionmailer (8.1.3) sha256=831f724891bb70d0aaa4d76581a6321124b6a752cb655c9346aae5479318448d
- actionpack (8.1.3) sha256=af998cae4d47c5d581a2cc363b5c77eb718b7c4b45748d81b1887b25621c29a3
- actiontext (8.1.3) sha256=d291019c00e1ea9e6463011fa214f6081a56d7b9a1d224e7d3f6384c1dafc7d2
- actionview (8.1.3) sha256=1347c88c7f3edb38100c5ce0e9fb5e62d7755f3edc1b61cce2eb0b2c6ea2fd5d
- activejob (8.1.3) sha256=a149b1766aa8204c3c3da7309e4becd40fcd5529c348cffbf6c9b16b565fe8d3
- activemodel (8.1.3) sha256=90c05cbe4cef3649b8f79f13016191ea94c4525ce4a5c0fb7ef909c4b91c8219
- activerecord (8.1.3) sha256=8003be7b2466ba0a2a670e603eeb0a61dd66058fccecfc49901e775260ac70ab
- activestorage (8.1.3) sha256=0564ce9309143951a67615e1bb4e090ee54b8befed417133cae614479b46384d
- activesupport (8.1.3) sha256=21a5e0dfbd4c3ddd9e1317ec6a4d782fa226e7867dc70b0743acda81a1dca20e
+ actioncable (8.1.3.1) sha256=e318528295c878a3efdfe25f0f2267c80cb7a76eba41bb5f64d44aa380a3d91b
+ actionmailbox (8.1.3.1) sha256=5f704972097d843ade8e435e93694a1dac732b926df1717aceba1f3840082b1c
+ actionmailer (8.1.3.1) sha256=88ea441b28ff02a0c6c006468892642a3d9942affce9d294e81a74504aa5c43c
+ actionpack (8.1.3.1) sha256=974cb7154548e81f470b1b0f247b99cb38e87825899dca58610596e2817723d0
+ actiontext (8.1.3.1) sha256=5da729d833d1a29cddb1eee938878e55e503d2613e00e735f5daf58c2ba98af2
+ actionview (8.1.3.1) sha256=2da68b8414c47b43bfbed1ce69c5afe1c04f78c267aacb5660a4cab5ca12cfb6
+ activejob (8.1.3.1) sha256=1c8dd275df930df40deecffec63d913a550a33fd94bd298f69721dd96939954a
+ activemodel (8.1.3.1) sha256=99cc02ce2faec371d14440949d85787ebd23a907c9baef0a9d4bcd4d21888f88
+ activerecord (8.1.3.1) sha256=0a2fb6c28f4938f6b013a3a549bec0a7e37d535f3dc8990e804bcc3258c0403b
+ activestorage (8.1.3.1) sha256=f555254f387b1cffa499d2fd3115d12635eadc5b15206a8534316a67036163ef
+ activesupport (8.1.3.1) sha256=85458765f25ea48b9019c46b6bb3fa5683197bf4280d9f06710a6e8d7a831376
addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
async (2.39.0) sha256=df18730073f2bbb45788077dfa20cb365ecc1b9453969f44de6796b5191a00aa
@@ -575,10 +575,10 @@ CHECKSUMS
commonmarker (2.9.0-arm64-darwin) sha256=1748dbfa4f5813b0d2a14bb4bbfa65a4ec293aa1c825016d60029ee0e132b046
commonmarker (2.9.0-x86_64-linux) sha256=8cfe92970eef585a19ddf6613224b91cab64d6029834661bda801f877c9c7f43
commonmarker (2.9.0-x86_64-linux-musl) sha256=293921398b839f79ceaf55010e061357e34f053822c3b003cd0be6686176335e
- concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
+ concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
console (1.34.3) sha256=869fbd74697efc4c606f102d2812b0b008e4e7fd738a91c591e8577140ec0dcc
- crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d
+ crass (1.0.7) sha256=94868719948664c89ddcaf0a37c65048413dfcb1c869470a5f7a7ceb5390b295
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
debug (1.11.1) sha256=2e0b0ac6119f2207a6f8ac7d4a73ca8eb4e440f64da0a3136c30343146e952b6
devise (5.0.4) sha256=d605f2b85854e74e56ee789e2d398702bc2d06e6bcd894717a670a3199c74cc1
@@ -589,7 +589,7 @@ CHECKSUMS
erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
et-orbi (1.4.0) sha256=6c7e3c90779821f9e3b324c5e96fda9767f72995d6ae435b96678a4f3e2de8bc
event_stream_parser (1.0.0) sha256=a2683bab70126286f8184dc88f7968ffc4028f813161fb073ec90d171f7de3c8
- faraday (2.14.2) sha256=73ccb9994a9e8648f010e32eca2ae82e41c57860aa10932cda29418b9e0223ad
+ faraday (2.14.3) sha256=1882247e6766615c8220b4392bf1d27f6ebb63d8e28267587cef1fb0bf37f278
faraday-multipart (1.2.0) sha256=7d89a949693714176f612323ca13746a2ded204031a6ba528adee788694ef757
faraday-net_http (3.4.2) sha256=f147758260d3526939bf57ecf911682f94926a3666502e24c69992765875906c
faraday-retry (2.4.0) sha256=7b79c48fb7e56526faf247b12d94a680071ff40c9fda7cf1ec1549439ad11ebe
@@ -612,21 +612,21 @@ CHECKSUMS
io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
io-event (1.15.1) sha256=c644cdcf48254015d63f558bf4492f35471f5bb204a42180ea49752be59b30cc
irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3
- json (2.19.5) sha256=218a18553e4801d579ca7e0f5bc72bafd776d7397238a1fb4e74db5b0a812c59
+ json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
jwt (3.2.0) sha256=5419b1fe37b1da0982bd07051f573a8b8789ab724c2aa7e785e4784a3ed217d7
kamal (2.11.0) sha256=1408864425e0dec7e0a14d712a3b13f614e9f3a425b7661d3f9d287a51d7dd75
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
- loofah (2.25.1) sha256=d436c73dbd0c1147b16c4a41db097942d217303e1f7728704b37e4df9f6d2e04
- mail (2.9.0) sha256=6fa6673ecd71c60c2d996260f9ee3dd387d4673b8169b502134659ece6d34941
+ loofah (2.25.2) sha256=2007f746959ac65552456e04b433e83deb22759ab38c838b4445c70e43425918
+ mail (2.9.1) sha256=06574eca475253d6c18145dd70af80d0eb970182d55053497c5f4d797ea160e8
marcel (1.1.0) sha256=fdcfcfa33cc52e93c4308d40e4090a5d4ea279e160a7f6af988260fa970e0bee
matrix (0.4.3) sha256=a0d5ab7ddcc1973ff690ab361b67f359acbb16958d1dc072b8b956a286564c5b
metrics (0.15.0) sha256=61ded5bac95118e995b1bc9ed4a5f19bc9814928a312a85b200abbdac9039072
mini_magick (5.3.1) sha256=29395dfd76badcabb6403ee5aff6f681e867074f8f28ce08d78661e9e4a351c4
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
- msgpack (1.8.0) sha256=e64ce0212000d016809f5048b48eb3a65ffb169db22238fb4b72472fecb2d732
+ msgpack (1.8.4) sha256=4411c22d350dd1c20250f7eada3cca2695438c2f769cf0782f0cd065d90a3e7b
multi_xml (0.9.1) sha256=7ce766b59c17241ed62976caeae1fae9b2431b263398c35396239a68c4a64e57
multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8
net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996
@@ -638,13 +638,13 @@ CHECKSUMS
net-smtp (0.5.1) sha256=ed96a0af63c524fceb4b29b0d352195c30d82dd916a42f03c62a3a70e5b70736
net-ssh (7.3.2) sha256=65029e213c380e20e5fd92ece663934ab0a0fe888e0cd7cc6a5b664074362dd4
nio4r (2.7.5) sha256=6c90168e48fb5f8e768419c93abb94ba2b892a1d0602cb06eef16d8b7df1dca1
- nokogiri (1.19.3-aarch64-linux-gnu) sha256=46b89e5d7b9e844c2ee360794240c6ea2a4e6fa0c5892a4ed487db621224b639
- nokogiri (1.19.3-aarch64-linux-musl) sha256=8392dfdcd21be7a94dbbe9ccc138dea01b97b24cb2dc02a114ca98bfb1d9a0b7
- nokogiri (1.19.3-arm-linux-gnu) sha256=3919d5ffc334ad778a4a9eb88fda7dcb8b1fb58c8a52ac640c6dcd2f038e774f
- nokogiri (1.19.3-arm-linux-musl) sha256=9ce1cb6346bb9c67b1550eb537aa183ead91e4b6eadb2f36ade02d8dd2a79fb6
- nokogiri (1.19.3-arm64-darwin) sha256=71b9bd424b1b7abc18b05052a1a3cfd3627abdca62be280854cc411791357e42
- nokogiri (1.19.3-x86_64-linux-gnu) sha256=2f5078620fe12e83669b5b17311b32532a8153d02eee7ad06948b926d6080976
- nokogiri (1.19.3-x86_64-linux-musl) sha256=248c906d2166eca5efb56d52fdee5f9a1f51d69a72e2b64fdac647b4ce39ea3f
+ nokogiri (1.19.4-aarch64-linux-gnu) sha256=1269fb644a6de405057a53dd5c762b1209b43ca7424f839454d3dbc677c31a8f
+ nokogiri (1.19.4-aarch64-linux-musl) sha256=35c65b9ce72b3bb03207bdbe7067915019dc18c1b9b59139684bd6690fdd01af
+ nokogiri (1.19.4-arm-linux-gnu) sha256=a301313e38bb065d68239e79734bcd6f56fb6efaacebde29e9abf2a4735340ca
+ nokogiri (1.19.4-arm-linux-musl) sha256=588923c101bcfa78869734d247d25b598674323e7f22474fc468f6e5647311eb
+ nokogiri (1.19.4-arm64-darwin) sha256=a46db9853286e6597b36ebc6953817d15acf3a299583eb3f89fdc6f91dd63527
+ nokogiri (1.19.4-x86_64-linux-gnu) sha256=379fae440b28915e3f19d752ce2dcf8465ed2b2fbefd2a7ca0dd497bc981a06a
+ nokogiri (1.19.4-x86_64-linux-musl) sha256=17dfb7c1fa194ae02fbf7c51a7afc8d278045ab3fdacfd86f91d02d7b274470b
oauth2 (2.0.22) sha256=8f4669f0c8de69f6db7ed786bda20996eaf0003966b886f1c519efb1a5682fa6
octokit (10.0.0) sha256=82e99a539b7637b7e905e6d277bb0c1a4bed56735935cc33db6da7eae49a24e8
omniauth (2.1.4) sha256=42a05b0496f0d22e1dd85d42aaf602f064e36bb47a6826a27ab55e5ba608763c
@@ -669,10 +669,10 @@ CHECKSUMS
rack-session (2.1.2) sha256=595434f8c0c3473ae7d7ac56ecda6cc6dfd9d37c0b2b5255330aa1576967ffe8
rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463
rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868
- rails (8.1.3) sha256=6d017ba5348c98fc909753a8169b21d44de14d2a0b92d140d1a966834c3c9cd3
+ rails (8.1.3.1) sha256=ccd11a36bfc171bf9c66d585d14c0ece91c0c9dde840aae60c0118d6f5c9c52a
rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d
- rails-html-sanitizer (1.7.0) sha256=28b145cceaf9cc214a9874feaa183c3acba036c9592b19886e0e45efc62b1e89
- railties (8.1.3) sha256=913eb0e0cb520aac687ffd74916bd726d48fa21f47833c6292576ef6a286de22
+ rails-html-sanitizer (1.7.1) sha256=e797a7c9b01e567307e317c576b49ab4168017e63eea4dba9ce3cb587e2f22c2
+ railties (8.1.3.1) sha256=2388a232579a00cefea4487de66c8553c3408c1300abdc6cf1799d86ffb04487
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192
@@ -698,13 +698,13 @@ CHECKSUMS
solid_cable (3.0.12) sha256=a168a54731a455d5627af48d8441ea3b554b8c1f6e6cd6074109de493e6b0460
solid_cache (1.0.10) sha256=bc05a2fb3ac78a6f43cbb5946679cf9db67dd30d22939ededc385cb93e120d41
solid_queue (1.4.0) sha256=e6a18d196f0b27cb6e3c77c5b31258b05fb634f8ed64fb1866ed164047216c2a
- sqlite3 (2.9.4-aarch64-linux-gnu) sha256=ecabed721e6eaad54601d2685f09029d90025efc8d931040dc89cb3f8a2080ec
- sqlite3 (2.9.4-aarch64-linux-musl) sha256=ffb4255947fb54c8c3eeca97460c9702b40de91ce390455ef7367ca6a3929a31
- sqlite3 (2.9.4-arm-linux-gnu) sha256=9ee2008b9fbec984c3c165b0d7eedd2bd2a415100b761bfa3a4c6fbec9208bf6
- sqlite3 (2.9.4-arm-linux-musl) sha256=8dc1fe4da6977992cd62decf4a93ccf6cc2e124a5e6a340160d52092f70e837a
- sqlite3 (2.9.4-arm64-darwin) sha256=1d5aad413a815d236e96d43f05a1acc600b6cd086800770342a3f9c2877499ff
- sqlite3 (2.9.4-x86_64-linux-gnu) sha256=537a3eda71b1df1336d0055cbebe55a7317c34870c192c7b6b9d8d0be6871847
- sqlite3 (2.9.4-x86_64-linux-musl) sha256=3fc5e865b4be9a85d998203ef8d0c0fdcb92f20acf34a254346ff8a19088efec
+ sqlite3 (2.9.6-aarch64-linux-gnu) sha256=d8b1f7d23efd7abac285775a9566562fc7debfef79d594e3a20354406fb7907c
+ sqlite3 (2.9.6-aarch64-linux-musl) sha256=3579e1c98cdc7ff5c3722847bb63ed4e1efb7ff675cb5e1e48ef2d4da5fb3bc9
+ sqlite3 (2.9.6-arm-linux-gnu) sha256=33541500e3615da02afe54a9cc38b17a6985d3cf9d8b76d6d0a83002f114e7ec
+ sqlite3 (2.9.6-arm-linux-musl) sha256=c5490af48bb228fefa54314e9541375c3907e70f8109f3881b5ff97e1c93ae33
+ sqlite3 (2.9.6-arm64-darwin) sha256=849b5d7f795e60fe25076d62c72dd722beb45b3850b516ad978d60ee848ec15b
+ sqlite3 (2.9.6-x86_64-linux-gnu) sha256=613188ce02f614126ddbc38c5e217ccffd6306d0dcd9adca9764547aa890a634
+ sqlite3 (2.9.6-x86_64-linux-musl) sha256=d493b11818a3573387a1d56e1ee8fa00da23a683a7a1cc063e7a0feeed843abf
sshkit (1.25.0) sha256=c8c6543cdb60f91f1d277306d585dd11b6a064cb44eab0972827e4311ff96744
stimulus-rails (1.3.4) sha256=765676ffa1f33af64ce026d26b48e8ffb2e0b94e0f50e9119e11d6107d67cb06
stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1
@@ -734,7 +734,7 @@ CHECKSUMS
warden (1.2.9) sha256=46684f885d35a69dbb883deabf85a222c8e427a957804719e143005df7a1efd0
web-console (4.3.0) sha256=e13b71301cdfc2093f155b5aa3a622db80b4672d1f2f713119cc7ec7ac6a6da4
websocket (1.2.11) sha256=b7e7a74e2410b5e85c25858b26b3322f29161e300935f70a0e0d3c35e0462737
- websocket-driver (0.8.0) sha256=ed0dba4b943c22f17f9a734817e808bc84cdce6a7e22045f5315aa57676d4962
+ websocket-driver (0.8.2) sha256=97c556b019bf3410b4961002ac501621e9322d3f8a7bc02161a09301cc4c4146
websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241
xpath (3.2.0) sha256=6dfda79d91bb3b949b947ecc5919f042ef2f399b904013eb3ef6d20dd3a4082e
zeitwerk (2.8.2) sha256=7212a61311083c604184b1ea2574b9aa05cd14f855a0841c06985cabe9181d12
From 5e0ef6bbfe6930a24b905ac365d4975db330b6e1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pawe=C5=82=20Strza=C5=82kowski?=
Date: Tue, 18 Aug 2026 23:21:24 +0200
Subject: [PATCH 4/4] Release 1.3.0
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Covers everything since the 1.2.0 entry: formatted chat replies, Sonnet 5 and
Opus 5 in the picker, the wildcard preview certificate switched on in
production, the model-registry breakage that made every non-default selection
fail, and this week's gem advisories.
Minor rather than patch, per the file's own rule — two of the entries are new
functionality.
The tenant-isolation write-up and the model-registry runbook are deliberately
absent: both are documentation and internal tooling, and neither changes
anything a user of hifumi.dev can observe.
---
CHANGELOG.md | 44 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f4a9bac..c8113fe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,50 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
versions follow [semantic versioning](https://semver.org/) (minor for new
functionality, patch for fixes and internal changes).
+## [1.3.0] - 2026-08-18
+
+### Added
+
+- Assistant replies in chat are now formatted. Bold, italics, bulleted and
+ numbered lists, inline code and multi-line code blocks render as intended,
+ instead of showing the raw `**`, `###` and backtick markers the model has
+ always written. The rendered set is deliberately small: headings, block
+ quotes, tables and strikethrough flatten to plain text, and links and images
+ are never rendered as such — a markdown link keeps its label, so the agent is
+ asked to write URLs plainly and leave them readable. Text still streams in as
+ plain text and formats once the reply finishes. Nothing is stored formatted,
+ so older conversations pick up the change too.
+- Claude Sonnet 5 and Claude Opus 5 can be selected for any of the six
+ generation stages, both as account defaults and per project. Stage defaults
+ are unchanged, so existing projects keep the models they were created with.
+
+### Changed
+
+- Preview subdomains now use the pre-issued wildcard certificate that 1.2.0
+ added as opt-in. A first visit no longer waits for a certificate to be
+ issued, and the certificate-transparency warning that a slightly fast clock
+ could trigger is gone.
+
+### Fixed
+
+- Selecting Claude Sonnet 4.6 or Claude Opus 4.6 did not actually work: chat
+ answered with "The configured model is unavailable", and a project using one
+ for its template stage failed mid-build. Only the default Haiku model
+ resolved. Both environments are corrected, and a new check fails loudly if a
+ model offered in the picker cannot be resolved, so the picker and what the
+ system can actually run cannot drift apart again silently.
+
+### Security
+
+- No model-authored HTML is ever rendered in chat. The new formatting passes
+ every reply through a CommonMark parser that omits raw HTML outright and then
+ an allowlist sanitizer, because the app's content-security policy permits
+ scripts from any HTTPS origin and so cannot be relied on as a second line of
+ defense.
+- Twelve gems updated for newly published advisories — among them nokogiri,
+ rails-html-sanitizer, loofah, sqlite3 and websocket-driver — together with
+ the Rails 8.1.3.1 patch releases.
+
## [1.2.0] - 2026-06-16
### Added