diff --git a/.claude/commands/logs.md b/.claude/commands/logs.md index 506f21a..f54322c 100644 --- a/.claude/commands/logs.md +++ b/.claude/commands/logs.md @@ -25,7 +25,5 @@ the file it points at. The shipped extension logs failures only; under the dev entry point (`make link`, which the nested shell reads too) it also logs `Enabled from …` on each enable and `Rebuilt` when a rescan or a setting rebuilds what is built. Only `[Library Menu]` lines are this extension's; other -extensions' errors (when enabled in the nested settings) are not. Exceptions inside a GNOME -extension only ever surface in these logs, never in a terminal, so this is the -place to look when something silently does nothing (no button beside Show Apps, -a grid that never fills). +extensions' errors (when enabled in the nested settings) are not. A missing button +beside Show Apps or a grid that never fills is looked for here first. diff --git a/.claude/commands/preview.md b/.claude/commands/preview.md index 1b4e86d..9e44ffa 100644 --- a/.claude/commands/preview.md +++ b/.claude/commands/preview.md @@ -11,9 +11,8 @@ never pressed). The user is watching the mirror window, so `say` before each ste Requested: $ARGUMENTS 1. `./scripts/nested.sh start --stand-in`: settings of its own and the made-up - library, nothing of the user's. A shell already running is reused as it is, so - check `./scripts/nested.sh status` says `data: stand-in`, and `stop` first if - not. A plain `start` only when what is asked is the look beside the user's other + library, nothing of the user's. A running shell is reused: check + `./scripts/nested.sh status` says `data: stand-in`, and `stop` first if not. A plain `start` only when what is asked is the look beside the user's other extensions (Dash to Panel, Blur my Shell), enabled in its own settings as the `drive-extension` skill says. 2. In **one** `./scripts/nested.sh do …` call, click through to what was requested and @@ -22,16 +21,5 @@ Requested: $ARGUMENTS 4. `./scripts/nested.sh stop`, even if a step failed. It closes the mirror; `./scripts/nested.sh status` then says `not running`. -Never press, unless the user asked for exactly that: - -- **Play, Continue, or an episode or film row**: the section's open command (VLC by - default) plays the user's real media, and the tracker writes their real watched - marks and positions. -- **Rescan**: an online scan with the user's real keys, writing - `~/.cache/library-menu@jackicus/`. -- **A watched disc**, or Mark watched from a key or the pad, outside `--stand-in`: it - writes the real `watched.json` and, with `tracking` `source`, a file into the - library folder itself. - -Check `./scripts/nested.sh logs` if the screenshot looks wrong or unchanged; a JS -exception leaves the previous UI up and reads as "nothing happened". +Never press what the `drive-extension` skill's "Never press" lists, unless the user +asked for exactly that. diff --git a/.claude/commands/reload.md b/.claude/commands/reload.md index 9e42853..c544e5a 100644 --- a/.claude/commands/reload.md +++ b/.claude/commands/reload.md @@ -28,12 +28,5 @@ How to see it: the mirror window on the desktop shows the nested shell live (`./scripts/nested.sh mirror on` if `status` says it is closed); the library opens from its button beside Show Apps, so `/preview` (or the `drive-extension` skill) opens it and screenshots it. Leave the nested shell running for that, and -stop it (`./scripts/nested.sh stop`) when the work is done. Never press Play, -Continue, an episode or film row, a watched disc or Rescan there: they reach the -user's real player, media, watched marks and keys. - -A reload re-imports `lib/` only. An edit to `scripts/dev-extension.js`, -`metadata.json` or the schema's keys needs `./scripts/nested.sh stop` then -`start` (`glib-compile-schemas src/schemas` first, for the schema), not a -reload, and no logout: only the real session needs one, and that is the user's -to do. +stop it (`./scripts/nested.sh stop`) when the work is done. Never press what the +`drive-extension` skill's "Never press" lists. diff --git a/.claude/rules/keyboard.md b/.claude/rules/keyboard.md index de0236d..23f2fbe 100644 --- a/.claude/rules/keyboard.md +++ b/.claude/rules/keyboard.md @@ -47,7 +47,7 @@ paths: outside a library. The arrows, Enter and Escape are never offered for binding. - Controllers are read with libmanette, loaded on demand (it runs without it), and acted on only while `_controlsActive()`, except Home, which opens the library when - no window has the focus. + no window has the focus and nothing is modal (`_controlsOpen()`). ## The shortcut diff --git a/.claude/rules/layout.md b/.claude/rules/layout.md index 31c9d4d..e2383dd 100644 --- a/.claude/rules/layout.md +++ b/.claude/rules/layout.md @@ -31,7 +31,9 @@ paths: shell does. Wrong, and the first tab's grid is built against a taller box than every later one. The box each view was built for is kept, and every view is rebuilt when it moves. -- Only the tabs and the detail rows track hover; tiles hover by crossing events. +- What tracks hover is what paints its own `:hover`: the section and season tabs, the + detail rows and their watched disc, and the icon and action buttons (`widgets.js`, + `detailView.js`). Tiles do not. - **A measurement of something just built**: the detail pop-up's height is asked of the side column after `anim.js` `ensureStyleDeep()`; without it `get_preferred_height` answers as if there were no spacing or margins, the panel comes out shorter than the diff --git a/.claude/rules/tracking.md b/.claude/rules/tracking.md index e9e6ec8..aee1b73 100644 --- a/.claude/rules/tracking.md +++ b/.claude/rules/tracking.md @@ -4,6 +4,7 @@ paths: - "src/lib/playback.js" - "src/lib/detailView.js" - "src/lib/widgets.js" + - "src/lib/library.js" --- # Watched marks, playback and Continue @@ -17,7 +18,7 @@ paths: - `local` uses the first alone. `source` also folds each folder's file in (later `at` wins; an unmark is kept as `watched: false` so it beats an older mark) and writes each folder its share back: on enable, on a folder change, when a rescan lands and on - every tick. `none` touches neither. `source` to `local` deletes the folder files; + every mark. `none` writes neither. `source` to `local` deletes the folder files; going back rewrites them. - The local file is never trimmed. A folder file is written only after it has been read, so another machine's marks are never overwritten unseen. The scanner skips @@ -33,18 +34,19 @@ paths: marked; short of it, where it stopped is kept in the same entry as `position`, so it travels in the folder file too. - A Play from the library seeks there less `resume-rewind` once the player has the file - (`resumeNext`, `SetPosition` over MPRIS), which is why the default VLC command turns + (`resumeNext`: MPRIS `SetPosition`, or `Seek` when the player gives no track id), which is why the default VLC command turns VLC's own `--qt-continue` off. - MPRIS never announces the position and a closed player cannot be asked, so the watcher keeps the last reading and its monotonic time and reckons forward while playing: the 30 s poll only corrects drift and catches the threshold; a pause, seek, - file change, the player going, or a disable each settle the position. Nothing is - written while a file plays. + file change, the player going, or a disable each settle the position. No position is + written while a file plays; the mark is written as the threshold is crossed. - Player controls are a separate extension (Media Controls), not this one. ## Continue (`detailView.js` `_syncPlay`, `Tracker.continueFrom`) The pane's primary button is the file touched last if it was left partway or unticked, else the first unwatched episode after it in the numbered seasons (Extras are not part -of the run). With nothing touched, or everything after it watched, it is the scan's own -`playLabel` ("Play S01E01"). It follows `changed`, which a kept position emits too. +of the run). With nothing touched, or everything after it watched, it is the item's +`playLabel` ("Play S01E01", from `library.js` `normalizeShow`). It follows `changed`, +which a kept position emits too. diff --git a/.claude/skills/drive-extension/SKILL.md b/.claude/skills/drive-extension/SKILL.md index 9f45805..4004242 100644 --- a/.claude/skills/drive-extension/SKILL.md +++ b/.claude/skills/drive-extension/SKILL.md @@ -17,7 +17,7 @@ CLAUDE.md's list, and why, unless the task asks for exactly that: by default) on the user's real media, and the tracker then writes their real watched marks and positions. A game's **Play** launches the real game through Steam or PCSX2 (under `--stand-in` it runs `true`). -- **Rescan**, or `make scan` (`/scan`, which runs only when the user types it), is an +- **Rescan** (and an empty Games tab's **Find Games**), or `make scan` (`/scan`, which runs only when the user types it), is an online scan with the user's real keys, writing `~/.cache/library-menu@jackicus/` (unless under `--stand-in`). - **A watched disc**, or Mark watched from a key or the pad, outside `--stand-in`, writes @@ -32,16 +32,12 @@ CLAUDE.md's list, and why, unless the task asks for exactly that: cache (`XDG_CACHE_HOME`), with fresh settings and watched marks of its own. Use it for anything that shows library content, and always for `docs/screenshots/`. A plain `start` (or `--clean`) has settings of its own too, but the user's real cache - and watched marks. A shell already running is reused as it is, so `stop` first; - `status` says which it is (`data:` stand-in or your own). + and watched marks. `status` says which a running one is (`data:` stand-in or your own). - **The idle stop** is `NESTED_IDLE=` at `start` (default 600, `0` never). - The nested Wayland display is `library-dev`; the prefs process to kill before - reopening is the one whose environment names it. -- **`overview on` is a flag, not only a command**: it marks the overview as wanted in - the run directory and sets `OverviewActive` only if it is not already set, so + The nested Wayland display is `library-menu-dev`. +- **`overview on` sets `OverviewActive` only if it is not already set**, so `do "overview on" "shot $S/x.png"` photographs an overview the extension opened (the - button pressed from the desktop) where a bare `shot` would dismiss it. `run python3 - scripts/nested_driver.py …` carries the same environment as `do`. + button pressed from the desktop). - **Remote keys**: `key XF86OK`, `XF86Back`, `XF86HomePage`, `XF86ChannelUp` and the rest a remote sends work as `key` steps, against the `keys-*` defaults. - **A game controller is `scripts/vpad.py`**, a virtual Xbox 360 pad on uinput driven @@ -50,21 +46,19 @@ CLAUDE.md's list, and why, unless the task asks for exactly that: (`"$PWD/scripts/vpad.py" FIFO &` from the repository): that is the name `nested.sh stop` sweeps it by (`NESTED_STRAYS` in `./scripts/ext.conf`). Controller input is acted on only while a library is up and no window has the focus, so close the prefs first. -- **Places are settings**: `library-opens-in` and `detail-opens-in`, changed with - `./scripts/nested.sh run timeout 5 gsettings --schemadir src/schemas set - org.gnome.shell.extensions.library-menu …` (the nested session's own settings) to - watch a live switch. +- **Places are settings**: `library-opens-in` and `detail-opens-in`; a change switches + live. - **The "Allow inhibiting shortcuts" prompt** (capturing `library-shortcut` in the - prefs) writes the real permission store. If a test answers it, delete the entry + prefs): if a test answers it, delete the entry afterwards (`PermissionStore.DeletePermission gnome shortcuts-inhibitor org.gnome.Shell.Extensions.desktop`) so the real session still asks. ## Reading the screen (1600×900) Measure from a fresh screenshot if the columns, the sections, the accent or the -geometry changed. The nested shell enables Library alone, so the button is in -the overview's dash at about (960, 838); the rest are the defaults' positions with -**Dash to Panel on** (below, "Its own traps"). There is one button, beside Show Apps, +geometry changed. The nested shell enables Library alone (and `--stand-in` always +does), so the button is in the overview's dash at about (960, 838); the rest are the +defaults' positions with **Dash to Panel on** (below, "Its own traps"). There is one button, beside Show Apps, tooltip "Library", and it is the only way in. - **The button** with Dash to Panel: Show Apps ≈ (30, 875), the library button @@ -82,18 +76,16 @@ tooltip "Library", and it is the only way in. - **`modal`**: a panel out of the button, about `210,78` to `1390,800`, tabs at y ≈ 104. - **A pop-up detail** (`detail-opens-in` `menu` or `modal`): the shade's edge is 48 px in from the work area, so `click 20 450` closes it, as `key Escape` does. -- **An empty section** shows a centred placeholder with Open Settings: normal until it - has a folder and a scan. +- **An empty section** shows a centred placeholder with Open Settings (Find Games for + Games): normal until it has a folder and a scan. A workspace slide is over in 250 ms and a `shot` takes longer to fire, so a frame -caught mid-slide is luck; `wait 1` after anything that changes workspace, `wait 0.6` -after opening or closing an item. +caught mid-slide is luck: `wait 1` after anything that changes workspace. ## Its own traps -- **A keyboard walk is one unbroken run of `key` steps.** A `say` or `shot` between - presses takes the keyboard away, and the focus watcher hands it back to the surface, - not the tile or row that had it. +- **No `say` or `shot` inside a keyboard walk**: either takes the keyboard away, and the + focus watcher hands it back to the surface, not the tile or row that had it. - **With the library on the surface, a window on its workspace gets no keys** (the prefs included). Test typing into a window with `library-opens-in` `menu` or `modal`. - **The look beside Dash to Panel, Blur my Shell and app folders** (`panel.js` @@ -105,11 +97,7 @@ after opening or closing an item. ## Screenshots for the README -`docs/screenshots/` are taken under `start --stand-in`, never of the user's -library. Full-screen shots go in as JPEG; windows (`window FILE`, the preferences) as -PNG. - -Full-screen shots lose the top 32 px, the top bar with the screencast indicator and the -clock: `jpegtran -crop 1600x868+0+32 -copy none -perfect` on the JPEG, lossless because -32 is a whole number of JPEG blocks. They are 1600×868. PNGs go through -`oxipng --opt 4 --strip safe`, which drops the `tEXt` chunks the screenshot carries. +The `gnome-ext:screenshots` skill, plus: full-screen shots lose the top 32 px, the top +bar with the screencast indicator and the clock: `jpegtran -crop 1600x868+0+32 -copy +none -perfect` on the JPEG, lossless because 32 is a whole number of JPEG blocks. They +are 1600×868. diff --git a/README.md b/README.md index 3203a9b..d9cecc9 100644 --- a/README.md +++ b/README.md @@ -111,8 +111,9 @@ whose **Fetch artwork and descriptions online** switch is on (on by default). - **Keys are yours.** TVmaze and Wikipedia need none. TMDB is skipped until you give it a free key of your own from your [TMDB account](https://www.themoviedb.org/settings/api). It is stored in your GNOME settings (dconf) in **plain text**, like any other - setting, and goes only to TMDB. If a file `keys/TMDB/API KEY.txt` exists in your - Documents folder, the key row offers **Import**, which reads it when pressed. + setting, and goes only to TMDB. The key row's **Import** reads a key from a file you + pick; the file dialog opens on `keys/TMDB/API KEY.txt` in your Documents folder if + there is one. - **What is stored:** the library index, artwork and fetched descriptions in `~/.cache/library-menu@jackicus/`; watched marks and where playback stopped in `~/.local/share/library-menu@jackicus/watched.json`. With **Keep marks in** set to @@ -203,7 +204,7 @@ journalctl -f -o cat SYSLOG_IDENTIFIER=org.gnome.Shell.Extensions ``` From a clone, `make status` says whether it is installed and running, and `make logs` -follows the first of those. +follows both. - **A tab says "Nothing in Films yet".** The section has no folder, or has not been scanned: its Open Settings button opens the preferences, where you add a folder on its @@ -214,7 +215,7 @@ follows the first of those. - **A PS2 game has no Play button.** PCSX2 itself was not found (its binary, an AppImage in `~/Applications`, or the Flatpak). - **Rescan says "Failed — see logs".** The reason is in the preferences' journal above, - after `Scan failed`. + after `Scanner failed`. - **No posters.** Check the section's online switch and its sources. TMDB is skipped without a key, and a key TMDB rejects is skipped for the rest of the scan. A title no source had artwork for is not looked up again for a week, unless you add a source. diff --git a/docs/private-api.md b/docs/private-api.md index 17fb91e..8d69391 100644 --- a/docs/private-api.md +++ b/docs/private-api.md @@ -19,10 +19,11 @@ an `if`, and then the only sign is the symptom, with nothing logged. | `workspace._keepAliveId` (read and set) | app.js | A workspace the library or pane is on is folded away under it; a workspace the shell holds mid drag-and-drop can be claimed | No, and silent | | `Main.wm._workspaceTracker._queueCheckWorkspaces()` | app.js | A released workspace folds away on the shell's next check, not at once | Yes, `?.` | | `Dash.ShowAppsIcon`, its `_createIcon`, `_iconActor` | libraryButton.js | The subclass throws building; no button, a warning | Yes, try/catch | +| `ShowAppsIcon._canRemoveApp()` (overridden) | libraryButton.js | The button becomes an unpin target again, silently | No | | `Main.overview.dash._dashContainer`, `dash._hookUpLabel` | libraryButton.js | No button in the dash; no hover label | Yes, `?.` | | `global.dashToPanel.panels[0]`, `panels-created`, `.showAppsIconWrapper.realShowAppsIcon`, `.panel`, `._updateGroupedElements` (wrapped), `.geom`, `.updateElementPositions` | libraryButton.js | The button goes in the dash instead | Yes, each checked first; read only with `dash-to-panel` on | | `panel._elementGroups`, a group's `.elements` and `.expandableIndex`, an element's `.actor` and `.position` | libraryButton.js | `_elementGroups` gone: the button is in the panel with no place in its layout. Another shape: the wrap throws inside `_attach()`'s try/catch, so no button and a warning | Partly | -| `Main.overview._overview.controls`, `.appDisplay`, `._box` | mediaMenu.js | No `menu` library; one warning, if a section is enabled | Yes | +| `Main.overview._overview.controls`, `.appDisplay`, `._box` | mediaMenu.js; `_overview.controls` also panel.js, overviewPreview.js | No `menu` library; one warning, if a section is enabled | Yes | | `Main.overview.dash.showAppsButton` (`.checked`) | mediaMenu.js | The menu can no longer tell the app grid is up (the kit's "Is the app grid up?" rule) | No | | `controls._searchController.searchActive` | mediaMenu.js | Workspaces do not come back for a search while the view is up | Yes, `?.` | | `controls._stateAdjustment` | mediaMenu.js | The workspace row is not folded in step with the overview | Yes, `?.` | @@ -125,7 +126,7 @@ one point of failure with no fallback. ## The folder's panel (panel.js, detailDialog.js, libraryWindow.js) `MediaPanel` is a copy of `AppFolderDialog`'s shape, not a subclass: the -folder's name entry and grid are built into its `_init`. To look like a folder +folder's name entry and grid are built into `AppFolderDialog`'s own `_init`. To look like a folder under Blur my Shell, which drops the shade, blurs behind and adds a class to the folder's box, `folderLook()` asks a real folder's `_dialog` once per open and copies its `Shell.BlurEffect` and extra `_viewBox` classes. A folder diff --git a/docs/publishing.md b/docs/publishing.md index c91833b..5344173 100644 --- a/docs/publishing.md +++ b/docs/publishing.md @@ -12,12 +12,13 @@ Private API is [private-api.md](private-api.md)'s; versions are 1. checks the schema with `glib-compile-schemas --strict --dry-run` and stops on a warning; -2. copies `src/` to a stage, drops every `CLAUDE.md` and - `schemas/gschemas.compiled`, and adds the root `LICENSE`; +2. copies what ships (the entry points, stylesheet, schema XML and + `./scripts/ext.conf`'s `EXT_SHIP` patterns) to a stage, and adds the root + `LICENSE`; 3. runs `gnome-extensions pack` there with `--extra-source` for `lib`, `backend`, `icons` and `LICENSE` (it adds `extension.js`, `metadata.json`, `prefs.js`, `stylesheet.css` and the schema XML itself); -4. `check_pack` diffs the zip against the files that should ship (those, plus +4. diffs the zip against the files that should ship (those, plus every `lib/*.js`, `backend/*.js` and `icons/*.svg`) and fails on anything missing or extra; 5. writes `dist/library-menu@jackicus.shell-extension.zip`. @@ -117,7 +118,7 @@ Checked against both pages as read on 2026-10-02, before the 1.0 release, and ag - **Schemas**: ID and path under `org.gnome.shell.extensions`, file named after the ID, XML only. - **Licence**: GPL-2.0-or-later, packed from the root `LICENSE`. -- **No unnecessary files**: `check_pack` enforces it. +- **No unnecessary files**: `make pack` diffs the zip against what ships and fails on a stray file. - **A linter**: `make lint`, gjs.guide's ESLint rules, run by `make check` and CI. diff --git a/src/backend/CLAUDE.md b/src/backend/CLAUDE.md index cc0e6ab..8689412 100644 --- a/src/backend/CLAUDE.md +++ b/src/backend/CLAUDE.md @@ -35,8 +35,8 @@ settings and credentials come from. - **Games are the launchers' bookkeeping, not a folder walk.** `gamesScanner.js` reads `steamapps/libraryfolders.vdf` for every library root, one `appmanifest_.acf` per title, `userdata/*/config/localconfig.vdf` for - playtime, and `PCSX2.ini` for the disc folders and the covers folder. Both roots - are auto-detected from `~`; a machine without Steam, or with PCSX2 never launched, + playtime, and `PCSX2.ini` for the disc folders and the covers folder. Each root is + `games-steam-path` or `games-pcsx2-path`, auto-detected from `~` when empty (the default); a machine without Steam, or with PCSX2 never launched, yields an empty list. Proton, the runtimes and the redistributables are skipped. Every scan reads them all again: a manifest is one small file. A game carries its own `launch` argv; a disc with no PCSX2 found has none, and no Play button. @@ -48,7 +48,7 @@ settings and credentials come from. - **A credential is one slotted value.** `credential()` returns a slot's fields (`tmdb@1` has one, `igdb@1` two: Twitch's client id and secret, tab-separated); TMDB and IGDB are the sources that need one. Where they come - from is the root `CLAUDE.md`'s. Never print one. + from is the root `CLAUDE.md`'s. ## Gotchas @@ -102,6 +102,6 @@ settings and credentials come from. waits for the first; a scan with no session bus at all goes ahead unlocked. - **A name that is not UTF-8 is skipped, not fatal.** GJS cannot turn one - into a string, so it cannot be opened either; `list` drops just that entry + into a string, so it cannot be opened either; `mediaScanner.js` `list` drops just that entry and says so. Letting the error through would lose the whole folder — at a section's root, the whole section and its artwork.