# sb-builder: one UI frame for every tab Date: 2026-10-20 Status: approved Builds on: phases 1, 2 and 4 (tabs, Jobs, Packages, Results, Hints) ## Goal Every tab looks and works the same, and a multi-step task (check upstream, update hints, preview, build) never sends the user hunting for the next button in another place. Rules go into AGENTS.md so later tabs follow them. ## What is wrong today - Action buttons sit at the bottom in Packages and Hints, in a side pane in Results, at the top in Jobs. - The ribbon opens tabs; tabs open and close. - A step's follow-up lives elsewhere: Packages action -> auto switch to Jobs -> Run it at the top of Jobs. Hints: Check upstream (bottom) -> tick -> Update selected (bottom) -> diff dialog -> Build these -> Jobs -> Run it (top). - The Jobs Start menu name says nothing of what it holds. ## Window frame Top to bottom: main ribbon, small ribbon, tab bar, the tab's view (its filter bar first), status bar. - **Permanent tabs**: Dashboard (default), Packages, Hints, Results, Jobs, created at startup, not closable. Each loads its data the first time it is shown (Packages, Hints and Results call `inventory`/`results` then, not at startup). Navigation: tab bar, Ctrl+PgUp/PgDn, Dashboard tiles (Packages, Recent jobs, slackrepo results as today). The ribbon does not open tabs. `File` menu unchanged. - **Main ribbon**: `QToolBar`, 32 px icons, text under icon, the current tab's actions in workflow order. Refilled on tab change. - **Small ribbon**: `QToolBar` right under it, 16 px icons, text beside icon, the tab's view controls. Hidden when the tab has none. - Each tab exposes `actions` and `view_actions`, lists of `QAction` it owns, so enabled and checked state survive tab switches. Icons are freedesktop theme names with a `QStyle` fallback, as the ribbon does now. - An action that does not apply is disabled with a tooltip saying why; never hidden, so the ribbon does not shift. - Filter bars (search, chips, `N shown · M selected` or `N of M`) stay at the top of the view; their Refresh buttons move to the small ribbon. ## Selection - Change actions act on the **ticked** rows; view actions (Show build log) on the **highlighted** row. - A click anywhere on a row toggles its tick (done for Packages and Hints). - Ctrl+A ticks the visible rows, Ctrl+Shift+A unticks all (done); the small ribbon's Select visible and Clear carry the same shortcuts. ## Ribbons per tab | Tab | Main ribbon | Small ribbon | |---|---|---| | Dashboard | Refresh | none | | Packages | Build, Update, Rebuild, Remove, Update all, New hint, Publish | Select visible, Clear, Refresh | | Hints | Check upstream, Update selected, Delete hint | Select visible, Clear, No download (checkable), Refresh | | Results | Show build log | Refresh | | Jobs | Pull overlay (menu: one entry per overlay), Check shadowing, Run it, Kill | none | Disabled reasons (tooltips): - Build, Update, Rebuild, Remove: "tick packages first". - New hint: "none of the ticked packages is without a hint". - Update selected: "tick outdated packages after Check upstream". - Delete hint: "none of the ticked packages has a hint". - Show build log: "select a result with a log". - Run it: "select a finished preview". - Kill: "select a running job". - Check upstream while running: "checking…". Update all, Publish, Pull overlay and Check shadowing are always enabled (the helper refuses a second job with the busy answer, as today). Packages' detail pane keeps **Open in Hints** as a link (navigation, not an action). Its New hint button moves to the ribbon. ### New hint on ticked packages `hint-new` takes one or more packages and no version: `hint-new ... [--no-dl]`. Each gets the `.info` pin; Update selected from upstream sets a version later. The GUI sends no `--no-dl` here (Update selected has the toggle); the helper keeps the flag. Every package must be without a hint, else the job is refused before it starts, as today for one. Helper `VERSION` 0.6.0 in both files, VM redeploy. The New hint dialog goes away. ## Next-step dialogs - Shown only for jobs started from this GUI session: the main window keeps the ids `job-start` returned. Jobs seen on reconnect or started elsewhere only notify, as today. - Non-modal, one per job, never reopened by polling. - **Preview ended ok** (`slackrepo` with `--preview`): dialog titled `Preview: `, with the whole job log (`job-follow` from offset 0; the job has ended, so the stream closes by itself), a **publish** checkbox (default `builds.publish`), **Run it** (default button) and **Not now**. Run it goes through the signing-key check (`start_signed`) and starts the real run. - **sbpull or sbshadow preview ended ok with a shadow plan** (`shadow:` or `unshadow:` lines, as `SHADOW_PLAN` today): same dialog, **Apply shadowing** (`sbshadow --yes`, after a confirm: it changes the repo) and **Not now**. Without a plan, no dialog. - **Hint job ended** (`hint-update`, `hint-new`) or a delete: the existing diff dialog; Build these starts `slackrepo update --preview`, whose end opens the preview dialog. - A real run ending, a failed or killed job: notification only. - **Run it** in the Jobs ribbon, with a finished preview selected, reopens that preview's dialog (also for a shadowing preview), so Not now loses nothing. The Jobs top bar (Start, Apply shadowing, Run it, publish, Kill) goes away. The hugo flow becomes: Hints, Check upstream -> Update selected -> diff dialog, Build these -> preview dialog, Run it. No tab switch. ## Jobs tab signal - Starting a job no longer switches to Jobs; the user stays on the tab. - While a job runs, the Jobs tab shows a running icon (`system-run`) and reads "Jobs (running)": the tab style sheet's text color would override a per-tab color. - When it ends, the icon becomes the end status (`✔` ok, `✘` failed, killed, lost) and stays until the Jobs tab is shown, then clears. - The status bar job button stays. ## AGENTS.md: UI guidelines A new section, binding for every tab: 1. Tabs are permanent; each loads lazily on first show. 2. Main ribbon: the tab's actions in workflow order. Small ribbon: view controls. Nothing actionable lives inside a view except navigation links. 3. Change actions act on ticked rows, view actions on the highlighted row. A row click toggles its tick; Ctrl+A / Ctrl+Shift+A select visible / clear. 4. An inapplicable action is disabled with a tooltip saying why, never hidden. 5. When a job this session started ends, its next step opens as a non-modal dialog where the user is; the Jobs tab shows activity; a dismissed step can be taken again from the ribbon. 6. A modal confirm only for destructive or outward steps (delete hint, publish, apply shadowing). ## Testing `test_gui.py` with the fake remote, offscreen: - ribbon contents per tab (action texts in order) after switching tabs; - disabled states and tooltips (nothing ticked, no preview selected, no job running); - tabs are not closable and present from the start; data loaded on first show only (no `inventory` call before Packages or Hints is shown); - a preview this session started ends -> dialog -> Run it starts the real run through the signing check; Not now, then Jobs' Run it reopens it; - a preview not started by this session ends -> no dialog; - Jobs tab icon running -> end status -> cleared on showing Jobs; - New hint on two ticked packages -> `hint-new a b`. `test_remote.py`: `hint-new` with several packages, refusing a version and a package that has a hint.