feat(web): chat with an uploaded transcript, reusing the ADR-027 path (ADR-033)

GET/POST /uploads/{id}/chat, mounted only when both an Uploader and a Chatter
are wired. The row comes from the user-scoped private store, so another user's
upload is a 404 before any model is involved. The owner's transcript goes to
the SAME Chatter with App.UploadChatMaxChars instead of the 18,000-char caption
default. The chat panel is shared, not forked: chatView gains PostURL/Subject
(empty = a YouTube video, so existing callers are unchanged). The done card
links to chat only when chat is wired.

Mutation-checked: dropping the upload budget, ignoring the done-status guard,
or scoping the lookup to the wrong user each turn tests red.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 16:58:25 +02:00
co-authored by Claude Sonnet 5.5
parent a39f58b730
commit 4fdb212cef
7 changed files with 609 additions and 341 deletions
+4 -4
View File
@@ -583,7 +583,7 @@ templ chatPanel(v chatView) {
</div>
}
if v.Truncated {
<p class="chat-note muted">Working from a bounded portion of a long transcript — answers about the end of the video may be incomplete.</p>
<p class="chat-note muted">Working from a bounded portion of a long transcript — answers about the end of the { v.subject() } may be incomplete.</p>
}
if v.Error != "" {
<p class="chat-error" role="alert">{ v.Error }</p>
@@ -591,8 +591,8 @@ templ chatPanel(v chatView) {
<form
class="chat-form"
method="post"
action={ chatURL(v.VideoID) }
hx-post={ string(chatURL(v.VideoID)) }
action={ v.postURL() }
hx-post={ string(v.postURL()) }
hx-target="#chat-panel"
hx-swap="outerHTML"
>
@@ -609,7 +609,7 @@ templ chatPanel(v chatView) {
</select>
<span class="chat-model-hint muted">Switch models to compare answers on the same transcript.</span>
</label>
<textarea name="question" rows="3" placeholder="Ask a question about this video…" required aria-label="Your question"></textarea>
<textarea name="question" rows="3" placeholder={ "Ask a question about this " + v.subject() + "…" } required aria-label="Your question"></textarea>
<button type="submit" class="btn">Ask</button>
<span class="htmx-indicator chat-thinking">thinking…</span>
</form>