Repository navigation
Restore the stacked merges lost in #72 (layouts, realism, window chrome, UI Library) and reconcile fork main - #85
Merged
aaronsmulktis merged 89 commits intoOct 10, 2026
Conversation
An eighth app: an account index and a per-account transaction ledger,
modelled on a real retail-banking dashboard. Design tokens are sampled
from that dashboard's own stylesheet rather than eyeballed.
Built in the post-migration shape: shared `theme` (design tokens) +
per-app `layout` (structure), with no `appearance/` group. Building it
the old way would have added an eighth app to the migration backlog;
a test asserts the directory stays absent.
config/apps/theme/openbanking{,_dark}.yaml global, shared by all apps
config/apps/openbanking/layout/ default | card_list
config/apps/openbanking/content/ 6 variants
The banking-only tokens (--color-header-bg and friends) are consumed
through `var(--token, <fallback>)`, so the app stays legible under the
seven themes that do not define them.
Read-only by design. AppStateComparison builds every task's target by
deep-copying the initial cross-app state and then requires the whole
dict to match, so an app that drifted on its own would silently break
unrelated todo/calendar rewards. `/openbanking_all` is therefore sorted
by primary key and asserted byte-stable across fetches and unchanged
after browsing every route, including the click-to-reveal disclosure.
Account and routing numbers are masked to their last four digits and
revealed by clicking the figure itself. Disclosure is stateless --
visibility travels in the query string, nothing is written back -- and
each control carries the other row's state so opening one does not
re-hide the other. Account-number tails match the digits in each
account's display name; the three accounts belong to three different
businesses so an agent cannot assume one holder.
Tasks live in their own `tasks=openbanking` group rather than in
`all_tasks`, which is the paper's canonical 30-task inventory. Unlike
longer_horizon they deliberately do not quote their answers in the
goal -- the value is in the ledger, so the agent has to read the right
cell before it can act, and the scorable delta lands in another app.
That makes them rot silently if the seed changes, so
TestTaskAnswersMatchTheSeed re-derives each answer from the config, and
a second test pins every amount identical across content variants (a
translated balance would need a different answer per language).
Also fixes tests/save_screenshots.py, which still named an `appearance`
group for `todo` after that app was migrated -- `--variation dark_theme`
had been failing Hydra composition. Migrated apps now take the look from
a global `apps/theme=` override instead.
Test plan: uv run -m pytest tests/ -> 817 passed, 7 skipped (was 724).
Not visually verified: chromium will not launch in this environment, so
the two new screenshot baselines still need generating.
Rebased onto the appearance removal (5c267af), which had already rewritten this section around the theme/layout split. What survives from the original commit is the OpenBanking-specific half; the migration-status prose it also carried described `appearance` co-existing with `theme`, which is no longer true of any app. * `openbanking` and `openbanking_dark` join the shipped-themes list. * `card_list` joins the layout inventory. * A new paragraph on `tokens` being open-ended: an app may introduce tokens of its own, and should read them through `var(--token, <fallback>)` so it stays legible under the themes that do not define them. OpenBanking is the worked example, so this belongs with the app rather than on its own. Note that git applied the original patch without a conflict -- it matched on the `#### Content` heading -- and produced a second `#### Theme and layout` section contradicting the one above it. Resolved by hand.
1. rename banking methods for uniqueness 1. retries in `safe_get_json` and fallback return
The card account rendered through the same `account_summary` as checking:
the same balance hero, the same account/routing disclosure. Nothing on the
page said "card", and the hero read $0.00 because a card's
`available_balance` is zero by definition.
Accounts now carry a `kind` (`deposit` | `credit_card`, omitted means
deposit, so the noise variants can keep appending plain checking accounts).
A card renders instead:
* a card graphic -- brand, chip, PAN in 4-4-4-4 groups, holder, expiry,
invented two-disc network mark -- with the show/hide control in its
top-right corner;
* a payment panel: statement balance, minimum payment due, payment due
date, current balance, available to spend, credit limit, and the
statement close date underneath.
No routing number. That is the reason `kind` exists rather than a pile of
optional fields: a card has none, so the row is absent instead of blank,
and `routing_number` is now `Optional[str]`.
The face reuses `--color-header-bg`/`--color-header-fg` rather than adding
banking tokens, so a theme that restyles the masthead restyles the card,
and the existing fallback chain keeps it legible under the seven themes
that define neither. Chip and mark are inline SVG in `currentColor`: a
raster asset would want a per-theme light/dark pair and a remote one would
put a CDN in the request path that test_no_egress forbids.
Still read-only. Disclosure state travels in the query string exactly as
before, and a test asserts `/openbanking_all` is byte-identical after
revealing the PAN.
Seed data. The PAN is deliberately unmistakably synthetic -- leading 9 is
not an assigned network prefix, and the number fails Luhn, both asserted.
The payment figures are one coherent cycle rather than three invented
numbers: closed Aug 21 at 872.19, the Aug 23 charge of 412.00 posted after
the close, 872.19 + 412.00 = 1284.19 is today's balance, and
1284.19 + 8715.81 = the 10000.00 limit. So the statement balance genuinely
differs from the current balance, which is what gives "read the statement
balance" a single right answer.
Three new tasks read the payment panel rather than the ledger. Two
distractors do real work: an agent that takes the largest figure on the
page, or the number nearest the word "payment", gets the wrong one. The due
date scores as a *calendar event*, not a string -- the date string is
translated in the german and mandarin variants, and a string answer would
need one expected value per language.
Test plan: uv run -m pytest tests/ -> 853 passed, 7 skipped (was 825).
Also served it and fetched both states of the card page.
Not visually verified: chromium still will not launch here, so the new
`openbanking_card` screenshot baseline needs generating.
1. better dark theme 1. improve multilingual 1. CLI for adding transactions 1. view more transactions toggle
…l, fix transaction dates, fix dark theme, tests
The docs/ sources landed across this branch without a matching mkdocs build, so site/ was missing the Inspecting the Databases page entirely and the nav on every existing page still had no link to it. Regenerated with `uv run mkdocs build` on the rebased tree, so the output reflects both this branch's docs and the base's.
…itor
The theme/layout split landed with the layout axis nearly empty: calendar,
messenger, maps and code_editor all declared `- layout: default` in their
defaults but nothing read `config.<app>.layout`, so their layout groups were
inert placeholders and structure was held almost constant across sweeps.
calendar agenda_first lands on the agenda, not the month grid
sidebar_nav month nav + view toggle become a left rail
messenger split_inbox chat list stays beside the open thread
compact_list dense avatar-less rows; flat messages
maps sidebar_left search + Saved Locations left of the map
bottom_sheet sidebar becomes a panel under the map
code_editor sidebar_right file tree right of the editor
top_tree file tree as a strip above the editor
Follows the `todo` convention: a `current_layout()` helper reads the config
and the page handlers branch on it. Three supporting refactors, each of which
removes a reason to duplicate:
- code_editor: the three page handlers all built the same
`Div(cls="flex space-x-2")(side_bar, main_screen)`; that is now a single
`editor_page()` so a new variant is added in one place instead of three.
- messenger: the chat list is extracted to `conversation_list(selected=)` so
`split_inbox` can render it beside a thread. The layout is applied as a
class on <body>, because the `max-w-lg` cap that makes this app
phone-shaped lives there and a descendant cannot widen its own ancestor.
- maps: the layout reaches templates/map.html as a <body> class; both
variants are CSS-only, so none of the template's JS knows about them.
Layouts change structure only. Routes, element ids and the `/<app>_all`
endpoints are identical across variants, so rewards -- which read state, not
the DOM -- are unaffected. Screenshot coverage adds the eight variations plus
a `messages_thread` route: both messenger layouts change the thread view,
which the existing list route never renders. Alice exists in every content
variation.
The file tree, the "No file selected" heading and the Language/Theme labels are all marked `text-white`, from when this app painted its panels a hard-coded blue. The theme migration repointed `.main-content` at `var(--color-bg)` -- white in the default theme -- so that chrome became invisible in every layout, including `default`. Point `.text-white` inside `.main-content` at `var(--color-fg)`, and restore white for the elements that carry their own dark fill: the mode/theme selects and tab strip (`bg-gray-*`) and the highlighted folder row (`bg-blue-*`). Found by reading the generated screenshots rather than the DOM -- the markup assertions passed throughout, since the text is present and merely the same color as what is behind it. Not covered by the Code Editor Tasks PR (aaronsmulktis/editor-tasks), whose code-editor changes are the save method and an `editor_binding()` refactor; it touches neither `text-white` nor `.main-content`.
… system The design system had no surface. Tokens lived in yaml, components in src/open_apps/ui, and the only way to see what either looked like was to open an app that happened to use them. This adds /uilibrary: a Storybook for the library, serving two audiences deliberately. Researchers get tokens as swatches and type specimens next to the components those tokens paint, read from the *resolved* theme rather than a hardcoded list -- so `apps/theme=solarized` visibly repaints the page, and a theme that adds a token documents itself without a code change. Agents get an app whose state is small and diffable: one row per documented component holding its active variant and whether it is pinned. Switching a variant or pinning a story writes through to sqlite and re-renders just that story, so "set UIButton to the danger variant" is a checkable task. Task classes come in a follow-up; this lands the surface and `/uilibrary_all`. Nothing here defines a component. Atoms, molecules and the wordmark all come from open_apps.ui and the tokens from open_apps.theme -- a storybook that reimplements its own examples stops being evidence about the real library the moment the two drift. Molecules render as static specimens with inert URLs: several post to /desktop/* and swap #desktop-shell, so wiring them up here would either 404 or mutate another app's state. Three layouts, following the convention in config/apps/<app>/layout/: `default` (nav rail beside a canvas), `single_column`, `grid_gallery`. Content carries the intro and section labels, so the content axis reaches this app's chrome the same way it reaches every other app's. `ui_library` is excluded from the generic reward diff alongside `codeeditor`, for a different reason: a gallery exists to be poked at, so an agent that wanders in and flips a knob mid-task would otherwise register a spurious diff and fail an otherwise-correct episode. Two test expectations move because the app inventory grew: `pinned: all` now resolves to six apps, and the generated "how many shortcuts" question answers 6. Both were hardcoded to the old inventory and the new value is correct. Note for reviewers: the tile takes position 7 and a new palette icon in both icon sets. The OpenBanking PR also claims position 7 -- whichever lands second needs to renumber, and a duplicate is easier to spot in a conflict than a silent gap.
Playwright sizes a headed window to the capture viewport, and the default 1440x1100 is taller than a laptop screen -- the top of the page lands off-display with nothing to scroll back to, so --headed cannot actually be used to watch a capture on a laptop. Adds --viewport WxH, defaulting to the existing 1440x1100 so every captured image keeps its current dimensions and stays comparable against tests/reference_screenshots. Only a run that explicitly passes a different size changes, which the help text warns about.
…nsmulktis/ui-library
…title Two bugs the generated screenshots caught, neither visible to the markup assertions that were already passing. The page emitted the theme's token block and then never used it, so it rendered on the browser's default white whatever theme was selected -- `apps/theme=dark` produced dark swatches sitting on a light page. The one page whose purpose is to show what a theme does was the one page not demonstrating it. Style `body` from `--color-bg` / `--color-fg` / `--font-family`, the same way the other apps do. `logo_title_container` already renders the app icon and title, so the extra H1 printed the name twice.
`.chat .chat-bubble` sets color with !important at higher specificity than `.chat-bubble-primary`, so --color-on-primary never applied. Scope the bubble rules under `.chat` so they win.
The restore-white rule matches [class*="bg-gray-"], which every tree row carries as hover:bg-gray-700, so the previous fix never reached the tree. Give the tree theme-token fills and plain-label folder toggles.
…tions Search becomes a rounded field with a search glyph; Saved Locations are place rows with a quiet remove control; Current Location Info shows a hint until the map is clicked; Return to List of Apps is quiet chrome; Leaflet's zoom bar, layer switcher and popups follow the theme. sidebar_left becomes a Google Maps-style panel emitted before the map with the search pinned on top, layers bottom-left and zoom bottom-right. bottom_sheet becomes a mobile sheet overlapping the map with a grab handle and chip row. Both fold the layer switcher into a button; default keeps it open because the UI questions built from its screenshot ask about it. Ids, labels, handlers and routes are unchanged.
The sheet is a short scroll container, so the absolutely positioned .search-results-mini and .custom-select-options were cut off at its edge. In bottom_sheet they become static (wrapping below their input) and the sheet rises to 75vh while any result list is open. CSS only; default and sidebar_left are untouched.
…yout Month grid: today's date is a filled marker (aria-current="date"), out-of-month cells are shaded blanks, weekends are tinted, and events are compact title-only chips truncated with an ellipsis -- the "(None)" that a missing location printed under every event is gone (location moves to the chip tooltip and the agenda). Toolbar: a "Today" link to the existing /calendar route, compact "< Prev"/"Next >" controls (labels unchanged), and a segmented Calendar | Agenda switcher whose selected segment is tinted, not filled, so "Add Event" is the page's only filled primary. "Return to List of Apps" keeps its bottom-right spot as a smaller unfilled button. Agenda: one date header per day, an "All day" time column (events have no times), title plus location, and an empty state that says what to do. Layouts stay distinct: default is one left-aligned toolbar with actions in the footer (every default-screenshot UI question still holds); agenda_first is a centred masthead over a readable-width agenda; sidebar_nav's rail gains an aria-hidden mini-month under the month nav. All colours come from theme tokens via color-mix tints.
Covers the today marker, title-only chips and no "(None)", blank out-of-month cells, the segmented toggle's single selection, resolve_view falling back per layout, one "Add Event" per page, the Today link target, the default toolbar order the UI questions rely on, and the structural markers that keep the three layouts distinct. Also guards the calendar CSS against hex/rgb literals.
Replace the Story template's flex rows and nested double tile with one colored square tile per app (icon centred, name along the bottom edge) on a fixed three-column grid, styled only from theme tokens: radius, gaps and sizes follow --radius/--space, hover mixes toward --color-fg, keyboard focus gets a --color-primary ring. The header drops the empty sub-header paragraph, the footer becomes a slim bar pinned to the bottom, and phones get two tiles per row. Content variants with descriptions switch to listing cards (colored icon and name, description below) instead of a paragraph of white text on a saturated tile. Under dark/mono tones the single fill still collapses; the black bw glyphs are drawn inverted so they stay visible on it. Tile geometry keys the tokens now own (item_border_radius, item_padding, hover_scale, ...) and the non-functional style/lightbox/use_theme_colors keys are removed; the label ink joins the fill palette as tile_label_*. The broken_logos shuffle moves into shuffle_icons() unchanged so tests can call it.
Render / via TestClient for each layout, theme tone and description variant: every layout links the same five apps in position order, clickable_logos leaves the launcher identical to default, broken_logos still deals icons to the wrong tiles (bw set included), non-light tones collapse the fill, and the launcher CSS carries no color literals.
…ayout
Chat list: initials avatars in a theme-tinted disc (stable per name via
crc32; group chats get two overlapping member discs) instead of identical
outline icons; bold name that ellipsises before the time, muted one-line
preview, short right-aligned date. 'Return to List of Apps' keeps its text,
href and role=button but becomes a small tonal button (a quiet pane link in
split_inbox) below the list.
Thread: header with avatar, name and (for groups) member names; consecutive
messages from one sender are grouped -- tucked corners, tight spacing, the
sender's avatar and the time only on the run's last bubble, group sender
names once per run, no label on your own bubbles. Sender and time stay in
the DOM for every message (visually hidden), so the search script and the
accessibility tree keep them. Timestamps display in one format
('Sep 17, 9:32 AM'); stored values and /messages_all are untouched.
Composer: pill input and a round send button on --color-primary /
--color-on-primary (Pico's role=group was squaring it); icon-only controls
get aria-labels. Search highlight/flash colours move from hex to tokens.
split_inbox: /messages now shows the two-pane window with an empty reading
pane; the list pane is a <nav aria-label='Conversations'>. compact_list:
one-line chat rows (name, preview, date), Slack-compact thread lines with
sender + time on the first line of a run, boxy composer.
All new rules stand on their own (theme tokens only, radii as multiples of
--radius) so the page still lays out when the daisyUI/Tailwind CDNs are
unreachable.
Turns on BrowserGym's Playwright screencast (window chrome and agent cursor included) and copies the result out of <exp_dir>/task_video/<hash>.webm to <record_video_dir>/<time>_<task>_<pass|fail>[_job<N>].webm, hard-linked when possible. Works for launch_agent.py and launch_parallel_agents.py; logged to W&B as episode_video when use_wandb is on. Relative paths resolve from the launch directory, not the Hydra run dir (shared _absolute_path helper, also used for logs_dir).
…SLURM
One switch (record_video) drives BrowserGym's recording; record_video_dir
defaults to ${logs_dir}/recordings and is frozen to an absolute path once, in
the launching process (freeze_recording_dir), before job configs are derived.
A sweep's videos therefore share one folder whether its jobs run locally or
on SLURM, and a relative path never resolves against a compute node's cwd.
Names gain the agent (<time>_<task>_<agent>_<pass|fail>[_job<N>]), made
filename-safe, and are claimed with an exclusive create so concurrent
conduct.sh runs cannot overwrite each other.
[Fix] Parallel root configs could not run a single job on this branch line:
no `device` (InterpolationKeyError resolving browsergym_env_args) and no
`headless` (the launcher passes headless=True to every web app subprocess),
both introduced with the device axis on desktop-shell. Also add apps/chrome,
without which sweeps silently ran without the window chrome.
A disabled app (e.g. the online shop) 404s on its /<app>_all route; retrying only added the full backoff to every state probe.
…ions The appended BUS ANALYSIS CHK (41288.06) outranked BUS SELECT SAVINGS, so the expected answer was wrong in that variant. Shift its running balances down by 25000, and drop 'three accounts' from the goal since three variants append one.
The ledger controls pushed the /ledger partial's URL into history, so a reload showed a bare fragment. The partial now pushes the account page URL via HX-Push-Url and serves the full page to non-htmx requests. Also run the seed-answer tests across the English content variants.
The bank's extra tokens already fall back to the active theme's primary/ accent/fg, so it renders correctly under 'dark'. Per-theme light/dark variants are punted to a separate change.
…okens No app-specific themes: a palette only one app reads doesn't restyle with the rest of the environment. The bank-only tokens (--color-header-bg and friends) collapse to the shared tokens they already fell back to, and a test pins the stylesheet to the shared token contract.
OpenBanking App
… into fork main Syncs fork main with upstream main (facebookresearch#78 shop rewrite) via the conflict- resolved themes-and-layouts. Conflicts were all "both sides registered a new app" -- OpenBanking here, the rewritten shop upstream -- and resolve as a union across start_page, save_screenshots, test_apps and test_no_egress. test_apps drops the old Java-gated shop test for upstream's replacement. Also moves openbanking_app off the legacy `src.open_apps.` import prefix, which upstream removed from every other app in 34d333a.
…ream main) into app-layouts Brings facebookresearch#82 up to date with its base: upstream main (facebookresearch#78 shop rewrite, facebookresearch#77), facebookresearch#72's conflict resolution, fork #7 (code editor tasks) and fork #3 (desktop shell), plus the single online-shop gate in open_apps.apps.availability. Conflicts: - start_page layout/default.yaml: #3 split it into the shared start_page/default.yaml + layout/gallery.yaml, with `desktop` as the default. facebookresearch#82's edits (dead Story knobs dropped, tile_label_color/shadow added) are carried into the shared file; none of the dropped knobs is read anywhere. - start_page/main.py: keep both sides' new helpers (shuffle_icons, light_glyphs; _prerender_wallpaper). - calendar, messenger: facebookresearch#82's versions with the `open_apps.` import prefix; the old calendar_specific_styles block stays deleted. - code editor: facebookresearch#82's structure and design, with #3/#7's behaviour ported on: editor_binding save fix, editor/design theme split, live theme switcher that survives navigation (incl. the Copilot removeProperty fix), no_egress, offline utility fallbacks, semantic sidebar rows and inline SVG icons. test_codeeditor_ui and test_codeeditor_theme both pass. - docs/index.md: facebookresearch#82's per-app layout table (now listing desktop/gallery and the shop's grid/compact_table) followed by #3's start page section; drops the stale "shop is gated on OpenJDK 21" note. test_start_page follows the new defaults: the launcher tests select `gallery` explicitly, and the shop's tile is on the page.
dark_theme/ and challenging_font/ are named after appearance variations the screenshot grid no longer has (it captures theme_dark / theme_challenging_font now), so they only ever showed the old visuals. The split_inbox file with a GitHub-upload name was a leftover from a manual before/after comparison.
Each gallery tab now carries the command that produces it (`uv run launch.py apps/theme=dark`) instead of exporting a variable used by a block further down.
The 400-line Style block made main.py hard to read as handlers. It is request-independent, so it moves verbatim; main imports it as before and todo_main.styles still resolves for the tests.
…ry, episode video Restores what facebookresearch#83 (merged into upstream's desktop-shell branch, never main) and the unpushed desktop-chrome work carried. Conflicts: - codeeditor_app: keep the realism chrome; carry over desktop-chrome's window-chrome-aware height (--oa-titlebar-h / --oa-dock-space). - start_page/main.py: union of availability re-exports and chrome imports. - save_screenshots.py: UI Library routes and layouts alongside the rewritten shop routes and facebookresearch#82 layouts. - tests: keep main's shop-on-by-default expectations; drop the Java-gated shop test; keep the UI Library tests. - Keep the .claude/internet-mode-used marker (859063d restored it).
Conflicts were all inventory additions on both sides (OpenBanking vs the online shop and UI Library), resolved as unions. OpenBanking moves into start_page/default.yaml, where the app inventory lives since the desktop shell deleted layout/default.yaml, at position 8: it and the UI Library both claimed 7. docs/index.md keeps main's layout table plus OpenBanking's card_list and visible_transactions. site/ is regenerated separately.
…w chrome OpenBanking is the seventh visible tile (OpenUI's is disabled by default): tile lists, pin resolution and the shortcut-count question grow to 7. test_chrome's app pages swap /uilibrary for the shop and OpenBanking, the apps actually visible by default. test_start_page renders without the window chrome, whose dock adds links the launcher tests don't own.
TESTING.md lists uv commands to see each recently merged surface: window chrome, the realism sweep, layout variants, desktop shell, devices, themes, OpenBanking, the WebShop rewrite, the UI Library and episode video.
Every page now carries the window chrome, the realism styles and the merged app inventory (OpenBanking, the rewritten shop). Adds the routes that had no baseline: the shop's category/search pages, OpenBanking's three pages, messages_thread and the UI Library, in every variation.
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.
Why
#82 and #83 were merged into upstream's copies of their stacked base branches (
themes-and-layouts,desktop-shell) seconds before #72 merged the fork'sthemes-and-layoutsintomain. The fork branch never had those merges, so neither reachedmain. Two other lines never got a PR: the realism styles sweep and the window chrome work. Forkmainalso carries OpenBanking (fork #4), which upstream doesn't have.What this restores
Merged onto
main, in order:app-layouts(App Layout Variants #82): calendaragenda_first/sidebar_nav, messengersplit_inbox/compact_list, mapssidebar_left/bottom_sheet, code editorsidebar_right/top_treerealism/integration: every app styled like the product it imitates, in every themedesktop-chrome: global title bar (minimise / maximise / close), app dock, animated agent cursor (apps/chrome=default|windows|none); the UI Library (OpenUI - Taskable Component Library #83);record_video=Trueui-library: keeps the OpenUI tile off by defaultmain: OpenBankingWith this merged (as a merge commit, not a squash), fork
mainfast-forwards to upstreammain.Conflict resolutions worth reviewing
--oa-titlebar-h/--oa-dock-space).todo_app/styles.py, where f8e6142 moved the stylesheet.test_start_pagerenders without the chrome, because the dock adds links the launcher tests don't own..gitignore: now ignores the leftover onlineshop JDK directory.Test plan
uv run -m pytest tests/: 1621 passedTESTING.mdlists auvcommand for each restored surfaceuv run launch.py, open a few apps and check the title bar, dock and cursor; thenuv run launch.py apps/messenger/layout=split_inbox apps/theme=darkKnown, not addressed here (both predate these merges)
vscode_dark, the code editor's page title is dark on dark.