From dae3c6fab01058ff6fbce092a9ed244bf5af0df5 Mon Sep 17 00:00:00 2001 From: Andre Brait Date: Thu, 12 Nov 2026 18:59:01 +0000 Subject: [PATCH 1/2] feat(chat): restyle omp's ask dialogs and forms --- CHANGELOG.md | 1 + app/globals.css | 103 +++++++++++ components/ExtensionDialog.mobile.test.mjs | 5 +- components/ExtensionDialog.tsx | 206 +++++---------------- graphify-out/graph.json | 2 +- 5 files changed, 155 insertions(+), 162 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 244493b4f..a046ad637 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,7 @@ All notable changes to **omp-web** (`@kahme247/ompweb`) are documented in this f - **Subagents show the names the main session gives them.** The subagent chips above the composer, the per-subagent rows of an expanded `task` call and the subagent dialog title now lead with the subagent's name (for example `StartupTimeoutReview`), followed by its agent type. Before, only the agent type appeared, so several subagents of one type were indistinguishable. The dialog lists the agent, model, reasoning effort and session file on labeled rows of their own, and wraps long values (the description, a long model name or file path) instead of cutting them at the dialog's width. - **Back closes the right panel on phones.** With the right panel open, which covers the whole screen, the Back button or gesture now only closes it. Before, Back went on to the page behind the panel (the sidebar, then the previous page), so a few taps meant to close the panel could leave omp-web. - **Back closes the right panel on phones and narrow windows.** With the right panel open, which covers the whole screen there, the Back button or gesture now only closes it. Before, Back went on to the page behind the panel (the sidebar, then the previous page), so a few taps meant to close the panel could leave omp-web. +- **omp's questions and forms are easier to read and answer.** Every choice, in a question, a form, a pick list or an approval, is now a row of the same size, and a checked row is highlighted, including the options the agent pre-checked in a form. Text fields have visible borders and show the keyboard focus ring again, a field left invalid after Submit is outlined in red, a list's **Done selecting** row is marked as an action, and form sections and field labels are spaced apart. Rows and fields follow the touch-target setting, and native controls in the warm dark theme are dark. - **Git commands stay on the folder's own repository.** A host daemon started from a Git hook or with `GIT_DIR` set in its environment no longer runs Git status, file search, worktree, clone or pool preparation against that other repository. The Git panel of a bare repository now reports "not a Git repository" instead of failing. On Windows pool workers, a long creation ID no longer makes preparation fail on Git's path-length limit, and the reported CPU busy time no longer counts interrupt time twice. - **Multiple-choice questions show which options are already checked.** When omp asks one checkbox question at a time, each option now shows ☑ or ☐, and screen readers announce "checked" or "not checked". Before, every option looked unchecked, so tapping an option the agent had pre-checked unchecked it instead. In the composer, tapping a checkbox option now toggles it at once instead of waiting for **Next**, so choosing **Done selecting** next can no longer drop it. This needs an omp that sends checkbox state on `select` requests (can1357/oh-my-pi#18929); older omp versions show the options as before. - **Reuse unchanged chat Markdown during streaming.** Markdown bodies skip rendering when their props are unchanged, while relevant tool-result changes still refresh their message. Back/forward session navigation requests the selected session instead of downloading the full session list. diff --git a/app/globals.css b/app/globals.css index d6cc805b4..8aa26b1a8 100644 --- a/app/globals.css +++ b/app/globals.css @@ -325,6 +325,7 @@ html.omp .omp-wordmark { html.dark:not([data-theme]):not(.omp), html[data-theme="dark"], html.theme-dark { + color-scheme: dark; --bg: #1B1916; --bg-panel: #231F1B; --bg-hover: #2B2721; @@ -4829,6 +4830,108 @@ html[data-touch-targets="accessible"] .extension-dialog-footer > button { } } +/* omp's ask dialogs: one choice-row look for select, approve, ask and the + agent's own forms. A chosen or pre-checked row is tinted; Recommended stays + a text tag so it never reads as a selection. */ +.extension-dialog-header { padding: 12px 16px; border-bottom: 1px solid var(--border); } +.extension-dialog-body { padding: 16px; } +@media (max-width: 640px) { + .extension-dialog-header { padding: 10px 12px; } + .extension-dialog-body { padding: 12px; } +} +.extension-choice { + display: flex; + align-items: flex-start; + gap: 10px; + box-sizing: border-box; + width: 100%; + min-width: 0; + min-height: 36px; + padding: 8px 12px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--bg-panel); + color: var(--text); + font: inherit; + font-size: 13px; + line-height: 20px; + text-align: left; + cursor: pointer; +} +.extension-choice > input { flex-shrink: 0; width: 16px; height: 16px; margin: 2px 0 0; accent-color: var(--accent-strong); cursor: pointer; } +.extension-choice-mark { flex-shrink: 0; width: 16px; text-align: center; color: var(--text-dim); } +.extension-choice[data-checked] .extension-choice-mark { color: var(--accent); } +@media (hover: hover) { + .extension-choice:not(:disabled, [data-checked], :has(input:checked)):hover { + background: var(--bg-hover); + border-color: color-mix(in srgb, var(--text-muted) 35%, var(--border)); + } +} +.extension-choice:is([data-checked], :has(input:checked)) { + border-color: var(--accent); + background: color-mix(in srgb, var(--accent) 10%, var(--bg-panel)); +} +/* The Other row's text box has no frame of its own, so the row shows its focus. */ +.extension-choice:has(textarea:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; } +html.omp .extension-choice:has(textarea:focus-visible) { outline-color: var(--accent-2); } +.extension-choice:disabled { cursor: default; opacity: 0.6; } +/* omp's trailing "Done selecting" row is an action, not one more answer. */ +.extension-choice-action { justify-content: center; border-style: dashed; background: transparent; color: var(--text-muted); font-weight: 600; } +.extension-choice textarea::placeholder, +.extension-field::placeholder { color: var(--text-dim); opacity: 1; } +.extension-field { + box-sizing: border-box; + min-width: 0; + min-height: 36px; + padding: 7px 12px; + /* At least 3:1 against the fill on the darkest themes (omp, warm dark). */ + border: 1px solid color-mix(in srgb, var(--text-muted) 55%, var(--border)); + border-radius: 8px; + background: var(--bg-panel); + color: var(--text); + font: inherit; + font-size: 13px; + line-height: 20px; +} +.extension-field:focus-visible { border-radius: 8px; outline-offset: 1px; } +.extension-dialog-form { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 6px; + color: var(--text); + font-size: 13px; + line-height: 20px; +} +.extension-dialog-form p { margin: 0 0 4px; color: var(--text-muted); } +.extension-dialog-form fieldset { display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px; min-width: 0; margin: 0; padding: 0; border: 0; } +.extension-dialog-form legend { margin-bottom: 8px; padding: 0; font-weight: 600; } +/* A field's own label opens a new block: more room above it than below it. */ +.extension-dialog-form label:not(.extension-choice, .extension-choice > label) { margin-top: 8px; color: var(--text-muted); font-size: 12px; font-weight: 600; } +.extension-dialog-form fieldset + fieldset, +.extension-dialog-form .extension-field + .extension-choice { margin-top: 10px; } +.extension-choice > label { flex: 1; min-width: 0; cursor: pointer; } +.extension-dialog-form :user-invalid { border-color: var(--status-error); } +.extension-dialog-button { + min-height: 32px; + padding: 6px 14px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + color: var(--text-muted); + font: inherit; + font-size: 13px; + font-weight: 500; + cursor: pointer; +} +.extension-dialog-button:hover:not(:disabled) { background: var(--bg-hover); color: var(--text); } +.extension-dialog-button-primary { border-color: var(--accent-strong); background: var(--accent-strong); color: var(--on-accent); } +.extension-dialog-button-primary:hover:not(:disabled) { background: var(--accent-strong); color: var(--on-accent); filter: brightness(1.12); } +.extension-dialog-button-primary:disabled { background: var(--bg-subtle); color: var(--text-dim); cursor: not-allowed; opacity: 0.65; } +html[data-touch-targets="accessible"] :is(.extension-choice, .extension-field) { min-height: 44px; } +@media (max-width: 640px) and (pointer: coarse) and (hover: none) { + html:not([data-touch-targets="compact"]):not([data-touch-targets="accessible"]) :is(.extension-choice, .extension-field) { min-height: 40px; } +} + /* Pagination restores history without replaying entrance motion. */ .chat-message-card { animation: none; } .chat-message[data-live="true"] { animation: chat-message-in var(--dur-med) var(--ease-out-warm) both; } diff --git a/components/ExtensionDialog.mobile.test.mjs b/components/ExtensionDialog.mobile.test.mjs index 72d27b5ba..94c65451c 100644 --- a/components/ExtensionDialog.mobile.test.mjs +++ b/components/ExtensionDialog.mobile.test.mjs @@ -109,7 +109,7 @@ test("both ask grids pin their column so a wide question cannot stretch the pane const grids = html.match(/grid-template-columns:minmax\(0, 1fr\)/g) ?? []; assert.equal(grids.length, 2, "the questions grid and each question's fieldset"); assert.equal((html.match(/
]*min-width:0/g) ?? []).length, 1); - assert.match(html, /
/); + assert.match(html, /
/); }); test("the option row, its text and its preview cannot push past the panel edge", () => { @@ -117,7 +117,8 @@ test("the option row, its text and its preview cannot push past the panel edge", // The label is a flex row inside the fieldset's grid: it needs a zero // minimum and wrapping text of its own, otherwise a long option label or // preview keeps the track at min-content and overflows. - assert.match(html, /