Commit graph

56 commits

Author SHA1 Message Date
b6fe86f418 chore: bump versions to 0.10.0 2026-06-25 20:37:56 -05:00
3d41f39ad9 fix: bundle dbus.js in the pack and share logError via util.js
extension.js imports ./dbus.js, but gnome-extensions pack never bundled it,
so the packed extension (the EGO zip) was missing the module and would fail
to load. Add dbus.js and the new util.js to the pack sources.

util.js holds the shared logError helper, replacing the per-file copies in
extension.js and panel.js and the raw console.error calls in clipboardItem.js.
2026-06-25 20:29:02 -05:00
c9f3b6b1ad ci: add ESLint workflow 2026-06-25 20:08:08 -05:00
bb792cee95 style: conform the extension to the GJS ESLint config
- clipboardItem: convert _init() to constructor()/super() and give the class
  an explicit GTypeName (no-restricted-syntax, no-shadow).
- clipboardItem: replace new URL() with GLib.Uri.parse().get_host(). GJS has
  no global URL, so the old call always threw and the URL hostname subtitle
  never rendered; this makes it work.
- Use optional catch bindings (catch {}); logError guards with
  instanceof GLib.Error instead of a try/catch.
- prefs: drop an unused GLib import.
- panel: block-body promise executors, and a no-await-in-loop disable on the
  batched renders (the per-batch yield is intentional).
- Drop the remaining restate-the-code comments.
2026-06-25 19:43:47 -05:00
376c6442f9 build: add the GJS ESLint flat config and lint script 2026-06-25 19:43:46 -05:00
7b3a25eaaf chore: bump versions to 0.9.0 2026-06-25 18:36:33 -05:00
ec8d208f35 docs: point AGENTS.md at the upstream gjs.guide guidelines 2026-06-25 18:19:18 -05:00
6796a44802 refactor: drop dead fields and stale comments 2026-06-25 18:19:18 -05:00
306cedfd68 fix: retry the initial history load when the first fetch fails 2026-06-25 18:17:42 -05:00
189a52798b fix: ignore the D-Bus proxy callback after disable 2026-06-25 18:17:09 -05:00
7abd247247 fix: skip excluded apps before submitting to the daemon 2026-06-25 18:16:53 -05:00
452fb171a6 docs: bring AGENTS.md in line with current extension conventions
The logging line was the old "use console.error directly" rule; it now
points at the logError helper. Adds three short sections: D-Bus client
patterns (makeProxyWrapper, connectSignal, async-only), extension
lifecycle cleanup rules surfaced by the shexli static analyzer
(_addIdleSource, explicit signal disconnects), and a short EGO
submission checklist.
2026-06-25 16:56:01 -05:00
0e9c83f2d8 extension: address shexli warnings before EGO submission
Funnel console.error through a logError helper per file so the static
analyzer sees one logging site instead of 11+13. The helper also strips the
GDBus.Error prefix, which removes the per-site strip_remote_error call.

Track the indicator's button-press signal ID and disconnect it explicitly in
disable, even though indicator.destroy() would clean it up too.

Track GLib.idle_add source IDs in a Set and flush pending ones in disable,
so deferred panel mutations cannot run on a torn-down extension.
2026-06-25 16:44:41 -05:00
72a2d073e2 extension: declare support for GNOME Shell 50 only
The 45-49 entries were aspirational; the extension has only ever been tested
on Shell 50. EGO requires shell-version to match versions the extension
actually works on, so drop the untested ones and align the README.
2026-06-25 16:24:57 -05:00
28b68b4dc4 extension: clearer wording in the daemon-missing notification
"Daemon not installed" reads better than "daemon not found", and pointing to
the project page matches the homepage URL EGO already exposes on the listing.
2026-06-25 16:20:29 -05:00
8943136384 extension: align D-Bus client with the GJS guide
Route ItemAdded, ItemDeleted and HistoryCleared through proxy.connectSignal
instead of the low-level Gio.DBus.session.signal_subscribe. The wrapper is
already constructed and pre-unpacks signal args.

Strip the GDBus.Error: remote prefix from D-Bus errors before logging so the
journal shows the actual message.
2026-06-25 16:09:42 -05:00
43a68e7970 extension: cleanup before any future EGO submission
Remove narration breadcrumb comments, the unused _strataPreview field, the
self-referencing this.actor alias, and three non-error console.log calls.
Rename #busy to _busy for consistency with the rest of the class. Tighten a
couple of over-long docstrings.
2026-06-25 15:33:13 -05:00
Elijah Lynn
3fff367364
add screenshot to readme (#2)
* add screenshot to readme

* Add UI appearance section to README

Added a section to showcase the UI of Strata with an image.

---------

Co-authored-by: Eduard Tolosa <tolosaeduard@gmail.com>
2026-06-25 12:25:05 -05:00
40722526d4
(docs): typo 2026-05-28 23:21:31 -05:00
77f26422bf
(docs): add link to the motivation behind this 2026-05-28 23:20:51 -05:00
9f60f42071 docs: add project logo and social/OG image
Add a simple staggered-layers logo (assets/logo.svg mark, assets/icon.svg
rounded-square app icon) in the extension's blue accent, a 1200x630 social
card (assets/og-card.svg source + assets/og-image.jpg), and show the icon at
the top of the README.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 00:41:19 -05:00
885e64ff85 docs: add a Features section to the README
Document the supported content types (text, URLs, colors, images, files),
full-history FTS search, automatic light/dark theming, lazy loading, dedup,
reliability and privacy behavior, and the panel controls.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 00:25:05 -05:00
a055dc5f2b
Add GNU General Public License v3 2026-05-27 00:09:32 -05:00
d861bbac4d chore: bump versions to 0.8.0
Cut the 0.8.0 release (dependency upgrades + single-instance and
error-handling hardening): daemon Cargo.toml/Cargo.lock to 0.8.0, extension
metadata.json version to 8, and add the [0.8.0] CHANGELOG entry.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 21:34:41 -05:00
2dc15a4849 docs: document the single-instance daemon contract
Add a "Single instance" note to ARCHITECTURE explaining the two layers that
keep exactly one daemon on the bus: the extension skips spawning when the name
is owned, and the daemon requests it with DoNotQueue (exits if taken, never
steals/orphans).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 21:33:10 -05:00
107de1c3ff fix: extension supervisor + panel hardening
- Daemon supervisor: a _spawnPending guard so an in-flight GetNameOwner check
  plus a firing backoff timer can't spawn two daemons; wait_async now binds the
  subprocess and _onDaemonExited ignores foreign/stale exits, so backoff
  accounting can't be corrupted by an overlapping spawn.
- Panel: clearItems() invalidates the search snapshot (_resultsEpoch = -1) so a
  scroll after HistoryCleared can't render stale rows; _ensureVisible's idle
  callback null-guards the overlay like the other idle callbacks.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 21:33:04 -05:00
aa56cac958 deps+fix: upgrade zbus 5 / rusqlite 0.40 / dirs 6; daemon hardening
Dependency major upgrades (zbus 4->5, rusqlite 0.31->0.40, dirs 5->6) and the
migration + hardening that came with the review:

- zbus 5: SignalContext -> SignalEmitter, signal_context() -> signal_emitter().
- Single-instance D-Bus name: request with DoNotQueue instead of
  ReplaceExisting|AllowReplacement. If the name is already owned the daemon now
  exits instead of running without owning it (zbus 5 returns InQueue as Ok) or
  stealing the name and orphaning the running instance (zbus does not terminate
  a replaced owner). The extension already skips spawning when the name is
  owned, so replacement was never needed.
- Stop swallowing real SQLite errors: upsert_item's dedup SELECT and
  get_thumbnail/get_raw_item now use .optional().context()? so a missing row is
  Ok(None) and a genuine error propagates instead of masquerading as not-found.
- clippy pedantic/nursery cleanup: uninlined format args, redundant mime clone,
  pass ObjectId/Connection by reference where not consumed, match->if/is_ok,
  doc backticks; #[allow] with rationale for the intentional lints
  (significant_drop_tightening on the single-writer lock, unused_async on zbus
  interface methods).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 21:32:56 -05:00
e2972de37a docs: add AUR install (stable and -git package sets)
Document the two AUR channels: strata-daemon + gnome-shell-extension-strata
(stable, tagged) and strata-daemon-git + gnome-shell-extension-strata-git
(latest main). Install both packages from one channel; don't mix.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 19:36:30 -05:00
6f0f7db513 docs: explain the light.css class-toggle mechanism and theme-switch caveat
Clarify how theming actually works: light.css is loaded once but inert until
the panel adds the `.strata-theme-light` class (the more-specific scoped rules
then win); the live light/dark switch is just that class toggle. Document the
single caveat - a full GNOME Shell theme switch replaces the theme object and
drops light.css, so light mode needs a re-enable until then (dark is
unaffected; ordinary/system light-dark switching is fine).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 19:33:23 -05:00
7a203c70a7 docs: align ARCHITECTURE with 0.6/0.7 behavior
- Search: describe the lazy full-history paged search (snapshot + scroll
  paging, epoch/results-epoch guards) instead of the old "disables
  scroll-driven appends" behavior.
- Theming: light.css is loaded once with no theme-context 'changed'
  subscription (removed in 0.7.0 to stop the reload recursion); document the
  trade-off, not the old re-load-on-changed claim.
- Thumbnail size: ~256 px -> ~200 px to match THUMB_PX.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 19:24:34 -05:00
ffa1a407fe chore: bump versions to 0.7.0
Cut the 0.7.0 release (D-Bus rename + theme-recursion fix): daemon
Cargo.toml to 0.7.0, refresh Cargo.lock (gif/half/log patch bumps + version),
extension metadata.json version to 7, and add the [0.7.0] CHANGELOG entry.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 19:20:18 -05:00
b41c9662cb fix: rename D-Bus name to dev.edu4rdshl.Strata; drop recursive theme reload
Two changes for 0.7.0:

BREAKING (D-Bus): the service is renamed from org.gnome.Strata to
dev.edu4rdshl.Strata. The org.gnome.* namespace is reserved for official
GNOME software; Strata is third-party, so it now uses the reverse-DNS of its
own domain (matching the strata@edu4rdshl.dev UUID). Bus name, object path
(/dev/edu4rdshl/Strata), and interface (dev.edu4rdshl.Strata.Manager) all
change. Daemon and extension are updated together; only external busctl
scripts / non-GNOME front-ends that hard-coded the old name need updating.
The GSettings schema (org.gnome.shell.extensions.strata) is unchanged - that
namespace is correct for GNOME Shell extension settings.

Fix recursion: the theme-context 'changed' handler reloaded light.css, but
load_stylesheet itself emits 'changed', so it fed back into itself and hit
"too much recursion" - flooding the journal and spinning the CPU on screen
unlock (which restyles widgets and fires 'changed'). The subscription is
removed; light.css is loaded once. Dark/light switching is unaffected (it is
the panel's class toggle, not this load). A GNOME Shell theme switch no
longer auto-re-applies the sheet, which is recoverable by re-enabling.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 19:20:12 -05:00
3f32c19fd0 chore: bump versions to 0.6.0
Cut the 0.6.0 release (lazy full-history search + image-format and audit
fixes): daemon Cargo.toml/Cargo.lock to 0.6.0, extension metadata.json
version to 6, and add the [0.6.0] CHANGELOG entry.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 17:30:59 -05:00
332ec4064a fix: extension lifecycle leaks, image allowlist, generic image label
Audit fixes for the extension:

- Track and disconnect the changed::excluded-apps GSettings handler in
  disable() (it was the one connection whose id was never captured).
- Track the _stopDaemon 1.5s force-kill timer and cancel any leftover on
  enable(), so the source can't outlive a disable->enable cycle.
- Guard the exclusion-path delete with _proxy?.DeleteItemAsync.
- Mirror the daemon's image allowlist (drop avif/svg) in _pickMime.
- Show a generic "Image" label for all image rows instead of singling out
  PNG; the thumbnail identifies the image and the on-clipboard format (often
  PNG even for a copied GIF/WebP) is an implementation detail.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 17:29:37 -05:00
e7b0392d92 fix: daemon image decoding, preview alignment, ordering and lifecycle
Audit fixes for the daemon:

- Decode gif/webp/bmp/tiff/ico (enable the pure-Rust image features). These
  mimes were accepted by pick_mime but the image crate only had png/jpeg, so
  thumbnail decode failed and the item was silently dropped. Drop avif (needs
  heavy C deps) and svg (not a raster format the image crate decodes) from the
  allowlist so unsupported types are never selected.
- ItemAdded signal preview now uses db::PREVIEW_CHARS (200), matching the
  history/search queries, so a just-copied row and the same row after a reload
  carry identical text.
- Add a deterministic ", rowid DESC" tiebreaker to the three created_at DESC
  orderings (history, search, prune) so items sharing a millisecond have a
  stable order and prune can't evict the wrong one at the boundary.
- SetConfig now prunes immediately (and emits ItemDeleted) when max_history is
  lowered, instead of waiting for the next copy.
- Request the bus name with AllowReplacement (plus ReplaceExisting) so a
  reload's new instance can cleanly take the name; fix the misleading comment.
- Wayland monitor clears its pending-offers map on Selection{None} so it can't
  grow across copy-then-clear cycles (wlroots path).
- Document that SubmitItem is a trusted-caller method (the password-manager
  hint is enforced client-side; forwarding it is a future contract change).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 17:29:29 -05:00
46ce549b2f feat: lazy full-history search, rendered blink-free
Search now covers the entire stored history (bounded by max-history) instead
of an arbitrary 500-result cap, and renders a page at a time as you scroll
rather than building every match at once - so a broad query on a large
history no longer builds thousands of row widgets up front.

The full match set is snapshotted once (lightweight truncated metadata) and
paged from memory, so scrolling never re-queries and cannot race the search.
The first chunk renders synchronously in the same frame as the list clear, so
the list never paints empty between keystrokes (no "blink").

Concurrency hardening for the render path:
- epoch-ownership of the shared loading guard so a fast new query cannot be
  blocked by, or have its guard cleared by, a superseded render;
- a results-epoch guard so a scroll during a query's fetch window cannot
  render the previous query's stale snapshot;
- removeItem drops the id from the snapshot (adjusting the rendered index) so
  a pruned item in the unrendered tail cannot reappear as a phantom row.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 17:29:18 -05:00
764911b32d chore: bump versions to 0.5.0
Cut the 0.5.0 release (light/dark theme support): daemon
Cargo.toml/Cargo.lock to 0.5.0 (no code change, kept in lockstep with the
tag), extension metadata.json version to 5, and promote the CHANGELOG
[Unreleased] entry to [0.5.0] - 2026-05-26.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 14:50:08 -05:00
945ad61bf7 feat: light/dark theme support
Add a Theme preference (Automatic / Light / Dark). Automatic follows the
system org.gnome.desktop.interface color-scheme; the dark theme is
unchanged. Light styling lives in light.css, every rule scoped under a
.strata-theme-light class the panel toggles on its root box, loaded into the
St theme context by the extension. St's CSS engine has no custom properties,
so this specificity-based scoped override is how the palette is switched
without generating a stylesheet at runtime; switching is a single class
toggle off the ingest/render hot paths.

Also in this change:
- fix: the keyboard shortcut now hides the panel when it is already open.
  The binding lacked Shell.ActionMode.POPUP, so while the panel's modal grab
  was active the second press was swallowed and toggle() never ran.
- fix: the search placeholder ("Search...") is now legible in the light
  theme; it previously inherited a light-on-dark Shell color.
- pack: bundle light.css via --extra-source (gnome-extensions pack only
  auto-includes stylesheet.css), so packaged installs ship the light theme.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 14:49:08 -05:00
42230470fc chore: bump versions to 0.4.0
Cut the 0.4.0 release: daemon Cargo.toml/Cargo.lock to 0.4.0, extension
metadata.json version to 4, and promote the CHANGELOG [Unreleased] entry
to [0.4.0] - 2026-05-26.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 13:08:55 -05:00
8a7091755b docs: correct Wayland monitor protocol availability and post-0.3.0 drift
The monitor.rs comments claimed Mutter implements ext-data-control-v1 on
GNOME 47+, contradicting ARCHITECTURE.md. Mutter exposes neither
ext-data-control-v1 nor zwlr-data-control-v1, so the monitor never binds
on GNOME and ingest comes from the extension via Meta.Selection +
SubmitItem; the monitor only runs on wlroots. Correct the comments
(module doc, Ext probe, bail! message) and the main.rs spawn comment.

Also reconcile ARCHITECTURE.md with the code: supervisor backoff
(1/2/4/8/16 s, 5 s reset, log-only give-up), SIGTERM at 1.5 s, prune as a
separate post-insert call, multi-thread tokio default, the 200 px
load-more threshold, and the SQL-side preview truncation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 13:08:50 -05:00
a4a6f1a3f9 perf: truncate content_text preview in history/search queries
GetHistory and SearchHistory selected the full content_text column for
every row, so a page of large text items serialized up to megabytes of
JSON over D-Bus and parsed it on the GJS main loop, only to render a
~140-char snippet. The client-side cut never bounded the wire cost.

Truncate to PREVIEW_CHARS (200) with substr() in SQL on both list paths.
Full content is still served on demand by GetItemContent for paste-back.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 13:08:43 -05:00
ab230856a4 chore: bump versions to 0.3.0
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-25 04:06:05 -05:00
5d23254c8e refactor: remove stale thumbnail_blob migration
The column is defined in the initial CREATE TABLE statement, so the
ALTER TABLE migration was redundant from day one -- it always failed
silently via .ok(). No existing user databases lack this column.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-25 04:03:53 -05:00
375f50e962 docs: update CHANGELOG, ARCHITECTURE and AGENTS for post-0.2.0 fixes
- CHANGELOG: add [Unreleased] section with all fixes and perf improvements
  since the 0.2.0 tag
- ARCHITECTURE: note that GetItemContent also returns ay (no base64)
- AGENTS: update D-Bus method summary -- GetItemContent returns (s, ay),
  SetFocusedApp removed

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-25 04:02:07 -05:00
f9826c63a8 perf: return raw bytes from GetItemContent instead of base64
The D-Bus interface previously returned content as a base64-encoded
string (type 's'), requiring the daemon to encode and GJS to decode
on every paste. D-Bus natively supports byte arrays (type 'ay'),
eliminating both encode and decode steps entirely.

Changes:
- dbus_service.rs: return (String, Vec<u8>); drop base64 calls
- dbus.js: content_b64 arg changed from type 's' to type 'ay'
- panel.js: remove GLib.base64_decode(); pass ay bytes directly
- Cargo.toml/Cargo.lock: remove base64 dependency (no longer used)

For a 1 MB text paste this avoids allocating a 1.33 MB intermediate
string and decoding it on the compositor main thread.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-25 04:00:06 -05:00
40a9a3bbd5 style: apply cargo fmt
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-25 03:56:29 -05:00
4a6a5fc4e3 fix: remove dead SetFocusedApp D-Bus call
The daemon stored the focused app ID but never read it. App exclusion
runs entirely in the extension before SubmitItem is called, so the
D-Bus round-trip on every window focus change was pure overhead.

Removed from: extension.js call site, dbus.js XML interface,
dbus_service.rs method + struct field, main.rs Arc allocation.
Focus tracking in _connectFocusTracking is kept since _currentFocusedApp
is still used for the JS-side exclusion check.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-25 03:55:46 -05:00
575e216658 perf: replace O(n) hover loop with Clutter hit-test in motion-event
The previous implementation iterated all visible widgets on every
mouse-move event, calling get_transformed_position/size on each to
find which one was under the cursor. With a full page of items this
fires dozens of times per second on the compositor thread.

Use event.get_source() to let Clutter do its own hit-test (already
computed), then walk up the parent chain to find the owning
ClipboardItem. The walk is O(tree-depth), typically 3-4 steps,
regardless of history size.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-25 03:51:50 -05:00
8d681a16fd fix: panic with clear message if XDG data dir is unavailable
The previous fallback to PathBuf::from(".") silently placed the
database in whatever the current working directory was, making it
impossible to find later. Since the daemon cannot function without a
persistent database, failing fast with an actionable message is the
correct behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-25 03:43:52 -05:00
eb64476a2a perf: eliminate unnecessary clones in write_to_clipboard
write_to_clipboard took ownership of WriteRequest but immediately
cloned both fields into locals, then cloned bytes again for the text
path -- three heap allocations on a potentially large byte buffer.

Destructure the owned request directly and move the fields. The text
path still needs one clone (bytes goes into two MimeSources), but the
extra upfront clones are gone.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-25 03:43:33 -05:00