Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .claude/commands/logs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
20 changes: 4 additions & 16 deletions .claude/commands/preview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
11 changes: 2 additions & 9 deletions .claude/commands/reload.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 1 addition & 1 deletion .claude/rules/keyboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion .claude/rules/layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 8 additions & 6 deletions .claude/rules/tracking.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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.
52 changes: 20 additions & 32 deletions .claude/skills/drive-extension/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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=<seconds>` 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
Expand All @@ -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
Expand All @@ -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`
Expand All @@ -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.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
5 changes: 3 additions & 2 deletions docs/private-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, `?.` |
Expand Down Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions docs/publishing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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.

Expand Down
Loading
Loading