Several changes across the entire application; UI #13
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lyssieth/sermones!13
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "indev"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Does a lot of things. See commit log.
Phase 1 of the UI push (issues #6/#7/#8). Rust owns live conversation state; the frontend now speaks the intent surface end to end. - lib/bridge.ts: rewrite the projection layer around the intent surface — sendMessage / editTurn / regenerateTurn / navigateBranch / newConversation / cancelStream / loadHistory — with a typed IntentResult<T> = {ok: true, value: T} | {ok: false, error: SermonError} so callers handle the typed path and the engine-error path uniformly. The old startStream / saveConversation helpers are gone (no slot to call). Signal wiring (onStreamChunk / onStreamEnded / onConversationUpdated / onConversationListChanged / onErrorRaised) returns disconnect() closures so the store can unbind cleanly. The configuration and error-store surfaces stay as they were. - lib/conversations.svelte.ts: the UI's ConversationStore (Svelte 5 runes class, mirroring errorStore.svelte.ts conventions). Holds the summary sidebar, the per-conversation views, the active id, and the unread-error set; routes stream_chunk / stream_ended by sub_id into the matching turn's assistant_html (wholesale replacement — no per- token diffing, no markdown logic); replays conversation_updated / conversation_list_changed signals; re-points sub_id mappings after edit / regenerate when the leaf turn id changes. Selecting a conversation only flips activeId — never cancels subscriptions, never touches the error store (subscription-cleanup.md and error-visibility.md). - qwebchannel.d.ts: replace the old QObject surface (start_stream / save_conversation / load_history / history_loaded) with the intent surface (send_message / edit_turn / regenerate_turn / navigate_branch / new_conversation / cancel_stream / load_history / conversation_updated / conversation_list_changed / error_raised). Signal connections now expose disconnect(). - App.svelte: minimal placeholder so the page still renders + svelte-check passes; the Phase 5 shell (sidebar + chat view + composer) replaces it. - lib/conversations.test.ts: 16 vitest cases covering intent envelopes, chunk routing, terminal transitions (EndOfStream / Cancelled / ProviderError), the unread-error flag, the select-does-not-cancel rule, stale-chunk drop, the sub-id re-pointing on edit, and detach. Slot answers are real IpcEnvelopes built via wrapCall so the parse layer exercises the same code path it would on a live bridge.The message surface the new composer streams into. Two components: one turn and one column. - MessageView.svelte: one rendered turn. Sanitized HTML replaced wholesale via {@html}; no markdown logic. Hover- and focus-within-revealed toolbar with Copy / Edit / Regenerate, identical position and vocabulary on every bubble. Inline edit = progressive disclosure (textarea in place of the user text, no modal), save → onedit, Escape cancels. Branch pager (‹ k of n ›) on turns with sibling_count > 1, quiet, wired through the siblingIds prop. TurnStatus-driven visuals: Streaming = quiet accent live indicator (no typing theater); Abandoned = dimmed, no error surfaced (error-visibility.md); Errored = inline error styling on the bubble, the actual error lives in ErrorsPanel. Reasoning <details> stays open during the stream, collapses on finish (handled by the streaming flag through assistant_html replacement — no client-side re-render needed). - ChatView.svelte: the centered column at --measure-prose. Keyed {#each view.turns as turn (turn.turn_id)} so a chunk re-renders exactly one MessageView (streaming-backpressure.md). CSS containment (content-visibility + contain-intrinsic-size) on message blocks for long-conversation perf — no JS windowing. Scroll contract: auto-scroll only when within ~1 line of the bottom; "Jump to bottom" pill visible only while scrolled up AND chunks arriving; reading history stays stable. Empty state teaches the interface (Enter / Shift+Enter / branching). The module-scoped siblingIdsFor() exports the sibling resolution so future tests or other surfaces can call it directly.Two issues addressed. 1. Toolbar visibility leaked across instances. Svelte 5 scopes a descendant selector like `.message:hover .toolbar` as `.message.svelte-hash:hover .toolbar:where(.svelte-hash)` — the deeper `:where(.svelte-hash)` has zero specificity but matches the hash class on *any* element in the document, including toolbars inside sibling MessageView instances. Hovering one message then revealed every message's toolbars. Fix: drive toolbar visibility from event handlers on the message element (`onmouseenter` / `onmouseleave` / `onfocusin` / `onfocusout` on the article) and bind a `has-toolbar` class to a `$derived(hovered || focused)` flag. The CSS now reads `.has-toolbar .toolbar` — a self-referencing class on the same element — and there is no cross-instance ambiguity. Keyboard reachability (the brief asks for hover *and* focus-within) is kept. 2. SCSS pipeline swap. Earlier versions routed <style lang="scss"> blocks through Vite's CSS pipeline with lightningcss as the transformer; lightningcss does not understand Svelte's `:global()` marker and surfaces it as an unknown pseudo-class. The split now is: - svelte-preprocess (with sass) compiles SCSS inside <style> blocks before the Svelte compiler adds the scope hash. `:global()` is expanded in this pass. - lightningcss runs as Vite's CSS transformer for the final post-Svelte pass — plain CSS files like app.scss, plus the minification step. Drop sass-embedded in favour of the sass peer dep svelte-preprocess needs; add svelte-preprocess to devDependencies; approve its postinstall script in pnpm-workspace.yaml.Three fixes from testing on the desktop. 1. Toolbar reveal was per-article, not per-section. The message article wraps both the user and assistant halves; hovering anywhere in the article popped both toolbars. Now each section (user / assistant) tracks its own hover + focus state and toggles its own `has-toolbar` class, so hovering the user text reveals only the user toolbar and hovering the reply reveals only the reply toolbar. The sections gained role="group" + aria-label to satisfy the a11y rule that elements with mouseenter/mouseleave handlers carry an ARIA role (svelte-check was flagging it). 2. Copy didn't work. Two bugs: the assistant toolbar's Copy was copying `turn.user_text` (the user message) instead of the reply, and `navigator.clipboard.writeText` can reject silently in QtWebEngine / sandboxed contexts. Rewrote it as `copyText` with a fallback chain: try the async Clipboard API, fall back to a hidden textarea + `document.execCommand('copy')` on rejection. The user Copy copies the user's source text; the assistant Copy strips the rendered HTML to its text content via innerText so the clipboard gets clean text, not markup. 3. Chromium warning about form fields needing id/name: the composer form and its textarea now carry name attributes (name="composer" / name="composer-input").Copy was still dead. The JS clipboard paths are blocked inside QtWebEngine: javascriptCanAccessClipboard is off by default, and even when enabled, navigator.clipboard.writeText throws "Write permission denied" because the clipboard-write permission grant stays denied (QTBUG-77450). document.execCommand('copy') is deprecated and equally blocked. No amount of JS-side fallback was going to fix it — the sandbox has to be bypassed, not worked around. Fix: route copy through Qt itself, exactly the "native workaround" Qt recommends for this case. - shell.cpp/shell.h: copy_to_clipboard(const QString&) calls QGuiApplication::clipboard()->setText(text). The system clipboard is owned by QGuiApplication; the web sandbox is not involved. - bridge.rs: declares copy_to_clipboard in the extern "C++" block and exposes it as the qinvokable slot copy_text(text: QString) -> QString. Plain-text slot (like get_schema_hash), fire-and-forget, returns empty on success. - bridge.ts: copyText(bridge, text) helper; qwebchannel.d.ts declares the slot. - MessageView: the Copy buttons now route through an oncopy prop (wired App.svelte → ChatView → MessageView) instead of doing JS clipboard work. copyUser sends the user source text; copyAssistant strips assistant_html to innerText first so the clipboard gets clean text. The checkmark affordance stays local. - App.svelte: onCopy calls the bridge slot, with a navigator.clipboard fallback for browser-dev-server workflows (localhost is a secure context there).Replace the turn-based tree (TurnNode { user, exchanges }) with a flat list of UserMessage / AssistantMessage nodes. Edit and regenerate become structurally distinct operations on different message kinds, each with its own sibling pager. Wire format is a clean break: renamed slots, new IPC types, new schema hash. Old-format files migrate one-shot on load. - Core: MessageId, UserMessage, AssistantMessage, Message, AssistantStatus; append/edit/regenerate/navigate/mark/set-content ops; recover_path - Runtime: rewritten ConversationStore intents + context projection that skips the in-progress streaming assistant; persistence migration step - Bridge: renamed slots, StreamTarget now names assistant_message_id, MessageView projection with per-kind sibling_index/sibling_ids - Frontend: store routes subs to assistant messages; MessageView renders user-or-assistant with two independent pagers - Docs: updated component/architecture docs; new user-message, assistant-message, and conversation-migration docs[DRAFT] fix: pager buttons re-enable after a stream ends; cancelled messages keep their textto WIP: [DRAFT] fix: pager buttons re-enable after a stream ends; cancelled messages keep their textWIP: [DRAFT] fix: pager buttons re-enable after a stream ends; cancelled messages keep their textto WIP: Several changes across the entire application; UIWIP: Several changes across the entire application; UIto Several changes across the entire application; UI