Conversation
A conversation created by a delegated plugin query kept whatever title the plugin passed, and nothing else. Started from the web UI, where no title is passed at all, every conversation stayed "Untitled" forever, while the same prompt through `jp query` would have been named from its leading heading or by the title model. The delegated path takes the same two routes as `jp query`. A leading markdown heading is written to the conversation before the turn starts; anything else hands `conversation.title.generate` a task that runs alongside the turn, so the name lands in the list while the answer is still streaming. A title model that fails is logged and skipped rather than taking the turn down with it. `TitleGeneratorTask::generate` is the entry point for a caller that already holds the conversation lock: it asks the model and hands the title back instead of writing it, which the delegated path needs because the lock belongs to the turn. Signed-off-by: Jean Mertz <git@jeanmertz.com>
The three page scripts lived in raw Rust string literals, where nothing could look at them. A stray double quote ended the literal, a redeclaration between two scripts on the same page went unnoticed, and the first sign of either was a blank page and a browser console. The scripts also had to avoid double quotes entirely, which is why every inline SVG attribute is single-quoted. Each script is a file next to the view that serves it, pulled in with `include_str!`, so the bytes reaching the browser are unchanged. A build script parses them with `oxc` and applies the spec's early-error rules, grouped per page because classic scripts share one global lexical scope: a name declared in `list.js` collides with the same name in `filter.js`, and neither file is wrong on its own. Diagnostics are reported as `cargo::error` directives, so a broken script fails `cargo build` with a line and column. Signed-off-by: Jean Mertz <git@jeanmertz.com>
Reaching the controls around a transcript meant reaching for the mouse. Single keys now stand for them: `r` quotes the selection, `c` opens the configuration picker, `e` opens the larger field, `/` focuses the composer, `[` and `]` step between turns, and Home and End jump to either end. Escape leaves the composer, so the keys are reachable again after typing. Each one clicks the control it stands for rather than calling its handler, so a shortcut cannot drift from the button. They are held back while a field has focus or a dialog is open. Quoting also survives a touch: the tap that reaches the Quote button is the tap that dismisses the selection, so the handler ran with nothing selected and did nothing at all. The last selection made inside the transcript is remembered and used as the fallback, then dropped, so a second tap with nothing selected does not quote the same passage twice. Signed-off-by: Jean Mertz <git@jeanmertz.com>
The build script parses the page scripts and applies the spec's early errors, which catches a syntax error or a redeclaration but nothing about the DOM: a misspelled property, a call with the wrong arity, or a method that does not exist on the element it is reached through all build and then fail in the browser. `just typecheck-js-ci` runs `tsc` over `crates/plugins/command/ serve-web/tsconfig.json` with `checkJs`, which reads the scripts as JavaScript against the DOM type definitions and emits nothing. The workflow runs it as its own matrix task, gated on `.js` and `tsconfig.json` changes, and installs Node only for that task. Signed-off-by: Jean Mertz <git@jeanmertz.com>
A table wider than the column overflowed into `.conversation-detail`, whose `overflow-y: auto` makes the horizontal axis scrollable too, so dragging the table dragged the entire transcript sideways. Every rendered table is wrapped in a `.table-scroll` div that scrolls on its own, with `overscroll-behavior-x: contain` so a swipe past either end stops there rather than reaching the transcript or the browser's back gesture. The wrapper carries a shadow on whichever side still has table beyond it: iOS shows no resting scrollbar, and a cut that lands on a column border reads as the end of the table. The shadows are masked by covers painted in the surface colour, which is why each block that holds rendered markdown declares `--surface`. Signed-off-by: Jean Mertz <git@jeanmertz.com>
The conversation rows painted over the header as they scrolled under it, so the header read as if it had no background at all. A sticky element with `z-index: auto` creates no stacking context, which leaves the later elements in the list free to paint on top of it. Giving the header a `z-index` puts it where a sticky header belongs. Signed-off-by: Jean Mertz <git@jeanmertz.com>
Developing the web UI from a checkout took a manual build, a manual
copy into JP's plugin directory, and a manual restart between every
edit. `just serve-web` does the first two and starts the server.
`just serve-web-watch` keeps it running across edits. A plain
`cargo watch -s 'just serve-web'` cannot be used here: a turn started
from the browser runs inside the host process the plugin is attached
to, so restarting on a file change aborts whatever the assistant was in
the middle of, including the assistant editing these very files. The
watcher instead rebuilds, waits for the server to fall idle — no
conversation lock naming its pid, and `/status` reporting nothing in
flight — and only then stops it, leaving a supervisor loop to start the
new build. A tree that does not compile is left alone, and the
supervisor waits on a pipe until a change fixes it.
`_install-serve-web` writes the binary into JP's plugin directory
rather than `cargo install`-ing it: `jp` resolves a plugin from its own
plugin directory first, and `serve-web` is a registry plugin, so
anything the registry put there would shadow a build in `~/.cargo/bin`
forever.
just serve-web --port 4000
JP_SERVE_WEB_WORKSPACE=~/code/other just serve-web-watch
Signed-off-by: Jean Mertz <git@jeanmertz.com>
The ticket command has no format flag of its own; machine-readable output comes from `jp -F json`, which switches the whole invocation. The recipe documentation still advertised `--json`, and `ticket-promote` passed it, so promoting a ticket failed on an unrecognized argument. Signed-off-by: Jean Mertz <git@jeanmertz.com>
`ticket-promote` seeds an RFD draft and then marks the ticket as promoted. A run interrupted between the two leaves the ticket unmarked, so running it again seeds a second draft for the same ticket and leaves the first one orphaned. The seeded Summary names the ticket it came from, which makes it findable: a retry looks for that line under `docs/rfd`, links the draft it finds, and stops instead of seeding another. Signed-off-by: Jean Mertz <git@jeanmertz.com>
Signed-off-by: Jean Mertz <git@jeanmertz.com>
The configuration chooser is an ordered list: each row is either one of the configurations on the load paths or a `--cfg` argument written out, and the order of the rows is the order the host applies them, so a later row overrides an earlier one. A row is dragged by its grip, or moved with the arrow keys while the grip has focus. Until now names were applied before assignments whatever the reader meant, with no way to say otherwise. `+ Configuration` opens a picker over the load paths: type to filter, Enter ticks the first match, and `⌘ Enter` adds everything ticked, in the order it was ticked. `+ Value` adds a row to write an argument into, which is one field rather than a key and a value, so `@path`, a JSON object and the reset keywords all reach `--cfg` now. On the new-conversation page `⌘ Enter` starts the conversation. The chooser needs JavaScript: a list that starts empty and is filled by buttons has nothing to degrade to. The rest of both pages still works without it. The build script also checks the chooser's script, which it had never been told about. Signed-off-by: Jean Mertz <git@jeanmertz.com>
Sending a message from the web UI while a turn is running no longer fails with "A turn is still running. Stop it first, then send." The assistant is interrupted where it is, whatever it had produced is kept as context, and the message becomes the next thing it reads, inside the turn that was already going. This is what Ctrl-C then `[r] Reply` does at a terminal. Staying inside the turn is what makes it reliable. The conversation is never unlocked in between, so there is no window in which the message could be refused as already-locked, and no need to guess when a stopped turn has finished unwinding. A message that arrives just as a turn ends is told so, and starts a turn of its own rather than being lost. The web UI's stop button now also takes effect while the assistant is streaming, instead of when the stream ends on its own. An interrupt naming a conversation used to reach only the handler that owns the gaps between turn phases, which read it at the next phase boundary. `jp` and its plugins must be updated together: `interrupt` carries an `action` (`stop`, `abort` or `reply`) and the protocol is now 9. An `interrupt` without one still means stop, so an older plugin keeps working; a plugin needing 9 is refused by an older `jp` at the handshake, with a message saying which is stale. Signed-off-by: Jean Mertz <git@jeanmertz.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🚧