maquina_stream 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rdoc_options +30 -0
- data/CHANGELOG.md +38 -0
- data/LICENSE.txt +21 -0
- data/README.md +163 -0
- data/app/assets/stylesheets/maquina_stream/components/attachment.css +35 -0
- data/app/assets/stylesheets/maquina_stream/components/code_block.css +30 -0
- data/app/assets/stylesheets/maquina_stream/components/shimmer.css +31 -0
- data/app/assets/stylesheets/maquina_stream/components/snippet.css +22 -0
- data/app/assets/stylesheets/maquina_stream/components/source_citation.css +14 -0
- data/app/assets/stylesheets/maquina_stream/components/suggestion.css +32 -0
- data/app/assets/stylesheets/maquina_stream/reveal.css +48 -0
- data/app/assets/stylesheets/maquina_stream/themes/dark.css +235 -0
- data/app/assets/stylesheets/maquina_stream/themes/light.css +117 -0
- data/app/controllers/maquina_stream/application_controller.rb +21 -0
- data/app/controllers/maquina_stream/blocks_controller.rb +41 -0
- data/app/controllers/maquina_stream/manifests_controller.rb +15 -0
- data/app/helpers/maquina_stream/components_helper.rb +80 -0
- data/app/javascript/maquina_stream/controllers/application_controller.js +169 -0
- data/app/javascript/maquina_stream/controllers/ms_autoscroll_controller.js +110 -0
- data/app/javascript/maquina_stream/controllers/ms_code_controller.js +96 -0
- data/app/javascript/maquina_stream/controllers/ms_deferred_controller.js +223 -0
- data/app/javascript/maquina_stream/controllers/ms_diagram_controller.js +40 -0
- data/app/javascript/maquina_stream/controllers/ms_link_safety_controller.js +196 -0
- data/app/javascript/maquina_stream/controllers/ms_math_controller.js +32 -0
- data/app/javascript/maquina_stream/controllers/ms_repair_controller.js +167 -0
- data/app/javascript/maquina_stream/controllers/ms_reveal_controller.js +320 -0
- data/app/javascript/maquina_stream/controllers/ms_table_controller.js +183 -0
- data/app/javascript/maquina_stream/index.js +59 -0
- data/app/views/maquina_stream/components/_attachment.html.erb +139 -0
- data/app/views/maquina_stream/components/_code_block.html.erb +74 -0
- data/app/views/maquina_stream/components/_shimmer.html.erb +36 -0
- data/app/views/maquina_stream/components/_snippet.html.erb +50 -0
- data/app/views/maquina_stream/components/_source_citation.html.erb +42 -0
- data/app/views/maquina_stream/components/_suggestion.html.erb +73 -0
- data/config/importmap.rb +10 -0
- data/config/locales/en.yml +79 -0
- data/config/locales/es.yml +82 -0
- data/config/routes.rb +11 -0
- data/docs/configuration.md +219 -0
- data/docs/deferred-renderers.md +184 -0
- data/docs/getting-started.md +356 -0
- data/docs/javascript.md +298 -0
- data/docs/registries.md +283 -0
- data/docs/repair.md +162 -0
- data/docs/security.md +247 -0
- data/docs/streaming.md +308 -0
- data/lib/generators/maquina_stream/install/USAGE +26 -0
- data/lib/generators/maquina_stream/install/install_generator.rb +199 -0
- data/lib/generators/maquina_stream/install/templates/initializer.rb.tt +121 -0
- data/lib/generators/maquina_stream/streamable/USAGE +28 -0
- data/lib/generators/maquina_stream/streamable/streamable_generator.rb +187 -0
- data/lib/generators/maquina_stream/streamable/templates/migration.rb.tt +21 -0
- data/lib/generators/maquina_stream/streamable/templates/model.rb.tt +4 -0
- data/lib/maquina_stream/block.rb +99 -0
- data/lib/maquina_stream/broadcaster.rb +233 -0
- data/lib/maquina_stream/component_cache.rb +0 -0
- data/lib/maquina_stream/components/contract.rb +184 -0
- data/lib/maquina_stream/components.rb +135 -0
- data/lib/maquina_stream/configuration.rb +240 -0
- data/lib/maquina_stream/document.rb +296 -0
- data/lib/maquina_stream/engine.rb +46 -0
- data/lib/maquina_stream/errors.rb +17 -0
- data/lib/maquina_stream/export.rb +66 -0
- data/lib/maquina_stream/frame.rb +73 -0
- data/lib/maquina_stream/manifest.rb +137 -0
- data/lib/maquina_stream/registries.rb +116 -0
- data/lib/maquina_stream/renderer/fence.rb +115 -0
- data/lib/maquina_stream/renderer/post_pass.rb +363 -0
- data/lib/maquina_stream/renderer/tag_blocks.rb +276 -0
- data/lib/maquina_stream/renderer/view_context.rb +72 -0
- data/lib/maquina_stream/renderer.rb +128 -0
- data/lib/maquina_stream/sanitizer.rb +392 -0
- data/lib/maquina_stream/streamable.rb +281 -0
- data/lib/maquina_stream/text_direction.rb +56 -0
- data/lib/maquina_stream/themes.rb +84 -0
- data/lib/maquina_stream/version.rb +5 -0
- data/lib/maquina_stream.rb +175 -0
- metadata +204 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b102e4961a1ab8c838ac27577f9358003a64c31491569600600e5782f1d0b9e0
|
|
4
|
+
data.tar.gz: 0c4c875bd048b17c0533fa86dd0c739d32344a2c376d26034b3c45896d056ff5
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ab81d7757223c030d6748fc87abe5124f528497a1562994947c16a1e2ec4a4992e504144beacb83d951b7ebfd36d8d33d43bb6ef542d34e825963c9133f96c5c
|
|
7
|
+
data.tar.gz: 81e39ef8e44f0437cbcd6d83cd8c2fd78d61aa3a945cca8294b663dda8c6869b30f6534f491ae8d57104696f00e7cb4546dbb2ca8b851eb1cae8b64da407c57b
|
data/.rdoc_options
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
# RDoc reads this file from the gem root. Comments in lib/ and app/ are written
|
|
3
|
+
# in Markdown, not RDoc markup — see the `markup` key below.
|
|
4
|
+
markup: markdown
|
|
5
|
+
title: maquina_stream — server-rendered streaming markdown for Rails
|
|
6
|
+
main_page: README.md
|
|
7
|
+
# Everything that documents how the gem was BUILT rather than how it is USED.
|
|
8
|
+
# sdd/ holds the phase specs, test/ includes the whole dummy host application,
|
|
9
|
+
# and docs/{plan,design,remend-patterns,spike-sourcepos}.md are the same four
|
|
10
|
+
# files the gemspec does not package: none of them documents the gem to
|
|
11
|
+
# somebody who installs it.
|
|
12
|
+
#
|
|
13
|
+
# app/javascript is excluded because the JavaScript is documented as a DOM and
|
|
14
|
+
# controller contract in docs/javascript.md, which is a better description of
|
|
15
|
+
# it than RDoc's JavaScript parser produces.
|
|
16
|
+
#
|
|
17
|
+
# These are matched against ABSOLUTE paths, so each pattern is anchored to a
|
|
18
|
+
# path boundary rather than to the start of the string.
|
|
19
|
+
exclude:
|
|
20
|
+
- "(?:\\A|/)test/"
|
|
21
|
+
- "(?:\\A|/)sdd/"
|
|
22
|
+
- "(?:\\A|/)bin/"
|
|
23
|
+
- "(?:\\A|/)tmp/"
|
|
24
|
+
- "(?:\\A|/)pkg/"
|
|
25
|
+
- "(?:\\A|/)doc/"
|
|
26
|
+
- "(?:\\A|/)\\.github/"
|
|
27
|
+
- "(?:\\A|/)app/javascript/"
|
|
28
|
+
- "(?:\\A|/)docs/(?:plan|design|remend-patterns|spike-sourcepos)\\.md\\z"
|
|
29
|
+
- "(?:\\A|/)CLAUDE\\.md\\z"
|
|
30
|
+
- "(?:\\A|/)Gemfile(?:\\.lock)?\\z"
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased — 0.1.0
|
|
4
|
+
|
|
5
|
+
First working version. The engine renders a streaming markdown buffer to HTML on
|
|
6
|
+
the server and broadcasts it over Turbo Streams; only rendered HTML reaches the
|
|
7
|
+
browser.
|
|
8
|
+
|
|
9
|
+
- **Render pipeline** — `maquina_remend` → commonmarker with sourcepos →
|
|
10
|
+
a single Nokogiri post-pass → sanitizer. Pure: it runs outside a Rails request
|
|
11
|
+
with no stubbing, asserted in a subprocess with no application booted.
|
|
12
|
+
- **Sealing and broadcast** — blocks seal behind a configurable lag, ids are
|
|
13
|
+
index-derived, and a sealed block is never re-broadcast. The seal pointer
|
|
14
|
+
stops at an unresolved link reference definition, because a definition
|
|
15
|
+
rewrites links arbitrarily far above it.
|
|
16
|
+
- **Repair** — a windowed manifest of block digests, four triggers, and a silent
|
|
17
|
+
morph of only the blocks that differ. Deltas are an optimization; correctness
|
|
18
|
+
lives here.
|
|
19
|
+
- **Interaction** — copy, download, table export, fullscreen, link safety and
|
|
20
|
+
autoscroll, every control individually disableable and inert while streaming.
|
|
21
|
+
- **Client-deferred renderers** — diagrams and math render in the browser from
|
|
22
|
+
one JSON payload per leaf node, with split ownership across morphs.
|
|
23
|
+
- **Registries** — elements, custom tags and fences, the last with server,
|
|
24
|
+
passthrough and client strategies.
|
|
25
|
+
- **Text direction per block**, decided by the first strong character. Fences
|
|
26
|
+
carrying bidi control characters are pinned, against Trojan Source.
|
|
27
|
+
- **History** — sealed messages are frozen and fragment-cached by buffer digest;
|
|
28
|
+
1942x on a warm cache.
|
|
29
|
+
- **Generators** — `maquina_stream:install` wires the engine into a host (the
|
|
30
|
+
initializer, the mount, the Turbo and engine importmap pins, the Stimulus
|
|
31
|
+
registration, the stylesheets) and `maquina_stream:streamable Message` makes
|
|
32
|
+
one model streamable (the migration, generated from the `Streamable` contract
|
|
33
|
+
itself, and the macro). Idempotent, non-destructive, and loud about anything
|
|
34
|
+
they cannot do. Both seams stay the host's: `authorize` is generated as a stub
|
|
35
|
+
that denies.
|
|
36
|
+
- Spanish default locale, English secondary. No Node build step anywhere.
|
|
37
|
+
|
|
38
|
+
Not yet released: `maquina_remend` is still a path reference.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mario Alberto Chávez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# maquina_stream
|
|
2
|
+
|
|
3
|
+
Server-rendered streaming markdown for Rails, over Turbo, with a repair path.
|
|
4
|
+
|
|
5
|
+
A model writes markdown a token at a time. `maquina_stream` renders it to HTML
|
|
6
|
+
on the server, broadcasts small patches as it grows, and reconciles the browser
|
|
7
|
+
with the truth when frames go missing — which they do, because Action Cable
|
|
8
|
+
offers no delivery guarantee.
|
|
9
|
+
|
|
10
|
+
**Only rendered HTML reaches the browser.** The client never parses markdown.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
gem "maquina_stream"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Rails 8, Ruby 3.3+. Depends on `maquina_remend`, `commonmarker`, `nokogiri` and
|
|
19
|
+
`rouge`. `maquina_components` is optional — without it, components render plain
|
|
20
|
+
Tailwind fallbacks. Turbo is required: repair applies Turbo Stream morphs, and
|
|
21
|
+
with `window.Turbo` undefined every repair fails silently.
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
bin/rails generate maquina_stream:install
|
|
25
|
+
bin/rails generate maquina_stream:streamable Message
|
|
26
|
+
bin/rails db:migrate
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The first wires the engine into the app — the initializer, the mount, the
|
|
30
|
+
importmap pins, the Stimulus registration, the stylesheets. The second is per
|
|
31
|
+
model, because a host may have several: the migration carrying the contract
|
|
32
|
+
columns, and the `include` plus macro in the model. Both are idempotent and
|
|
33
|
+
neither overwrites a file; a host missing an ingredient is told what to paste
|
|
34
|
+
rather than left with a silent no-op.
|
|
35
|
+
|
|
36
|
+
## Two seams the generators will not guess
|
|
37
|
+
|
|
38
|
+
**`authorize`**, in `config/initializers/maquina_stream.rb`. It is generated as
|
|
39
|
+
a stub that denies everything, which is the safe failure and a broken feature
|
|
40
|
+
both — the browser can never repair a message until you replace it. Guessing a
|
|
41
|
+
host's authorization is how an engine leaks other people's messages:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
c.authorize = ->(record, request) { record.conversation.member?(request.session[:user_id]) }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**`stream_for:`**, in the model. Who may subscribe to a stream is your question,
|
|
48
|
+
not the engine's:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
class Message < ApplicationRecord
|
|
52
|
+
include MaquinaStream::Streamable
|
|
53
|
+
|
|
54
|
+
maquina_stream buffer: :content,
|
|
55
|
+
stream_for: ->(record) { [:conversation, record.conversation_id, :messages] }
|
|
56
|
+
end
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Streaming a message
|
|
60
|
+
|
|
61
|
+
Feed the broadcaster and seal exactly once:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
broadcaster = MaquinaStream::Broadcaster.new(message)
|
|
65
|
+
chat.ask(prompt) { |chunk| broadcaster.append(chunk.content.to_s) }
|
|
66
|
+
broadcaster.seal!
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Render it, live or from history. The engine appends block HTML into
|
|
70
|
+
`#ms-msg-<sid>` and never creates that element — the wrapper is yours:
|
|
71
|
+
|
|
72
|
+
```erb
|
|
73
|
+
<div id="ms-msg-<%= message.maquina_stream_id %>"
|
|
74
|
+
data-controller="ms-repair ms-reveal"
|
|
75
|
+
data-ms-repair-manifest-url-value="<%= maquina_stream.manifest_path(sid: message.maquina_stream_id) %>"
|
|
76
|
+
data-ms-repair-blocks-url-value="<%= maquina_stream.blocks_path(sid: message.maquina_stream_id) %>"
|
|
77
|
+
<%= "data-ms-streaming" if message.maquina_stream_open? %>><%= MaquinaStream.render(message) %></div>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
That is the whole integration. [Getting started](docs/getting-started.md) walks
|
|
81
|
+
the same path with a working end-to-end run, and lists every step the
|
|
82
|
+
generators take for hosts that would rather do them by hand.
|
|
83
|
+
|
|
84
|
+
## What you get
|
|
85
|
+
|
|
86
|
+
Markdown in:
|
|
87
|
+
|
|
88
|
+
````markdown
|
|
89
|
+
## Status
|
|
90
|
+
|
|
91
|
+
All **good**. See [docs](https://example.com).
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
puts 1
|
|
95
|
+
```
|
|
96
|
+
````
|
|
97
|
+
|
|
98
|
+
HTML out — sanitized, block-addressed, and highlighted by CSS class so a theme
|
|
99
|
+
switch needs no re-render (attributes elided for width):
|
|
100
|
+
|
|
101
|
+
```html
|
|
102
|
+
<h2 id="ms-42-b0" data-ms-element="h2" data-ms-block data-ms-block-digest="fa59d158d6fc403e">Status…</h2>
|
|
103
|
+
<p id="ms-42-b1" data-ms-element="p" data-ms-block data-ms-block-digest="c39b9098233d53d5">All <strong>good</strong>. See <a href="https://example.com" rel="noopener noreferrer">docs</a>.</p>
|
|
104
|
+
<div id="ms-42-b2" data-ms-code data-ms-code-lang="ruby" data-controller="ms-code" data-ms-block …>
|
|
105
|
+
<pre><code><span class="nb">puts</span> <span class="mi">1</span></code></pre>
|
|
106
|
+
<pre hidden data-ms-code-source>puts 1</pre>
|
|
107
|
+
</div>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Plus: copy and download on code blocks and tables, a link-safety dialog, a
|
|
111
|
+
word-level reveal animation, autoscroll that gets out of the reader's way,
|
|
112
|
+
client-rendered diagrams and math, and markdown export.
|
|
113
|
+
|
|
114
|
+
## Agents
|
|
115
|
+
|
|
116
|
+
This engine is built for agent output, and a tool call is its own `Streamable`
|
|
117
|
+
record rather than a block inside the assistant's message — so each step of a
|
|
118
|
+
run seals, repairs and exports on its own, and two steps can be open at once,
|
|
119
|
+
which is what a parallel tool call is.
|
|
120
|
+
|
|
121
|
+
[Nexo](https://maquina.app/documentation/nexo/) is the agent harness it is built
|
|
122
|
+
alongside: `Agent#prompt` reports tool activity through a block, and each
|
|
123
|
+
`:tool_call` opens a record. `Message.stream_agent_run` in
|
|
124
|
+
`test/dummy/app/models/message.rb` is the whole bridge, and `/harness/agent`
|
|
125
|
+
runs it against a real model. See [streaming.md](docs/streaming.md#streaming-an-agent-run).
|
|
126
|
+
|
|
127
|
+
Nothing here depends on Nexo. Bare `ruby_llm`, or any client that hands you text
|
|
128
|
+
as it arrives, works the same way — `/harness/chat` is that version.
|
|
129
|
+
|
|
130
|
+
## Documentation
|
|
131
|
+
|
|
132
|
+
| Document | What it covers |
|
|
133
|
+
|---|---|
|
|
134
|
+
| [getting-started.md](docs/getting-started.md) | Install, the model, the migration, the view, the JavaScript, one message end to end |
|
|
135
|
+
| [configuration.md](docs/configuration.md) | Every option, its default, and how to choose |
|
|
136
|
+
| [streaming.md](docs/streaming.md) | The `Streamable` contract, the broadcaster, sealing, agent runs, history |
|
|
137
|
+
| [repair.md](docs/repair.md) | Why repair exists, the two routes, `find_stream` and `authorize` |
|
|
138
|
+
| [registries.md](docs/registries.md) | Fences, custom tags, element overrides |
|
|
139
|
+
| [javascript.md](docs/javascript.md) | The Stimulus controllers, importmap setup, controls, events |
|
|
140
|
+
| [security.md](docs/security.md) | The sanitizer's guarantees and limits, URL hardening |
|
|
141
|
+
| [deferred-renderers.md](docs/deferred-renderers.md) | Diagrams, math, writing your own |
|
|
142
|
+
|
|
143
|
+
`test/dummy` is a working host: the `Streamable` model, both seams, all three
|
|
144
|
+
fence strategies, the tag registry, and live pages at `/harness/chat` and
|
|
145
|
+
`/harness/agent` that talk to a real model.
|
|
146
|
+
|
|
147
|
+
## What it deliberately does not do
|
|
148
|
+
|
|
149
|
+
- **Deltas do not converge on their own.** Only the open tail is patched; a
|
|
150
|
+
block that changes after it stops being the tail is fixed by the repair path.
|
|
151
|
+
- **Sealed blocks are never re-broadcast.** That is what keeps bandwidth
|
|
152
|
+
tracking drift instead of message length.
|
|
153
|
+
- **An open fence is never highlighted**, and a client-deferred fence emits no
|
|
154
|
+
payload until it closes. Expect no syntax colours and no diagram until the
|
|
155
|
+
closing ``` arrives.
|
|
156
|
+
|
|
157
|
+
## maquina
|
|
158
|
+
|
|
159
|
+
Part of [maquina](https://maquina.app) — open source for Ruby and Ruby AI.
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT, © Mario Alberto Chávez. See [LICENSE.txt](LICENSE.txt).
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* Vendored fallback for the `attachment` component. Loaded only while the
|
|
2
|
+
* vendored partial is the one rendering.
|
|
3
|
+
*
|
|
4
|
+
* Layout is Tailwind, in the partial. What lives here is what a utility class
|
|
5
|
+
* cannot express: the variant shapes, truncation of a long filename, and the
|
|
6
|
+
* hidden state the download control takes when its image fails to load.
|
|
7
|
+
*/
|
|
8
|
+
[data-component="attachment"] {
|
|
9
|
+
--attachment-radius: var(--surface-radius, 0.5rem);
|
|
10
|
+
|
|
11
|
+
border-radius: var(--attachment-radius);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
[data-component="attachment"][data-variant="inline"] {
|
|
15
|
+
--attachment-radius: 9999px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
[data-component="attachment"] [data-attachment-part="filename"],
|
|
19
|
+
[data-component="attachment"] [data-attachment-part="details"] {
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
text-overflow: ellipsis;
|
|
22
|
+
white-space: nowrap;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
[data-component="attachment"] [data-attachment-part="thumbnail"] {
|
|
26
|
+
object-fit: cover;
|
|
27
|
+
background-color: var(--attachment-thumbnail-bg, rgb(0 0 0 / 0.04));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* `ms-attachment` sets this when the thumbnail errors: a blob that will not
|
|
31
|
+
* load must not offer a download that cannot work.
|
|
32
|
+
*/
|
|
33
|
+
[data-component="attachment"] [data-attachment-part="download"][hidden] {
|
|
34
|
+
display: none;
|
|
35
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* Vendored fallback for the `code-block` component.
|
|
2
|
+
*
|
|
3
|
+
* Loaded ONLY while maquina_stream renders the vendored partial. The day
|
|
4
|
+
* maquina_components ships `code-block`, the resolver stops returning our
|
|
5
|
+
* partial, MaquinaStream::Components.stylesheets stops listing this file, and
|
|
6
|
+
* the same selectors are not defined twice.
|
|
7
|
+
*
|
|
8
|
+
* Colour lives in the two Rouge theme stylesheets, never inline, so a dark
|
|
9
|
+
* mode switch is a stylesheet swap and not a re-render.
|
|
10
|
+
*/
|
|
11
|
+
[data-component="code-block"] {
|
|
12
|
+
--code-block-radius: var(--surface-radius, 0.5rem);
|
|
13
|
+
|
|
14
|
+
position: relative;
|
|
15
|
+
border-radius: var(--code-block-radius);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
[data-component="code-block"] [data-code-block-part="pre"] {
|
|
19
|
+
margin: 0;
|
|
20
|
+
overflow-x: auto;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
[data-component="code-block"] [data-code-block-part="code"] {
|
|
24
|
+
font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
25
|
+
tab-size: 2;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
[data-component="code-block"] script[data-ms-code-source] {
|
|
29
|
+
display: none;
|
|
30
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* Engine-owned and permanent: the one skeleton for open blocks and
|
|
2
|
+
* unrendered deferred payloads. Pure CSS, zero JS.
|
|
3
|
+
*/
|
|
4
|
+
[data-component="shimmer"] [data-shimmer-part="line"] {
|
|
5
|
+
position: relative;
|
|
6
|
+
overflow: hidden;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
[data-component="shimmer"] [data-shimmer-part="line"]::after {
|
|
10
|
+
content: "";
|
|
11
|
+
position: absolute;
|
|
12
|
+
inset: 0;
|
|
13
|
+
transform: translateX(-100%);
|
|
14
|
+
background-image: linear-gradient(
|
|
15
|
+
90deg,
|
|
16
|
+
rgb(255 255 255 / 0) 0,
|
|
17
|
+
rgb(255 255 255 / 0.35) 50%,
|
|
18
|
+
rgb(255 255 255 / 0) 100%
|
|
19
|
+
);
|
|
20
|
+
animation: ms-shimmer 1.4s infinite;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@keyframes ms-shimmer {
|
|
24
|
+
100% { transform: translateX(100%); }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@media (prefers-reduced-motion: reduce) {
|
|
28
|
+
[data-component="shimmer"] [data-shimmer-part="line"]::after {
|
|
29
|
+
animation: none;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/* Vendored fallback for the `snippet` component. Loaded only while the
|
|
2
|
+
* vendored partial is the one rendering.
|
|
3
|
+
*/
|
|
4
|
+
[data-component="snippet"] {
|
|
5
|
+
--snippet-radius: var(--surface-radius, 0.375rem);
|
|
6
|
+
|
|
7
|
+
border-radius: var(--snippet-radius);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
[data-component="snippet"] [data-snippet-part="command"] {
|
|
11
|
+
font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
12
|
+
white-space: pre;
|
|
13
|
+
overflow-x: auto;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
[data-component="snippet"] [data-snippet-part="prompt"] {
|
|
17
|
+
user-select: none;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
[data-component="snippet"] script[data-ms-code-source] {
|
|
21
|
+
display: none;
|
|
22
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* Engine-owned and permanent: the reference implementation of
|
|
2
|
+
* `register_tag :source`.
|
|
3
|
+
*/
|
|
4
|
+
[data-component="source-citation"] {
|
|
5
|
+
--source-citation-radius: var(--surface-radius, 0.25rem);
|
|
6
|
+
|
|
7
|
+
border-radius: var(--source-citation-radius);
|
|
8
|
+
line-height: 1.2;
|
|
9
|
+
vertical-align: baseline;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
[data-component="source-citation"] [data-source-citation-part="index"] {
|
|
13
|
+
font-variant-numeric: tabular-nums;
|
|
14
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* Vendored fallback for the `suggestion` component. Loaded only while the
|
|
2
|
+
* vendored partial is the one rendering.
|
|
3
|
+
*
|
|
4
|
+
* No JavaScript and no client state: a chip is a link or a form submission, so
|
|
5
|
+
* everything here is shape and focus.
|
|
6
|
+
*/
|
|
7
|
+
[data-component="suggestion"] {
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-wrap: wrap;
|
|
10
|
+
gap: var(--suggestion-gap, 0.5rem);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
[data-component="suggestion"] [data-suggestion-part="chip"] {
|
|
14
|
+
--suggestion-radius: 9999px;
|
|
15
|
+
|
|
16
|
+
border-radius: var(--suggestion-radius);
|
|
17
|
+
cursor: pointer;
|
|
18
|
+
white-space: nowrap;
|
|
19
|
+
max-width: 100%;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
text-overflow: ellipsis;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* button_to wraps its button in a form; the chip must still sit in the row. */
|
|
25
|
+
[data-component="suggestion"] form {
|
|
26
|
+
display: contents;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
[data-component="suggestion"] [data-suggestion-part="chip"]:focus-visible {
|
|
30
|
+
outline: var(--focus-ring-width, 2px) solid var(--focus-ring-color, currentColor);
|
|
31
|
+
outline-offset: 2px;
|
|
32
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/* maquina_stream — streaming reveal.
|
|
2
|
+
*
|
|
3
|
+
* Load once in the host layout, next to the theme:
|
|
4
|
+
*
|
|
5
|
+
* <%= stylesheet_link_tag "maquina_stream/reveal" %>
|
|
6
|
+
*
|
|
7
|
+
* `ms-reveal` wraps the text that just arrived in one
|
|
8
|
+
* `<span data-ms-revealing>` and unwraps it again on `animationend`. This file
|
|
9
|
+
* is therefore one keyframe and one rule: the controller decides *what*
|
|
10
|
+
* animates, the stylesheet decides only how it looks.
|
|
11
|
+
*
|
|
12
|
+
* There is no mask here on purpose. A gradient mask placed from a character
|
|
13
|
+
* fraction hides a horizontal band, and on a block wrapped over several lines
|
|
14
|
+
* that band crosses lines the reader has already read — they flash out and back
|
|
15
|
+
* in on every frame. Animating the new text itself needs no mapping from
|
|
16
|
+
* characters to pixels at all. See app/javascript/maquina_stream/controllers/
|
|
17
|
+
* ms_reveal_controller.js.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
@keyframes ms-reveal-in {
|
|
21
|
+
from {
|
|
22
|
+
opacity: 0;
|
|
23
|
+
filter: blur(4px);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
to {
|
|
27
|
+
opacity: 1;
|
|
28
|
+
filter: blur(0);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
[data-ms-revealing] {
|
|
33
|
+
animation: ms-reveal-in var(--ms-reveal-duration, 320ms) ease-out both;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* No animation, and the text is fully present the moment it arrives. The
|
|
37
|
+
* controller answers the same query and stops wrapping entirely — with
|
|
38
|
+
* `animation: none` no `animationend` fires, so a span wrapped anyway would
|
|
39
|
+
* never be unwrapped. This rule is the second half of that: it also covers a
|
|
40
|
+
* span left mid-flight by the preference changing while a message streams.
|
|
41
|
+
*/
|
|
42
|
+
@media (prefers-reduced-motion: reduce) {
|
|
43
|
+
[data-ms-revealing] {
|
|
44
|
+
animation: none !important;
|
|
45
|
+
opacity: 1 !important;
|
|
46
|
+
filter: none !important;
|
|
47
|
+
}
|
|
48
|
+
}
|