Background generation, unread replies, send/stop, PLAN.md
**Replies now run in the background.** Generation was driven by the SSE request, so navigating away or opening another chat cut the answer off mid-sentence. services/generation.py owns the work as its own task and the SSE endpoint merely follows it. Verified: attached briefly, closed the connection, went to another page -- the reply finished anyway, 832 characters, not marked stopped, auto-titled. Reattaching works because both `render` and `reasoning` frames now carry the whole block rather than a delta. A follower arriving late has no earlier fragments to append to, so deltas would leave it permanently missing the beginning. Verified: attached six seconds in and the first frame already contained 517 characters written while nobody watched. **Unread indicator.** A reply that lands with no follower attached marks its chat unread; the sidebar polls every 10s for out-of-band dot spans plus an HX-Trigger that raises a toast. Polled rather than pushed: a browser sitting on another chat has no connection to the one that finished, and an always-on channel per tab is a lot of machinery for a green dot. `unread_notified` stops the same arrival being announced every tick. Follower count is what decides "was anyone watching", so reading it as it arrives does not mark it unread -- verified both ways. **Stop is the send button.** While a reply is being written the send button becomes a red stop square, found via a MutationObserver on the thread since the composer and the streaming bubble are far apart in the document. The in-bubble Stop is gone. **Attachment border removed.** As asked -- an attachment is a picture, and the frame only ever drew at the wrong width. The anchor now shrink-wraps and the img's width/height attributes are overridden so a small image shows at its own size. Adds PLAN.md: what is built, what is not, known limits, and the decisions that look like oversights until you know the reason. 239 tests, ruff clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -208,8 +208,21 @@
|
||||
the dots go, but Stop must stay reachable until the stream ends. */
|
||||
.msg__body--live:not(:empty) + .msg__waiting .dots { display: none; }
|
||||
|
||||
.msg__stop { color: var(--ink-muted); }
|
||||
.msg__stop:hover { color: var(--danger); border-color: var(--danger); }
|
||||
.composer__stop {
|
||||
background: var(--danger);
|
||||
border-color: var(--danger);
|
||||
color: var(--ink-inverse);
|
||||
}
|
||||
.composer__stop:hover:not(:disabled) {
|
||||
background: var(--danger-hover);
|
||||
border-color: var(--danger-hover);
|
||||
}
|
||||
.composer__stop-square {
|
||||
width: 0.7rem;
|
||||
height: 0.7rem;
|
||||
border-radius: 2px;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
.msg__note {
|
||||
display: flex;
|
||||
@@ -480,25 +493,28 @@
|
||||
.attachments {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
gap: var(--sp-2);
|
||||
margin-bottom: var(--sp-2);
|
||||
}
|
||||
/* inline-block, not block: as a block the anchor filled the column and drew its
|
||||
border at full width around a narrow image. Now the frame is the picture. */
|
||||
/* No frame: an attachment is a picture, and a border around it only ever drew
|
||||
at the wrong width. The anchor shrink-wraps its image rather than filling the
|
||||
column, and the width/height attributes on the <img> are overridden so a
|
||||
small image is shown at its own size instead of being stretched. */
|
||||
.attachments__image {
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
max-width: 100%;
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border);
|
||||
line-height: 0;
|
||||
}
|
||||
.attachments__image img {
|
||||
display: block;
|
||||
max-width: min(22rem, 100%);
|
||||
max-height: 20rem;
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: min(22rem, 100%);
|
||||
max-height: 20rem;
|
||||
object-fit: contain;
|
||||
}
|
||||
.attachments__doc {
|
||||
display: flex;
|
||||
@@ -549,3 +565,15 @@
|
||||
/* Alpine sets x-cloak until it has initialised; without this, collapsed
|
||||
folders flash open on every page load. */
|
||||
[x-cloak] { display: none !important; }
|
||||
|
||||
/* --- Unread indicator ------------------------------------------------------ */
|
||||
.unread-dot {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--success);
|
||||
flex: none;
|
||||
/* A ring so it stays visible against the active row's lighter background. */
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--success) 25%, transparent);
|
||||
}
|
||||
.unread-dot[hidden] { display: none; }
|
||||
|
||||
Reference in New Issue
Block a user