Skip to content

Fixes from the drowse.ai port: phone layouts, dialog focus, raw colours, R naming, the link card, recorded message lists - #71

Merged
a9lim merged 11 commits into
mainfrom
webui/port-fixes-2
Nov 2, 2026
Merged

a9lim merged 11 commits into
mainfrom
webui/port-fixes-2

Conversation

@a9lim

@a9lim a9lim commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Seven fixes the drowse.ai port of 6.0 found, each with a test.

Engine: a stateful message list records the conversation it answers. generate([system, user]) hung the reply off the active node and recorded none of the list, so the node's path was the bare root and every replay of it rendered a different prompt: token_anatomy replayed 11 positions where generation saw 46 (tiny qwen3, reproduced). A message list is now a whole conversation. split_message_list checks the whole list before the tree changes (a leading system message becomes the call's system prompt, kept in the reply's recipe; parent_node_id, raw mode, a second different system prompt, other roles, empty content, thinking the family cannot render and, on a legacy template, non-alternating turns are refused with a 400). The turns are then recorded from the root, each message its own turn, reusing an identical child at every step (add_user_turn's dedup, and add_assistant_turn(dedup_existing=True), which skips nodes inside the reservation). No existing node is edited, [user, user] renders as two turns as it does statelessly, and re-sending a multi-turn list (a regen, or each row of a fan or sweep) shares the history, so the replies are siblings. The reply renders from those turns, so its node replays exactly and a follow-up continues the conversation. Stateless lists, so the OpenAI and Ollama routes, render alone as before. loom/ops.py::split_message_list, begin_generation_turn, loom/tree.py::add_assistant_turn, engine.generate_one; tests in tests/loom/test_loom_ops.py, tests/session/test_engine.py (a fan of [system, user, assistant, user, user] matches the stateless prompt, 111 tokens; the old code gave 102), tests/session/test_batch.py (the stub now runs the real split and record).

Phone layouts. Measured on a tiny drowse serve with WebKit's iPhone 13 emulation:

  • A phone on its side (667x375) gave the conversation log 39 px between the Thread row and the dock; at 844x390 the Page | Inspector layout left it about 14 px. A phone is now narrow or short ((max-width: 720px), (max-height: 520px), in App's phone and the phone blocks of App, drowse.css, Chat, RawBuffer and LoomSidebar). A short phone at least 480 px wide lays the conversation out in two columns: the log under the Thread row, and beside it the composer at the foot of its own column, or the Inspector while it shows (narrower, two columns would leave the log a 44 px column, so 320x480 keeps one). Log height x text width: 480x320 144x172, 568x320 192x260, 667x375 247x352, 1280x500 380x872; the dock fits its column at each. A tapped word opens the Inspector beside the log.
  • The Loom page at 440x796 gave the map 96 px from y=740 (the thread, opened by its Map link, held the column, and the sidebar's overflow-y: clip cut the map), and 0 px at 844x390. Opening the loom on a phone folds the thread, the loom's column grows with its tools and scrolls instead of clipping, and a short phone's loom page scrolls to a map most of a screen tall. The map fits at 440x796 (316 px), 320x568, 844x390 and 667x375.
  • scripts/workbench-layout.test.mjs computes the log's height and text width and the dock's room from the CSS at each short screen and pins which screens are phones (fails on the old CSS); the CSS rule walker moves from modal-surfaces.test.mjs to markup.mjs to share it.
  • A short desktop window (520 px tall or less) now gets the phone layout too.

Focus after a dialog. Escape, Close or the scrim left focus on the body. It now goes back to what opened the dialog while that is still a tab stop, else to the header's Menu (lib/focusTrap.ts::returnFocus; scripts/focus-trap.test.mjs).

Raw completion's colour selector. This was a regression, not a choice. 5.4 drew the reading tools above both formats, with a raw-mode tip. 6.0 moved them into the chat's dock, which raw mode doesn't draw, but left behind the select's raw-mode label, automaticComparisonAvailable's !rawMode and RawBuffer's tinting and empty-colour notice. The reading tools are now one snippet in Chat. Raw mode shows them under the completion's head behind its own Colors button, and both formats share one open state (scripts/raw-colors.test.mjs, server-rendered; fails without the fix).

R naming. The correlation drawer said "J-lens readout" for jlens/ probes; it says "R-lens readout" now (scripts/profile-analytics.test.mjs).

Link card. public/social/drowse.png was the 5.x shader orb. It is now drawn in Ledger as the dashboard in miniature: the mark and name, the term 0.4 calm, a reply tinted by its probe, and one word's reading across the layers on the depth ramp. It is still 1200 x 630, drawn by node scripts/social-card.mjs from scripts/social-card.html with Playwright's Chromium (@playwright/test 1.63.0 joins the webui devDependencies, as on neuron-lens.com and standardlogits.com). The readings on the card are illustrative. The orb's SVG source is deleted. The alt text is recorded in the HTML: "The drowse mark and name, the steering term 0.4 calm, a reply whose words are tinted by how calm the model reads them, and one word's calm reading across the model's layers." drowse.ai's site-metadata needs that text and must drop its SVG checks when its pin moves past this.

Checks: npm run check (svelte-check 0/0, theme, ledger ratchet at zero, 41 script tests) and npm run build with the dashboard rebuilt; pytest -m "not gpu" (6043 passed); ruff check .; pyright (0 errors); generate_webui_types.py --check.

a9lim added 11 commits November 2, 2026 02:42
A stateful generate() from a message list hung the reply off the active node
and recorded none of the list, so the node's path was the bare root and every
replay (token anatomy, unit activity, a fork) rendered a different prompt from
the one the reply was generated from: 11 positions where generation saw 46.

The list is now a whole conversation: its turns land from the root through
append_turn, a leading system message becomes the call's system prompt (kept
in the reply's recipe), and the reply renders from those turns. A parent_node_id,
raw mode, a second differing system prompt or other roles are refused (400).
Stateless lists render alone as before.
Escape, Close or the scrim left focus on the body: the dialog focused its
first stop on open and restored nothing. Focus now goes back to what opened
the dialog while it is still a tab stop, else to the header's Menu (a Menu
item that opened it is gone with the menu).
6.0 moved the reading tools into the chat's dock, which raw mode does not
draw, so a completion could not change what its tokens are coloured by
(RawBuffer still tints them and explains an empty colour, and the select
still carries a raw-mode label). The tools are one snippet now: under the
composer in a chat, under the completion's head behind its own Colors
button in raw mode, sharing one open state.
…creens

drowse.ai's WebKit iPhone checks found two layouts with no room. On a phone
on its side (667x375) the log got 39 px between the Thread row and the dock,
its words scrolled under the Thread toggle; at 844x390 the Page | Inspector
layout left it 14 px. The Loom page at 440x796 gave the map 96 px from
y=740 and none at 844x390.

A phone is now narrow or short (520 px tall or less): App's phone layout and
the stylesheets it leans on take the same query. A short phone lays the
conversation out in two columns, the log under the Thread row and the dock
or the inspector beside it; the loom's page scrolls to a map most of a
screen tall. Opening the loom on a phone folds the thread, and the loom's
column grows with its tools rather than clipping the map under them.
scripts/workbench-layout.test.mjs reads the layout from the CSS; the CSS
rule walker moves to markup.mjs for it and modal-surfaces.
The card was still the 5.x shader orb. Ledger makes a card its product in
miniature: the mark and name, a steering term as the recipe bar draws it, a
reply tinted by its probe as the Page colours words, and one word's reading
across the layers on the depth ramp, the term's layers on a wash. It is drawn
by scripts/social-card.mjs from scripts/social-card.html with Playwright's
Chromium at the old card's 1200 x 630; the alt text sits in the HTML. The
orb's SVG source goes. Also documents the focus return, the short phone
layouts and the raw-mode colours in drowse/web/AGENTS.md.
…reuse its whole matching path

split_message_list now validates every turn before the tree changes (empty
content, labels and thinking, tokenization, thinking the family cannot render,
and alternation on a legacy template) and returns MessageTurns. The list is
recorded from the root without append_turn: each message lands as its own turn,
an identical child (user or assistant, outside the reservation) is reused, and
no existing node is edited. Re-sending a multi-turn list, or each row of a fan
or sweep of one, now shares the history and makes the replies siblings, and
[user, user] renders as two turns, as a stateless render of it does.
…s own column

Under 480 px the side column left the log a 44 px column (12 px of text at
320x480), so a short portrait screen keeps the one column; the loom page's
scroll still applies to every short screen. The dock now spans the side column
at its foot instead of sitting under the Thread row, which pushed it 32 px past
the bottom at 568x320. The layout test computes the log's height and text width
and the dock's room from the CSS at each short screen (measured in WebKit:
480x320 144x172, 568x320 192x260, 667x375 247x352, 1280x500 380x872), pins which
screens are phones, and drops the row arithmetic that could not fail.
…rd its turns and reserve them under one hold of the tree's lock
@a9lim
a9lim merged commit c86d8b6 into main Nov 2, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant