Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,6 @@ The values are the ones in `src/styles.css`. Every token but `--fs-input` is `ca
At most 200 characters, with `;`, `{`, `}`, `<`,
`>`, `\` and control characters stripped and names with spaces quoted.
- Composer Enter behavior and folded thinking visibility are preferences, not typography tokens.
- `highlightCode` colors code in the chat and the file viewer by its language (default on; off
shows plain text).
- File viewer: `wrapCode` (wrap long lines instead of scrolling sideways; default off),
`markdownWidth`: `readable` (the chat lane, `--chat-w`, centered; default) or `full`, and
`diffStyle`: `unified` (default) or `split` for a repository diff (a phone draws one column
Expand Down Expand Up @@ -1252,9 +1250,8 @@ One set for both themes: the card is island black wherever it shows.
still `Discard`.
- `/` completions come from `GET /api/pane/commands` and group built-in, user and project commands;
`@` completions query `GET /api/pane/files`. Arrow keys navigate, Enter/Tab accepts, Escape closes.
- Paste, picker or drag/drop attaches up to **Files at once** files per action (Settings → Chat, 20 unless the
device chose 1 to 50); the message box names how many it left out. An image is stored as a paste, any other
file under its own sanitised name. Each uploads through `POST /api/pane/image`, three at a time
- Paste, picker or drag/drop attaches up to 100 files per action; the message box names how many it left out.
An image is stored as a paste, any other file under its own sanitised name. Each uploads through `POST /api/pane/image`, three at a time
across all picks, and puts a removable editable `@path` mention at the start of the message (after
a leading `/command`), in the order the files were picked. A path waits while the box holds only a
`/command` still being typed or an IME composition, and a path that does not fit whole under the
Expand Down Expand Up @@ -1350,11 +1347,11 @@ One set for both themes: the card is island black wherever it shows.
control, the X, Escape and the scrim take the same entries off. Beside the list, turning pages
replaces the one entry. A reload steps out of the entries it finds; Forward reopens the page.
- Pages, in order: **Appearance** (theme, colors, density, language, sidebar rows), **Chat**
(panes open in, show thinking, chat width, chat font size and family, highlight code; then
**Composer**: optional plain PageUp/PageDown chat scrolling (off by default), Enter sends, files at once; then **Quick replies**; then **File viewer**: wrap long
(panes open in, show thinking, chat width, chat font size and family; then
**Composer**: Enter sends; then **Quick replies**; then **File viewer**: wrap long
lines, Markdown width, Diff layout), **Terminal** (font size, font family, wheel
speed, input mode, Key bar), **Alerts**, **Voice input**, **Subscription usage**,
**Shortcuts** (the platform-resolved global bindings, then **Paging** with a 0–100 line overlap stepper; one line by default, zero for a full viewport), **Phone & devices** (the phone address,
**Shortcuts** (the platform-resolved global bindings), **Phone & devices** (the phone address,
Keep screen on, Install; then Portal, where the server offers it; then paired devices),
**Remote PCs**, **About** (Updates, herdr, the repository links). A button that points at
Updates opens the dialog on About.
Expand Down Expand Up @@ -1429,9 +1426,10 @@ One set for both themes: the card is island black wherever it shows.
including the TUI. Pending pointer moves are coalesced, requests retain their owning PC/pane,
and a failed request displays an error instead of inventing a local history position.
- Mod+Shift+PageUp/PageDown pages the selected pane only, preserving chat composer focus and
selection. Chat moves instantly by its viewport minus the configured prose-line overlap;
Terminal and focused scrollbar PageUp/PageDown use native viewport rows minus that overlap.
A large overlap still advances one pixel or one terminal row. Paging is blocked by modals;
selection. Chat moves instantly by its viewport minus one prose line;
Terminal and focused scrollbar PageUp/PageDown use native viewport rows minus one. A page still
advances at least one pixel or one terminal row. Plain PageUp/PageDown always scroll the chat
transcript. Paging is blocked by modals;
repeated terminal pages accumulate against the pending offset until herdr acknowledges it.
- Explicit Find cancels unsent scrollbar offsets and waits for a native scroll already sent;
the scrollbar is disabled until that search ends, so an older drag cannot overwrite its result.
Expand Down
5 changes: 5 additions & 0 deletions changelog.d/trim-settings-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
### Removed
- Four more settings: Page overlap (one line stays between pages), Files at once (one paste, pick
or drop takes up to 100 files), Highlight code (always on) and PageUp / PageDown scroll chat
(plain page keys now always scroll the chat transcript, also while typing; in the terminal they
still go to the running program).
18 changes: 8 additions & 10 deletions docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ The snapshot is the last one a browser reported to this server, not a list of de
| --- | --- |
| `Mod+Shift+K` | Command palette |
| `Mod+Shift+J` | Switch Chat / Terminal |
| `Mod+Shift+PageUp` / `PageDown` | Scroll the current pane up / down, retaining the configured overlap |
| `Mod+Shift+PageUp` / `PageDown` | Scroll the current pane up / down, retaining one line |
| `Mod+Shift+B` | Toggle sidebar |
| `Mod+Shift+O` | New workspace (`Mod+Shift+N` too, in the installed app: a Chrome tab keeps `Ctrl+Shift+N` for an incognito window) |
| `Mod+Shift+↑` / `↓` | Previous / next pane |
Expand All @@ -525,31 +525,29 @@ List and tab controls also have focus-local keys; those do not become global ter
On a focused tab, Alt+Shift+Left/Right reorders it and Shift+F10 opens its menu. For the pane menu
on desktop, tab to the pane header's **⋯** (it appears on keyboard focus) and press Enter.
Shift+F10 inside the terminal stays with the terminal program. On the terminal
scrollbar, arrows move a line, Page Up/Down a viewport minus the configured overlap, and Home/End the start/end of history.
scrollbar, arrows move a line, Page Up/Down a viewport minus one line, and Home/End the start/end of history.

On a phone, use the header, the tab's ⋯ and the More menu for app actions. The command palette
and its plugin actions are not available in the phone touch UI. Use a wider screen or an external
keyboard's `Mod+Shift+K` shortcut (or your configured binding).
The terminal key bar is configured separately under **Settings → Terminal**.
An external keyboard uses the same app bindings as a desktop.

**Settings → Chat → Composer → PageUp / PageDown scroll chat** opts into plain page keys
for the transcript, even while its message box is focused. It is off by default and saved on
this device; the draft, caret, selection and focus stay intact. Modified page keys, composition,
Plain PageUp/PageDown scroll the chat transcript, even while its message box is focused;
the draft, caret, selection and focus stay intact. Modified page keys, composition,
other editable controls and modal input retain their own behavior. Plain terminal page keys
continue to belong to the running program.

Paging shortcuts work while the message box is focused, preserving the draft, caret and selection.
Chat scrolls its transcript immediately; Terminal scrolls herdr's shared history, so other
clients see the same terminal position. When herdr explicitly reports zero native history,
the shortcut instead sends plain PageUp/PageDown to the running application through its live
input lease. That application owns its page length; the overlap setting cannot change it.
input lease. That application owns its page length.
Unavailable or invalid history data never causes application input, and no key is queued
while disconnected.
**Settings → Shortcuts → Paging → Page overlap** sets the lines retained between pages:
one by default, from 0 to 100; 0 scrolls a full viewport. Chat uses the displayed text's line
height, and Terminal uses native rows. A large overlap still advances by at least one pixel
in chat or one terminal row. Focused scrollbar PageUp/PageDown uses the same setting.
One line stays visible between pages. Chat uses the displayed text's line height, and Terminal
uses native rows. A page still advances by at least one pixel in chat or one terminal row.
Focused scrollbar PageUp/PageDown pages the same way.
For an iPad with an external keyboard that lacks PageUp/PageDown, assign letters such as
U and D to **Scroll pane up** and **Scroll pane down** in Settings → Shortcuts. Mod+Shift
stays fixed. This lets you read history without leaving the message box or reaching for
Expand Down
2 changes: 0 additions & 2 deletions scripts/application-paging-regression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,6 @@ sys.stdout.flush()
await until(async () => read() === "\x1b[5~\x1b[6~\x1b[5~", "plain PageUp remains application input in the terminal lens");
await page.keyboard.press("ControlOrMeta+Shift+Comma");
await openSettingsPage(page, "Chat");
const toggle = page.getByRole("switch", { name: "PageUp / PageDown scroll chat", exact: true });
assert.equal(await toggle.getAttribute("aria-checked"), "false", "plain chat paging stays off for an existing settings record without this new field");
const before = read();
await page.keyboard.press("ControlOrMeta+Shift+PageDown");
assert.equal(read(), before, "a modal prevents application paging");
Expand Down
22 changes: 0 additions & 22 deletions scripts/file-viewer-regression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -931,28 +931,6 @@ try {
assert.deepEqual(errors, []);
console.log("PASS A file path in a plain code block opens the file, and the block's text is unchanged");

// Settings → Highlight code, off: code is plain text, in the chat and the viewer
await page.evaluate(() => {
const settings = JSON.parse(localStorage.getItem("herdr-web-ui:settings") ?? "{}") as Record<string, unknown>;
localStorage.setItem("herdr-web-ui:settings", JSON.stringify({ ...settings, highlightCode: false }));
});
await page.goto(`${origin}/?pane=${encodeURIComponent(pane)}`);
await page.locator(".conn-live").waitFor();
await page.locator(".markdown-code .hl-code").first().waitFor();
assert.equal(await page.locator(".markdown-code .hl-keyword, .markdown-code .hl-number").count(), 0, "chat code is plain");
assert.equal(await page.locator(".markdown-code .hl-note").count(), 0, "plain by choice is not too long");
await checkCodeFidelity("highlight-off");
await page.getByRole("button", { name: "guide", exact: true }).click();
const plainGuide = page.getByRole("dialog", { name: "guide.md", exact: true });
await plainGuide.getByRole("heading", { name: "Guide", exact: true }).waitFor();
await plainGuide.getByRole("button", { name: "Show source", exact: true }).click();
await plainGuide.locator(".file-viewer-text .hl-line").first().waitFor();
assert.equal(await plainGuide.locator(".hl-keyword, .hl-function").count(), 0, "the viewer's code is plain");
assert.equal(await plainGuide.locator(".file-viewer-notice").count(), 0, "and no notice says it is too long");
console.log("PASS Settings → Highlight code off shows code plain, in the chat and the viewer");
await page.getByRole("button", { name: "Close file", exact: true }).click();
await page.locator(".file-viewer").waitFor({ state: "hidden" });

// the file found, then its body refused (the file gone since, a remote PC dropped): the error's
// JSON must never show as the file's text
await page.route("**/api/fs/file?**", (route) => route.fulfill({ status: 502, contentType: "application/json", body: JSON.stringify({ error: { code: "machine_unavailable", message: "The PC connection was interrupted" } }) }), { times: 1 });
Expand Down
Loading
Loading