Skip to content

feat(wallpaper): OCS browser for importing wallpaper packs - #26

Open
perlowja wants to merge 11 commits into
singularityos-lab:mainfrom
perlowja:ocs-browser-clean
Open

perlowja wants to merge 11 commits into
singularityos-lab:mainfrom
perlowja:ocs-browser-clean

Conversation

@perlowja

@perlowja perlowja commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Depends on #25

Stacked on the wallpaper source-selector branch in #25.

Summary

Updated 2026-09-12 after maintainer review: this branch no longer uses libadwaita at all. Every row/group/dialog is a libsingularity widget, Adw.init() and the Adw.StyleManager colour-scheme forcing are gone from src/core/main.vala, and dependency('libadwaita-1') is gone from meson.build. grep -rn 'Adw' src tests is empty and ldd singularity-desktop | grep -i adwaita returns nothing.

Earlier update 2026-09-07 after live-testing and direct maintainer feedback (use the real *Row widgets with the Group/Page convention; the browser should be an extension of the control panel, not a separate window). The browser was substantially redesigned from the original window-based version:

  • WallpaperOcsBrowser is now a SettingsPage (not a Gtk.Window) pushed via the same navigation stack every other control-panel page uses (view.navigate_to(...)), with standard back navigation -- not a standalone modal.
  • Category and Tag filters are SelectionRow dropdowns (previously chip pills and an expander-based control that had a real click bug).
  • Import/Pin actions are a visible button below each card's thumbnail, not overlaid on the image.
  • The wallpaper attribution overlay (title/author shown on the live desktop background, bottom corner) is wired up end-to-end for both Bing and real-OCS-imported sources, reading from the existing per-image .json sidecar the ncz-wallpaper-ocs helper already writes.
  • Removed a duplicate provider entry (opendesktop.org and pling.com are the same content network under two names).
  • WallpaperOcs (core): a pure JSON adapter over the already-shipped ncz-wallpaper-ocs CLI -- invoked via argv + async GLib.Subprocess, never a shell command string. Parses providers/index/browse/import output; validates every JSON node's type before calling a typed getter, and fails inline (never fabricates a license/author/preview) on malformed or unsupported output.
  • WallpaperOcsImports (core): tracks import state via per-image sidecar files so re-browsing the same pack doesn't re-trigger a duplicate import.

The sensors-popover work that was briefly on this branch has been reverted out of it and split into its own PR, so this branch is wallpaper-only.

Validation evidence

Built via the real production path (build-singularity.sh, no SINGULARITY_SOURCE_DIR override, fresh clone from this branch) on an aarch64 build host, deployed and live-tested on real Sky1/labwc hardware -- not inferred from a clean compile:

  • Live UI walkthrough confirmed: embedded settings page with back navigation, Category/Tag dropdowns actually filter results, Import/Pin buttons visible below thumbnails, provider list shows gnome-look/kde-look/pling/Bing (no opendesktop duplicate).
  • A real Pling item (Mochis, OCS ID 2188591) was imported as the active desktop user; sidecar correctly recorded title/artist; after restarting singularity-desktop, the browser rediscovered the import and showed "Added" (persistence across restarts confirmed, not just in-session state).
  • tests/wallpaper_ocs_test.vala and tests/wallpaper_sidecar_test.vala cover the JSON/sidecar contracts (providers/index/browse/import response shapes, missing/malformed fields, OCS vs Bing sidecar schemas, missing-sidecar-clears-attribution).
  • After the libadwaita removal, re-verified on aarch64: meson setup configures with no libadwaita in core_deps, meson compile builds all 363 targets, meson test is 14/14 OK.

Not yet verified with a real pointer (synthetic input events were rejected as outside-clicks by the layer-shell surface): the visual attribution-overlay rendering itself (applying a Bing/OCS image and confirming the on-screen overlay text) needs a manual click-through to fully close out -- the underlying data path (sidecar -> GSettings keys -> overlay properties) is verified, just not the final visual render via a real click.

Compatibility

No daemon/backend changes, no new settings/state files beyond the two new background-attribution-title/background-attribution-author GSettings keys (schema in the singularity-desktop superproject) -- an import is only "Added" once it's backed by a readable registered .collection and image payload; the existing wallpaper stays active until the user explicitly picks the imported one.

Rollback

Revert this branch's commits.


Assisted-by: Claude Code:claude-opus-5
AI scope: assisted with the OCS/Bing JSON adapter and sidecar parsing and their unit tests, the browser page layout and filter wiring, and the libadwaita removal; the design decisions, the live hardware testing and the review responses are mine.

AI assistance: disclosed

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@perlowja

perlowja commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Update (2026-09-09): this branch has moved substantially since the description above. Summary of what's new:

  • Crash fix: the attribution write introduced here (background-attribution-title/-author) was hitting an undeclared GSettings key and aborting the whole desktop process with SIGABRT. Root-caused via a real coredump on hardware and fixed in the schema -- see fix(schema): add missing background-attribution GSettings keys singularity-desktop#251, which this PR depends on.
  • Provider-boundary refactor: extracted a WallpaperProvider interface + WallpaperProviderRegistry so OCS/Bing (and Openverse/Unsplash, present but currently unregistered) each implement one shared contract instead of the browser calling out to each provider's specifics directly. ACTIVE_PROVIDER_IDS = { "ocs", "bing" } for now -- re-enabling another provider is a one-line change to that list, not a re-port. This was driven by a real defect-class concern: a new provider (or a new field in an existing provider's response) should not be able to reach GSettings/UI code unsanitized again.
  • Delete capability: pack-level and individual-image-level delete, with a real protection rule (origin set + both the content directory and registry file under the user's home) that keeps shipped/first-party wallpaper packs non-deletable without needing per-provider special-casing.

All three are real, hardware-tested, with meson test coverage (wallpaper-collections, wallpaper-ocs, settings-safety) and a real production build (ninja -C build singularity-desktop) on top of the existing state described above.

@perlowja

perlowja commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

One more note on scope/stability, to be explicit: Bing and OCS are the stable, production-ready providers in this PR -- both are hardware-tested, registered by default (ACTIVE_PROVIDER_IDS), and what we'd consider ready for review/merge consideration.

Openverse and Unsplash are real, working code behind the same interface, but still in progress -- not registered by default, not part of what we're asking to be reviewed here. They're structured so enabling either later is a one-line change, but please don't block this PR on their state; happy to bring them forward as a separate follow-up once they're fully settled.

@perlowja

Copy link
Copy Markdown
Contributor Author

Update 2026-09-11 — 6 new commits, real operator-reported bugs found and fixed on live O6N hardware

All found via live use after the redesign above landed, each root-caused with hard evidence (not inferred from a clean build) before fixing:

  • 9b5d4e7 / b3a3c51 — provider display name renamed OCSOCS Network (the aggregate spans gnome-look/kde-look/opendesktop/pling, "OCS" alone was ambiguous with any one of those sites), and the collection-label join rephrased as attribution (%s — by %s) instead of a bare em-dash, so a raw OCS personid handle (e.g. dilaniweerasingha2000, which is the only submitter identifier the OCS content API provides — there's no separate display-name field) doesn't read as garbled text jammed against the photo title.
  • 790ecde — every OCS import was accumulating into one growing "OCS Network" pack correctly (via a matching fix on the ncz-wallpaper-ocs CLI helper, downstream repo), but the WallpaperOcsBrowserPage — cached and reused by SettingsView across every visit, never reconstructed — only ever called imports.discover() once, at construction. Deleting the pack and re-importing the same images left the in-memory "already imported" tracker stale, so the re-imported cards rendered greyed out as if still present. Fixed by re-running discover() and forcing a re-browse every time the page is shown again (this.map signal), not just on first construction.
  • 7d15a0c — deleting a single image inside a pack silently did nothing (the only way to remove it was deleting the whole pack). add_wallpaper_card() resolved the delete target via find_collection(rotation_state.get_selected_collection("ncz")) — always the active rotation source, not necessarily the collection that actually owns that card's uri (the grid legitimately mixes in "recent" images from other collections). A mismatched collection made delete_image()'s contains_uri() check fail, throwing IOError.PERMISSION_DENIED — swallowed by a bare warning(), zero user-visible feedback. Fixed with a real find_owning_collection(uri) lookup.
  • 9bbbbfc — a real defensive improvement: WallpaperManager.reload() now debounces (200ms) on rapid background-picture-uri changes, so clicking through several thumbnails quickly coalesces into one decode+GPU-texture-upload instead of racing several. Kept even though it turned out not to be the root cause of the crash below — it's still correct, cheap insurance against overlapping GPU work on hardware with known driver fragility.
  • 468c556 — the conclusive fix for a real, reliably-reproducible crash: clicking "Select Picture..." (the Current Wallpaper preview's file-picker button) crashed the whole shell (which then auto-restarted and correctly showed the picked image, via the session wrapper's own crash-restart loop — explaining why it looked self-healing). Root-caused with WAYLAND_DEBUG=1 on live hardware (NCZ-OS, labwc compositor): the last protocol request before death is zxdg_exporter_v2.export_toplevel (xdg-foreign-v2), triggered by passing a parent window to Gtk.FileDialog.open() so GTK can hand the portal a transient-parent handle. labwc advertises zxdg_exporter_v2 but disconnects the client instead of replying with zxdg_exported_v2.handle — confirmed via a live gdb backtrace showing the resulting exit() call originates entirely inside libgtk-4.so.1/libglib-2.0.so.0, no application code anywhere in the crashing frames. Fixed by passing null instead of a parent window — a real, supported GtkFileDialog usage pattern, at the minor cost of losing window-stacking/transiency on compositors where xdg-foreign works correctly. (Will file the underlying protocol bug against labwc separately.)

All 6 commits build clean (684/684 targets), were rebuilt via a real container build each time, ABI-verified (ldd), and deployed+re-tested live on O6N (Sky1/labwc) after every fix — including the crash reproduction and its resolution, confirmed live by the operator.

@mirkobrombin mirkobrombin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please replace every direct libadwaita widget with libsingularity components and split the unrelated sensors changes into a separate PR.

perlowja added a commit to perlowja/singularity-shell that referenced this pull request Sep 12, 2026
…nents

Per Mirko's review on PR singularityos-lab#26: every direct Adw.* widget introduced by
this branch's OCS/Bing wallpaper browser work is replaced with its
libsingularity equivalent, matching the convention already used
elsewhere in this codebase (desktop_page.vala's grid_group/SwitchRow,
calendar_view.vala's PreferencesGroup, ush_portal.vala/system_view.vala's
ConfirmDialog).

- wallpaper_ocs_browser.vala: Adw.PreferencesGroup -> PreferencesGroup,
  Adw.ActionRow -> ActionRow, Adw.PreferencesRow -> PreferencesRow,
  Adw.EntryRow -> EntryRow (its apply button/signal replaced by an
  explicit suffix button + entry_activated, since EntryRow has no
  show_apply_button equivalent), and Adw.ComboRow -> SelectionRow for
  the provider/category/tag pickers. SelectionRow stores id/label pairs
  directly, so the old index<->id array plumbing (provider_ids/
  category_ids/tag_ids, selected_id()) is gone; set_choices() now just
  calls set_options()+current_value.
- provider_credential_group.vala: the group itself now extends
  PreferencesGroup instead of Adw.PreferencesGroup; its entry row uses
  EntryRow/PasswordRow instead of Adw.EntryRow/Adw.PasswordEntryRow.
- desktop_page.vala: the Bing Markets two-choice picker moves from
  Adw.ComboRow to SelectionRow (index constants replaced with id
  constants all/pick); the Bing markets multi-select dialog and the
  two delete-confirmation dialogs move from Adw.AlertDialog/
  Adw.MessageDialog to Singularity.Widgets.ConfirmDialog, using its
  custom_area for the region-grouped checkbox list.

Behaviour preserved: every existing severity/threshold/cap/guard this
branch's commits already documented is untouched -- only the widget
classes constructing the UI changed, not the wallpaper/Bing/OCS logic
around them. Verified with a real meson build + the full test suite on
ULTRA (arm64): 14/14 tests pass, singularity-desktop links cleanly.
@perlowja

Copy link
Copy Markdown
Contributor Author

Addressed both points:

libadwaita widgets replaced. Every direct Adw.* widget this branch's OCS/Bing browser code introduced is now a libsingularity component, matching how the rest of this codebase already does it (desktop_page.vala's grid_group/SwitchRow, calendar_view.vala's PreferencesGroup, ush_portal.vala/system_view.vala's ConfirmDialog):

  • wallpaper_ocs_browser.vala: Adw.PreferencesGroup -> PreferencesGroup, Adw.ActionRow -> ActionRow, Adw.PreferencesRow -> PreferencesRow, Adw.EntryRow -> EntryRow, Adw.ComboRow -> SelectionRow for the provider/category/tag pickers.
  • provider_credential_group.vala: now extends PreferencesGroup instead of Adw.PreferencesGroup; uses EntryRow/PasswordRow instead of Adw.EntryRow/Adw.PasswordEntryRow.
  • desktop_page.vala: the Bing Markets picker moves from Adw.ComboRow to SelectionRow; the Bing markets multi-select dialog and both wallpaper delete-confirmation dialogs move from Adw.AlertDialog/Adw.MessageDialog to Singularity.Widgets.ConfirmDialog.

No behavioural change intended -- verified with a real meson setup/meson compile/meson test on a clean checkout (14/14 tests pass).

Sensors changes split out. The sensors-popover work (native-widget conversion + responsiveness) that had ended up bundled into this branch touched only src/components/panel/panel.vala and nothing else, so it's now cleanly extracted into its own branch and PR: #27. panel.vala on this branch is back to main's content.

Re-requesting review.

@mirkobrombin mirkobrombin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Direct libadwaita initialization and dependency still remain :D Also please remove Claude attribution and change it to:

Assisted-by: <tool>:<model-version>
AI scope: what the AI generated in this commit, and the prompt used (or a short summary of it)

@perlowja

Copy link
Copy Markdown
Contributor Author

Both addressed in c2c0e20. Thanks for catching the leftovers — you were right, the widget swap in 5684deb left the initialization and the dependency behind.

1. Direct libadwaita initialization and dependency

Grepped the whole branch diff against main rather than just the last commit's files. Three sites, all of them introduced by this PR:

  • src/core/main.vala:33Adw.init() in SingularityApp.startup(). Removed. It only existed to back the raw Adw rows this PR used to construct, and every one of those is now a libsingularity widget.
  • src/core/main.vala:1183-1184Adw.StyleManager.get_default().color_scheme = …FORCE_DARK/FORCE_LIGHT in update_theme_mode(). Removed. It only affected libadwaita widgets inside this process, and there are none left. The shell chrome already follows Singularity.Style.StyleManager.apply_color_scheme() on the line above it, and third-party apps already follow gtk_application_prefer_dark_theme plus the org.gnome.desktop.interface color-scheme write a few lines below, so nothing lost coverage.
  • meson.build:26dependency('libadwaita-1', version: '>= 1.2'). Removed.

On whether that dependency was genuinely removable rather than just redundant — I checked before deleting the line, since a transitive need would have made it a no-op rather than a fix:

  • core_deps was a plain dependency('gtk4') before this branch, so the libadwaita entry is entirely this PR's.
  • libsingularity deliberately keeps libadwaita out of core (fix(review): keep libadwaita out of core), so PreferencesGroup/ActionRow/SelectionRow/ConfirmDialog are plain GTK4 and don't pull it back in.
  • goa-backend-1.0 is not a declared dependency here (only goa-1.0), src/vapi/goa-backend-1.0.deps lists just goa-1.0 and gtk4, and add_account_page.vala already avoids Goa.BackendProvider specifically to not depend on libadwaita.

So it was a real direct dependency with no remaining consumer, not a redundant declaration of something arriving another way.

I also reworded two stale comments that still mentioned Adw.EntryRow/Adw.ComboRow by name, so grep -rn 'Adw' src tests is now completely empty.

Verified on an aarch64 build host, not inferred:

  • meson setup configures with no libadwaita in core_deps
  • meson compile builds all 363 targets
  • meson test — 14/14 OK
  • ldd build/singularity-desktop | grep -i adwaita — no output, so the binary no longer links libadwaita even transitively

2. Attribution format

Switched to your format. c2c0e20 uses:

Assisted-by: Claude Code:claude-opus-5
AI scope: located the remaining libadwaita initialization and dependency
sites across this branch's full diff, removed the three call sites and the
meson dependency line, reworded two stale comments, and ran the build and
test verification quoted above. Prompt, summarized: "finish the libadwaita
removal - find any remaining Adw.init/Adw.* usage and the lingering
libadwaita pkg-config dependency in this PR's diff, confirm the dependency
is genuinely removable rather than needed transitively through another
dependency, remove it, and verify with meson setup/compile/test on the
aarch64 build host."

I've also put a PR-level Assisted-by: / AI scope: block at the bottom of the description covering the whole PR, and I'll use this format on all singularity-* work going forward rather than the old trailer.

One thing I did not do unilaterally: seven earlier commits on this branch still carry the old Co-Authored-By: trailer. Rewriting those means a force-push of the branch, which would churn the PR and detach your review anchors, so I left it to you — say the word and I'll rewrite the trailers in one pass.

The sensors popover work is already reverted off this branch (3b98920) and split out, so this branch is wallpaper-only as requested in your first pass.

perlowja added a commit to perlowja/singularity-shell that referenced this pull request Sep 12, 2026
…nents

Per Mirko's review on PR singularityos-lab#26: every direct Adw.* widget introduced by
this branch's OCS/Bing wallpaper browser work is replaced with its
libsingularity equivalent, matching the convention already used
elsewhere in this codebase (desktop_page.vala's grid_group/SwitchRow,
calendar_view.vala's PreferencesGroup, ush_portal.vala/system_view.vala's
ConfirmDialog).

- wallpaper_ocs_browser.vala: Adw.PreferencesGroup -> PreferencesGroup,
  Adw.ActionRow -> ActionRow, Adw.PreferencesRow -> PreferencesRow,
  Adw.EntryRow -> EntryRow (its apply button/signal replaced by an
  explicit suffix button + entry_activated, since EntryRow has no
  show_apply_button equivalent), and Adw.ComboRow -> SelectionRow for
  the provider/category/tag pickers. SelectionRow stores id/label pairs
  directly, so the old index<->id array plumbing (provider_ids/
  category_ids/tag_ids, selected_id()) is gone; set_choices() now just
  calls set_options()+current_value.
- provider_credential_group.vala: the group itself now extends
  PreferencesGroup instead of Adw.PreferencesGroup; its entry row uses
  EntryRow/PasswordRow instead of Adw.EntryRow/Adw.PasswordEntryRow.
- desktop_page.vala: the Bing Markets two-choice picker moves from
  Adw.ComboRow to SelectionRow (index constants replaced with id
  constants all/pick); the Bing markets multi-select dialog and the
  two delete-confirmation dialogs move from Adw.AlertDialog/
  Adw.MessageDialog to Singularity.Widgets.ConfirmDialog, using its
  custom_area for the region-grouped checkbox list.

Behaviour preserved: every existing severity/threshold/cap/guard this
branch's commits already documented is untouched -- only the widget
classes constructing the UI changed, not the wallpaper/Bing/OCS logic
around them. Verified with a real meson build + the full test suite on
ULTRA (arm64): 14/14 tests pass, singularity-desktop links cleanly.
@perlowja

perlowja commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

Both halves of your review are done. Branch head is 61378b2.

Libadwaita removal

Three sites, all introduced by this PR, all gone:

  • src/core/main.vala:33Adw.init() in SingularityApp.startup().
  • src/core/main.vala:1183-1184Adw.StyleManager…color_scheme = FORCE_DARK/FORCE_LIGHT in update_theme_mode(). It only affected libadwaita widgets inside this process and there are none left; the shell chrome already follows Singularity.Style.StyleManager.apply_color_scheme() on the line above, and third-party apps already follow gtk_application_prefer_dark_theme plus the org.gnome.desktop.interface color-scheme write just below.
  • meson.build:26dependency('libadwaita-1', version: '>= 1.2').

I checked the dependency was genuinely removable rather than needed transitively before deleting it: core_deps was a plain dependency('gtk4') before this branch, libsingularity deliberately keeps libadwaita out of core, and goa-backend-1.0 is not a declared dependency here (src/vapi/goa-backend-1.0.deps lists only goa-1.0 and gtk4, and add_account_page.vala already avoids Goa.BackendProvider for exactly that reason).

Verified on an aarch64 build host: meson setup configures with no libadwaita in core_deps, meson compile builds all 363 targets, meson test is 14/14 OK, and ldd build/singularity-desktop | grep -i adwaita returns nothing — so it isn't linked directly or transitively. Two stale comments naming Adw.EntryRow/Adw.ComboRow were reworded too, so grep -rn 'Adw' src tests is empty.

Attribution trailers in history

Rewrote the seven commits that carried the old trailer — bfa5f5c, c3f9970, 468c556, 9bbbbfc, 7d15a0c, 032bb6d, 4dc9d30 — replacing Co-Authored-By:/Claude-Session: with your format, keeping the scope to one sentence per commit — e.g. on the debounce fix:

Assisted-by: Claude Code:claude-opus-5
AI scope: generated schedule_reload() and its 200ms debounce, from a
prompt to diagnose the rapid-click Wayland disconnect from the app log
and coalesce the overlapping decode and upload reloads.

Verification after the rewrite:

  • git log main..HEADCo-Authored-By: / Claude-Session: / noreply@anthropic.com / Generated with: 0 matches. No other bot footer anywhere.
  • 54 commits before, 54 after; every one still authored Jason Perlow <jperlow@gmail.com>.
  • The rewrite was message-only — git diff between the pre- and post-rewrite heads is empty, not a byte of tree content changed, so the build verification above still applies unchanged.

The PR description carries the same trailer pair, scoped to the PR as a whole. This is the format I'll use on singularity-* work from here on.

Sorry for the force-push churn on your review anchors — reaching the older messages required it. The sensors popover was already reverted off this branch in 3b98920 and split out, so this is wallpaper-only. Ready for another look whenever you have time.

perlowja added a commit to perlowja/singularity-shell that referenced this pull request Sep 12, 2026
…nents

Per Mirko's review on PR singularityos-lab#26: every direct Adw.* widget introduced by
this branch's OCS/Bing wallpaper browser work is replaced with its
libsingularity equivalent, matching the convention already used
elsewhere in this codebase (desktop_page.vala's grid_group/SwitchRow,
calendar_view.vala's PreferencesGroup, ush_portal.vala/system_view.vala's
ConfirmDialog).

- wallpaper_ocs_browser.vala: Adw.PreferencesGroup -> PreferencesGroup,
  Adw.ActionRow -> ActionRow, Adw.PreferencesRow -> PreferencesRow,
  Adw.EntryRow -> EntryRow (its apply button/signal replaced by an
  explicit suffix button + entry_activated, since EntryRow has no
  show_apply_button equivalent), and Adw.ComboRow -> SelectionRow for
  the provider/category/tag pickers. SelectionRow stores id/label pairs
  directly, so the old index<->id array plumbing (provider_ids/
  category_ids/tag_ids, selected_id()) is gone; set_choices() now just
  calls set_options()+current_value.
- provider_credential_group.vala: the group itself now extends
  PreferencesGroup instead of Adw.PreferencesGroup; its entry row uses
  EntryRow/PasswordRow instead of Adw.EntryRow/Adw.PasswordEntryRow.
- desktop_page.vala: the Bing Markets two-choice picker moves from
  Adw.ComboRow to SelectionRow (index constants replaced with id
  constants all/pick); the Bing markets multi-select dialog and the
  two delete-confirmation dialogs move from Adw.AlertDialog/
  Adw.MessageDialog to Singularity.Widgets.ConfirmDialog, using its
  custom_area for the region-grouped checkbox list.

Behaviour preserved: every existing severity/threshold/cap/guard this
branch's commits already documented is untouched -- only the widget
classes constructing the UI changed, not the wallpaper/Bing/OCS logic
around them. Verified with a real meson build + the full test suite on
ULTRA (arm64): 14/14 tests pass, singularity-desktop links cleanly.
perlowja added a commit to perlowja/singularity-shell that referenced this pull request Sep 12, 2026
…nents

Per Mirko's review on PR singularityos-lab#26: every direct Adw.* widget introduced by
this branch's OCS/Bing wallpaper browser work is replaced with its
libsingularity equivalent, matching the convention already used
elsewhere in this codebase (desktop_page.vala's grid_group/SwitchRow,
calendar_view.vala's PreferencesGroup, ush_portal.vala/system_view.vala's
ConfirmDialog).

- wallpaper_ocs_browser.vala: Adw.PreferencesGroup -> PreferencesGroup,
  Adw.ActionRow -> ActionRow, Adw.PreferencesRow -> PreferencesRow,
  Adw.EntryRow -> EntryRow (its apply button/signal replaced by an
  explicit suffix button + entry_activated, since EntryRow has no
  show_apply_button equivalent), and Adw.ComboRow -> SelectionRow for
  the provider/category/tag pickers. SelectionRow stores id/label pairs
  directly, so the old index<->id array plumbing (provider_ids/
  category_ids/tag_ids, selected_id()) is gone; set_choices() now just
  calls set_options()+current_value.
- provider_credential_group.vala: the group itself now extends
  PreferencesGroup instead of Adw.PreferencesGroup; its entry row uses
  EntryRow/PasswordRow instead of Adw.EntryRow/Adw.PasswordEntryRow.
- desktop_page.vala: the Bing Markets two-choice picker moves from
  Adw.ComboRow to SelectionRow (index constants replaced with id
  constants all/pick); the Bing markets multi-select dialog and the
  two delete-confirmation dialogs move from Adw.AlertDialog/
  Adw.MessageDialog to Singularity.Widgets.ConfirmDialog, using its
  custom_area for the region-grouped checkbox list.

Behaviour preserved: every existing severity/threshold/cap/guard this
branch's commits already documented is untouched -- only the widget
classes constructing the UI changed, not the wallpaper/Bing/OCS logic
around them. Verified with a real meson build + the full test suite on
ULTRA (arm64): 14/14 tests pass, singularity-desktop links cleanly.
@perlowja

Copy link
Copy Markdown
Contributor Author

Pushed ba53644 onto this branch: the Bing source in the online browser now offers the same de-duplicated "combined" view the wallpaper theme picker already uses.

Bing serves the same photograph to several regional markets on a given day. This page crawls one listing per market and merges them into a single grid, so with every market enabled it was showing each photo once per market that served it — 213 cards for 41 distinct photographs on the board I tested, while the theme picker next to it showed the correct 41.

De-duplication has to happen across markets, which a per-market listing structurally can't do, so the provider asks the helper for the combined view rather than trying to reconcile the markets itself:

  • The helper advertises a consolidated pseudo-market from markets, and only when the user's configured markets are the "all" sentinel. I deliberately didn't derive that on this side: desktop_page.vala's Bing Markets picker treats an absent config file as "all" while the helper treats it as en-US only, so the helper's own answer is the contract.
  • When that choice is present it replaces the market list rather than joining it (WallpaperBing.combined_view). Since browse_all() crawls one listing per choice into one grid, offering both would put the de-duplicated set and every raw per-market set in the same grid and bring the duplication straight back. Picking specific markets in the Bing Markets picker is what restores per-market browsing, and is also what makes the helper stop advertising the combined view.
  • BingWallpaperProvider.display_name becomes "Bing (Combined, All Markets)" when it's serving that view, matching the collection label on the theme-picker side so the two name one thing one way. The name isn't known until the helper replies, so the "Online source" row is rebuilt from the registry's live names (rebuild_provider_row) instead of being snapshotted in initialize().
  • rebuild_category_row() hides the row when it holds a single choice — it filters nothing in that state, and an "Any category / Combined (All Markets)" dropdown above a grid that is already exactly that is just noise.

Tests cover both directions of the replacement rule. Built and ran the full suite on aarch64: 14/14 green, including the two new cases.

The helper half is in cix-installer (post-install/45-wallpaper-rotator.sh, list --consolidated); list <market> keeps its existing meaning there, so nothing else changes shape.

@mirkobrombin mirkobrombin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch still contains the pre-runtime-consumer wallpaper stack instead of the current #25 head, so please rebase it after #25 lands and resolve the overlapping files before re-review.

perlowja added a commit to perlowja/singularity-shell that referenced this pull request Sep 13, 2026
…ake it responsive

Extracted from PR singularityos-lab#26 (OCS wallpaper browser), which bundled these
sensors-popover changes alongside unrelated wallpaper work. Squashes
four commits from that branch that touched only panel.vala:

- convert popover to native Singularity.Widgets (PreferencesGroup/
  PreferencesRow/ActionRow), same convention the rest of the desktop
  surface (desktop_page, calendar_view) already uses for grouped lists
- full Adw/PreferencesGroup convention audit pass
- make the popover responsive
- swap the one remaining raw Adw.PreferencesRow (the compact-row
  container in append_compact_row) for Singularity.Widgets.PreferencesRow,
  so this surface is 100% libsingularity widgets, no direct libadwaita

Behaviour preserved: every severity threshold, MAX_ROWS_PER_GROUP cap,
polling interval, schema-key guard, map/unmap gate, and the
synchronous-refresh-on-popover-open path are unchanged. The heat bar
stays a raw Gtk.DrawingArea (Gtk.LevelBar's battery-style
level-low/level-high semantics paint every short bar red -- documented
inline where make_heat_bar() is defined).
perlowja added a commit to perlowja/singularity-shell that referenced this pull request Sep 13, 2026
…ake it responsive

Extracted from PR singularityos-lab#26 (OCS wallpaper browser), which bundled these
sensors-popover changes alongside unrelated wallpaper work. Squashes
four commits from that branch that touched only panel.vala:

- convert popover to native Singularity.Widgets (PreferencesGroup/
  PreferencesRow/ActionRow), same convention the rest of the desktop
  surface (desktop_page, calendar_view) already uses for grouped lists
- full Adw/PreferencesGroup convention audit pass
- make the popover responsive
- swap the one remaining raw Adw.PreferencesRow (the compact-row
  container in append_compact_row) for Singularity.Widgets.PreferencesRow,
  so this surface is 100% libsingularity widgets, no direct libadwaita

Behaviour preserved: every severity threshold, MAX_ROWS_PER_GROUP cap,
polling interval, schema-key guard, map/unmap gate, and the
synchronous-refresh-on-popover-open path are unchanged. The heat bar
stays a raw Gtk.DrawingArea (Gtk.LevelBar's battery-style
level-low/level-high semantics paint every short bar red -- documented
inline where make_heat_bar() is defined).
@perlowja

Copy link
Copy Markdown
Contributor Author

Rebased and consolidated onto the current PR #25 branch tip (wallpaper-source-selector-clean at 8ddf523) because #25 has not merged yet. The overlap in meson.build and desktop_page.vala was resolved against #25's current runtime rotator/source-selector implementation (including its comment/ASCII cleanup), while retaining the refreshable source selector required by OCS imports/deletions.

The rewritten series now contains:

  • c8a83f0 — consolidated OCS browser integration on the runtime source selector
  • cbd0bb4 / 13f9d84 — native responsive sensors popover + restored severity colours
  • 41e5481 — Bing helper version-skew documentation/warning
  • caa4b7a — inline Bing region picker

Verified in a Debian forky Podman container: Ninja 365/365; Meson tests 14/14 passing.

This is necessarily an interim rebase onto #25's branch tip. I will do one final re-rebase onto upstream main after #25 actually merges.

@perlowja

Copy link
Copy Markdown
Contributor Author

@mirkobrombin, please re-review the rebased series. GitHub returned 404 when I attempted the requested-reviewers API call from the fork-owner account, so I am explicitly tagging you here.

perlowja added a commit to perlowja/singularity-shell that referenced this pull request Sep 13, 2026
…ake it responsive

Extracted from PR singularityos-lab#26 (OCS wallpaper browser), which bundled these
sensors-popover changes alongside unrelated wallpaper work. Squashes
four commits from that branch that touched only panel.vala:

- convert popover to native Singularity.Widgets (PreferencesGroup/
  PreferencesRow/ActionRow), same convention the rest of the desktop
  surface (desktop_page, calendar_view) already uses for grouped lists
- full Adw/PreferencesGroup convention audit pass
- make the popover responsive
- swap the one remaining raw Adw.PreferencesRow (the compact-row
  container in append_compact_row) for Singularity.Widgets.PreferencesRow,
  so this surface is 100% libsingularity widgets, no direct libadwaita

Behaviour preserved: every severity threshold, MAX_ROWS_PER_GROUP cap,
polling interval, schema-key guard, map/unmap gate, and the
synchronous-refresh-on-popover-open path are unchanged. The heat bar
stays a raw Gtk.DrawingArea (Gtk.LevelBar's battery-style
level-low/level-high semantics paint every short bar red -- documented
inline where make_heat_bar() is defined).
perlowja added a commit to perlowja/singularity-shell that referenced this pull request Sep 13, 2026
…ake it responsive

Extracted from PR singularityos-lab#26 (OCS wallpaper browser), which bundled these
sensors-popover changes alongside unrelated wallpaper work. Squashes
four commits from that branch that touched only panel.vala:

- convert popover to native Singularity.Widgets (PreferencesGroup/
  PreferencesRow/ActionRow), same convention the rest of the desktop
  surface (desktop_page, calendar_view) already uses for grouped lists
- full Adw/PreferencesGroup convention audit pass
- make the popover responsive
- swap the one remaining raw Adw.PreferencesRow (the compact-row
  container in append_compact_row) for Singularity.Widgets.PreferencesRow,
  so this surface is 100% libsingularity widgets, no direct libadwaita

Behaviour preserved: every severity threshold, MAX_ROWS_PER_GROUP cap,
polling interval, schema-key guard, map/unmap gate, and the
synchronous-refresh-on-popover-open path are unchanged. The heat bar
stays a raw Gtk.DrawingArea (Gtk.LevelBar's battery-style
level-low/level-high semantics paint every short bar red -- documented
inline where make_heat_bar() is defined).
mirkobrombin pushed a commit that referenced this pull request Sep 13, 2026
…ake it responsive

Extracted from PR #26 (OCS wallpaper browser), which bundled these
sensors-popover changes alongside unrelated wallpaper work. Squashes
four commits from that branch that touched only panel.vala:

- convert popover to native Singularity.Widgets (PreferencesGroup/
  PreferencesRow/ActionRow), same convention the rest of the desktop
  surface (desktop_page, calendar_view) already uses for grouped lists
- full Adw/PreferencesGroup convention audit pass
- make the popover responsive
- swap the one remaining raw Adw.PreferencesRow (the compact-row
  container in append_compact_row) for Singularity.Widgets.PreferencesRow,
  so this surface is 100% libsingularity widgets, no direct libadwaita

Behaviour preserved: every severity threshold, MAX_ROWS_PER_GROUP cap,
polling interval, schema-key guard, map/unmap gate, and the
synchronous-refresh-on-popover-open path are unchanged. The heat bar
stays a raw Gtk.DrawingArea (Gtk.LevelBar's battery-style
level-low/level-high semantics paint every short bar red -- documented
inline where make_heat_bar() is defined).
@perlowja

Copy link
Copy Markdown
Contributor Author

@mirkobrombin the last review on this PR (pinned to 67eeec3) asked for exactly the rebase that already happened -- the branch was rebased onto the current PR #25 tip and the overlapping files resolved, as noted in the comment above requesting re-review of the rebased series (current head 9a628da, 16 commits ahead / 58 behind 67eeec3). I do not have permission to dismiss the review from this account (the dismissal API 404s the same way the reviewer-request API does) -- could you dismiss it or take another look when you have a moment?

perlowja added a commit to perlowja/singularity-shell that referenced this pull request Sep 13, 2026
…nents

Per Mirko's review on PR singularityos-lab#26: every direct Adw.* widget introduced by
this branch's OCS/Bing wallpaper browser work is replaced with its
libsingularity equivalent, matching the convention already used
elsewhere in this codebase (desktop_page.vala's grid_group/SwitchRow,
calendar_view.vala's PreferencesGroup, ush_portal.vala/system_view.vala's
ConfirmDialog).

- wallpaper_ocs_browser.vala: Adw.PreferencesGroup -> PreferencesGroup,
  Adw.ActionRow -> ActionRow, Adw.PreferencesRow -> PreferencesRow,
  Adw.EntryRow -> EntryRow (its apply button/signal replaced by an
  explicit suffix button + entry_activated, since EntryRow has no
  show_apply_button equivalent), and Adw.ComboRow -> SelectionRow for
  the provider/category/tag pickers. SelectionRow stores id/label pairs
  directly, so the old index<->id array plumbing (provider_ids/
  category_ids/tag_ids, selected_id()) is gone; set_choices() now just
  calls set_options()+current_value.
- provider_credential_group.vala: the group itself now extends
  PreferencesGroup instead of Adw.PreferencesGroup; its entry row uses
  EntryRow/PasswordRow instead of Adw.EntryRow/Adw.PasswordEntryRow.
- desktop_page.vala: the Bing Markets two-choice picker moves from
  Adw.ComboRow to SelectionRow (index constants replaced with id
  constants all/pick); the Bing markets multi-select dialog and the
  two delete-confirmation dialogs move from Adw.AlertDialog/
  Adw.MessageDialog to Singularity.Widgets.ConfirmDialog, using its
  custom_area for the region-grouped checkbox list.

Behaviour preserved: every existing severity/threshold/cap/guard this
branch's commits already documented is untouched -- only the widget
classes constructing the UI changed, not the wallpaper/Bing/OCS logic
around them. Verified with a real meson build + the full test suite on
ULTRA (arm64): 14/14 tests pass, singularity-desktop links cleanly.
@perlowja

Copy link
Copy Markdown
Contributor Author

Brought this branch up to the current OCS quality bar with 6 commits: raised the aggregate crawl cap to the real measured feed size (3352 unique items, was silently truncating at 1500), added a disk-backed thumbnail cache with LRU eviction so revisits are instant, bounded thumbnail memory to roughly a viewport worth via scroll-driven load/evict instead of decoding the entire feed up front (was measured at 1.35 GiB resident for 3352 cards), added stale-while-revalidate so a cached crawl paints immediately while refreshing in the background, sanitized free-text OCS tags against Unicode bidi-override/control characters before they reach the tag dropdown, and added a Clear Filters action. Verified with a full ninja build (228/228, 0 errors) and meson test (11/11) in a debian:forky container.

…t broke OCS browsing

Root-caused the operator-reported OCS regression from tonight's
fork/ocs-browser-clean deploy (67eeec3) to cixmini. Evidence, not guess:

67eeec3 ("Bing region picker sets a dedup preference, not a fetch filter")
redefined what ~/.config/ncz-wallpaper/bing-markets means -- from "the set
of markets to fetch" to "which region wins a dedup tie" -- and states in
its own commit message that this depends on "the matching cix-installer
change" to 45-wallpaper-rotator.sh's ncz-wallpaper-bing helper, which
lives in a separate repo this session did not touch or build.

WallpaperBing.CONSOLIDATED_ID's own comment in wallpaper_ocs.vala still
described the OLD contract ("advertises this id only when the file holds
the 'all' sentinel") after 67eeec3 shipped, which is direct evidence the
two sides of this cross-repo contract drifted in the same commit that
changed one side of it. BingWallpaperProvider.choices() in
wallpaper_provider.vala is the OCS browser's Bing tab -- it depends on
the SAME file and the SAME helper contract. If the deployed
ncz-wallpaper-bing predates 67eeec3's change, it still treats a non-"all"
file as a fetch restriction; tonight's redesigned picker (this branch,
previous commit) makes picking a single specific region the natural,
inviting action, where the old multi-select dialog defaulted to
all-13-checked and rarely produced a genuinely single-market file. That
shift in what the file typically contains, against a helper that has not
also moved to the new contract, is what most plausibly collapses both
the rotator's feed AND the OCS browser's Bing results down to one
market -- exactly the "OCS is broken" symptom reported live, and NOT
something the existing test suite (14/14 passes on 67eeec3 unmodified)
can catch, since it never exercises the deployed helper binary.

Verification limits: this session did not have access to cix-installer
or cixmini (out of scope per instructions), so the deployed helper's
actual contract version was not directly confirmed. This is the
strongest evidence-backed hypothesis available from the singularity-shell
side alone; verifying /usr/local/bin/ncz-wallpaper-bing's behavior on
cixmini is the next diagnostic step before another live test.

Changes:
- wallpaper_ocs.vala: rewrote the CONSOLIDATED_ID comment to describe the
  current contract and flag the version-skew risk at the exact spot the
  assumption lives.
- wallpaper_provider.vala: BingWallpaperProvider.choices() now logs a
  warning() when the helper does not advertise the combined view, so a
  future occurrence is diagnosable from the journal instead of silently
  presenting a narrowed result set as if Bing just has fewer photos today.

Verified: full ninja build (365/365, 0 errors) and meson test (14/14
pass, including wallpaper-ocs) in a debian:forky podman container on
ULTRA; zoder (nemotron35 via the TYDEUS reviewer agent) reviewed the
combined diff against the SelectionRow/warning() usage, no findings.
Operator feedback on tonight's cixmini deploy: opening "Choose Preferred
Region..." raised a modal ConfirmDialog with a scrollable checkbox list
grouped by region and a separate "Apply" button, which reads as out of
place next to every other setting on this page -- interval_row,
decorations_side_row and the rest are all inline SelectionRow expanders
that reveal their choices in place and commit on click.

Replaced the dialog with exactly that pattern: bing_markets_row is now a
single SelectionRow.with_options() listing "All Markets, No Preference"
followed by all 13 markets from BING_MARKETS_TABLE, each labelled
"<Region> - <Market>" (e.g. "Europe - Germany") so the region grouping the
old dialog expressed with section headers survives as label text, and
SelectionRow's own search entry (which appears past 5 items) lets a
region or market name filter the now-14-entry list. Clicking any row is
a single action: SelectionRow always collapses its own expander and
fires `selected` with exactly the one id chosen, matching the operator's
"inline within the settings/control-panel row itself" request with no
new widget class and no popup.

Removed the now-dead popup implementation entirely: bing_markets_dialog,
bing_markets_checkboxes, open_bing_markets_dialog(),
on_bing_markets_dialog_response(), the BING_MARKETS_ID_PICK sentinel, and
the bing_markets_regions bookkeeping list that only existed to group the
dialog's checkboxes (the flat SelectionRow needs no group index; region
is just a label prefix now). Net -62 lines.

Verified: full ninja build (365/365, 0 errors) and meson test (14/14
pass) in a debian:forky podman container on ULTRA; ldd confirms every
library resolves under /usr/lib/aarch64-linux-gnu (Debian multiarch, zero
/lib64 references) -- no Fedora-vs-Debian libc mismatch. zoder (nemotron35
via the TYDEUS reviewer agent) reviewed the SelectionRow usage, the
current-value fallback logic for a stale/unrecognized market code in the
config file, and the removed-code cleanup; no findings.
Request the OCS server maximum of 100 items in the existing single page per category. This keeps the crawl at the same 45 requests and within the same per-category timeout budget.

A forced live crawl against api.pling.com from the CIX Sky1 target on 2026-09-13 returned 3,485 importable results across all 45 usable pling categories: 3,352 unique item IDs after removing 133 cross-category duplicates, with zero category failures in 14.19 seconds. Raise the aggregate safety cap from 1,500 to 4,000, 648 items (19%) above the measured unique total, so it no longer truncates the current feed.

Gtk.FlowBox retains one real child hierarchy per card and the browser eagerly decodes every 344x208 thumbnail. A target-side representative benchmark settled at 703 MiB RSS for 1,500 cards and 1.35 GiB for 3,352; construction took 0.43s and 0.92s respectively. That linear cost is worth a future virtualized/lazy-loading change, but is not a present resource limit on the 64 GiB Sky1 system (59 GiB available during the test).

Verified after rebasing the concurrent wallpaper history/favorites work: clean full ninja build (376/376, 0 errors) and meson test (16/16 pass) in a debian:forky podman container on ULTRA.

Assisted-by: Codex:gpt-5

AI-Scope: Measured the live page-size-100 OCS aggregate and target-side FlowBox memory behavior, raised the page size and crawl safety cap, and verified the full build and test suite.
OCS previews were downloaded again on every page visit, while three strided loader lanes made populating large filtered grids unnecessarily slow.

Persist successful preview responses in a bounded atomic LRU cache and use eight synchronized loader lanes so revisits avoid network work and uncached grids fill faster.

AI-Scope: Implemented the upstream-specified thumbnail cache, browser integration, concurrency change, and verification.

Assisted-by: Claude Code:claude-sonnet-5
AI-scope: Implemented the dispatched cache and concurrency changes from the upstream diagnosis and specification.
The 4000-item aggregate eagerly decoded every FlowBox thumbnail, driving measured resident memory to roughly 1.35 GiB for 3352 cards even though only a screenful was visible.

Queue only cards intersecting the viewport plus a bounded prefetch margin, and evict decoded paintables outside a wider hysteresis margin so scrolling through the full feed cannot retain every texture.

Assisted-by: Claude Code:claude-sonnet-5
AI-scope: Implemented viewport-driven thumbnail scheduling and in-memory texture eviction while preserving the existing cache and cancellation machinery.
Expired metadata cache files previously withheld a usable grid until every live category request completed, making normal page opens unnecessarily dependent on network latency.

Paint any valid persisted snapshot immediately, re-crawl through the existing bounded worker pool into detached metadata, and atomically repaint only when background revalidation yields usable results.

Assisted-by: Claude Code:claude-sonnet-5
AI-scope: Implemented stale-while-revalidate metadata loading with generation-safe background crawling and a no-empty-frame result swap.
Uploader-supplied tag text is rendered directly in filter controls, so invisible format and control characters can corrupt nearby UI while oversized labels can break the row layout. Sanitize and cap tags at the shared parser boundary so every provider path receives the same safe, deduplicated values.

Assisted-by: Claude Code:claude-sonnet-5
AI-Scope: Implemented Unicode category filtering, tag truncation, and focused parser tests from the requested OCS tag-hardening requirements.
Resetting category, tag, and search independently makes returning to the full wallpaper view unnecessarily tedious. Add one visible action that synchronizes all three controls under the existing update guard and refreshes the grid only after the complete reset.

Assisted-by: Claude Code:claude-sonnet-5
AI-Scope: Added the filter-group reset button, guarded UI synchronization, and single-pass grid filtering requested for the wallpaper browser.
Tonight's OCS browser work (79a885a raising the aggregate feed to its
real ~3358-item size, cc5c861/b9d8a8b/e5ee222 thumbnail caching, viewport
paging and stale-revalidation) made the page noticeably slower to open,
reported live on cixmini after a fresh desktop-session restart.

Root cause, isolated with real timing instrumentation against the actual
3358-item OCS cache on CIX Sky1 target hardware (cixmini): load_cached()
built every WallpaperCard widget hierarchy for the cached aggregate in
one unbroken synchronous main-thread loop.

    PERF load_cached: read=1.5ms parse=96.6ms entries=3358
    PERF load_cached: add_card_loop=2139.6ms filter_cards=57.0ms TOTAL=2295.8ms

JSON parsing was not the bottleneck (96.6ms); GTK widget construction
was (2139.6ms, ~0.64ms/card), and it ran on every cache repaint --
i.e. on every page open, and again whenever a stale cache silently
revalidates in the background (revalidate_cached() has the identical
pattern). Viewport-paged thumbnail *decoding* (b9d8a8b) does not help
here: add_card() still builds a full card/button/badge widget subtree
for every item regardless of visibility, so raising the real feed size
from an effective ~400 items (pre-79a885a page-size-10 default) to 3358
multiplied this synchronous block by roughly 8x.

Fix: build the card list in bounded batches (CARD_BUILD_BATCH_SIZE=150),
yielding to the main loop between batches via the same Idle.add()
pattern already used elsewhere in this file for polling loops, so the
compositor stays responsive throughout. queue_viewport_thumbnails() runs
after every batch so visible rows start decoding as soon as they exist.
load_cached() became async (single call site in browse_all() updated to
yield it). revalidate_cached() keeps its no visible empty grid promise
because the first batch still lands in the same synchronous continuation
as the clear() that precedes it -- only the remaining batches spread
across further main-loop turns.

Measured after the fix, same real cache/hardware:

    PERF-FIX load_cached: read+parse=94.8ms entries=3358
    PERF-FIX batch: processed=150 batch_build=48.87ms
    ... (22 batches, worst observed 129.55ms)
    PERF-FIX load_cached: populate_cards_batched=4566.6ms TOTAL=4719.4ms

Worst single main-loop-turn block dropped from 2139.6ms to 129.55ms
(>16x). Total wall-clock to fully populate the grid from cache went up
(~4.7s vs ~2.3s) from Idle-scheduling overhead across ~22 batches; that
tradeoff is deliberate -- the reported complaint was the shell appearing
to hang, not total elapsed time, and the grid now visibly fills in
instead of freezing then appearing all at once.

Verified: clean ninja build (228/228) and meson test (11/11 pass) in a
debian:forky podman container on ULTRA. Timing measured with temporary
stderr instrumentation against a real 3358-item OCS cache (copied from
the live cixmini session) driven through the actual settings page code
path in an isolated headless nested Wayland session (WLR_BACKENDS=headless
labwc) on cixmini target hardware, not a synthetic benchmark; all
diagnostic instrumentation was removed before this commit. zoder
(reviewer agent, cross-family from this session) reviewed the diff with
file access to the real working tree and returned APPROVE after
verifying gen/cancellation handling, the async conversion's single call
site, closure/ownership correctness in the Idle.add() pattern, and the
no empty grid invariant, each against verbatim-quoted lines.

Assisted-by: Claude Code:claude-sonnet-5
AI-Scope: Root-caused the freeze with real timing instrumentation on target hardware, implemented and verified the batched-construction fix, and ran the review gate.
@perlowja

Copy link
Copy Markdown
Contributor Author

Addressed all three threads on this branch (now at c18a1f6):

Compile verification: static checks above are solid; a full build hit an environment gap (libsingularity not available as an installed dependency or fetchable subproject on my available build hosts) unrelated to this diff — noting honestly rather than claiming something I didn't verify.

Assisted-by: Claude Code:claude-sonnet-5

@perlowja

Copy link
Copy Markdown
Contributor Author

Pushed 6c24ca4 (was c18a1f6): the OCS wallpaper browser simplification requested by the operator -- category is now the only filter (tag filtering and the "Clear Filters" action are removed), and browsing is lazy per-category instead of the previous eager crawl that pulled every usable category into one merged aggregate before the user picked anything (measured against pling: 45 categories, 3485 results, 3300+ items in a single load). Selecting a provider now only loads its category list; picking a category loads (and, on a repeat visit or Refresh, re-validates) just that one category, reusing the existing cache-then-revalidate-if-stale flow -- now keyed by provider+category instead of provider alone. Verified via the wallpaper-ocs-test target: 34/34 pass, unchanged from before this change.

On the three open review threads, all pinned to commits well behind the current head -- I want to lay out precisely why each is resolved rather than just asserting it, since I don't have dismiss rights on this repo (confirmed: push: false) and this needs a maintainer action either way:

  1. "replace every direct libadwaita widget... split the sensors changes" (bfa5f5c) -- resolved. The sensors-popover work was split out and merged separately (#27, now in main's history). No Adw.* widget remains in this branch's diff.
  2. "Direct libadwaita initialization and dependency still remain... remove Claude attribution" (5684deb) -- resolved. grep -rn 'Adw' src tests on the current head matches only the literal string "Adwaita" as a GTK theme/cursor-scheme NAME (a real, selectable system theme a user can pick, e.g. desktop_page.vala's theme list) -- never a widget or namespace reference. meson.build has no libadwaita dependency. Commit messages use Assisted-by:/AI scope:, no Co-Authored-By.
  3. "still contains the pre-runtime-consumer wallpaper stack... rebase after feat(wallpaper): source selector, per-source grid scoping, and rotation controls #25 lands" (67eeec3) -- resolved. #25 merged into main on 2026-09-14 (c10ad57); this branch is rebased on top of that merge commit (confirmed via git merge-base --is-ancestor).

Given I can't dismiss these myself, @mirkobrombin -- could you take a look and either dismiss what's stale or flag anything I've misjudged? Happy to address anything still live.

One more thing, flagged honestly rather than worked around: singularity-desktop (the actual app binary) currently fails to build on this branch, but for a cause entirely unrelated to wallpapers -- performance_page.vala/fan_controller.vala reference FanControlChannel and SensorMonitor.gpu_sampling, neither of which exist in the current libsingularity subproject. I confirmed this is pre-existing and not something this branch introduced: the same sensors.gpu_sampling = false; line is present verbatim on a fresh, independent clone of upstream main (the fan-monitoring feature, 91b5f55, merged same day as #25). I did not attempt to fix it -- it's a different subsystem than what I was asked to change, and I don't have enough context on the intended libsingularity/fan-control contract to risk a blind fix. Wanted to surface it rather than let a green wallpaper-only verification imply the whole app builds, which it currently does not.


Assisted-by: Claude Code:claude-opus-5
AI scope: wrote this status/verification comment summarizing the pushed commit, the stale-review analysis, and the unrelated pre-existing build break found while verifying.

…gory loading

Removes tag-based filtering (UI, active/known-tag bookkeeping, the
card_matches tag check) and the now-redundant Clear Filters action,
leaving category as the only filter dimension.

Replaces the eager, load-every-category-and-merge-into-one-aggregate
crawl with a lazy, per-category load: selecting a provider now only
populates the category dropdown, and nothing is fetched until the user
actually picks a category. Re-picking (or re-entering the page on) a
category re-runs the same load, reusing the existing cache-then-
revalidate-if-stale flow -- now scoped to provider+category instead of
provider alone (WallpaperBrowseCache.path_for/save/load gain an
optional category argument; path_for(provider) alone is unchanged, so
existing single-arg callers and the test suite are unaffected).

This was previously pulling 3300+ items across every usable category
on every provider select (measured against pling: 45 categories, 3485
results) before the user had picked anything, which is real time lost
per open and a UI freeze while the resulting merged grid was built.

Verified via the wallpaper-ocs-test target (34/34 pass, unchanged from
before this change) and by reading through every call site touched by
the add_card()/card_matches()/store_cache() signature simplifications
that removing tag bookkeeping and per-item category lookup made
possible. singularity-desktop itself currently fails to build for an
unrelated, pre-existing reason (FanControlChannel/SensorMonitor.gpu_
sampling missing from libsingularity, confirmed present on a fresh
clone of upstream main independent of this branch) -- not touched
here, flagged separately on the PR.

Assisted-by: Claude Code:claude-opus-5
AI-Scope: implemented the tag-removal and lazy-loading rearchitecture across wallpaper_ocs_browser.vala and wallpaper_browse_cache.vala from the operator's specification (drop tag filtering, load a category only when picked, refresh on re-browse); verification and the PR-state investigation are mine.
@perlowja

Copy link
Copy Markdown
Contributor Author

Amended to 6b64018 (was 6c24ca4) -- the AI-disclosure check correctly caught a real formatting bug in my own commit trailer (AI scope: with a space instead of the required AI-Scope: with a hyphen; verified the fix against the checkers exact regex before re-pushing). Check now passes. Everything else in the comment above still applies to this SHA.

Assisted-by: Claude Code:claude-opus-5
AI-Scope: wrote this follow-up noting the amended commit and the now-passing check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants